summaryrefslogtreecommitdiffstats
path: root/layout/generic
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /layout/generic
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/generic')
-rw-r--r--layout/generic/AnonymousContentKey.h54
-rw-r--r--layout/generic/AspectRatio.cpp32
-rw-r--r--layout/generic/AspectRatio.h147
-rw-r--r--layout/generic/AutoCopyListener.h60
-rw-r--r--layout/generic/BRFrame.cpp265
-rw-r--r--layout/generic/BlockReflowState.cpp1067
-rw-r--r--layout/generic/BlockReflowState.h427
-rw-r--r--layout/generic/CSSAlignUtils.cpp129
-rw-r--r--layout/generic/CSSAlignUtils.h65
-rw-r--r--layout/generic/CSSOrderAwareFrameIterator.cpp88
-rw-r--r--layout/generic/CSSOrderAwareFrameIterator.h268
-rw-r--r--layout/generic/ColumnSetWrapperFrame.cpp311
-rw-r--r--layout/generic/ColumnSetWrapperFrame.h88
-rw-r--r--layout/generic/ColumnUtils.cpp47
-rw-r--r--layout/generic/ColumnUtils.h39
-rw-r--r--layout/generic/FrameClass.py22
-rw-r--r--layout/generic/FrameClasses.py203
-rw-r--r--layout/generic/GenerateFrameLists.py46
-rw-r--r--layout/generic/JustificationUtils.h127
-rw-r--r--layout/generic/LayoutMessageUtils.h54
-rw-r--r--layout/generic/MathMLTextRunFactory.cpp683
-rw-r--r--layout/generic/MathMLTextRunFactory.h45
-rw-r--r--layout/generic/MiddleCroppingBlockFrame.cpp218
-rw-r--r--layout/generic/MiddleCroppingBlockFrame.h64
-rw-r--r--layout/generic/PrintedSheetFrame.cpp404
-rw-r--r--layout/generic/PrintedSheetFrame.h144
-rw-r--r--layout/generic/ReflowInput.cpp3042
-rw-r--r--layout/generic/ReflowInput.h1045
-rw-r--r--layout/generic/ReflowOutput.cpp90
-rw-r--r--layout/generic/ReflowOutput.h280
-rw-r--r--layout/generic/RubyUtils.cpp185
-rw-r--r--layout/generic/RubyUtils.h223
-rw-r--r--layout/generic/ScrollAnchorContainer.cpp797
-rw-r--r--layout/generic/ScrollAnchorContainer.h183
-rw-r--r--layout/generic/ScrollAnimationBezierPhysics.cpp152
-rw-r--r--layout/generic/ScrollAnimationBezierPhysics.h94
-rw-r--r--layout/generic/ScrollAnimationMSDPhysics.cpp157
-rw-r--r--layout/generic/ScrollAnimationMSDPhysics.h70
-rw-r--r--layout/generic/ScrollAnimationPhysics.h60
-rw-r--r--layout/generic/ScrollGeneration.cpp49
-rw-r--r--layout/generic/ScrollGeneration.h70
-rw-r--r--layout/generic/ScrollOrigin.h68
-rw-r--r--layout/generic/ScrollPositionUpdate.cpp156
-rw-r--r--layout/generic/ScrollPositionUpdate.h136
-rw-r--r--layout/generic/ScrollSnap.cpp788
-rw-r--r--layout/generic/ScrollSnap.h92
-rw-r--r--layout/generic/ScrollSnapInfo.cpp105
-rw-r--r--layout/generic/ScrollSnapInfo.h154
-rw-r--r--layout/generic/ScrollSnapTargetId.h41
-rw-r--r--layout/generic/ScrollVelocityQueue.cpp93
-rw-r--r--layout/generic/ScrollVelocityQueue.h93
-rw-r--r--layout/generic/ScrollbarActivity.cpp280
-rw-r--r--layout/generic/ScrollbarActivity.h120
-rw-r--r--layout/generic/ScrollbarPreferences.h22
-rw-r--r--layout/generic/SelectionMovementUtils.cpp682
-rw-r--r--layout/generic/SelectionMovementUtils.h177
-rw-r--r--layout/generic/StickyScrollContainer.cpp415
-rw-r--r--layout/generic/StickyScrollContainer.h115
-rw-r--r--layout/generic/TextDrawTarget.h616
-rw-r--r--layout/generic/TextOverflow.cpp936
-rw-r--r--layout/generic/TextOverflow.h332
-rw-r--r--layout/generic/ViewportFrame.cpp481
-rw-r--r--layout/generic/ViewportFrame.h118
-rw-r--r--layout/generic/Visibility.h45
-rw-r--r--layout/generic/WBRFrame.cpp68
-rw-r--r--layout/generic/WritingModes.h2244
-rw-r--r--layout/generic/broken-image.pngbin0 -> 160 bytes
-rw-r--r--layout/generic/crashtests/1001233.html18
-rw-r--r--layout/generic/crashtests/1001258-1.html26
-rw-r--r--layout/generic/crashtests/1001994.html14
-rw-r--r--layout/generic/crashtests/1003441.xhtml37
-rw-r--r--layout/generic/crashtests/1015562.html15
-rw-r--r--layout/generic/crashtests/1015563-1.html4
-rw-r--r--layout/generic/crashtests/1015563-2.html7
-rw-r--r--layout/generic/crashtests/1015844.html25
-rw-r--r--layout/generic/crashtests/1032450.html12
-rw-r--r--layout/generic/crashtests/1032613-1.svg10
-rw-r--r--layout/generic/crashtests/1032613-2.html17
-rw-r--r--layout/generic/crashtests/1037903.html6
-rw-r--r--layout/generic/crashtests/1039454-1.html12
-rw-r--r--layout/generic/crashtests/1042489.html6
-rw-r--r--layout/generic/crashtests/1054010-1.html97
-rw-r--r--layout/generic/crashtests/1058954-1.html13
-rw-r--r--layout/generic/crashtests/1059138-1.html38
-rw-r--r--layout/generic/crashtests/1102175-2.html47
-rw-r--r--layout/generic/crashtests/1134531.html4
-rw-r--r--layout/generic/crashtests/1134667.html2
-rw-r--r--layout/generic/crashtests/1137723-1.html29
-rw-r--r--layout/generic/crashtests/1137723-2.html29
-rw-r--r--layout/generic/crashtests/1140043-1.html7
-rw-r--r--layout/generic/crashtests/1140043-2.html7
-rw-r--r--layout/generic/crashtests/1140043-3.html7
-rw-r--r--layout/generic/crashtests/1140268-1.html18
-rw-r--r--layout/generic/crashtests/1145768.html21
-rw-r--r--layout/generic/crashtests/1145931.html16
-rw-r--r--layout/generic/crashtests/1145950-1.html20
-rw-r--r--layout/generic/crashtests/1146103.html6
-rw-r--r--layout/generic/crashtests/1146107.html6
-rw-r--r--layout/generic/crashtests/1146114.html6
-rw-r--r--layout/generic/crashtests/1153478-iframe.html18
-rw-r--r--layout/generic/crashtests/1153478.html11
-rw-r--r--layout/generic/crashtests/1153695.html25
-rw-r--r--layout/generic/crashtests/1156222.html6
-rw-r--r--layout/generic/crashtests/1156257.html16
-rw-r--r--layout/generic/crashtests/1157011.html4
-rw-r--r--layout/generic/crashtests/1157975-1.html14
-rw-r--r--layout/generic/crashtests/1169420-1.html8
-rw-r--r--layout/generic/crashtests/1169420-2.html8
-rw-r--r--layout/generic/crashtests/1178783-1.html157
-rw-r--r--layout/generic/crashtests/1183431.html6
-rw-r--r--layout/generic/crashtests/1186147-1.html6
-rw-r--r--layout/generic/crashtests/1209952.html38
-rw-r--r--layout/generic/crashtests/1221112-1.html32
-rw-r--r--layout/generic/crashtests/1221112-2.html27
-rw-r--r--layout/generic/crashtests/1221874-1.html16
-rw-r--r--layout/generic/crashtests/1221904.html24
-rw-r--r--layout/generic/crashtests/1222783.xhtml19
-rw-r--r--layout/generic/crashtests/1223522.xhtml5
-rw-r--r--layout/generic/crashtests/1223568-1.html2
-rw-r--r--layout/generic/crashtests/1223568-2.html6
-rw-r--r--layout/generic/crashtests/1224230-1.html22
-rw-r--r--layout/generic/crashtests/1225005.html4
-rw-r--r--layout/generic/crashtests/1225118.html4
-rw-r--r--layout/generic/crashtests/1225376.html10
-rw-r--r--layout/generic/crashtests/1225592.html13
-rw-r--r--layout/generic/crashtests/1229437-1.html8
-rw-r--r--layout/generic/crashtests/1229437-2.html5
-rw-r--r--layout/generic/crashtests/1230378.xhtml16
-rw-r--r--layout/generic/crashtests/1231692-1.html5
-rw-r--r--layout/generic/crashtests/1233191.html9
-rw-r--r--layout/generic/crashtests/1233607.html9
-rw-r--r--layout/generic/crashtests/1234701-1.html19
-rw-r--r--layout/generic/crashtests/1234701-2.html18
-rw-r--r--layout/generic/crashtests/1248227.html13
-rw-r--r--layout/generic/crashtests/1271765.html8
-rw-r--r--layout/generic/crashtests/1272983-1.html15
-rw-r--r--layout/generic/crashtests/1272983-2.html15
-rw-r--r--layout/generic/crashtests/1275059.html3
-rw-r--r--layout/generic/crashtests/1278007.html26
-rw-r--r--layout/generic/crashtests/1278080.html29
-rw-r--r--layout/generic/crashtests/1278461-1.html23
-rw-r--r--layout/generic/crashtests/1278461-2.html22
-rw-r--r--layout/generic/crashtests/1279814.html35
-rw-r--r--layout/generic/crashtests/1281102.html30
-rw-r--r--layout/generic/crashtests/1297427-non-equal-centers.html14
-rw-r--r--layout/generic/crashtests/1304441.html9
-rw-r--r--layout/generic/crashtests/1308876-1.html38
-rw-r--r--layout/generic/crashtests/1316649.html54
-rw-r--r--layout/generic/crashtests/1316884-1.html21
-rw-r--r--layout/generic/crashtests/1343552-1.html32
-rw-r--r--layout/generic/crashtests/1343552-2.html31
-rw-r--r--layout/generic/crashtests/1346454-1.html34
-rw-r--r--layout/generic/crashtests/1346454-2.html12
-rw-r--r--layout/generic/crashtests/1349650.html11
-rw-r--r--layout/generic/crashtests/1349816-1.html6
-rw-r--r--layout/generic/crashtests/1350372.html31
-rw-r--r--layout/generic/crashtests/1364361-1.html45
-rw-r--r--layout/generic/crashtests/1367413-1.html54
-rw-r--r--layout/generic/crashtests/1368617-1.html9
-rw-r--r--layout/generic/crashtests/1373586.html19
-rw-r--r--layout/generic/crashtests/1375858.html12
-rw-r--r--layout/generic/crashtests/1381134-2.html45
-rw-r--r--layout/generic/crashtests/1381134.html45
-rw-r--r--layout/generic/crashtests/1401420-1.html5
-rw-r--r--layout/generic/crashtests/1401709.html10
-rw-r--r--layout/generic/crashtests/1401807.html15
-rw-r--r--layout/generic/crashtests/1404222-empty-shape.html2
-rw-r--r--layout/generic/crashtests/1405443.html19
-rw-r--r--layout/generic/crashtests/1405813.html10
-rw-r--r--layout/generic/crashtests/1405896.html31
-rw-r--r--layout/generic/crashtests/1406252-1.html13
-rw-r--r--layout/generic/crashtests/1415185.html18
-rw-r--r--layout/generic/crashtests/1416544.html32
-rw-r--r--layout/generic/crashtests/1427824.html10
-rw-r--r--layout/generic/crashtests/1431781-2.html26
-rw-r--r--layout/generic/crashtests/1431781.html22
-rw-r--r--layout/generic/crashtests/1458028.html13
-rw-r--r--layout/generic/crashtests/1459697.html13
-rw-r--r--layout/generic/crashtests/1460158-1.html15
-rw-r--r--layout/generic/crashtests/1460158-2.html17
-rw-r--r--layout/generic/crashtests/1460158-3.html14
-rw-r--r--layout/generic/crashtests/1461039.html15
-rw-r--r--layout/generic/crashtests/1461979-1.html13
-rw-r--r--layout/generic/crashtests/1463977.html5
-rw-r--r--layout/generic/crashtests/1466224.html27
-rw-r--r--layout/generic/crashtests/1467239.html14
-rw-r--r--layout/generic/crashtests/1472403.html6
-rw-r--r--layout/generic/crashtests/1474768.html22
-rw-r--r--layout/generic/crashtests/1478178.html6
-rw-r--r--layout/generic/crashtests/1483972.html10
-rw-r--r--layout/generic/crashtests/1486457.html19
-rw-r--r--layout/generic/crashtests/1488762-1.html16
-rw-r--r--layout/generic/crashtests/1488910-1.html19
-rw-r--r--layout/generic/crashtests/1488910-2.html19
-rw-r--r--layout/generic/crashtests/1489287.html17
-rw-r--r--layout/generic/crashtests/1489770.html23
-rw-r--r--layout/generic/crashtests/1489863.html15
-rw-r--r--layout/generic/crashtests/1490032.html20
-rw-r--r--layout/generic/crashtests/1490685.html1
-rw-r--r--layout/generic/crashtests/1493708.html15
-rw-r--r--layout/generic/crashtests/1493710.html26
-rw-r--r--layout/generic/crashtests/1493741.html6
-rw-r--r--layout/generic/crashtests/1494380.html10
-rw-r--r--layout/generic/crashtests/1505817.html27
-rw-r--r--layout/generic/crashtests/1506216.html4
-rw-r--r--layout/generic/crashtests/1506306.html8
-rw-r--r--layout/generic/crashtests/1507196.html30
-rw-r--r--layout/generic/crashtests/1513275.html9
-rw-r--r--layout/generic/crashtests/1513282.html16
-rw-r--r--layout/generic/crashtests/1515124.html26
-rw-r--r--layout/generic/crashtests/1517033.html24
-rw-r--r--layout/generic/crashtests/1517297.html22
-rw-r--r--layout/generic/crashtests/1520798-1.xhtml10
-rw-r--r--layout/generic/crashtests/1520798-2.html13
-rw-r--r--layout/generic/crashtests/1528771.html10
-rw-r--r--layout/generic/crashtests/1539656.html14
-rw-r--r--layout/generic/crashtests/1542441.html37
-rw-r--r--layout/generic/crashtests/1543140-1.html24
-rw-r--r--layout/generic/crashtests/1544060-1.html3
-rw-r--r--layout/generic/crashtests/1544060-2.html1
-rw-r--r--layout/generic/crashtests/1553824.html86
-rw-r--r--layout/generic/crashtests/1554824.html25
-rw-r--r--layout/generic/crashtests/1555142.html15
-rw-r--r--layout/generic/crashtests/1560349.html12
-rw-r--r--layout/generic/crashtests/1560397-2.html25
-rw-r--r--layout/generic/crashtests/1560397.html21
-rw-r--r--layout/generic/crashtests/1562105.html6
-rw-r--r--layout/generic/crashtests/1563131.html17
-rw-r--r--layout/generic/crashtests/1568001-1.html19
-rw-r--r--layout/generic/crashtests/1568001-2.html13
-rw-r--r--layout/generic/crashtests/1569639.html13
-rw-r--r--layout/generic/crashtests/1571239.html22
-rw-r--r--layout/generic/crashtests/1571460.html29
-rw-r--r--layout/generic/crashtests/1571598.html14
-rw-r--r--layout/generic/crashtests/1571897.html15
-rw-r--r--layout/generic/crashtests/1572901.html19
-rw-r--r--layout/generic/crashtests/1573216.html20
-rw-r--r--layout/generic/crashtests/1574552.html6
-rw-r--r--layout/generic/crashtests/1574993.html21
-rw-r--r--layout/generic/crashtests/1582019.html22
-rw-r--r--layout/generic/crashtests/1586470.html9
-rw-r--r--layout/generic/crashtests/1588955-very-large-frameset.html9
-rw-r--r--layout/generic/crashtests/1590569.html24
-rw-r--r--layout/generic/crashtests/1596310.html17
-rw-r--r--layout/generic/crashtests/1601819-1.html11
-rw-r--r--layout/generic/crashtests/1608851-1.html12
-rw-r--r--layout/generic/crashtests/1608851-2.html12
-rw-r--r--layout/generic/crashtests/1613210.html31
-rw-r--r--layout/generic/crashtests/1614101.html44
-rw-r--r--layout/generic/crashtests/1618312.html19
-rw-r--r--layout/generic/crashtests/1618564.html12
-rw-r--r--layout/generic/crashtests/1625051-1.html13
-rw-r--r--layout/generic/crashtests/1625051-2.html13
-rw-r--r--layout/generic/crashtests/1626970.html14
-rw-r--r--layout/generic/crashtests/1628804.html21
-rw-r--r--layout/generic/crashtests/1629575-1.html21
-rw-r--r--layout/generic/crashtests/1629575-2.html69
-rw-r--r--layout/generic/crashtests/1630385.html26
-rw-r--r--layout/generic/crashtests/1633434.html15
-rw-r--r--layout/generic/crashtests/1633737-1.html8
-rw-r--r--layout/generic/crashtests/1633737-2.html8
-rw-r--r--layout/generic/crashtests/1633737-3.html34
-rw-r--r--layout/generic/crashtests/1633737-4.html53
-rw-r--r--layout/generic/crashtests/1633737-5.html39
-rw-r--r--layout/generic/crashtests/1633828.html36
-rw-r--r--layout/generic/crashtests/1638860-1.html24
-rw-r--r--layout/generic/crashtests/1638860-2.html26
-rw-r--r--layout/generic/crashtests/1638906.html25
-rw-r--r--layout/generic/crashtests/1640028.html31
-rw-r--r--layout/generic/crashtests/1640051.html20
-rw-r--r--layout/generic/crashtests/1640275.html14
-rw-r--r--layout/generic/crashtests/1644819.html20
-rw-r--r--layout/generic/crashtests/1645549-1.html19
-rw-r--r--layout/generic/crashtests/1648577.html18
-rw-r--r--layout/generic/crashtests/1652618.html15
-rw-r--r--layout/generic/crashtests/1652897.html13
-rw-r--r--layout/generic/crashtests/1654925.html16
-rw-r--r--layout/generic/crashtests/1663222.html19
-rw-r--r--layout/generic/crashtests/1666592.html2
-rw-r--r--layout/generic/crashtests/1670336.html33
-rw-r--r--layout/generic/crashtests/1676970.html20
-rw-r--r--layout/generic/crashtests/1677518-1.html15
-rw-r--r--layout/generic/crashtests/1677518-1.jpgbin0 -> 1295 bytes
-rw-r--r--layout/generic/crashtests/1677518-1.svg29
-rw-r--r--layout/generic/crashtests/1679794.html27
-rw-r--r--layout/generic/crashtests/1680406.html16
-rw-r--r--layout/generic/crashtests/1681788.html13
-rw-r--r--layout/generic/crashtests/1682032.html17
-rw-r--r--layout/generic/crashtests/1682686-1.html60
-rw-r--r--layout/generic/crashtests/1682686-2.html35
-rw-r--r--layout/generic/crashtests/1682882.html27
-rw-r--r--layout/generic/crashtests/1683126.html15
-rw-r--r--layout/generic/crashtests/1697262-1.html29
-rw-r--r--layout/generic/crashtests/1699263.html18
-rw-r--r--layout/generic/crashtests/1699468.html30
-rw-r--r--layout/generic/crashtests/1728319.html11
-rw-r--r--layout/generic/crashtests/1730506.html21
-rw-r--r--layout/generic/crashtests/1730570.html33
-rw-r--r--layout/generic/crashtests/1734015.html21
-rw-r--r--layout/generic/crashtests/1776079.html19
-rw-r--r--layout/generic/crashtests/1791606.html29
-rw-r--r--layout/generic/crashtests/1799749.html5
-rw-r--r--layout/generic/crashtests/1807958.html33
-rw-r--r--layout/generic/crashtests/1816574.html39
-rw-r--r--layout/generic/crashtests/1821603.html4
-rw-r--r--layout/generic/crashtests/1822118.html11
-rw-r--r--layout/generic/crashtests/1825434.html16
-rw-r--r--layout/generic/crashtests/225868-1-inner.html14
-rw-r--r--layout/generic/crashtests/225868-1.html7
-rw-r--r--layout/generic/crashtests/255468.xhtml24
-rw-r--r--layout/generic/crashtests/255982-1.html13
-rw-r--r--layout/generic/crashtests/255982-2.html10
-rw-r--r--layout/generic/crashtests/255982-3.html10
-rw-r--r--layout/generic/crashtests/255982-4.html13
-rw-r--r--layout/generic/crashtests/25888-1.html6
-rw-r--r--layout/generic/crashtests/25888-2.html8
-rw-r--r--layout/generic/crashtests/264937-1.html18
-rw-r--r--layout/generic/crashtests/265867-1.html11
-rw-r--r--layout/generic/crashtests/265867-2.html3
-rw-r--r--layout/generic/crashtests/286491.html26
-rw-r--r--layout/generic/crashtests/289864-1.html5
-rw-r--r--layout/generic/crashtests/289864-1.jpgbin0 -> 42186 bytes
-rw-r--r--layout/generic/crashtests/295292-1.html13
-rw-r--r--layout/generic/crashtests/295292-2.html23
-rw-r--r--layout/generic/crashtests/302260-1.html21
-rw-r--r--layout/generic/crashtests/307979-1.html27
-rw-r--r--layout/generic/crashtests/309322-1.html56
-rw-r--r--layout/generic/crashtests/309322-2.html56
-rw-r--r--layout/generic/crashtests/309322-3.html48
-rw-r--r--layout/generic/crashtests/309322-4.html48
-rw-r--r--layout/generic/crashtests/310556-1.xhtml21
-rw-r--r--layout/generic/crashtests/321224.xhtml6
-rw-r--r--layout/generic/crashtests/322780-1.xhtml6
-rw-r--r--layout/generic/crashtests/323381-1.html1
-rw-r--r--layout/generic/crashtests/323381-2.html1
-rw-r--r--layout/generic/crashtests/323386-1.html1
-rw-r--r--layout/generic/crashtests/323389-1.html7
-rw-r--r--layout/generic/crashtests/323389-2.html8
-rw-r--r--layout/generic/crashtests/323493-1.html16
-rw-r--r--layout/generic/crashtests/323495-1.html14
-rw-r--r--layout/generic/crashtests/324318-1.html29
-rw-r--r--layout/generic/crashtests/328946-1.html1
-rw-r--r--layout/generic/crashtests/331284-1.xhtml13
-rw-r--r--layout/generic/crashtests/331292.html258
-rw-r--r--layout/generic/crashtests/334105-1.xhtml35
-rw-r--r--layout/generic/crashtests/334107-1.xhtml9
-rw-r--r--layout/generic/crashtests/334147-1.xhtml16
-rw-r--r--layout/generic/crashtests/334148-1.xhtml14
-rw-r--r--layout/generic/crashtests/334602-1.html12
-rw-r--r--layout/generic/crashtests/337412-1.html29
-rw-r--r--layout/generic/crashtests/337883-1.html20
-rw-r--r--layout/generic/crashtests/337883-2.html21
-rw-r--r--layout/generic/crashtests/339769-1.html22
-rw-r--r--layout/generic/crashtests/342322-1.html28
-rw-r--r--layout/generic/crashtests/343206-1.xhtml21
-rw-r--r--layout/generic/crashtests/344557-1.html32
-rw-r--r--layout/generic/crashtests/345139-1.xhtml53
-rw-r--r--layout/generic/crashtests/345617-1.html8
-rw-r--r--layout/generic/crashtests/348510-1.html7
-rw-r--r--layout/generic/crashtests/348510-2.html7
-rw-r--r--layout/generic/crashtests/348887-1-inner.html21
-rw-r--r--layout/generic/crashtests/348887-1.html9
-rw-r--r--layout/generic/crashtests/350370.html42
-rw-r--r--layout/generic/crashtests/354458-1.html10
-rw-r--r--layout/generic/crashtests/354458-2.html26
-rw-r--r--layout/generic/crashtests/355426-1.html27
-rw-r--r--layout/generic/crashtests/359371-1.html66
-rw-r--r--layout/generic/crashtests/359371-2.html64
-rw-r--r--layout/generic/crashtests/360599.html25
-rw-r--r--layout/generic/crashtests/361109.html9
-rw-r--r--layout/generic/crashtests/363448.html23
-rw-r--r--layout/generic/crashtests/363722-1.html9
-rw-r--r--layout/generic/crashtests/363722-2.html10
-rw-r--r--layout/generic/crashtests/364220.html17
-rw-r--r--layout/generic/crashtests/364407-1.html44
-rw-r--r--layout/generic/crashtests/364686-1.xhtml12
-rw-r--r--layout/generic/crashtests/366021-1.xhtml24
-rw-r--r--layout/generic/crashtests/366667-1.html6
-rw-r--r--layout/generic/crashtests/366952-1.html17
-rw-r--r--layout/generic/crashtests/367246-1.html9
-rw-r--r--layout/generic/crashtests/367360.html30
-rw-r--r--layout/generic/crashtests/368330-1.html15
-rw-r--r--layout/generic/crashtests/368461-1.xhtml11
-rw-r--r--layout/generic/crashtests/368568.html14
-rw-r--r--layout/generic/crashtests/368752.html20
-rw-r--r--layout/generic/crashtests/368860-1.html12
-rw-r--r--layout/generic/crashtests/368863-1.html5
-rw-r--r--layout/generic/crashtests/369150-1.html22
-rw-r--r--layout/generic/crashtests/369150-2.html22
-rw-r--r--layout/generic/crashtests/369227-1.xhtml19
-rw-r--r--layout/generic/crashtests/369542-1.html7
-rw-r--r--layout/generic/crashtests/369542-2.html15
-rw-r--r--layout/generic/crashtests/369547-1.html16
-rw-r--r--layout/generic/crashtests/370174-1.html566
-rw-r--r--layout/generic/crashtests/370174-2.html13
-rw-r--r--layout/generic/crashtests/370174-3.html25
-rw-r--r--layout/generic/crashtests/370699-1.html14
-rw-r--r--layout/generic/crashtests/370794-1.html12
-rw-r--r--layout/generic/crashtests/370884-1.xhtml14
-rw-r--r--layout/generic/crashtests/371348-1.xhtml41
-rw-r--r--layout/generic/crashtests/371561-1.html8
-rw-r--r--layout/generic/crashtests/371566-1.xhtml13
-rw-r--r--layout/generic/crashtests/372376-1.xhtml39
-rw-r--r--layout/generic/crashtests/373859-1.html16
-rw-r--r--layout/generic/crashtests/373868-1.xhtml19
-rw-r--r--layout/generic/crashtests/375462-1.html781
-rw-r--r--layout/generic/crashtests/375831.html11
-rw-r--r--layout/generic/crashtests/376419.html28
-rw-r--r--layout/generic/crashtests/377522.html18
-rw-r--r--layout/generic/crashtests/37757-1.html1
-rw-r--r--layout/generic/crashtests/379217-1.xhtml7
-rw-r--r--layout/generic/crashtests/379217-2.xhtml10
-rw-r--r--layout/generic/crashtests/379917-1.xhtml35
-rw-r--r--layout/generic/crashtests/380012-1.html42
-rw-r--r--layout/generic/crashtests/381152-1.html11
-rw-r--r--layout/generic/crashtests/382129-1.xhtml7
-rw-r--r--layout/generic/crashtests/382131-1.html25
-rw-r--r--layout/generic/crashtests/382199-1.html8
-rw-r--r--layout/generic/crashtests/382208-1.xhtml7
-rw-r--r--layout/generic/crashtests/382262-1.html10
-rw-r--r--layout/generic/crashtests/382396-1.xhtml7
-rw-r--r--layout/generic/crashtests/383089-1.html85
-rw-r--r--layout/generic/crashtests/385265-1.xhtml13
-rw-r--r--layout/generic/crashtests/385295-1.xhtml5
-rw-r--r--layout/generic/crashtests/385344-1.html12
-rw-r--r--layout/generic/crashtests/385344-2.html10
-rw-r--r--layout/generic/crashtests/385414-1.html5
-rw-r--r--layout/generic/crashtests/385414-2.html5
-rw-r--r--layout/generic/crashtests/385426-1.html5
-rw-r--r--layout/generic/crashtests/385526.html116
-rw-r--r--layout/generic/crashtests/385681.html34
-rw-r--r--layout/generic/crashtests/386799-1.html7
-rw-r--r--layout/generic/crashtests/386807-1.html19
-rw-r--r--layout/generic/crashtests/386812-1.html23
-rw-r--r--layout/generic/crashtests/386827-1.html16
-rw-r--r--layout/generic/crashtests/387058-1.html16
-rw-r--r--layout/generic/crashtests/387058-2.html17
-rw-r--r--layout/generic/crashtests/387088-1.html5
-rw-r--r--layout/generic/crashtests/387209-1.html6
-rw-r--r--layout/generic/crashtests/387213-1.html10
-rw-r--r--layout/generic/crashtests/387215-1.xhtml15
-rw-r--r--layout/generic/crashtests/387219-1.xhtml8
-rw-r--r--layout/generic/crashtests/387233-1.html21
-rw-r--r--layout/generic/crashtests/387233-2.html18
-rw-r--r--layout/generic/crashtests/387282-1.html7
-rw-r--r--layout/generic/crashtests/388049.html43
-rw-r--r--layout/generic/crashtests/388175-1.html24
-rw-r--r--layout/generic/crashtests/388367-1.html7
-rw-r--r--layout/generic/crashtests/388709-1.html15
-rw-r--r--layout/generic/crashtests/389635-1.html14
-rw-r--r--layout/generic/crashtests/390050-1.html48
-rw-r--r--layout/generic/crashtests/390050-2.html22
-rw-r--r--layout/generic/crashtests/390050-3.html4
-rw-r--r--layout/generic/crashtests/390052.html13
-rw-r--r--layout/generic/crashtests/390417.html17
-rw-r--r--layout/generic/crashtests/390762-1.html30
-rw-r--r--layout/generic/crashtests/391053-1.xhtml16
-rw-r--r--layout/generic/crashtests/391894-1.html17
-rw-r--r--layout/generic/crashtests/392698-1.html16
-rw-r--r--layout/generic/crashtests/393758-1.xhtml10
-rw-r--r--layout/generic/crashtests/393906-1.html12
-rw-r--r--layout/generic/crashtests/393923-1.html15
-rw-r--r--layout/generic/crashtests/393956-1.html25
-rw-r--r--layout/generic/crashtests/393956-2.html26
-rw-r--r--layout/generic/crashtests/393956-3.html11
-rw-r--r--layout/generic/crashtests/393956-4.html11
-rw-r--r--layout/generic/crashtests/394237-1.html38
-rw-r--r--layout/generic/crashtests/394818-1.html13
-rw-r--r--layout/generic/crashtests/394818-2.html16
-rw-r--r--layout/generic/crashtests/394820-1.html19
-rw-r--r--layout/generic/crashtests/395316-1.html13
-rw-r--r--layout/generic/crashtests/395450-1.xhtml28
-rw-r--r--layout/generic/crashtests/397007-1.html37
-rw-r--r--layout/generic/crashtests/397187-1.html32
-rw-r--r--layout/generic/crashtests/397844-1.xhtml55
-rw-r--r--layout/generic/crashtests/397844-2.xhtml55
-rw-r--r--layout/generic/crashtests/397852-1.xhtml7
-rw-r--r--layout/generic/crashtests/398181-1.html10
-rw-r--r--layout/generic/crashtests/398181-2.html11
-rw-r--r--layout/generic/crashtests/398322-1.html17
-rw-r--r--layout/generic/crashtests/398322-2.html12
-rw-r--r--layout/generic/crashtests/398332-1.html19
-rw-r--r--layout/generic/crashtests/398332-2.html27
-rw-r--r--layout/generic/crashtests/398332-3.html4
-rw-r--r--layout/generic/crashtests/399407-1.xhtml25
-rw-r--r--layout/generic/crashtests/399412-1.html32
-rw-r--r--layout/generic/crashtests/399843-1.html64
-rw-r--r--layout/generic/crashtests/400078-1.html20
-rw-r--r--layout/generic/crashtests/400190.html63
-rw-r--r--layout/generic/crashtests/400223-1.html24
-rw-r--r--layout/generic/crashtests/400232-1.html11
-rw-r--r--layout/generic/crashtests/400244-1.html31
-rw-r--r--layout/generic/crashtests/400768-1.xhtml9
-rw-r--r--layout/generic/crashtests/400768-2.xhtml7
-rw-r--r--layout/generic/crashtests/401042-2.html5
-rw-r--r--layout/generic/crashtests/402380-1.html13
-rw-r--r--layout/generic/crashtests/402380-2.html18
-rw-r--r--layout/generic/crashtests/402872-1.html3
-rw-r--r--layout/generic/crashtests/402872-2.html2
-rw-r--r--layout/generic/crashtests/403004.html3
-rw-r--r--layout/generic/crashtests/403143-1.html19
-rw-r--r--layout/generic/crashtests/403576-1.html5
-rw-r--r--layout/generic/crashtests/404140-1.html7
-rw-r--r--layout/generic/crashtests/404146-1.html28
-rw-r--r--layout/generic/crashtests/404204-1.html7
-rw-r--r--layout/generic/crashtests/404215-1.html29
-rw-r--r--layout/generic/crashtests/404215-2.html37
-rw-r--r--layout/generic/crashtests/404215-3.html32
-rw-r--r--layout/generic/crashtests/404219-1.html30
-rw-r--r--layout/generic/crashtests/404219-2.html31
-rw-r--r--layout/generic/crashtests/404624.html7
-rw-r--r--layout/generic/crashtests/406137.html16
-rw-r--r--layout/generic/crashtests/406380.html12
-rw-r--r--layout/generic/crashtests/406902-1.html47
-rw-r--r--layout/generic/crashtests/407009-1.xhtml7
-rw-r--r--layout/generic/crashtests/408304-1.xhtml5
-rw-r--r--layout/generic/crashtests/408602-1.html12
-rw-r--r--layout/generic/crashtests/408737-1.html14
-rw-r--r--layout/generic/crashtests/408737-2.html14
-rw-r--r--layout/generic/crashtests/408749-1.xhtml1
-rw-r--r--layout/generic/crashtests/408883-1.html39
-rw-r--r--layout/generic/crashtests/410198.html8
-rw-r--r--layout/generic/crashtests/410228-1.html7
-rw-r--r--layout/generic/crashtests/410232-1.html14
-rw-r--r--layout/generic/crashtests/410595-1.html7
-rw-r--r--layout/generic/crashtests/411213-1.html9
-rw-r--r--layout/generic/crashtests/411213-2.xml8
-rw-r--r--layout/generic/crashtests/411835.html19
-rw-r--r--layout/generic/crashtests/411851-1.html8
-rw-r--r--layout/generic/crashtests/412014-1.html17
-rw-r--r--layout/generic/crashtests/412201-1.xhtml1
-rw-r--r--layout/generic/crashtests/412543-1.html17
-rw-r--r--layout/generic/crashtests/413048-1.html9
-rw-r--r--layout/generic/crashtests/413079-1.xhtml10
-rw-r--r--layout/generic/crashtests/413079-2.xhtml12
-rw-r--r--layout/generic/crashtests/413079-3.xhtml12
-rw-r--r--layout/generic/crashtests/413085-1.html23
-rw-r--r--layout/generic/crashtests/413085-2.html14
-rw-r--r--layout/generic/crashtests/413582-1.xhtml9
-rw-r--r--layout/generic/crashtests/413582-2.html9
-rw-r--r--layout/generic/crashtests/413712-1.xhtml18
-rw-r--r--layout/generic/crashtests/414061-1.html12
-rw-r--r--layout/generic/crashtests/414180-1.xhtml7
-rw-r--r--layout/generic/crashtests/414719-1.html25
-rw-r--r--layout/generic/crashtests/415685-1.html14
-rw-r--r--layout/generic/crashtests/415818.xhtml9
-rw-r--r--layout/generic/crashtests/416165.html23
-rw-r--r--layout/generic/crashtests/416264-1.html8
-rw-r--r--layout/generic/crashtests/416476-1.html2
-rw-r--r--layout/generic/crashtests/417848-1.xhtml6
-rw-r--r--layout/generic/crashtests/417902-1.html23
-rw-r--r--layout/generic/crashtests/417902-2.html28
-rw-r--r--layout/generic/crashtests/418532-1.html9
-rw-r--r--layout/generic/crashtests/418932-1.html2
-rw-r--r--layout/generic/crashtests/419352.html3
-rw-r--r--layout/generic/crashtests/420000-1.html10
-rw-r--r--layout/generic/crashtests/420718.html1
-rw-r--r--layout/generic/crashtests/421404-1.html20
-rw-r--r--layout/generic/crashtests/421671.html202
-rw-r--r--layout/generic/crashtests/422283-1.html10
-rw-r--r--layout/generic/crashtests/422301-1.html24
-rw-r--r--layout/generic/crashtests/423055-1.html10
-rw-r--r--layout/generic/crashtests/423098.html22
-rw-r--r--layout/generic/crashtests/423264-1.html19
-rw-r--r--layout/generic/crashtests/424629.html21
-rw-r--r--layout/generic/crashtests/425253-1.html5
-rw-r--r--layout/generic/crashtests/426040-1.html28
-rw-r--r--layout/generic/crashtests/426272-1.html18
-rw-r--r--layout/generic/crashtests/428263-1.html18
-rw-r--r--layout/generic/crashtests/429960-1.html17
-rw-r--r--layout/generic/crashtests/429960-2.html18
-rw-r--r--layout/generic/crashtests/429969-1.html24
-rw-r--r--layout/generic/crashtests/429981-1.html27
-rw-r--r--layout/generic/crashtests/430332-1.html17
-rw-r--r--layout/generic/crashtests/430344-1.html5
-rw-r--r--layout/generic/crashtests/430352-1.html5
-rw-r--r--layout/generic/crashtests/430744-1.html10
-rw-r--r--layout/generic/crashtests/430991.html24
-rw-r--r--layout/generic/crashtests/431260-1.html34
-rw-r--r--layout/generic/crashtests/431260-2.html26
-rw-r--r--layout/generic/crashtests/435529.html20
-rw-r--r--layout/generic/crashtests/436194-1.html18
-rw-r--r--layout/generic/crashtests/436602-1.html8
-rw-r--r--layout/generic/crashtests/436822-1.html22
-rw-r--r--layout/generic/crashtests/436823.html10
-rw-r--r--layout/generic/crashtests/436969-1.html10
-rw-r--r--layout/generic/crashtests/437156-1.html10
-rw-r--r--layout/generic/crashtests/437565-1.xhtml7
-rw-r--r--layout/generic/crashtests/437565-2.xhtml24
-rw-r--r--layout/generic/crashtests/437565-3.xhtml23
-rw-r--r--layout/generic/crashtests/438259-1.html13
-rw-r--r--layout/generic/crashtests/438266-1.html33
-rw-r--r--layout/generic/crashtests/438509-1.html80
-rw-r--r--layout/generic/crashtests/443528-1.html19
-rw-r--r--layout/generic/crashtests/444230-1.html1
-rw-r--r--layout/generic/crashtests/444484-1.html27
-rw-r--r--layout/generic/crashtests/444726-1.xhtml10
-rw-r--r--layout/generic/crashtests/444861-1.html18
-rw-r--r--layout/generic/crashtests/445288.html15
-rw-r--r--layout/generic/crashtests/448903-1.html5
-rw-r--r--layout/generic/crashtests/448996-1.html26
-rw-r--r--layout/generic/crashtests/451315-1.html5
-rw-r--r--layout/generic/crashtests/451317-1.html24
-rw-r--r--layout/generic/crashtests/451334-1.html10
-rw-r--r--layout/generic/crashtests/452157-1.html8
-rw-r--r--layout/generic/crashtests/452157-2.html39
-rw-r--r--layout/generic/crashtests/452157-3.html39
-rw-r--r--layout/generic/crashtests/453762-1.html4
-rw-r--r--layout/generic/crashtests/455171-1.html5
-rw-r--r--layout/generic/crashtests/455171-2.html7
-rw-r--r--layout/generic/crashtests/455171-3.html2
-rw-r--r--layout/generic/crashtests/455643-1.xhtml19
-rw-r--r--layout/generic/crashtests/457375.html5
-rw-r--r--layout/generic/crashtests/457380-1.html26
-rw-r--r--layout/generic/crashtests/459968.html33
-rw-r--r--layout/generic/crashtests/460910-1.xml14
-rw-r--r--layout/generic/crashtests/461294-1.html1
-rw-r--r--layout/generic/crashtests/462968.xhtml5
-rw-r--r--layout/generic/crashtests/463350-1.html17
-rw-r--r--layout/generic/crashtests/463350-2.html17
-rw-r--r--layout/generic/crashtests/463350-3.html15
-rw-r--r--layout/generic/crashtests/463741-1.html20
-rw-r--r--layout/generic/crashtests/465651-1.html45
-rw-r--r--layout/generic/crashtests/467137-1.html24
-rw-r--r--layout/generic/crashtests/467213-1.html9
-rw-r--r--layout/generic/crashtests/467487-1.html11
-rw-r--r--layout/generic/crashtests/467493-1.html7
-rw-r--r--layout/generic/crashtests/467493-2.html25
-rw-r--r--layout/generic/crashtests/467875-1.xhtml10
-rw-r--r--layout/generic/crashtests/467914-1.html3
-rw-r--r--layout/generic/crashtests/468207-1.html5
-rw-r--r--layout/generic/crashtests/468771-1.xhtml27
-rw-r--r--layout/generic/crashtests/468771-2.xhtml22
-rw-r--r--layout/generic/crashtests/469859-1.xhtml32
-rw-r--r--layout/generic/crashtests/471360.html61
-rw-r--r--layout/generic/crashtests/472587-1.xhtml28
-rw-r--r--layout/generic/crashtests/472617-1.xhtml4
-rw-r--r--layout/generic/crashtests/472774-1.html25
-rw-r--r--layout/generic/crashtests/472776-1.html20
-rw-r--r--layout/generic/crashtests/472950-1.html21
-rw-r--r--layout/generic/crashtests/473278-1.xhtml1
-rw-r--r--layout/generic/crashtests/473894-1.html6
-rw-r--r--layout/generic/crashtests/476241-1.html2
-rw-r--r--layout/generic/crashtests/477731-1.html6
-rw-r--r--layout/generic/crashtests/477928.html18
-rw-r--r--layout/generic/crashtests/478131-1.html7
-rw-r--r--layout/generic/crashtests/478170-1.html17
-rw-r--r--layout/generic/crashtests/478185-1.html61
-rw-r--r--layout/generic/crashtests/478504.html33
-rw-r--r--layout/generic/crashtests/479938-1.html23
-rw-r--r--layout/generic/crashtests/480345-1.html5
-rw-r--r--layout/generic/crashtests/481921-iframe.html12
-rw-r--r--layout/generic/crashtests/481921.html20
-rw-r--r--layout/generic/crashtests/481921.oggbin0 -> 42852 bytes
-rw-r--r--layout/generic/crashtests/489462-1.html21
-rw-r--r--layout/generic/crashtests/489477.html21
-rw-r--r--layout/generic/crashtests/489480-1.xhtml1
-rw-r--r--layout/generic/crashtests/489647-1.html13
-rw-r--r--layout/generic/crashtests/493111-1.html22
-rw-r--r--layout/generic/crashtests/493118-1.html6
-rw-r--r--layout/generic/crashtests/493649.html5
-rw-r--r--layout/generic/crashtests/494283-1.xhtml4
-rw-r--r--layout/generic/crashtests/494283-2.html6
-rw-r--r--layout/generic/crashtests/494332-1.html7
-rw-r--r--layout/generic/crashtests/495875-1.html7
-rw-r--r--layout/generic/crashtests/495875-2.html7
-rw-r--r--layout/generic/crashtests/496742.html11
-rw-r--r--layout/generic/crashtests/499138-iframe.html17
-rw-r--r--layout/generic/crashtests/499138.html18
-rw-r--r--layout/generic/crashtests/499857-1.html33
-rw-r--r--layout/generic/crashtests/499862-1.html9
-rw-r--r--layout/generic/crashtests/501535-1.html6
-rw-r--r--layout/generic/crashtests/503961-1.xhtml25
-rw-r--r--layout/generic/crashtests/503961-2.html32
-rw-r--r--layout/generic/crashtests/507566.html33
-rw-r--r--layout/generic/crashtests/508154-1.xhtml1
-rw-r--r--layout/generic/crashtests/508168-1.html6
-rw-r--r--layout/generic/crashtests/508816-1.xhtml9
-rw-r--r--layout/generic/crashtests/509749-1.html5
-rw-r--r--layout/generic/crashtests/511482.html42
-rw-r--r--layout/generic/crashtests/512724-1.html1
-rw-r--r--layout/generic/crashtests/512725-1.html6
-rw-r--r--layout/generic/crashtests/512749-1.html1
-rw-r--r--layout/generic/crashtests/513110-1.html23
-rw-r--r--layout/generic/crashtests/513110-2.xhtml5
-rw-r--r--layout/generic/crashtests/513394-1.html16
-rw-r--r--layout/generic/crashtests/514098-1.xhtml16
-rw-r--r--layout/generic/crashtests/514800-1.html4
-rw-r--r--layout/generic/crashtests/515811-1.html5
-rw-r--r--layout/generic/crashtests/517968.html6
-rw-r--r--layout/generic/crashtests/519031.xhtml6
-rw-r--r--layout/generic/crashtests/520340.html2
-rw-r--r--layout/generic/crashtests/522170-1.html1
-rw-r--r--layout/generic/crashtests/526217.html16
-rw-r--r--layout/generic/crashtests/533379-1.html16
-rw-r--r--layout/generic/crashtests/533379-2.html16
-rw-r--r--layout/generic/crashtests/534082-1.html7
-rw-r--r--layout/generic/crashtests/534366-1.html38
-rw-r--r--layout/generic/crashtests/534366-2.html42
-rw-r--r--layout/generic/crashtests/536692-1.xhtml5
-rw-r--r--layout/generic/crashtests/537645.xhtml11
-rw-r--r--layout/generic/crashtests/541277-1.html5
-rw-r--r--layout/generic/crashtests/541277-2.html5
-rw-r--r--layout/generic/crashtests/541714-1.html3
-rw-r--r--layout/generic/crashtests/541714-2.html3
-rw-r--r--layout/generic/crashtests/542136-1.html23
-rw-r--r--layout/generic/crashtests/545571-1.html8
-rw-r--r--layout/generic/crashtests/547843-1.xhtml1
-rw-r--r--layout/generic/crashtests/551635-1.html16
-rw-r--r--layout/generic/crashtests/553504-1.xhtml4
-rw-r--r--layout/generic/crashtests/564368-1.xhtml27
-rw-r--r--layout/generic/crashtests/564968.xhtml30
-rw-r--r--layout/generic/crashtests/569193-1.html6
-rw-r--r--layout/generic/crashtests/570160.html53
-rw-r--r--layout/generic/crashtests/570289-1.html1
-rw-r--r--layout/generic/crashtests/571618-1.svg1
-rw-r--r--layout/generic/crashtests/571975-1.html5
-rw-r--r--layout/generic/crashtests/571995.xhtml8
-rw-r--r--layout/generic/crashtests/574958.xhtml16
-rw-r--r--layout/generic/crashtests/578977.html11
-rw-r--r--layout/generic/crashtests/578977.xhtml10
-rw-r--r--layout/generic/crashtests/580504-1.xhtml22
-rw-r--r--layout/generic/crashtests/582793-1.html850
-rw-r--r--layout/generic/crashtests/585598-1.xhtml7
-rw-r--r--layout/generic/crashtests/586806-1.html27
-rw-r--r--layout/generic/crashtests/586806-2.html1
-rw-r--r--layout/generic/crashtests/586806-3.html9
-rw-r--r--layout/generic/crashtests/586973-1.html9
-rw-r--r--layout/generic/crashtests/589002-1.html4
-rw-r--r--layout/generic/crashtests/590404.html1
-rw-r--r--layout/generic/crashtests/591141.html7
-rw-r--r--layout/generic/crashtests/592118.html4
-rw-r--r--layout/generic/crashtests/594808-1.html7
-rw-r--r--layout/generic/crashtests/595435-1.xhtml8
-rw-r--r--layout/generic/crashtests/595740-1.html8
-rw-r--r--layout/generic/crashtests/597240-1.xhtml20
-rw-r--r--layout/generic/crashtests/600100.xhtml1
-rw-r--r--layout/generic/crashtests/603490-1.html16
-rw-r--r--layout/generic/crashtests/603510-1.html23
-rw-r--r--layout/generic/crashtests/604314-1.html16
-rw-r--r--layout/generic/crashtests/604843.html28
-rw-r--r--layout/generic/crashtests/605340.html12
-rw-r--r--layout/generic/crashtests/606642.xhtml16
-rw-r--r--layout/generic/crashtests/613455-1.svg12
-rw-r--r--layout/generic/crashtests/613629-1.xhtml14
-rw-r--r--layout/generic/crashtests/616052-1.html4
-rw-r--r--layout/generic/crashtests/619021.html5
-rw-r--r--layout/generic/crashtests/621424-1.html1
-rw-r--r--layout/generic/crashtests/621841-1.html20
-rw-r--r--layout/generic/crashtests/622596.html6
-rw-r--r--layout/generic/crashtests/641724.html315
-rw-r--r--layout/generic/crashtests/645072-1.html16
-rw-r--r--layout/generic/crashtests/645072-2.html17
-rw-r--r--layout/generic/crashtests/646561-1.html2
-rw-r--r--layout/generic/crashtests/646983-1.html6
-rw-r--r--layout/generic/crashtests/647332-1.html2
-rw-r--r--layout/generic/crashtests/650499-1.html15
-rw-r--r--layout/generic/crashtests/654002-1.html24
-rw-r--r--layout/generic/crashtests/654002-2.html26
-rw-r--r--layout/generic/crashtests/655462-1.html10
-rw-r--r--layout/generic/crashtests/656130-1.html17
-rw-r--r--layout/generic/crashtests/656130-2.html24
-rw-r--r--layout/generic/crashtests/660416.html17
-rw-r--r--layout/generic/crashtests/665853.html29
-rw-r--r--layout/generic/crashtests/667025.html22
-rw-r--r--layout/generic/crashtests/673770.html20
-rw-r--r--layout/generic/crashtests/679933-1.html13
-rw-r--r--layout/generic/crashtests/681489-1.html1
-rw-r--r--layout/generic/crashtests/682649-1.html18
-rw-r--r--layout/generic/crashtests/683702-1.xhtml24
-rw-r--r--layout/generic/crashtests/683712.html9
-rw-r--r--layout/generic/crashtests/688996-1.html18
-rw-r--r--layout/generic/crashtests/688996-2.html15
-rw-r--r--layout/generic/crashtests/691210.html5
-rw-r--r--layout/generic/crashtests/700031.xhtml9
-rw-r--r--layout/generic/crashtests/709398-1.html12
-rw-r--r--layout/generic/crashtests/718516.html70
-rw-r--r--layout/generic/crashtests/723108.html10
-rw-r--r--layout/generic/crashtests/724235.html28
-rw-r--r--layout/generic/crashtests/724978.xhtml219
-rw-r--r--layout/generic/crashtests/730559.html1
-rw-r--r--layout/generic/crashtests/734777.html2
-rw-r--r--layout/generic/crashtests/737313-1.html5
-rw-r--r--layout/generic/crashtests/737313-2.html5
-rw-r--r--layout/generic/crashtests/737313-3.html5
-rw-r--r--layout/generic/crashtests/740199-1.xhtml1
-rw-r--r--layout/generic/crashtests/742602.html6
-rw-r--r--layout/generic/crashtests/743364.html24
-rw-r--r--layout/generic/crashtests/747688.html6
-rw-r--r--layout/generic/crashtests/750066-iframe.html32
-rw-r--r--layout/generic/crashtests/750066.html34
-rw-r--r--layout/generic/crashtests/757413-2.html12
-rw-r--r--layout/generic/crashtests/757413.xhtml34
-rw-r--r--layout/generic/crashtests/762764-1.html18
-rw-r--r--layout/generic/crashtests/762902.html12
-rw-r--r--layout/generic/crashtests/765409.html25
-rw-r--r--layout/generic/crashtests/765621.html21
-rw-r--r--layout/generic/crashtests/767765.html32
-rw-r--r--layout/generic/crashtests/769120.html11
-rw-r--r--layout/generic/crashtests/769303-1.html33
-rw-r--r--layout/generic/crashtests/769303-2.html19
-rw-r--r--layout/generic/crashtests/777838.html27
-rw-r--r--layout/generic/crashtests/783228.html40
-rw-r--r--layout/generic/crashtests/784600.html17
-rw-r--r--layout/generic/crashtests/785555.html12
-rw-r--r--layout/generic/crashtests/786740-1.html31
-rw-r--r--layout/generic/crashtests/790252-1.html20
-rw-r--r--layout/generic/crashtests/790252-2.html36
-rw-r--r--layout/generic/crashtests/790260-1.html12
-rw-r--r--layout/generic/crashtests/791601.xhtml4
-rw-r--r--layout/generic/crashtests/794693.html9
-rw-r--r--layout/generic/crashtests/798020-1.html4
-rw-r--r--layout/generic/crashtests/798235-1.html8
-rw-r--r--layout/generic/crashtests/799207-1.html6
-rw-r--r--layout/generic/crashtests/799207-2.html6
-rw-r--r--layout/generic/crashtests/801268-1.html6
-rw-r--r--layout/generic/crashtests/804089-1.xhtml15
-rw-r--r--layout/generic/crashtests/807565-1.html2
-rw-r--r--layout/generic/crashtests/807565-2.html8
-rw-r--r--layout/generic/crashtests/810303.html15
-rw-r--r--layout/generic/crashtests/810726-2.html57
-rw-r--r--layout/generic/crashtests/810726.html8
-rw-r--r--layout/generic/crashtests/812822-1.html8
-rw-r--r--layout/generic/crashtests/812879-1.html6
-rw-r--r--layout/generic/crashtests/812879-2.html35
-rw-r--r--layout/generic/crashtests/812893.html15
-rw-r--r--layout/generic/crashtests/814995.html20
-rw-r--r--layout/generic/crashtests/822910.xhtml34
-rw-r--r--layout/generic/crashtests/824297-1.html12
-rw-r--r--layout/generic/crashtests/825810-1.html11
-rw-r--r--layout/generic/crashtests/825810-2.html11
-rw-r--r--layout/generic/crashtests/826483-1.html16
-rw-r--r--layout/generic/crashtests/826532-1.html15
-rw-r--r--layout/generic/crashtests/827076.html2
-rw-r--r--layout/generic/crashtests/827168-1.html12
-rw-r--r--layout/generic/crashtests/836895.html13
-rw-r--r--layout/generic/crashtests/837007.xhtml9
-rw-r--r--layout/generic/crashtests/840787.html18
-rw-r--r--layout/generic/crashtests/840818.html8
-rw-r--r--layout/generic/crashtests/842132-1.html27
-rw-r--r--layout/generic/crashtests/842166.html22
-rw-r--r--layout/generic/crashtests/844529-1.html4
-rw-r--r--layout/generic/crashtests/847130.xhtml15
-rw-r--r--layout/generic/crashtests/847208.html16
-rw-r--r--layout/generic/crashtests/847209.html16
-rw-r--r--layout/generic/crashtests/847211-1.html19
-rw-r--r--layout/generic/crashtests/849603.html47
-rw-r--r--layout/generic/crashtests/849987.html9
-rw-r--r--layout/generic/crashtests/850931.html32
-rw-r--r--layout/generic/crashtests/851396-1.html9
-rw-r--r--layout/generic/crashtests/854263-1.html27
-rw-r--r--layout/generic/crashtests/862185.html5
-rw-r--r--layout/generic/crashtests/863935.html25
-rw-r--r--layout/generic/crashtests/866547-1.html14
-rw-r--r--layout/generic/crashtests/866767-1.html7
-rw-r--r--layout/generic/crashtests/868906.html54
-rw-r--r--layout/generic/crashtests/876074-1.html20
-rw-r--r--layout/generic/crashtests/876155.html15
-rw-r--r--layout/generic/crashtests/883514-1.html18
-rw-r--r--layout/generic/crashtests/883514-2.html15
-rw-r--r--layout/generic/crashtests/885009-1.html7
-rw-r--r--layout/generic/crashtests/893496-1.html12
-rw-r--r--layout/generic/crashtests/893523.html7
-rw-r--r--layout/generic/crashtests/898871-iframe.xhtml7
-rw-r--r--layout/generic/crashtests/898871.html44
-rw-r--r--layout/generic/crashtests/898871.jpgbin0 -> 15000 bytes
-rw-r--r--layout/generic/crashtests/914501.html17
-rw-r--r--layout/generic/crashtests/914891.html9
-rw-r--r--layout/generic/crashtests/915475.xhtml5
-rw-r--r--layout/generic/crashtests/927558.html24
-rw-r--r--layout/generic/crashtests/942794-1.html20
-rw-r--r--layout/generic/crashtests/943509-1.html9
-rw-r--r--layout/generic/crashtests/944909-1.html9
-rw-r--r--layout/generic/crashtests/946167-1.html20
-rw-r--r--layout/generic/crashtests/947158-iframe.html777
-rw-r--r--layout/generic/crashtests/947158.html32
-rw-r--r--layout/generic/crashtests/949932.html13
-rw-r--r--layout/generic/crashtests/961859.html18
-rw-r--r--layout/generic/crashtests/963878.html37
-rw-r--r--layout/generic/crashtests/964078.html4
-rw-r--r--layout/generic/crashtests/970710.html40
-rw-r--r--layout/generic/crashtests/973701-1.xhtml5
-rw-r--r--layout/generic/crashtests/973701-2.xhtml6
-rw-r--r--layout/generic/crashtests/986899.html12
-rw-r--r--layout/generic/crashtests/987041-1.html7
-rw-r--r--layout/generic/crashtests/crashtests.list825
-rw-r--r--layout/generic/crashtests/details-containing-only-text.html9
-rw-r--r--layout/generic/crashtests/details-display-none-summary-1.html11
-rw-r--r--layout/generic/crashtests/details-display-none-summary-2.html12
-rw-r--r--layout/generic/crashtests/details-display-none-summary-3.html13
-rw-r--r--layout/generic/crashtests/details-open-overflow-auto.html39
-rw-r--r--layout/generic/crashtests/details-open-overflow-hidden.html39
-rw-r--r--layout/generic/crashtests/details-three-columns.html30
-rw-r--r--layout/generic/crashtests/empty.html1
-rw-r--r--layout/generic/crashtests/file_324318-1.html1
-rw-r--r--layout/generic/crashtests/first-letter-638937-1.html45
-rw-r--r--layout/generic/crashtests/first-letter-638937-2.html11
-rw-r--r--layout/generic/crashtests/flex-nested-abspos-1.html7
-rw-r--r--layout/generic/crashtests/font-inflation-762332.html2
-rw-r--r--layout/generic/crashtests/image.jpgbin0 -> 2646 bytes
-rw-r--r--layout/generic/crashtests/large-border-radius-dashed.html1
-rw-r--r--layout/generic/crashtests/large-border-radius-dashed2.html1
-rw-r--r--layout/generic/crashtests/large-border-radius-dotted.html1
-rw-r--r--layout/generic/crashtests/large-border-radius-dotted2.html1
-rw-r--r--layout/generic/crashtests/outline-on-frameset.xhtml1
-rw-r--r--layout/generic/crashtests/simple_blank.swfbin0 -> 37 bytes
-rw-r--r--layout/generic/crashtests/solidblue.pngbin0 -> 135 bytes
-rw-r--r--layout/generic/crashtests/summary-position-out-of-flow.html30
-rw-r--r--layout/generic/crashtests/text-overflow-bug666751-1.html12
-rw-r--r--layout/generic/crashtests/text-overflow-bug666751-2.html12
-rw-r--r--layout/generic/crashtests/text-overflow-bug670564.xhtml3
-rw-r--r--layout/generic/crashtests/text-overflow-bug671796.xhtml5
-rw-r--r--layout/generic/crashtests/text-overflow-bug713610.html6
-rw-r--r--layout/generic/crashtests/text-overflow-form-elements.html144
-rw-r--r--layout/generic/crashtests/text-overflow-iframe.html115
-rw-r--r--layout/generic/folder.pngbin0 -> 529 bytes
-rw-r--r--layout/generic/frame-graph.py41
-rw-r--r--layout/generic/jar.mn6
-rw-r--r--layout/generic/moz.build296
-rw-r--r--layout/generic/nsAbsoluteContainingBlock.cpp886
-rw-r--r--layout/generic/nsAbsoluteContainingBlock.h182
-rw-r--r--layout/generic/nsAtomicContainerFrame.h44
-rw-r--r--layout/generic/nsBackdropFrame.cpp79
-rw-r--r--layout/generic/nsBackdropFrame.h40
-rw-r--r--layout/generic/nsBlockDebugFlags.h26
-rw-r--r--layout/generic/nsBlockFrame.cpp8626
-rw-r--r--layout/generic/nsBlockFrame.h1118
-rw-r--r--layout/generic/nsBlockReflowContext.cpp440
-rw-r--r--layout/generic/nsBlockReflowContext.h91
-rw-r--r--layout/generic/nsCanvasFrame.cpp835
-rw-r--r--layout/generic/nsCanvasFrame.h191
-rw-r--r--layout/generic/nsColumnSetFrame.cpp1355
-rw-r--r--layout/generic/nsColumnSetFrame.h201
-rw-r--r--layout/generic/nsContainerFrame.cpp3041
-rw-r--r--layout/generic/nsContainerFrame.h1201
-rw-r--r--layout/generic/nsContainerFrameInlines.h100
-rw-r--r--layout/generic/nsDirection.h19
-rw-r--r--layout/generic/nsFirstLetterFrame.cpp459
-rw-r--r--layout/generic/nsFirstLetterFrame.h96
-rw-r--r--layout/generic/nsFlexContainerFrame.cpp6464
-rw-r--r--layout/generic/nsFlexContainerFrame.h675
-rw-r--r--layout/generic/nsFloatManager.cpp3010
-rw-r--r--layout/generic/nsFloatManager.h464
-rw-r--r--layout/generic/nsFontInflationData.cpp373
-rw-r--r--layout/generic/nsFontInflationData.h70
-rw-r--r--layout/generic/nsFrameList.cpp487
-rw-r--r--layout/generic/nsFrameList.h497
-rw-r--r--layout/generic/nsFrameSelection.cpp3113
-rw-r--r--layout/generic/nsFrameSelection.h1140
-rw-r--r--layout/generic/nsFrameSetFrame.cpp1534
-rw-r--r--layout/generic/nsFrameSetFrame.h198
-rw-r--r--layout/generic/nsFrameState.cpp109
-rw-r--r--layout/generic/nsFrameState.h74
-rw-r--r--layout/generic/nsFrameStateBits.h737
-rw-r--r--layout/generic/nsGfxScrollFrame.cpp8022
-rw-r--r--layout/generic/nsGfxScrollFrame.h1090
-rw-r--r--layout/generic/nsGridContainerFrame.cpp10261
-rw-r--r--layout/generic/nsGridContainerFrame.h662
-rw-r--r--layout/generic/nsHTMLCanvasFrame.cpp534
-rw-r--r--layout/generic/nsHTMLCanvasFrame.h93
-rw-r--r--layout/generic/nsHTMLParts.h195
-rw-r--r--layout/generic/nsIAnonymousContentCreator.h70
-rw-r--r--layout/generic/nsIFrame.cpp12724
-rw-r--r--layout/generic/nsIFrame.h5679
-rw-r--r--layout/generic/nsIFrameInlines.h189
-rw-r--r--layout/generic/nsILineIterator.cpp75
-rw-r--r--layout/generic/nsILineIterator.h140
-rw-r--r--layout/generic/nsIScrollPositionListener.h22
-rw-r--r--layout/generic/nsIScrollableFrame.h645
-rw-r--r--layout/generic/nsIStatefulFrame.h40
-rw-r--r--layout/generic/nsImageFrame.cpp2906
-rw-r--r--layout/generic/nsImageFrame.h465
-rw-r--r--layout/generic/nsImageMap.cpp879
-rw-r--r--layout/generic/nsImageMap.h112
-rw-r--r--layout/generic/nsInlineFrame.cpp1083
-rw-r--r--layout/generic/nsInlineFrame.h210
-rw-r--r--layout/generic/nsIntervalSet.cpp76
-rw-r--r--layout/generic/nsIntervalSet.h72
-rw-r--r--layout/generic/nsLeafFrame.cpp63
-rw-r--r--layout/generic/nsLeafFrame.h84
-rw-r--r--layout/generic/nsLineBox.cpp625
-rw-r--r--layout/generic/nsLineBox.h1569
-rw-r--r--layout/generic/nsLineLayout.cpp3495
-rw-r--r--layout/generic/nsLineLayout.h709
-rw-r--r--layout/generic/nsPageContentFrame.cpp434
-rw-r--r--layout/generic/nsPageContentFrame.h72
-rw-r--r--layout/generic/nsPageFrame.cpp980
-rw-r--r--layout/generic/nsPageFrame.h173
-rw-r--r--layout/generic/nsPageSequenceFrame.cpp788
-rw-r--r--layout/generic/nsPageSequenceFrame.h191
-rw-r--r--layout/generic/nsPlaceholderFrame.cpp228
-rw-r--r--layout/generic/nsPlaceholderFrame.h187
-rw-r--r--layout/generic/nsQueryFrame.h145
-rw-r--r--layout/generic/nsRubyBaseContainerFrame.cpp820
-rw-r--r--layout/generic/nsRubyBaseContainerFrame.h93
-rw-r--r--layout/generic/nsRubyBaseFrame.cpp44
-rw-r--r--layout/generic/nsRubyBaseFrame.h41
-rw-r--r--layout/generic/nsRubyContentFrame.cpp31
-rw-r--r--layout/generic/nsRubyContentFrame.h31
-rw-r--r--layout/generic/nsRubyFrame.cpp419
-rw-r--r--layout/generic/nsRubyFrame.h66
-rw-r--r--layout/generic/nsRubyTextContainerFrame.cpp164
-rw-r--r--layout/generic/nsRubyTextContainerFrame.h71
-rw-r--r--layout/generic/nsRubyTextFrame.cpp79
-rw-r--r--layout/generic/nsRubyTextFrame.h55
-rw-r--r--layout/generic/nsSplittableFrame.cpp346
-rw-r--r--layout/generic/nsSplittableFrame.h165
-rw-r--r--layout/generic/nsSubDocumentFrame.cpp1413
-rw-r--r--layout/generic/nsSubDocumentFrame.h241
-rw-r--r--layout/generic/nsTextFrame.cpp10542
-rw-r--r--layout/generic/nsTextFrame.h1075
-rw-r--r--layout/generic/nsTextFrameUtils.cpp410
-rw-r--r--layout/generic/nsTextFrameUtils.h197
-rw-r--r--layout/generic/nsTextPaintStyle.cpp580
-rw-r--r--layout/generic/nsTextPaintStyle.h167
-rw-r--r--layout/generic/nsTextRunTransformations.cpp940
-rw-r--r--layout/generic/nsTextRunTransformations.h246
-rw-r--r--layout/generic/nsVideoFrame.cpp741
-rw-r--r--layout/generic/nsVideoFrame.h142
-rw-r--r--layout/generic/test/bug1174521.html14
-rw-r--r--layout/generic/test/bug344830_testembed.svg8
-rw-r--r--layout/generic/test/bug421839-2-page.html55
-rw-r--r--layout/generic/test/bug633762_iframe.html8
-rw-r--r--layout/generic/test/chrome.toml28
-rw-r--r--layout/generic/test/file_BrokenImageReference.pngbin0 -> 253 bytes
-rw-r--r--layout/generic/test/file_Dolske.pngbin0 -> 5976 bytes
-rw-r--r--layout/generic/test/file_IconTestServer.sjs93
-rw-r--r--layout/generic/test/file_LoadingImageReference.pngbin0 -> 268 bytes
-rw-r--r--layout/generic/test/file_SlowImage.sjs45
-rw-r--r--layout/generic/test/file_SlowPage.sjs43
-rw-r--r--layout/generic/test/file_SlowTallImage.sjs21
-rw-r--r--layout/generic/test/file_bug1307853.html23
-rw-r--r--layout/generic/test/file_bug1566783.html24
-rw-r--r--layout/generic/test/file_bug448987.html50
-rw-r--r--layout/generic/test/file_bug448987_notref.html20
-rw-r--r--layout/generic/test/file_bug448987_ref.html50
-rw-r--r--layout/generic/test/file_bug449653_1.html18
-rw-r--r--layout/generic/test/file_bug449653_1_ref.html13
-rw-r--r--layout/generic/test/file_bug514732_window.xhtml95
-rw-r--r--layout/generic/test/file_bug579767_1.html10
-rw-r--r--layout/generic/test/file_bug579767_2.html10
-rw-r--r--layout/generic/test/file_reframe_for_lazy_load_image.html39
-rw-r--r--layout/generic/test/file_scroll_position_restore.html111186
-rw-r--r--layout/generic/test/file_scroll_position_restore_no_bfcache.html30
-rw-r--r--layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-1.svg13
-rw-r--r--layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-2.svg13
-rw-r--r--layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-3.svg17
-rw-r--r--layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-ref.svg5
-rw-r--r--layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-1.svg13
-rw-r--r--layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-2.svg13
-rw-r--r--layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-ref.svg5
-rw-r--r--layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg5
-rw-r--r--layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg^headers^4
-rw-r--r--layout/generic/test/file_taintedfilters_red-flood-for-feImage.svg5
-rw-r--r--layout/generic/test/frame_selection_underline-ref.xhtml46
-rw-r--r--layout/generic/test/frame_selection_underline.css51
-rw-r--r--layout/generic/test/frame_selection_underline.xhtml48
-rw-r--r--layout/generic/test/frame_visibility_in_iframe.html52
-rw-r--r--layout/generic/test/frame_visibility_in_iframe_child.html2
-rw-r--r--layout/generic/test/mochitest.toml294
-rw-r--r--layout/generic/test/page_scroll_with_fixed_pos_window.html128
-rw-r--r--layout/generic/test/slow-stylesheet.sjs19
-rw-r--r--layout/generic/test/test_backspace_delete.xhtml325
-rw-r--r--layout/generic/test/test_bug1062406.html39
-rw-r--r--layout/generic/test/test_bug1174521.html39
-rw-r--r--layout/generic/test/test_bug1198135.html89
-rw-r--r--layout/generic/test/test_bug1307853.html28
-rw-r--r--layout/generic/test/test_bug1408607.html48
-rw-r--r--layout/generic/test/test_bug1499961.html409
-rw-r--r--layout/generic/test/test_bug1566783.html8
-rw-r--r--layout/generic/test/test_bug1623764.html292
-rw-r--r--layout/generic/test/test_bug1642588.html70
-rw-r--r--layout/generic/test/test_bug1644511.html120
-rw-r--r--layout/generic/test/test_bug1655135.html62
-rw-r--r--layout/generic/test/test_bug1756831.html149
-rw-r--r--layout/generic/test/test_bug1803209.html59
-rw-r--r--layout/generic/test/test_bug240933.html69
-rw-r--r--layout/generic/test/test_bug263683.html95
-rw-r--r--layout/generic/test/test_bug288789.html117
-rw-r--r--layout/generic/test/test_bug290397.html40
-rw-r--r--layout/generic/test/test_bug323656.html51
-rw-r--r--layout/generic/test/test_bug344830.html41
-rw-r--r--layout/generic/test/test_bug348681.html490
-rw-r--r--layout/generic/test/test_bug382429.html36
-rw-r--r--layout/generic/test/test_bug384527.html34
-rw-r--r--layout/generic/test/test_bug385751.html32
-rw-r--r--layout/generic/test/test_bug389630.html32
-rw-r--r--layout/generic/test/test_bug391747.html48
-rw-r--r--layout/generic/test/test_bug392746.html71
-rw-r--r--layout/generic/test/test_bug392923.html41
-rw-r--r--layout/generic/test/test_bug394173.html33
-rw-r--r--layout/generic/test/test_bug394239.html44
-rw-r--r--layout/generic/test/test_bug402380.html36
-rw-r--r--layout/generic/test/test_bug404872.html38
-rw-r--r--layout/generic/test/test_bug405178.html51
-rw-r--r--layout/generic/test/test_bug416168.html44
-rw-r--r--layout/generic/test/test_bug421436.html31
-rw-r--r--layout/generic/test/test_bug421839-1.html80
-rw-r--r--layout/generic/test/test_bug421839-2.html35
-rw-r--r--layout/generic/test/test_bug424627.html41
-rw-r--r--layout/generic/test/test_bug438840.html52
-rw-r--r--layout/generic/test/test_bug448860.html60
-rw-r--r--layout/generic/test/test_bug448987.html72
-rw-r--r--layout/generic/test/test_bug449653.html44
-rw-r--r--layout/generic/test/test_bug460532.html58
-rw-r--r--layout/generic/test/test_bug468167.html55
-rw-r--r--layout/generic/test/test_bug469613.xhtml85
-rw-r--r--layout/generic/test/test_bug469774.xhtml72
-rw-r--r--layout/generic/test/test_bug470212.html58
-rw-r--r--layout/generic/test/test_bug488417.html59
-rw-r--r--layout/generic/test/test_bug496275.html288
-rw-r--r--layout/generic/test/test_bug503813.html43
-rw-r--r--layout/generic/test/test_bug507902.html382
-rw-r--r--layout/generic/test/test_bug508115.xhtml66
-rw-r--r--layout/generic/test/test_bug514732-2.xhtml40
-rw-r--r--layout/generic/test/test_bug522632.html32
-rw-r--r--layout/generic/test/test_bug524925.html35
-rw-r--r--layout/generic/test/test_bug579767.html78
-rw-r--r--layout/generic/test/test_bug589621.html37
-rw-r--r--layout/generic/test/test_bug589623.html36
-rw-r--r--layout/generic/test/test_bug597333.html38
-rw-r--r--layout/generic/test/test_bug632379.xhtml223
-rw-r--r--layout/generic/test/test_bug633762.html68
-rw-r--r--layout/generic/test/test_bug666225.html42
-rw-r--r--layout/generic/test/test_bug719503.html20
-rw-r--r--layout/generic/test/test_bug719515.html22
-rw-r--r--layout/generic/test/test_bug719518.html26
-rw-r--r--layout/generic/test/test_bug719523.html20
-rw-r--r--layout/generic/test/test_bug735641.html46
-rw-r--r--layout/generic/test/test_bug748961.html45
-rw-r--r--layout/generic/test/test_bug756984.html138
-rw-r--r--layout/generic/test/test_bug784410.html86
-rw-r--r--layout/generic/test/test_bug785324.html44
-rw-r--r--layout/generic/test/test_bug791616.html65
-rw-r--r--layout/generic/test/test_bug831780.html32
-rw-r--r--layout/generic/test/test_bug841361.html56
-rw-r--r--layout/generic/test/test_bug904810.html83
-rw-r--r--layout/generic/test/test_bug938772.html23
-rw-r--r--layout/generic/test/test_bug970363.html51
-rw-r--r--layout/generic/test/test_crash_on_mouse_move.html36
-rw-r--r--layout/generic/test/test_dynamic_reflow_root_disallowal.html747
-rw-r--r--layout/generic/test/test_flex_interrupt.html107
-rw-r--r--layout/generic/test/test_frame_visibility_in_iframe.html28
-rw-r--r--layout/generic/test/test_grid_track_sizing_algo_001.html1641
-rw-r--r--layout/generic/test/test_grid_track_sizing_algo_002.html1641
-rw-r--r--layout/generic/test/test_image_selection.html90
-rw-r--r--layout/generic/test/test_image_selection_2.html42
-rw-r--r--layout/generic/test/test_image_selection_3.html48
-rw-r--r--layout/generic/test/test_image_selection_in_contenteditable.html85
-rw-r--r--layout/generic/test/test_intrinsic_size_on_loading.html53
-rw-r--r--layout/generic/test/test_key_enter_open_second_summary.html28
-rw-r--r--layout/generic/test/test_key_enter_prevent_default.html31
-rw-r--r--layout/generic/test/test_key_enter_single_summary.html27
-rw-r--r--layout/generic/test/test_key_space_single_summary.html26
-rw-r--r--layout/generic/test/test_movement_by_characters.html97
-rw-r--r--layout/generic/test/test_movement_by_words.html781
-rw-r--r--layout/generic/test/test_overflow_event.html50
-rw-r--r--layout/generic/test/test_overlay_scrollbar_position.html42
-rw-r--r--layout/generic/test/test_page_scroll_with_fixed_pos.html17
-rw-r--r--layout/generic/test/test_reframe_for_lazy_load_image.html27
-rw-r--r--layout/generic/test/test_scroll_animation_restore.html128
-rw-r--r--layout/generic/test/test_scroll_behavior.html262
-rw-r--r--layout/generic/test/test_scroll_on_display_contents.html187
-rw-r--r--layout/generic/test/test_scroll_position_iframe.html37
-rw-r--r--layout/generic/test/test_scroll_position_restore.html45
-rw-r--r--layout/generic/test/test_scroll_position_restore_after_stop.html76
-rw-r--r--layout/generic/test/test_scroll_position_restore_no_bfcache.html37
-rw-r--r--layout/generic/test/test_scrollframe_abspos_interrupt.html60
-rw-r--r--layout/generic/test/test_selection_changes_with_middle_mouse_button.html335
-rw-r--r--layout/generic/test/test_selection_doubleclick.html93
-rw-r--r--layout/generic/test/test_selection_expanding.html419
-rw-r--r--layout/generic/test/test_selection_multiclick_drag.html137
-rw-r--r--layout/generic/test/test_selection_preventDefault.html173
-rw-r--r--layout/generic/test/test_selection_splitText-normalize.html172
-rw-r--r--layout/generic/test/test_selection_touchevents.html55
-rw-r--r--layout/generic/test/test_selection_tripleclick.html63
-rw-r--r--layout/generic/test/test_selection_underline.html246
-rw-r--r--layout/generic/test/test_taintedfilters.html96
1178 files changed, 279957 insertions, 0 deletions
diff --git a/layout/generic/AnonymousContentKey.h b/layout/generic/AnonymousContentKey.h
new file mode 100644
index 0000000000..531754fd20
--- /dev/null
+++ b/layout/generic/AnonymousContentKey.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* values to identify particular subtrees of native anonymous content */
+
+#ifndef mozilla_AnonymousContentKey_h
+#define mozilla_AnonymousContentKey_h
+
+#include "mozilla/TypedEnumBits.h"
+#include <stdint.h>
+#include "X11UndefineNone.h"
+
+namespace mozilla {
+
+// clang-format off
+
+// We currently use cached anonymous content styles only for scrollbar parts,
+// and we can fit the type of scrollbar part element along with its different
+// options (such as orientation, and other attribute values that can affect
+// styling) into a uint8_t.
+//
+// The lower three bits hold a Type_* value identifying the type of
+// element, and the remaining bits store Flag_* values.
+//
+// A value of 0 is used to represent an anonymous content subtree that we don't
+// cache styles for.
+enum class AnonymousContentKey : uint8_t {
+ None = 0x00,
+
+ // all
+ Type_ScrollCorner = 0x01,
+ Type_Scrollbar = 0x02,
+ Type_ScrollbarButton = 0x03,
+ Type_Slider = 0x04,
+
+ // scrollbar, scrollbarbutton, slider
+ Flag_Vertical = 0x08,
+
+ // scrollbarbutton
+ Flag_ScrollbarButton_Down = 0x10,
+ Flag_ScrollbarButton_Bottom = 0x20,
+ Flag_ScrollbarButton_Decrement = 0x40,
+};
+
+// clang-format on
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AnonymousContentKey)
+
+} // namespace mozilla
+
+#endif // mozilla_AnonymousContentKey_h
diff --git a/layout/generic/AspectRatio.cpp b/layout/generic/AspectRatio.cpp
new file mode 100644
index 0000000000..2fe51fed5e
--- /dev/null
+++ b/layout/generic/AspectRatio.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AspectRatio.h"
+
+#include "mozilla/WritingModes.h"
+
+namespace mozilla {
+
+nscoord AspectRatio::ComputeRatioDependentSize(
+ LogicalAxis aRatioDependentAxis, const WritingMode& aWM,
+ nscoord aRatioDeterminingSize,
+ const LogicalSize& aContentBoxSizeToBoxSizingAdjust) const {
+ MOZ_ASSERT(*this,
+ "Infinite or zero ratio may have undefined behavior when "
+ "computing the size");
+ const LogicalSize& boxSizingAdjust = mUseBoxSizing == UseBoxSizing::No
+ ? LogicalSize(aWM)
+ : aContentBoxSizeToBoxSizingAdjust;
+ return aRatioDependentAxis == LogicalAxis::eLogicalAxisInline
+ ? ConvertToWritingMode(aWM).ApplyTo(aRatioDeterminingSize +
+ boxSizingAdjust.BSize(aWM)) -
+ boxSizingAdjust.ISize(aWM)
+ : ConvertToWritingMode(aWM).Inverted().ApplyTo(
+ aRatioDeterminingSize + boxSizingAdjust.ISize(aWM)) -
+ boxSizingAdjust.BSize(aWM);
+}
+
+} // namespace mozilla
diff --git a/layout/generic/AspectRatio.h b/layout/generic/AspectRatio.h
new file mode 100644
index 0000000000..6f48a07da3
--- /dev/null
+++ b/layout/generic/AspectRatio.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AspectRatio_h
+#define mozilla_AspectRatio_h
+
+/* The aspect ratio of a box, in a "width / height" format. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "nsCoord.h"
+#include <algorithm>
+#include <limits>
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+
+enum LogicalAxis : uint8_t;
+class LogicalSize;
+class WritingMode;
+
+enum class UseBoxSizing : uint8_t {
+ // The aspect ratio works with content box dimensions always.
+ No,
+ // The aspect ratio works with the dimensions of the box specified by
+ // box-sizing.
+ Yes,
+};
+
+struct AspectRatio {
+ friend struct IPC::ParamTraits<mozilla::AspectRatio>;
+
+ AspectRatio() = default;
+ explicit AspectRatio(float aRatio,
+ UseBoxSizing aUseBoxSizing = UseBoxSizing::No)
+ : mRatio(std::max(aRatio, 0.0f)), mUseBoxSizing(aUseBoxSizing) {}
+
+ static AspectRatio FromSize(float aWidth, float aHeight,
+ UseBoxSizing aUseBoxSizing = UseBoxSizing::No) {
+ if (aWidth == 0.0f || aHeight == 0.0f) {
+ // For the degenerate ratio, we don't care about which box sizing we are
+ // using, so using default constructor is fine.
+ return AspectRatio();
+ }
+ return AspectRatio(aWidth / aHeight, aUseBoxSizing);
+ }
+
+ template <typename T, typename Sub, typename Coord>
+ static AspectRatio FromSize(const gfx::BaseSize<T, Sub, Coord>& aSize) {
+ return FromSize(aSize.Width(), aSize.Height());
+ }
+
+ explicit operator bool() const { return mRatio != 0.0f; }
+
+ nscoord ApplyTo(nscoord aCoord) const {
+ MOZ_DIAGNOSTIC_ASSERT(*this);
+ return NSCoordSaturatingNonnegativeMultiply(aCoord, mRatio);
+ }
+
+ float ApplyToFloat(float aFloat) const {
+ MOZ_DIAGNOSTIC_ASSERT(*this);
+ return mRatio * aFloat;
+ }
+
+ // Inverts the ratio, in order to get the height / width ratio.
+ [[nodiscard]] AspectRatio Inverted() const {
+ if (!*this) {
+ return AspectRatio();
+ }
+ // Clamp to a small epsilon, in case mRatio is absurdly large & produces
+ // 0.0f in the division here (so that valid ratios always generate other
+ // valid ratios when inverted).
+ return AspectRatio(
+ std::max(std::numeric_limits<float>::epsilon(), 1.0f / mRatio),
+ mUseBoxSizing);
+ }
+
+ [[nodiscard]] inline AspectRatio ConvertToWritingMode(
+ const WritingMode& aWM) const;
+
+ /**
+ * This method computes the ratio-dependent size by the ratio-determining size
+ * and aspect-ratio (i.e. preferred aspect ratio). Basically this function
+ * will be used in the calculation of 'auto' sizes when the preferred
+ * aspect ratio is not 'auto'.
+ *
+ * @param aRatioDependentAxis The ratio depenedent axis of the box.
+ * @param aWM The writing mode of the box.
+ * @param aRatioDetermingSize The content-box size on the ratio determining
+ * axis. Basically, we use this size and |mRatio|
+ * to compute the size on the ratio-dependent
+ * axis.
+ * @param aContentBoxSizeToBoxSizingAdjust The border padding box size
+ * adjustment. We need this because
+ * aspect-ratio should take the
+ * box-sizing into account if its
+ * style is '<ratio>'. If its style
+ * is 'auto & <ratio>', we should use
+ * content-box dimensions always.
+ * If the callers want the ratio to
+ * apply to the content-box size, we
+ * should pass a zero LogicalSize.
+ * If mUseBoxSizing is No, we ignore
+ * this parameter because we should
+ * use content box dimensions always.
+ *
+ * The return value is the content-box size on the ratio-dependent axis.
+ * Plese see the definition of the ratio-dependent axis and the
+ * ratio-determining axis in the spec:
+ * https://drafts.csswg.org/css-sizing-4/#aspect-ratio
+ */
+ [[nodiscard]] nscoord ComputeRatioDependentSize(
+ LogicalAxis aRatioDependentAxis, const WritingMode& aWM,
+ nscoord aRatioDeterminingSize,
+ const LogicalSize& aContentBoxSizeToBoxSizingAdjust) const;
+
+ bool operator==(const AspectRatio& aOther) const {
+ return mRatio == aOther.mRatio && mUseBoxSizing == aOther.mUseBoxSizing;
+ }
+
+ bool operator!=(const AspectRatio& aOther) const {
+ return !(*this == aOther);
+ }
+
+ bool operator<(const AspectRatio& aOther) const {
+ MOZ_ASSERT(
+ mUseBoxSizing == aOther.mUseBoxSizing,
+ "Do not compare AspectRatio if their mUseBoxSizing are different.");
+ return mRatio < aOther.mRatio;
+ }
+
+ private:
+ // 0.0f represents no aspect ratio.
+ float mRatio = 0.0f;
+ UseBoxSizing mUseBoxSizing = UseBoxSizing::No;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AspectRatio_h
diff --git a/layout/generic/AutoCopyListener.h b/layout/generic/AutoCopyListener.h
new file mode 100644
index 0000000000..acb0aaa46d
--- /dev/null
+++ b/layout/generic/AutoCopyListener.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AutoCopyListener_h
+#define mozilla_AutoCopyListener_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/StaticPrefs_clipboard.h"
+#include "nsIClipboard.h"
+
+namespace mozilla {
+
+class AutoCopyListener final {
+ public:
+ /**
+ * OnSelectionChange() is called when a Selection whose NotifyAutoCopy() was
+ * called is changed.
+ *
+ * @param aDocument The document of the Selection. May be nullptr.
+ * @param aSelection The selection.
+ * @param aReason The reasons of the change.
+ * See nsISelectionListener::*_REASON.
+ */
+ static void OnSelectionChange(dom::Document* aDocument,
+ dom::Selection& aSelection, int16_t aReason);
+
+ /**
+ * Init() initializes all static members of this class. Should be called
+ * only once.
+ */
+ static void Init(int16_t aClipboardID) {
+ MOZ_ASSERT(IsValidClipboardID(aClipboardID));
+ static bool sInitialized = false;
+ if (!sInitialized && IsValidClipboardID(aClipboardID)) {
+ sClipboardID = aClipboardID;
+ sInitialized = true;
+ }
+ }
+
+ /**
+ * IsPrefEnabled() returns true if the pref enables auto-copy feature.
+ */
+ static bool IsPrefEnabled() { return StaticPrefs::clipboard_autocopy(); }
+
+ private:
+ static bool IsValidClipboardID(int16_t aClipboardID) {
+ return aClipboardID >= nsIClipboard::kSelectionClipboard &&
+ aClipboardID <= nsIClipboard::kSelectionCache;
+ }
+
+ static int16_t sClipboardID;
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_AutoCopyListener_h
diff --git a/layout/generic/BRFrame.cpp b/layout/generic/BRFrame.cpp
new file mode 100644
index 0000000000..52cc3a0b46
--- /dev/null
+++ b/layout/generic/BRFrame.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for HTML <br> elements */
+
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/HTMLBRElement.h"
+#include "gfxContext.h"
+#include "nsCOMPtr.h"
+#include "nsContainerFrame.h"
+#include "nsFontMetrics.h"
+#include "nsHTMLParts.h"
+#include "nsIFrame.h"
+#include "nsPresContext.h"
+#include "nsLineLayout.h"
+#include "nsStyleConsts.h"
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+
+// FOR SELECTION
+#include "nsIContent.h"
+// END INCLUDES FOR SELECTION
+
+using namespace mozilla;
+
+namespace mozilla {
+
+class BRFrame final : public nsIFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(BRFrame)
+
+ friend nsIFrame* ::NS_NewBRFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ ContentOffsets CalcContentOffsetsFromFramePoint(
+ const nsPoint& aPoint) override;
+
+ FrameSearchResult PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) override;
+ FrameSearchResult PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset,
+ PeekOffsetCharacterOptions aOptions =
+ PeekOffsetCharacterOptions()) override;
+ FrameSearchResult PeekOffsetWord(bool aForward, bool aWordSelectEatSpace,
+ bool aIsKeyboardSelect, int32_t* aOffset,
+ PeekWordState* aState,
+ bool aTrimSpaces) override;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+ void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) override;
+ void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) override;
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const override;
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"BR"_ns, aResult);
+ }
+#endif
+
+ protected:
+ BRFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID),
+ mAscent(NS_INTRINSIC_ISIZE_UNKNOWN) {}
+
+ virtual ~BRFrame();
+
+ nscoord mAscent;
+};
+
+} // namespace mozilla
+
+nsIFrame* NS_NewBRFrame(mozilla::PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) BRFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(BRFrame)
+
+BRFrame::~BRFrame() = default;
+
+void BRFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput, nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("BRFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize finalSize(wm);
+ finalSize.BSize(wm) = 0; // BR frames with block size 0 are ignored in quirks
+ // mode by nsLineLayout::VerticalAlignFrames .
+ // However, it's not always 0. See below.
+ finalSize.ISize(wm) = 0;
+ aMetrics.SetBlockStartAscent(0);
+
+ // Only when the BR is operating in a line-layout situation will it
+ // behave like a BR. Additionally, we suppress breaks from BR inside
+ // of ruby frames. To determine if we're inside ruby, we have to rely
+ // on the *parent's* ShouldSuppressLineBreak() method, instead of our
+ // own, because we may have custom "display" value that makes our
+ // ShouldSuppressLineBreak() return false.
+ nsLineLayout* ll = aReflowInput.mLineLayout;
+ if (ll && !GetParent()->Style()->ShouldSuppressLineBreak()) {
+ // Note that the compatibility mode check excludes AlmostStandards
+ // mode, since this is the inline box model. See bug 161691.
+ if (ll->LineIsEmpty() ||
+ aPresContext->CompatibilityMode() == eCompatibility_FullStandards) {
+ // The line is logically empty; any whitespace is trimmed away.
+ //
+ // If this frame is going to terminate the line we know
+ // that nothing else will go on the line. Therefore, in this
+ // case, we provide some height for the BR frame so that it
+ // creates some vertical whitespace. It's necessary to use the
+ // line-height rather than the font size because the
+ // quirks-mode fix that doesn't apply the block's min
+ // line-height makes this necessary to make BR cause a line
+ // of the full line-height
+
+ // We also do this in strict mode because BR should act like a
+ // normal inline frame. That line-height is used is important
+ // here for cases where the line-height is less than 1.
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+ if (fm) {
+ nscoord logicalHeight = aReflowInput.GetLineHeight();
+ finalSize.BSize(wm) = logicalHeight;
+ aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline(
+ fm, logicalHeight, wm.IsLineInverted()));
+ } else {
+ aMetrics.SetBlockStartAscent(aMetrics.BSize(wm) = 0);
+ }
+
+ // XXX temporary until I figure out a better solution; see the
+ // code in nsLineLayout::VerticalAlignFrames that zaps minY/maxY
+ // if the width is zero.
+ // XXX This also fixes bug 10036!
+ // Warning: nsTextControlFrame::CalculateSizeStandard depends on
+ // the following line, see bug 228752.
+ // The code below in AddInlinePrefISize also adds 1 appunit to width
+ finalSize.ISize(wm) = 1;
+ }
+
+ // Return our reflow status
+ aStatus.SetInlineLineBreakAfter(aReflowInput.mStyleDisplay->mClear);
+ ll->SetLineEndsInBR(true);
+ }
+
+ aMetrics.SetSize(wm, finalSize);
+ aMetrics.SetOverflowAreasToDesiredBounds();
+
+ mAscent = aMetrics.BlockStartAscent();
+}
+
+/* virtual */
+void BRFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ if (!GetParent()->Style()->ShouldSuppressLineBreak()) {
+ aData->ForceBreak();
+ }
+}
+
+/* virtual */
+void BRFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
+ nsIFrame::InlinePrefISizeData* aData) {
+ if (!GetParent()->Style()->ShouldSuppressLineBreak()) {
+ // Match the 1 appunit width assigned in the Reflow method above
+ aData->mCurrentLine += 1;
+ aData->ForceBreak();
+ }
+}
+
+/* virtual */
+nscoord BRFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result = 0;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ return result;
+}
+
+/* virtual */
+nscoord BRFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result = 0;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ return result;
+}
+
+Maybe<nscoord> BRFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const {
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return Nothing{};
+ }
+ return Some(mAscent);
+}
+
+nsIFrame::ContentOffsets BRFrame::CalcContentOffsetsFromFramePoint(
+ const nsPoint& aPoint) {
+ ContentOffsets offsets;
+ offsets.content = mContent->GetParent();
+ if (offsets.content) {
+ offsets.offset = offsets.content->ComputeIndexOf_Deprecated(mContent);
+ offsets.secondaryOffset = offsets.offset;
+ offsets.associate = CaretAssociationHint::After;
+ }
+ return offsets;
+}
+
+nsIFrame::FrameSearchResult BRFrame::PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ int32_t startOffset = *aOffset;
+ // If we hit the end of a BR going backwards, go to its beginning and stay
+ // there.
+ if (!aForward && startOffset != 0) {
+ *aOffset = 0;
+ return FOUND;
+ }
+ // Otherwise, stop if we hit the beginning, continue (forward) if we hit the
+ // end.
+ return (startOffset == 0) ? FOUND : CONTINUE;
+}
+
+nsIFrame::FrameSearchResult BRFrame::PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // Keep going. The actual line jumping will stop us.
+ return CONTINUE;
+}
+
+nsIFrame::FrameSearchResult BRFrame::PeekOffsetWord(
+ bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
+ int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // Keep going. The actual line jumping will stop us.
+ return CONTINUE;
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType BRFrame::AccessibleType() {
+ dom::HTMLBRElement* brElement = dom::HTMLBRElement::FromNode(mContent);
+ if (brElement->IsPaddingForEmptyEditor() ||
+ brElement->IsPaddingForEmptyLastLine()) {
+ // This <br> is a "padding <br> element" used when there is no text or an
+ // empty last line in an editor.
+ return a11y::eNoType;
+ }
+
+ return a11y::eHTMLBRType;
+}
+#endif
diff --git a/layout/generic/BlockReflowState.cpp b/layout/generic/BlockReflowState.cpp
new file mode 100644
index 0000000000..6163a200a3
--- /dev/null
+++ b/layout/generic/BlockReflowState.cpp
@@ -0,0 +1,1067 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* state used in reflow of block frames */
+
+#include "BlockReflowState.h"
+
+#include <algorithm>
+#include "LayoutLogging.h"
+#include "nsBlockFrame.h"
+#include "nsLineLayout.h"
+#include "nsPresContext.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "TextOverflow.h"
+
+#ifdef DEBUG
+# include "nsBlockDebugFlags.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+BlockReflowState::BlockReflowState(
+ const ReflowInput& aReflowInput, nsPresContext* aPresContext,
+ nsBlockFrame* aFrame, bool aBStartMarginRoot, bool aBEndMarginRoot,
+ bool aBlockNeedsFloatManager, const nscoord aConsumedBSize,
+ const nscoord aEffectiveContentBoxBSize, const nscoord aInset)
+ : mBlock(aFrame),
+ mPresContext(aPresContext),
+ mReflowInput(aReflowInput),
+ mContentArea(aReflowInput.GetWritingMode()),
+ mInsetForBalance(aInset),
+ mContainerSize(aReflowInput.ComputedSizeAsContainerIfConstrained()),
+ mPushedFloats(nullptr),
+ mOverflowTracker(nullptr),
+ mBorderPadding(
+ mReflowInput
+ .ComputedLogicalBorderPadding(mReflowInput.GetWritingMode())
+ .ApplySkipSides(aFrame->PreReflowBlockLevelLogicalSkipSides())),
+ mMinLineHeight(aReflowInput.GetLineHeight()),
+ mLineNumber(0),
+ mTrailingClearFromPIF(StyleClear::None),
+ mConsumedBSize(aConsumedBSize) {
+ NS_ASSERTION(mConsumedBSize != NS_UNCONSTRAINEDSIZE,
+ "The consumed block-size should be constrained!");
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+
+ if (aBStartMarginRoot || 0 != mBorderPadding.BStart(wm)) {
+ mFlags.mIsBStartMarginRoot = true;
+ mFlags.mShouldApplyBStartMargin = true;
+ }
+ if (aBEndMarginRoot || 0 != mBorderPadding.BEnd(wm)) {
+ mFlags.mIsBEndMarginRoot = true;
+ }
+ if (aBlockNeedsFloatManager) {
+ mFlags.mBlockNeedsFloatManager = true;
+ }
+
+ mFlags.mCanHaveOverflowMarkers = css::TextOverflow::CanHaveOverflowMarkers(
+ mBlock, css::TextOverflow::BeforeReflow::Yes);
+
+ MOZ_ASSERT(FloatManager(),
+ "Float manager should be valid when creating BlockReflowState!");
+
+ // Save the coordinate system origin for later.
+ FloatManager()->GetTranslation(mFloatManagerI, mFloatManagerB);
+ FloatManager()->PushState(&mFloatManagerStateBefore); // never popped
+
+ mNextInFlow = static_cast<nsBlockFrame*>(mBlock->GetNextInFlow());
+
+ LAYOUT_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedISize(),
+ "have unconstrained width; this should only result "
+ "from very large sizes, not attempts at intrinsic "
+ "width calculation");
+ mContentArea.ISize(wm) = aReflowInput.ComputedISize();
+
+ // Compute content area block-size. Unlike the inline-size, if we have a
+ // specified style block-size, we ignore it since extra content is managed by
+ // the "overflow" property. When we don't have a specified style block-size,
+ // then we may end up limiting our block-size if the available block-size is
+ // constrained (this situation occurs when we are paginated).
+ if (const nscoord availableBSize = aReflowInput.AvailableBSize();
+ availableBSize != NS_UNCONSTRAINEDSIZE) {
+ // We are in a paginated situation. The block-end edge of the available
+ // space to reflow the children is within our block-end border and padding.
+ // If we're cloning our border and padding, and we're going to request
+ // additional continuations because of our excessive content-box block-size,
+ // then reserve some of our available space for our (cloned) block-end
+ // border and padding.
+ const bool reserveSpaceForBlockEndBP =
+ mReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone &&
+ (aEffectiveContentBoxBSize == NS_UNCONSTRAINEDSIZE ||
+ aEffectiveContentBoxBSize + mBorderPadding.BStartEnd(wm) >
+ availableBSize);
+ const nscoord bp = reserveSpaceForBlockEndBP ? mBorderPadding.BStartEnd(wm)
+ : mBorderPadding.BStart(wm);
+ mContentArea.BSize(wm) = std::max(0, availableBSize - bp);
+ } else {
+ // When we are not in a paginated situation, then we always use a
+ // unconstrained block-size.
+ mContentArea.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ }
+ mContentArea.IStart(wm) = mBorderPadding.IStart(wm);
+ mBCoord = mContentArea.BStart(wm) = mBorderPadding.BStart(wm);
+
+ mPrevChild = nullptr;
+ mCurrentLine = aFrame->LinesEnd();
+}
+
+void BlockReflowState::ComputeFloatAvoidingOffsets(
+ nsIFrame* aFloatAvoidingBlock, const LogicalRect& aFloatAvailableSpace,
+ nscoord& aIStartResult, nscoord& aIEndResult) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+ // The frame is clueless about the float manager and therefore we
+ // only give it free space. An example is a table frame - the
+ // tables do not flow around floats.
+ // However, we can let its margins intersect floats.
+ NS_ASSERTION(aFloatAvailableSpace.IStart(wm) >= mContentArea.IStart(wm),
+ "bad avail space rect inline-coord");
+ NS_ASSERTION(aFloatAvailableSpace.ISize(wm) == 0 ||
+ aFloatAvailableSpace.IEnd(wm) <= mContentArea.IEnd(wm),
+ "bad avail space rect inline-size");
+
+ nscoord iStartOffset, iEndOffset;
+ if (aFloatAvailableSpace.ISize(wm) == mContentArea.ISize(wm)) {
+ // We don't need to compute margins when there are no floats around.
+ iStartOffset = 0;
+ iEndOffset = 0;
+ } else {
+ const LogicalMargin frameMargin =
+ SizeComputationInput(aFloatAvoidingBlock,
+ mReflowInput.mRenderingContext, wm,
+ mContentArea.ISize(wm))
+ .ComputedLogicalMargin(wm);
+
+ nscoord iStartFloatIOffset =
+ aFloatAvailableSpace.IStart(wm) - mContentArea.IStart(wm);
+ iStartOffset = std::max(iStartFloatIOffset, frameMargin.IStart(wm)) -
+ frameMargin.IStart(wm);
+ iStartOffset = std::max(iStartOffset, 0); // in case of negative margin
+ nscoord iEndFloatIOffset =
+ mContentArea.IEnd(wm) - aFloatAvailableSpace.IEnd(wm);
+ iEndOffset =
+ std::max(iEndFloatIOffset, frameMargin.IEnd(wm)) - frameMargin.IEnd(wm);
+ iEndOffset = std::max(iEndOffset, 0); // in case of negative margin
+ }
+ aIStartResult = iStartOffset;
+ aIEndResult = iEndOffset;
+}
+
+LogicalRect BlockReflowState::ComputeBlockAvailSpace(
+ nsIFrame* aFrame, const nsFlowAreaRect& aFloatAvailableSpace,
+ bool aBlockAvoidsFloats) {
+#ifdef REALLY_NOISY_REFLOW
+ printf("CBAS frame=%p has floats %d\n", aFrame,
+ aFloatAvailableSpace.HasFloats());
+#endif
+ WritingMode wm = mReflowInput.GetWritingMode();
+ LogicalRect result(wm);
+ result.BStart(wm) = mBCoord;
+ // Note: ContentBSize() and ContentBEnd() are not our content-box size and its
+ // block-end edge. They really mean "the available block-size for children",
+ // and "the block-end edge of the available space for children".
+ result.BSize(wm) = ContentBSize() == NS_UNCONSTRAINEDSIZE
+ ? NS_UNCONSTRAINEDSIZE
+ : ContentBEnd() - mBCoord;
+ // mBCoord might be greater than ContentBEnd() if the block's top margin
+ // pushes it off the page/column. Negative available block-size can confuse
+ // other code and is nonsense in principle.
+
+ // XXX Do we really want this condition to be this restrictive (i.e.,
+ // more restrictive than it used to be)? The |else| here is allowed
+ // by the CSS spec, but only out of desperation given implementations,
+ // and the behavior it leads to is quite undesirable (it can cause
+ // things to become extremely narrow when they'd fit quite well a
+ // little bit lower). Should the else be a quirk or something that
+ // applies to a specific set of frame classes and no new ones?
+ // If we did that, then for those frames where the condition below is
+ // true but nsBlockFrame::BlockCanIntersectFloats is false,
+ // nsBlockFrame::ISizeToClearPastFloats would need to use the
+ // shrink-wrap formula, max(MinISize, min(avail width, PrefISize))
+ // rather than just using MinISize.
+ NS_ASSERTION(
+ nsBlockFrame::BlockCanIntersectFloats(aFrame) == !aBlockAvoidsFloats,
+ "unexpected replaced width");
+ if (!aBlockAvoidsFloats) {
+ if (aFloatAvailableSpace.HasFloats()) {
+ // Use the float-edge property to determine how the child block
+ // will interact with the float.
+ const nsStyleBorder* borderStyle = aFrame->StyleBorder();
+ switch (borderStyle->mFloatEdge) {
+ default:
+ case StyleFloatEdge::ContentBox: // content and only content does
+ // runaround of floats
+ // The child block will flow around the float. Therefore
+ // give it all of the available space.
+ result.IStart(wm) = mContentArea.IStart(wm);
+ result.ISize(wm) = mContentArea.ISize(wm);
+ break;
+ case StyleFloatEdge::MarginBox: {
+ // The child block's margins should be placed adjacent to,
+ // but not overlap the float.
+ result.IStart(wm) = aFloatAvailableSpace.mRect.IStart(wm);
+ result.ISize(wm) = aFloatAvailableSpace.mRect.ISize(wm);
+ } break;
+ }
+ } else {
+ // Since there are no floats present the float-edge property
+ // doesn't matter therefore give the block element all of the
+ // available space since it will flow around the float itself.
+ result.IStart(wm) = mContentArea.IStart(wm);
+ result.ISize(wm) = mContentArea.ISize(wm);
+ }
+ } else {
+ nscoord iStartOffset, iEndOffset;
+ ComputeFloatAvoidingOffsets(aFrame, aFloatAvailableSpace.mRect,
+ iStartOffset, iEndOffset);
+ result.IStart(wm) = mContentArea.IStart(wm) + iStartOffset;
+ result.ISize(wm) = mContentArea.ISize(wm) - iStartOffset - iEndOffset;
+ }
+
+#ifdef REALLY_NOISY_REFLOW
+ printf(" CBAS: result %d %d %d %d\n", result.IStart(wm), result.BStart(wm),
+ result.ISize(wm), result.BSize(wm));
+#endif
+
+ return result;
+}
+
+LogicalSize BlockReflowState::ComputeAvailableSizeForFloat() const {
+ const auto wm = mReflowInput.GetWritingMode();
+ const nscoord availBSize = ContentBSize() == NS_UNCONSTRAINEDSIZE
+ ? NS_UNCONSTRAINEDSIZE
+ : std::max(0, ContentBEnd() - mBCoord);
+ return LogicalSize(wm, ContentISize(), availBSize);
+}
+
+bool BlockReflowState::FloatAvoidingBlockFitsInAvailSpace(
+ nsIFrame* aFloatAvoidingBlock,
+ const nsFlowAreaRect& aFloatAvailableSpace) const {
+ if (!aFloatAvailableSpace.HasFloats()) {
+ // If there aren't any floats here, then we always fit.
+ // We check this before calling ISizeToClearPastFloats, which is
+ // somewhat expensive.
+ return true;
+ }
+
+ // |aFloatAvailableSpace| was computed as having a negative size, which means
+ // there are floats on both sides pushing inwards past each other, and
+ // |aFloatAvoidingBlock| would necessarily intersect a float if we put it
+ // here. So, it doesn't fit.
+ if (aFloatAvailableSpace.ISizeIsActuallyNegative()) {
+ return false;
+ }
+
+ WritingMode wm = mReflowInput.GetWritingMode();
+ nsBlockFrame::FloatAvoidingISizeToClear replacedISize =
+ nsBlockFrame::ISizeToClearPastFloats(*this, aFloatAvailableSpace.mRect,
+ aFloatAvoidingBlock);
+ // The inline-start side of the replaced element should be offset by
+ // the larger of the float intrusion or the replaced element's own
+ // start margin. The inline-end side is similar, except for Web
+ // compatibility we ignore the margin.
+ return std::max(
+ aFloatAvailableSpace.mRect.IStart(wm) - mContentArea.IStart(wm),
+ replacedISize.marginIStart) +
+ replacedISize.borderBoxISize +
+ (mContentArea.IEnd(wm) - aFloatAvailableSpace.mRect.IEnd(wm)) <=
+ mContentArea.ISize(wm);
+}
+
+nsFlowAreaRect BlockReflowState::GetFloatAvailableSpaceWithState(
+ nscoord aBCoord, ShapeType aShapeType,
+ nsFloatManager::SavedState* aState) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+#ifdef DEBUG
+ // Verify that the caller setup the coordinate system properly
+ nscoord wI, wB;
+ FloatManager()->GetTranslation(wI, wB);
+
+ NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
+ "bad coord system");
+#endif
+
+ nscoord blockSize = (mContentArea.BSize(wm) == nscoord_MAX)
+ ? nscoord_MAX
+ : std::max(mContentArea.BEnd(wm) - aBCoord, 0);
+ nsFlowAreaRect result = FloatManager()->GetFlowArea(
+ wm, aBCoord, blockSize, BandInfoType::BandFromPoint, aShapeType,
+ mContentArea, aState, ContainerSize());
+ // Keep the inline size >= 0 for compatibility with nsSpaceManager.
+ if (result.mRect.ISize(wm) < 0) {
+ result.mRect.ISize(wm) = 0;
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("%s: band=%d,%d,%d,%d hasfloats=%d\n", __func__,
+ result.mRect.IStart(wm), result.mRect.BStart(wm),
+ result.mRect.ISize(wm), result.mRect.BSize(wm), result.HasFloats());
+ }
+#endif
+ return result;
+}
+
+nsFlowAreaRect BlockReflowState::GetFloatAvailableSpaceForBSize(
+ nscoord aBCoord, nscoord aBSize, nsFloatManager::SavedState* aState) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+#ifdef DEBUG
+ // Verify that the caller setup the coordinate system properly
+ nscoord wI, wB;
+ FloatManager()->GetTranslation(wI, wB);
+
+ NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
+ "bad coord system");
+#endif
+ nsFlowAreaRect result = FloatManager()->GetFlowArea(
+ wm, aBCoord, aBSize, BandInfoType::WidthWithinHeight,
+ ShapeType::ShapeOutside, mContentArea, aState, ContainerSize());
+ // Keep the width >= 0 for compatibility with nsSpaceManager.
+ if (result.mRect.ISize(wm) < 0) {
+ result.mRect.ISize(wm) = 0;
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("%s: space=%d,%d,%d,%d hasfloats=%d\n", __func__,
+ result.mRect.IStart(wm), result.mRect.BStart(wm),
+ result.mRect.ISize(wm), result.mRect.BSize(wm), result.HasFloats());
+ }
+#endif
+ return result;
+}
+
+/*
+ * Reconstruct the vertical margin before the line |aLine| in order to
+ * do an incremental reflow that begins with |aLine| without reflowing
+ * the line before it. |aLine| may point to the fencepost at the end of
+ * the line list, and it is used this way since we (for now, anyway)
+ * always need to recover margins at the end of a block.
+ *
+ * The reconstruction involves walking backward through the line list to
+ * find any collapsed margins preceding the line that would have been in
+ * the reflow input's |mPrevBEndMargin| when we reflowed that line in
+ * a full reflow (under the rule in CSS2 that all adjacent vertical
+ * margins of blocks collapse).
+ */
+void BlockReflowState::ReconstructMarginBefore(nsLineList::iterator aLine) {
+ mPrevBEndMargin.Zero();
+ nsBlockFrame* block = mBlock;
+
+ nsLineList::iterator firstLine = block->LinesBegin();
+ for (;;) {
+ --aLine;
+ if (aLine->IsBlock()) {
+ mPrevBEndMargin = aLine->GetCarriedOutBEndMargin();
+ break;
+ }
+ if (!aLine->IsEmpty()) {
+ break;
+ }
+ if (aLine == firstLine) {
+ // If the top margin was carried out (and thus already applied),
+ // set it to zero. Either way, we're done.
+ if (!mFlags.mIsBStartMarginRoot) {
+ mPrevBEndMargin.Zero();
+ }
+ break;
+ }
+ }
+}
+
+void BlockReflowState::SetupPushedFloatList() {
+ MOZ_ASSERT(!mFlags.mIsFloatListInBlockPropertyTable == !mPushedFloats,
+ "flag mismatch");
+ if (!mFlags.mIsFloatListInBlockPropertyTable) {
+ // If we're being re-Reflow'd without our next-in-flow having been
+ // reflowed, some pushed floats from our previous reflow might
+ // still be on our pushed floats list. However, that's
+ // actually fine, since they'll all end up being stolen and
+ // reordered into the correct order again.
+ // (nsBlockFrame::ReflowDirtyLines ensures that any lines with
+ // pushed floats are reflowed.)
+ mPushedFloats = mBlock->EnsurePushedFloats();
+ mFlags.mIsFloatListInBlockPropertyTable = true;
+ }
+}
+
+void BlockReflowState::AppendPushedFloatChain(nsIFrame* aFloatCont) {
+ SetupPushedFloatList();
+ while (true) {
+ aFloatCont->AddStateBits(NS_FRAME_IS_PUSHED_FLOAT);
+ mPushedFloats->AppendFrame(mBlock, aFloatCont);
+ aFloatCont = aFloatCont->GetNextInFlow();
+ if (!aFloatCont || aFloatCont->GetParent() != mBlock) {
+ break;
+ }
+ mBlock->StealFrame(aFloatCont);
+ }
+}
+
+/**
+ * Restore information about floats into the float manager for an
+ * incremental reflow, and simultaneously push the floats by
+ * |aDeltaBCoord|, which is the amount |aLine| was pushed relative to its
+ * parent. The recovery of state is one of the things that makes
+ * incremental reflow O(N^2) and this state should really be kept
+ * around, attached to the frame tree.
+ */
+void BlockReflowState::RecoverFloats(nsLineList::iterator aLine,
+ nscoord aDeltaBCoord) {
+ WritingMode wm = mReflowInput.GetWritingMode();
+ if (aLine->HasFloats()) {
+ // Place the floats into the float manager again. Also slide
+ // them, just like the regular frames on the line.
+ for (nsIFrame* floatFrame : aLine->Floats()) {
+ if (aDeltaBCoord != 0) {
+ floatFrame->MovePositionBy(nsPoint(0, aDeltaBCoord));
+ nsContainerFrame::PositionFrameView(floatFrame);
+ nsContainerFrame::PositionChildViews(floatFrame);
+ }
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
+ nscoord tI, tB;
+ FloatManager()->GetTranslation(tI, tB);
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("RecoverFloats: tIB=%d,%d (%d,%d) ", tI, tB, mFloatManagerI,
+ mFloatManagerB);
+ floatFrame->ListTag(stdout);
+ LogicalRect region =
+ nsFloatManager::GetRegionFor(wm, floatFrame, ContainerSize());
+ printf(" aDeltaBCoord=%d region={%d,%d,%d,%d}\n", aDeltaBCoord,
+ region.IStart(wm), region.BStart(wm), region.ISize(wm),
+ region.BSize(wm));
+ }
+#endif
+ FloatManager()->AddFloat(
+ floatFrame,
+ nsFloatManager::GetRegionFor(wm, floatFrame, ContainerSize()), wm,
+ ContainerSize());
+ }
+ } else if (aLine->IsBlock()) {
+ nsBlockFrame::RecoverFloatsFor(aLine->mFirstChild, *FloatManager(), wm,
+ ContainerSize());
+ }
+}
+
+/**
+ * Everything done in this function is done O(N) times for each pass of
+ * reflow so it is O(N*M) where M is the number of incremental reflow
+ * passes. That's bad. Don't do stuff here.
+ *
+ * When this function is called, |aLine| has just been slid by |aDeltaBCoord|
+ * and the purpose of RecoverStateFrom is to ensure that the
+ * BlockReflowState is in the same state that it would have been in
+ * had the line just been reflowed.
+ *
+ * Most of the state recovery that we have to do involves floats.
+ */
+void BlockReflowState::RecoverStateFrom(nsLineList::iterator aLine,
+ nscoord aDeltaBCoord) {
+ // Make the line being recovered the current line
+ mCurrentLine = aLine;
+
+ // Place floats for this line into the float manager
+ if (aLine->HasFloats() || aLine->IsBlock()) {
+ RecoverFloats(aLine, aDeltaBCoord);
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
+ FloatManager()->List(stdout);
+ }
+#endif
+ }
+}
+
+// This is called by the line layout's AddFloat method when a
+// place-holder frame is reflowed in a line. If the float is a
+// left-most child (it's x coordinate is at the line's left margin)
+// then the float is place immediately, otherwise the float
+// placement is deferred until the line has been reflowed.
+
+// XXXldb This behavior doesn't quite fit with CSS1 and CSS2 --
+// technically we're supposed let the current line flow around the
+// float as well unless it won't fit next to what we already have.
+// But nobody else implements it that way...
+bool BlockReflowState::AddFloat(nsLineLayout* aLineLayout, nsIFrame* aFloat,
+ nscoord aAvailableISize) {
+ MOZ_ASSERT(aLineLayout, "must have line layout");
+ MOZ_ASSERT(mBlock->LinesEnd() != mCurrentLine, "null ptr");
+ MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "aFloat must be an out-of-flow frame");
+
+ MOZ_ASSERT(aFloat->GetParent(), "float must have parent");
+ MOZ_ASSERT(aFloat->GetParent()->IsBlockFrameOrSubclass(),
+ "float's parent must be block");
+ if (aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT) ||
+ aFloat->GetParent() != mBlock) {
+ MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT |
+ NS_FRAME_FIRST_REFLOW),
+ "float should be in this block unless it was marked as "
+ "pushed float, or just inserted");
+ MOZ_ASSERT(aFloat->GetParent()->FirstContinuation() ==
+ mBlock->FirstContinuation());
+ // If, in a previous reflow, the float was pushed entirely to
+ // another column/page, we need to steal it back. (We might just
+ // push it again, though.) Likewise, if that previous reflow
+ // reflowed this block but not its next continuation, we might need
+ // to steal it from our own float-continuations list.
+ //
+ // For more about pushed floats, see the comment above
+ // nsBlockFrame::DrainPushedFloats.
+ auto* floatParent = static_cast<nsBlockFrame*>(aFloat->GetParent());
+ floatParent->StealFrame(aFloat);
+
+ aFloat->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
+
+ // Appending is fine, since if a float was pushed to the next
+ // page/column, all later floats were also pushed.
+ mBlock->mFloats.AppendFrame(mBlock, aFloat);
+ }
+
+ // Because we are in the middle of reflowing a placeholder frame
+ // within a line (and possibly nested in an inline frame or two
+ // that's a child of our block) we need to restore the space
+ // manager's translation to the space that the block resides in
+ // before placing the float.
+ nscoord oI, oB;
+ FloatManager()->GetTranslation(oI, oB);
+ nscoord dI = oI - mFloatManagerI;
+ nscoord dB = oB - mFloatManagerB;
+ FloatManager()->Translate(-dI, -dB);
+
+ bool placed = false;
+
+ // Now place the float immediately if possible. Otherwise stash it
+ // away in mBelowCurrentLineFloats and place it later.
+ // If one or more floats has already been pushed to the next line,
+ // don't let this one go on the current line, since that would violate
+ // float ordering.
+ bool shouldPlaceFloatBelowCurrentLine = false;
+ if (mBelowCurrentLineFloats.IsEmpty()) {
+ // If the current line is empty, we don't impose any inline-size constraint
+ // from the line layout.
+ Maybe<nscoord> availableISizeInCurrentLine =
+ aLineLayout->LineIsEmpty() ? Nothing() : Some(aAvailableISize);
+ PlaceFloatResult result =
+ FlowAndPlaceFloat(aFloat, availableISizeInCurrentLine);
+ if (result == PlaceFloatResult::Placed) {
+ placed = true;
+ // Pass on updated available space to the current inline reflow engine
+ WritingMode wm = mReflowInput.GetWritingMode();
+ // If we have mLineBSize, we are reflowing the line again due to
+ // LineReflowStatus::RedoMoreFloats. We should use mLineBSize to query the
+ // correct available space.
+ nsFlowAreaRect floatAvailSpace =
+ mLineBSize.isNothing() ? GetFloatAvailableSpace(mBCoord)
+ : GetFloatAvailableSpaceForBSize(
+ mBCoord, mLineBSize.value(), nullptr);
+ LogicalRect availSpace(wm, floatAvailSpace.mRect.IStart(wm), mBCoord,
+ floatAvailSpace.mRect.ISize(wm),
+ floatAvailSpace.mRect.BSize(wm));
+ aLineLayout->UpdateBand(wm, availSpace, aFloat);
+ // Record this float in the current-line list
+ mCurrentLineFloats.AppendElement(aFloat);
+ } else if (result == PlaceFloatResult::ShouldPlaceInNextContinuation) {
+ (*aLineLayout->GetLine())->SetHadFloatPushed();
+ } else {
+ MOZ_ASSERT(result == PlaceFloatResult::ShouldPlaceBelowCurrentLine);
+ shouldPlaceFloatBelowCurrentLine = true;
+ }
+ } else {
+ shouldPlaceFloatBelowCurrentLine = true;
+ }
+
+ if (shouldPlaceFloatBelowCurrentLine) {
+ // Always claim to be placed; we don't know whether we fit yet, so we
+ // deal with this in PlaceBelowCurrentLineFloats
+ placed = true;
+ // This float will be placed after the line is done (it is a
+ // below-current-line float).
+ mBelowCurrentLineFloats.AppendElement(aFloat);
+ }
+
+ // Restore coordinate system
+ FloatManager()->Translate(dI, dB);
+
+ return placed;
+}
+
+bool BlockReflowState::CanPlaceFloat(
+ nscoord aFloatISize, const nsFlowAreaRect& aFloatAvailableSpace) {
+ // A float fits at a given block-dir position if there are no floats
+ // at its inline-dir position (no matter what its inline size) or if
+ // its inline size fits in the space remaining after prior floats have
+ // been placed.
+ // FIXME: We should allow overflow by up to half a pixel here (bug 21193).
+ return !aFloatAvailableSpace.HasFloats() ||
+ aFloatAvailableSpace.mRect.ISize(mReflowInput.GetWritingMode()) >=
+ aFloatISize;
+}
+
+// Return the inline-size that the float (including margins) will take up
+// in the writing mode of the containing block. If this returns
+// NS_UNCONSTRAINEDSIZE, we're dealing with an orthogonal block that
+// has block-size:auto, and we'll need to actually reflow it to find out
+// how much inline-size it will occupy in the containing block's mode.
+static nscoord FloatMarginISize(WritingMode aCBWM,
+ const ReflowInput& aFloatRI) {
+ if (aFloatRI.ComputedSize(aCBWM).ISize(aCBWM) == NS_UNCONSTRAINEDSIZE) {
+ return NS_UNCONSTRAINEDSIZE; // reflow is needed to get the true size
+ }
+ return aFloatRI.ComputedSizeWithMarginBorderPadding(aCBWM).ISize(aCBWM);
+}
+
+// A frame property that stores the last shape source / margin / etc. if there's
+// any shape, in order to invalidate the float area properly when it changes.
+//
+// TODO(emilio): This could really belong to GetRegionFor / StoreRegionFor, but
+// when I tried it was a bit awkward because of the logical -> physical
+// conversion that happens there.
+//
+// Maybe all this code could be refactored to make this cleaner, but keeping the
+// two properties separated was slightly nicer.
+struct ShapeInvalidationData {
+ StyleShapeOutside mShapeOutside{StyleShapeOutside::None()};
+ float mShapeImageThreshold = 0.0;
+ LengthPercentage mShapeMargin;
+
+ ShapeInvalidationData() = default;
+
+ explicit ShapeInvalidationData(const nsStyleDisplay& aDisplay) {
+ Update(aDisplay);
+ }
+
+ static bool IsNeeded(const nsStyleDisplay& aDisplay) {
+ return !aDisplay.mShapeOutside.IsNone();
+ }
+
+ void Update(const nsStyleDisplay& aDisplay) {
+ MOZ_ASSERT(IsNeeded(aDisplay));
+ mShapeOutside = aDisplay.mShapeOutside;
+ mShapeImageThreshold = aDisplay.mShapeImageThreshold;
+ mShapeMargin = aDisplay.mShapeMargin;
+ }
+
+ bool Matches(const nsStyleDisplay& aDisplay) const {
+ return mShapeOutside == aDisplay.mShapeOutside &&
+ mShapeImageThreshold == aDisplay.mShapeImageThreshold &&
+ mShapeMargin == aDisplay.mShapeMargin;
+ }
+};
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(ShapeInvalidationDataProperty,
+ ShapeInvalidationData)
+
+BlockReflowState::PlaceFloatResult BlockReflowState::FlowAndPlaceFloat(
+ nsIFrame* aFloat, Maybe<nscoord> aAvailableISizeInCurrentLine) {
+ MOZ_ASSERT(aFloat->GetParent() == mBlock, "Float frame has wrong parent");
+
+ WritingMode wm = mReflowInput.GetWritingMode();
+ // Save away the block-dir coordinate before placing the float. We will
+ // restore mBCoord at the end after placing the float. This is
+ // necessary because any adjustments to mBCoord during the float
+ // placement are for the float only, not for any non-floating
+ // content.
+ AutoRestore<nscoord> restoreBCoord(mBCoord);
+
+ // Whether the block-direction position available to place a float has been
+ // pushed down due to the presence of other floats.
+ auto HasFloatPushedDown = [this, &restoreBCoord]() {
+ return mBCoord != restoreBCoord.SavedValue();
+ };
+
+ // Grab the float's display information
+ const nsStyleDisplay* floatDisplay = aFloat->StyleDisplay();
+
+ // The float's old region, so we can propagate damage.
+ LogicalRect oldRegion =
+ nsFloatManager::GetRegionFor(wm, aFloat, ContainerSize());
+
+ ShapeInvalidationData* invalidationData =
+ aFloat->GetProperty(ShapeInvalidationDataProperty());
+
+ // Enforce CSS2 9.5.1 rule [2], i.e., make sure that a float isn't
+ // ``above'' another float that preceded it in the flow.
+ mBCoord = std::max(FloatManager()->LowestFloatBStart(), mBCoord);
+
+ // See if the float should clear any preceding floats...
+ // XXX We need to mark this float somehow so that it gets reflowed
+ // when floats are inserted before it.
+ if (StyleClear::None != floatDisplay->mClear) {
+ // XXXldb Does this handle vertical margins correctly?
+ auto [bCoord, result] = ClearFloats(mBCoord, floatDisplay->mClear);
+ if (result == ClearFloatsResult::FloatsPushedOrSplit) {
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+ mBCoord = bCoord;
+ }
+
+ LogicalSize availSize = ComputeAvailableSizeForFloat();
+ const WritingMode floatWM = aFloat->GetWritingMode();
+ Maybe<ReflowInput> floatRI(std::in_place, mPresContext, mReflowInput, aFloat,
+ availSize.ConvertTo(floatWM, wm));
+
+ nscoord floatMarginISize = FloatMarginISize(wm, *floatRI);
+ LogicalMargin floatMargin = floatRI->ComputedLogicalMargin(wm);
+ nsReflowStatus reflowStatus;
+
+ // If it's a floating first-letter, we need to reflow it before we
+ // know how wide it is (since we don't compute which letters are part
+ // of the first letter until reflow!).
+ // We also need to do this early reflow if FloatMarginISize returned
+ // an unconstrained inline-size, which can occur if the float had an
+ // orthogonal writing mode and 'auto' block-size (in its mode).
+ bool earlyFloatReflow =
+ aFloat->IsLetterFrame() || floatMarginISize == NS_UNCONSTRAINEDSIZE;
+ if (earlyFloatReflow) {
+ mBlock->ReflowFloat(*this, *floatRI, aFloat, reflowStatus);
+ floatMarginISize = aFloat->ISize(wm) + floatMargin.IStartEnd(wm);
+ NS_ASSERTION(reflowStatus.IsComplete(),
+ "letter frames and orthogonal floats with auto block-size "
+ "shouldn't break, and if they do now, then they're breaking "
+ "at the wrong point");
+ }
+
+ // Now we've computed the float's margin inline-size.
+ if (aAvailableISizeInCurrentLine &&
+ floatMarginISize > *aAvailableISizeInCurrentLine) {
+ // The float cannot fit in the available inline-size of the current line.
+ // Let's notify our caller to place it later.
+ return PlaceFloatResult::ShouldPlaceBelowCurrentLine;
+ }
+
+ // Find a place to place the float. The CSS2 spec doesn't want
+ // floats overlapping each other or sticking out of the containing
+ // block if possible (CSS2 spec section 9.5.1, see the rule list).
+ StyleFloat floatStyle = floatDisplay->mFloat;
+ MOZ_ASSERT(StyleFloat::Left == floatStyle || StyleFloat::Right == floatStyle,
+ "Invalid float type!");
+
+ // Are we required to place at least part of the float because we're
+ // at the top of the page (to avoid an infinite loop of pushing and
+ // breaking).
+ bool mustPlaceFloat =
+ mReflowInput.mFlags.mIsTopOfPage && IsAdjacentWithBStart();
+
+ // Get the band of available space with respect to margin box.
+ nsFlowAreaRect floatAvailableSpace =
+ GetFloatAvailableSpaceForPlacingFloat(mBCoord);
+
+ for (;;) {
+ if (mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+ floatAvailableSpace.mRect.BSize(wm) <= 0 && !mustPlaceFloat) {
+ // No space, nowhere to put anything.
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+
+ if (CanPlaceFloat(floatMarginISize, floatAvailableSpace)) {
+ // We found an appropriate place.
+ break;
+ }
+
+ // Nope. try to advance to the next band.
+ mBCoord += floatAvailableSpace.mRect.BSize(wm);
+ floatAvailableSpace = GetFloatAvailableSpaceForPlacingFloat(mBCoord);
+ mustPlaceFloat = false;
+ }
+
+ // If the float is continued, it will get the same absolute x value as its
+ // prev-in-flow
+
+ // We don't worry about the geometry of the prev in flow, let the continuation
+ // place and size itself as required.
+
+ // Assign inline and block dir coordinates to the float. We don't use
+ // LineLeft() and LineRight() here, because we would only have to
+ // convert the result back into this block's writing mode.
+ LogicalPoint floatPos(wm);
+ bool leftFloat = floatStyle == StyleFloat::Left;
+
+ if (leftFloat == wm.IsBidiLTR()) {
+ floatPos.I(wm) = floatAvailableSpace.mRect.IStart(wm);
+ } else {
+ floatPos.I(wm) = floatAvailableSpace.mRect.IEnd(wm) - floatMarginISize;
+ }
+ // CSS2 spec, 9.5.1 rule [4]: "A floating box's outer top may not
+ // be higher than the top of its containing block." (Since the
+ // containing block is the content edge of the block box, this
+ // means the margin edge of the float can't be higher than the
+ // content edge of the block that contains it.)
+ floatPos.B(wm) = std::max(mBCoord, ContentBStart());
+
+ // Reflow the float after computing its vertical position so it knows
+ // where to break.
+ if (!earlyFloatReflow) {
+ const LogicalSize oldAvailSize = availSize;
+ availSize = ComputeAvailableSizeForFloat();
+ if (oldAvailSize != availSize) {
+ floatRI.reset();
+ floatRI.emplace(mPresContext, mReflowInput, aFloat,
+ availSize.ConvertTo(floatWM, wm));
+ }
+ // Normally the mIsTopOfPage state is copied from the parent reflow input.
+ // However, when reflowing a float, if we've placed other floats that force
+ // this float being pushed down, we should unset the mIsTopOfPage bit.
+ if (floatRI->mFlags.mIsTopOfPage && HasFloatPushedDown()) {
+ // HasFloatPushedDown() implies that we increased mBCoord, and we
+ // should've turned off mustPlaceFloat when we did that.
+ NS_ASSERTION(!mustPlaceFloat,
+ "mustPlaceFloat shouldn't be set if we're not at the "
+ "top-of-page!");
+ floatRI->mFlags.mIsTopOfPage = false;
+ }
+ mBlock->ReflowFloat(*this, *floatRI, aFloat, reflowStatus);
+ }
+ if (aFloat->GetPrevInFlow()) {
+ floatMargin.BStart(wm) = 0;
+ }
+ if (reflowStatus.IsIncomplete()) {
+ floatMargin.BEnd(wm) = 0;
+ }
+
+ // If the float cannot fit (e.g. via fragmenting itself if applicable), or if
+ // we're forced to break before it for CSS break-* reasons, then it needs to
+ // be pushed in its entirety to the next column/page.
+ //
+ // Note we use the available block-size in floatRI rather than use
+ // availSize.BSize() because nsBlockReflowContext::ReflowBlock() might adjust
+ // floatRI's available size.
+ const nscoord availBSize = floatRI->AvailableSize(floatWM).BSize(floatWM);
+ const bool isTruncated =
+ availBSize != NS_UNCONSTRAINEDSIZE && aFloat->BSize(floatWM) > availBSize;
+ if ((!floatRI->mFlags.mIsTopOfPage && isTruncated) ||
+ reflowStatus.IsInlineBreakBefore()) {
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+
+ // We can't use aFloat->ShouldAvoidBreakInside(mReflowInput) here since
+ // its mIsTopOfPage may be true even though the float isn't at the
+ // top when floatPos.B(wm) > 0.
+ if (ContentBSize() != NS_UNCONSTRAINEDSIZE && !mustPlaceFloat &&
+ (!mReflowInput.mFlags.mIsTopOfPage || floatPos.B(wm) > 0) &&
+ StyleBreakWithin::Avoid == aFloat->StyleDisplay()->mBreakInside &&
+ (!reflowStatus.IsFullyComplete() ||
+ aFloat->BSize(wm) + floatMargin.BStartEnd(wm) >
+ ContentBEnd() - floatPos.B(wm)) &&
+ !aFloat->GetPrevInFlow()) {
+ PushFloatPastBreak(aFloat);
+ return PlaceFloatResult::ShouldPlaceInNextContinuation;
+ }
+
+ // Calculate the actual origin of the float frame's border rect
+ // relative to the parent block; the margin must be added in
+ // to get the border rect
+ LogicalPoint origin(wm, floatMargin.IStart(wm) + floatPos.I(wm),
+ floatMargin.BStart(wm) + floatPos.B(wm));
+
+ // If float is relatively positioned, factor that in as well
+ const LogicalMargin floatOffsets = floatRI->ComputedLogicalOffsets(wm);
+ ReflowInput::ApplyRelativePositioning(aFloat, wm, floatOffsets, &origin,
+ ContainerSize());
+
+ // Position the float and make sure and views are properly
+ // positioned. We need to explicitly position its child views as
+ // well, since we're moving the float after flowing it.
+ bool moved = aFloat->GetLogicalPosition(wm, ContainerSize()) != origin;
+ if (moved) {
+ aFloat->SetPosition(wm, origin, ContainerSize());
+ nsContainerFrame::PositionFrameView(aFloat);
+ nsContainerFrame::PositionChildViews(aFloat);
+ }
+
+ // Update the float combined area state
+ // XXX Floats should really just get invalidated here if necessary
+ mFloatOverflowAreas.UnionWith(aFloat->GetOverflowAreasRelativeToParent());
+
+ // Place the float in the float manager
+ // calculate region
+ LogicalRect region = nsFloatManager::CalculateRegionFor(
+ wm, aFloat, floatMargin, ContainerSize());
+ // if the float split, then take up all of the vertical height
+ if (reflowStatus.IsIncomplete() && (NS_UNCONSTRAINEDSIZE != ContentBSize())) {
+ region.BSize(wm) =
+ std::max(region.BSize(wm), ContentBSize() - floatPos.B(wm));
+ }
+ FloatManager()->AddFloat(aFloat, region, wm, ContainerSize());
+
+ // store region
+ nsFloatManager::StoreRegionFor(wm, aFloat, region, ContainerSize());
+
+ const bool invalidationDataNeeded =
+ ShapeInvalidationData::IsNeeded(*floatDisplay);
+
+ // If the float's dimensions or shape have changed, note the damage in the
+ // float manager.
+ if (!region.IsEqualEdges(oldRegion) ||
+ !!invalidationData != invalidationDataNeeded ||
+ (invalidationData && !invalidationData->Matches(*floatDisplay))) {
+ // XXXwaterson conservative: we could probably get away with noting
+ // less damage; e.g., if only height has changed, then only note the
+ // area into which the float has grown or from which the float has
+ // shrunk.
+ nscoord blockStart = std::min(region.BStart(wm), oldRegion.BStart(wm));
+ nscoord blockEnd = std::max(region.BEnd(wm), oldRegion.BEnd(wm));
+ FloatManager()->IncludeInDamage(blockStart, blockEnd);
+ }
+
+ if (invalidationDataNeeded) {
+ if (invalidationData) {
+ invalidationData->Update(*floatDisplay);
+ } else {
+ aFloat->SetProperty(ShapeInvalidationDataProperty(),
+ new ShapeInvalidationData(*floatDisplay));
+ }
+ } else if (invalidationData) {
+ invalidationData = nullptr;
+ aFloat->RemoveProperty(ShapeInvalidationDataProperty());
+ }
+
+ if (!reflowStatus.IsFullyComplete()) {
+ mBlock->SplitFloat(*this, aFloat, reflowStatus);
+ } else {
+ MOZ_ASSERT(!aFloat->GetNextInFlow());
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyFloatManager) {
+ nscoord tI, tB;
+ FloatManager()->GetTranslation(tI, tB);
+ mBlock->ListTag(stdout);
+ printf(": FlowAndPlaceFloat: AddFloat: tIB=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
+ tI, tB, mFloatManagerI, mFloatManagerB, region.IStart(wm),
+ region.BStart(wm), region.ISize(wm), region.BSize(wm));
+ }
+
+ if (nsBlockFrame::gNoisyReflow) {
+ nsRect r = aFloat->GetRect();
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("placed float: ");
+ aFloat->ListTag(stdout);
+ printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height);
+ }
+#endif
+
+ return PlaceFloatResult::Placed;
+}
+
+void BlockReflowState::PushFloatPastBreak(nsIFrame* aFloat) {
+ // This ensures that we:
+ // * don't try to place later but smaller floats (which CSS says
+ // must have their tops below the top of this float)
+ // * don't waste much time trying to reflow this float again until
+ // after the break
+ StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
+ if (floatStyle == StyleFloat::Left) {
+ FloatManager()->SetPushedLeftFloatPastBreak();
+ } else {
+ MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float value!");
+ FloatManager()->SetPushedRightFloatPastBreak();
+ }
+
+ // Put the float on the pushed floats list, even though it
+ // isn't actually a continuation.
+ mBlock->StealFrame(aFloat);
+ AppendPushedFloatChain(aFloat);
+ mReflowStatus.SetOverflowIncomplete();
+}
+
+/**
+ * Place below-current-line floats.
+ */
+void BlockReflowState::PlaceBelowCurrentLineFloats(nsLineBox* aLine) {
+ MOZ_ASSERT(!mBelowCurrentLineFloats.IsEmpty());
+ nsTArray<nsIFrame*> floatsPlacedInLine;
+ for (nsIFrame* f : mBelowCurrentLineFloats) {
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("placing bcl float: ");
+ f->ListTag(stdout);
+ printf("\n");
+ }
+#endif
+ // Place the float
+ PlaceFloatResult result = FlowAndPlaceFloat(f);
+ MOZ_ASSERT(result != PlaceFloatResult::ShouldPlaceBelowCurrentLine,
+ "We are already dealing with below current line floats!");
+ if (result == PlaceFloatResult::Placed) {
+ floatsPlacedInLine.AppendElement(f);
+ }
+ }
+ if (floatsPlacedInLine.Length() != mBelowCurrentLineFloats.Length()) {
+ // We have some floats having ShouldPlaceInNextContinuation result.
+ aLine->SetHadFloatPushed();
+ }
+ aLine->AppendFloats(std::move(floatsPlacedInLine));
+ mBelowCurrentLineFloats.Clear();
+}
+
+std::tuple<nscoord, BlockReflowState::ClearFloatsResult>
+BlockReflowState::ClearFloats(nscoord aBCoord, StyleClear aClearType,
+ nsIFrame* aFloatAvoidingBlock) {
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("clear floats: in: aBCoord=%d\n", aBCoord);
+ }
+#endif
+
+ if (!FloatManager()->HasAnyFloats()) {
+ return {aBCoord, ClearFloatsResult::BCoordNoChange};
+ }
+
+ nscoord newBCoord = aBCoord;
+
+ if (aClearType != StyleClear::None) {
+ newBCoord = FloatManager()->ClearFloats(newBCoord, aClearType);
+
+ if (FloatManager()->ClearContinues(aClearType)) {
+ return {newBCoord, ClearFloatsResult::FloatsPushedOrSplit};
+ }
+ }
+
+ if (aFloatAvoidingBlock) {
+ for (;;) {
+ nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(newBCoord);
+ if (FloatAvoidingBlockFitsInAvailSpace(aFloatAvoidingBlock,
+ floatAvailableSpace)) {
+ break;
+ }
+ // See the analogous code for inlines in
+ // nsBlockFrame::DoReflowInlineFrames
+ if (!AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
+ // Stop trying to clear here; we'll just get pushed to the
+ // next column or page and try again there.
+ break;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("clear floats: out: y=%d\n", newBCoord);
+ }
+#endif
+
+ ClearFloatsResult result = newBCoord == aBCoord
+ ? ClearFloatsResult::BCoordNoChange
+ : ClearFloatsResult::BCoordAdvanced;
+ return {newBCoord, result};
+}
diff --git a/layout/generic/BlockReflowState.h b/layout/generic/BlockReflowState.h
new file mode 100644
index 0000000000..1dfaa4d9ad
--- /dev/null
+++ b/layout/generic/BlockReflowState.h
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* state used in reflow of block frames */
+
+#ifndef BlockReflowState_h
+#define BlockReflowState_h
+
+#include <tuple>
+
+#include "mozilla/ReflowInput.h"
+#include "nsFloatManager.h"
+#include "nsLineBox.h"
+
+class nsBlockFrame;
+class nsFrameList;
+class nsOverflowContinuationTracker;
+
+namespace mozilla {
+
+// BlockReflowState contains additional reflow input information that the
+// block frame uses along with ReflowInput. Like ReflowInput, this
+// is read-only data that is passed down from a parent frame to its children.
+class BlockReflowState {
+ using BandInfoType = nsFloatManager::BandInfoType;
+ using ShapeType = nsFloatManager::ShapeType;
+
+ // Block reflow input flags.
+ struct Flags {
+ Flags()
+ : mIsBStartMarginRoot(false),
+ mIsBEndMarginRoot(false),
+ mShouldApplyBStartMargin(false),
+ mHasLineAdjacentToTop(false),
+ mBlockNeedsFloatManager(false),
+ mIsLineLayoutEmpty(false),
+ mIsFloatListInBlockPropertyTable(false),
+ mCanHaveOverflowMarkers(false) {}
+
+ // Set in the BlockReflowState constructor when reflowing a "block margin
+ // root" frame (i.e. a frame with any of the NS_BLOCK_BFC_STATE_BITS flag
+ // set, for which margins apply by default).
+ //
+ // The flag is also set when reflowing a frame whose computed BStart border
+ // padding is non-zero.
+ bool mIsBStartMarginRoot : 1;
+
+ // Set in the BlockReflowState constructor when reflowing a "block margin
+ // root" frame (i.e. a frame with any of the NS_BLOCK_BFC_STATE_BITS flag
+ // set, for which margins apply by default).
+ //
+ // The flag is also set when reflowing a frame whose computed BEnd border
+ // padding is non-zero.
+ bool mIsBEndMarginRoot : 1;
+
+ // Set if the BStart margin should be considered when placing a linebox that
+ // contains a block frame. It may be set as a side-effect of calling
+ // nsBlockFrame::ShouldApplyBStartMargin(); once set,
+ // ShouldApplyBStartMargin() uses it as a fast-path way to return whether
+ // the BStart margin should apply.
+ //
+ // If the flag hasn't been set in the block reflow state, then
+ // ShouldApplyBStartMargin() will crawl the line list to see if a block
+ // frame precedes the specified frame. If so, the BStart margin should be
+ // applied, and the flag is set to cache the result. (If not, the BStart
+ // margin will be applied as a result of the generational margin collapsing
+ // logic in nsBlockReflowContext::ComputeCollapsedBStartMargin(). In this
+ // case, the flag won't be set, so subsequent calls to
+ // ShouldApplyBStartMargin() will continue crawl the line list.)
+ //
+ // This flag is also set in the BlockReflowState constructor if
+ // mIsBStartMarginRoot is set; that is, the frame being reflowed is a margin
+ // root by default.
+ bool mShouldApplyBStartMargin : 1;
+
+ // Set when mLineAdjacentToTop is valid.
+ bool mHasLineAdjacentToTop : 1;
+
+ // Set when the block has the equivalent of NS_BLOCK_*_BFC.
+ bool mBlockNeedsFloatManager : 1;
+
+ // Set when nsLineLayout::LineIsEmpty was true at the end of reflowing
+ // the current line.
+ bool mIsLineLayoutEmpty : 1;
+
+ // Set when our mPushedFloats list is stored on the block's property table.
+ bool mIsFloatListInBlockPropertyTable : 1;
+
+ // Set when we need text-overflow or -webkit-line-clamp processing.
+ bool mCanHaveOverflowMarkers : 1;
+ };
+
+ public:
+ BlockReflowState(const ReflowInput& aReflowInput, nsPresContext* aPresContext,
+ nsBlockFrame* aFrame, bool aBStartMarginRoot,
+ bool aBEndMarginRoot, bool aBlockNeedsFloatManager,
+ const nscoord aConsumedBSize,
+ const nscoord aEffectiveContentBoxBSize,
+ const nscoord aInset = 0);
+
+ /**
+ * Get the available reflow space (the area not occupied by floats)
+ * for the current y coordinate. The available space is relative to
+ * our coordinate system, which is the content box, with (0, 0) in the
+ * upper left.
+ *
+ * Returns whether there are floats present at the given block-direction
+ * coordinate and within the inline size of the content rect.
+ *
+ * Note: some codepaths clamp this structure's inline-size to be >=0 "for
+ * compatibility with nsSpaceManager". So if you encounter a nsFlowAreaRect
+ * which appears to have an ISize of 0, you can't necessarily assume that a
+ * 0-ISize float-avoiding block would actually fit; you need to check the
+ * InitialISizeIsNegative flag to see whether that 0 is actually a clamped
+ * negative value (in which case a 0-ISize float-avoiding block *should not*
+ * be considered as fitting, because it would intersect some float).
+ */
+ nsFlowAreaRect GetFloatAvailableSpace() const {
+ return GetFloatAvailableSpace(mBCoord);
+ }
+ nsFlowAreaRect GetFloatAvailableSpaceForPlacingFloat(nscoord aBCoord) const {
+ return GetFloatAvailableSpaceWithState(aBCoord, ShapeType::Margin, nullptr);
+ }
+ nsFlowAreaRect GetFloatAvailableSpace(nscoord aBCoord) const {
+ return GetFloatAvailableSpaceWithState(aBCoord, ShapeType::ShapeOutside,
+ nullptr);
+ }
+ nsFlowAreaRect GetFloatAvailableSpaceWithState(
+ nscoord aBCoord, ShapeType aShapeType,
+ nsFloatManager::SavedState* aState) const;
+ nsFlowAreaRect GetFloatAvailableSpaceForBSize(
+ nscoord aBCoord, nscoord aBSize,
+ nsFloatManager::SavedState* aState) const;
+
+ // @return true if AddFloat was able to place the float; false if the float
+ // did not fit in available space.
+ //
+ // Note: if it returns false, then the float's position and size should be
+ // considered stale/invalid (until the float is successfully placed).
+ bool AddFloat(nsLineLayout* aLineLayout, nsIFrame* aFloat,
+ nscoord aAvailableISize);
+
+ enum class PlaceFloatResult : uint8_t {
+ Placed,
+ ShouldPlaceBelowCurrentLine,
+ ShouldPlaceInNextContinuation,
+ };
+ // @param aAvailableISizeInCurrentLine the available inline-size of the
+ // current line if current line is not empty.
+ PlaceFloatResult FlowAndPlaceFloat(
+ nsIFrame* aFloat, mozilla::Maybe<nscoord> aAvailableISizeInCurrentLine =
+ mozilla::Nothing());
+
+ void PlaceBelowCurrentLineFloats(nsLineBox* aLine);
+
+ // Returns the first coordinate >= aBCoord that clears the
+ // floats indicated by aClearType and has enough inline size between floats
+ // (or no floats remaining) to accomodate aFloatAvoidingBlock.
+ enum class ClearFloatsResult : uint8_t {
+ BCoordNoChange,
+ BCoordAdvanced,
+ FloatsPushedOrSplit,
+ };
+ std::tuple<nscoord, ClearFloatsResult> ClearFloats(
+ nscoord aBCoord, StyleClear aClearType,
+ nsIFrame* aFloatAvoidingBlock = nullptr);
+
+ nsFloatManager* FloatManager() const {
+ MOZ_ASSERT(mReflowInput.mFloatManager,
+ "Float manager should be valid during the lifetime of "
+ "BlockReflowState!");
+ return mReflowInput.mFloatManager;
+ }
+
+ // Advances to the next band, i.e., the next horizontal stripe in
+ // which there is a different set of floats.
+ // Return false if it did not advance, which only happens for
+ // constrained heights (and means that we should get pushed to the
+ // next column/page).
+ bool AdvanceToNextBand(const LogicalRect& aFloatAvailableSpace,
+ nscoord* aBCoord) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+ if (aFloatAvailableSpace.BSize(wm) > 0) {
+ // See if there's room in the next band.
+ *aBCoord += aFloatAvailableSpace.BSize(wm);
+ } else {
+ if (mReflowInput.AvailableHeight() != NS_UNCONSTRAINEDSIZE) {
+ // Stop trying to clear here; we'll just get pushed to the
+ // next column or page and try again there.
+ return false;
+ }
+ MOZ_ASSERT_UNREACHABLE("avail space rect with zero height!");
+ *aBCoord += 1;
+ }
+ return true;
+ }
+
+ bool FloatAvoidingBlockFitsInAvailSpace(
+ nsIFrame* aFloatAvoidingBlock,
+ const nsFlowAreaRect& aFloatAvailableSpace) const;
+
+ // True if the current block-direction coordinate, for placing the children
+ // within the content area, is still adjacent with the block-start of the
+ // content area.
+ bool IsAdjacentWithBStart() const { return mBCoord == ContentBStart(); }
+
+ const LogicalMargin& BorderPadding() const { return mBorderPadding; }
+
+ // Reconstruct the previous block-end margin that goes before |aLine|.
+ void ReconstructMarginBefore(nsLineList::iterator aLine);
+
+ // Caller must have called GetFloatAvailableSpace for the correct position
+ // (which need not be the current mBCoord).
+ void ComputeFloatAvoidingOffsets(nsIFrame* aFloatAvoidingBlock,
+ const LogicalRect& aFloatAvailableSpace,
+ nscoord& aIStartResult,
+ nscoord& aIEndResult) const;
+
+ // Compute the amount of available space for reflowing a block frame at the
+ // current block-direction coordinate mBCoord. Caller must have called
+ // GetFloatAvailableSpace for the current mBCoord.
+ LogicalRect ComputeBlockAvailSpace(nsIFrame* aFrame,
+ const nsFlowAreaRect& aFloatAvailableSpace,
+ bool aBlockAvoidsFloats);
+
+ LogicalSize ComputeAvailableSizeForFloat() const;
+
+ void RecoverStateFrom(nsLineList::iterator aLine, nscoord aDeltaBCoord);
+
+ void AdvanceToNextLine() {
+ if (mFlags.mIsLineLayoutEmpty) {
+ mFlags.mIsLineLayoutEmpty = false;
+ } else {
+ mLineNumber++;
+ }
+ }
+
+ //----------------------------------------
+
+ // This state is the "global" state computed once for the reflow of
+ // the block.
+
+ // The block frame that is using this object
+ nsBlockFrame* const mBlock;
+
+ nsPresContext* const mPresContext;
+
+ const ReflowInput& mReflowInput;
+
+ // The coordinates within the float manager where the block is being
+ // placed <b>after</b> taking into account the blocks border and
+ // padding. This, therefore, represents the inner "content area" (in
+ // float manager coordinates) where child frames will be placed,
+ // including child blocks and floats.
+ nscoord mFloatManagerI, mFloatManagerB;
+
+ // XXX get rid of this
+ nsReflowStatus mReflowStatus;
+
+ // The float manager state as it was before the contents of this
+ // block. This is needed for positioning bullets, since we only want
+ // to move the bullet to flow around floats that were before this
+ // block, not floats inside of it.
+ nsFloatManager::SavedState mFloatManagerStateBefore;
+
+ // The content area to reflow child frames within. This is within
+ // this frame's coordinate system and writing mode, which means
+ // mContentArea.IStart == BorderPadding().IStart and
+ // mContentArea.BStart == BorderPadding().BStart.
+ // The block size may be NS_UNCONSTRAINEDSIZE, which indicates that there
+ // is no page/column boundary below (the common case).
+ // mContentArea.BEnd() should only be called after checking that
+ // mContentArea.BSize is not NS_UNCONSTRAINEDSIZE; otherwise
+ // coordinate overflow may occur.
+ LogicalRect mContentArea;
+ nscoord ContentIStart() const {
+ return mContentArea.IStart(mReflowInput.GetWritingMode());
+ }
+ nscoord ContentISize() const {
+ return mContentArea.ISize(mReflowInput.GetWritingMode());
+ }
+ nscoord ContentIEnd() const {
+ return mContentArea.IEnd(mReflowInput.GetWritingMode());
+ }
+ nscoord ContentBStart() const {
+ return mContentArea.BStart(mReflowInput.GetWritingMode());
+ }
+ nscoord ContentBSize() const {
+ return mContentArea.BSize(mReflowInput.GetWritingMode());
+ }
+ nscoord ContentBEnd() const {
+ NS_ASSERTION(
+ ContentBSize() != NS_UNCONSTRAINEDSIZE,
+ "ContentBSize() is unconstrained, so ContentBEnd() may overflow.");
+ return mContentArea.BEnd(mReflowInput.GetWritingMode());
+ }
+ LogicalSize ContentSize(WritingMode aWM) const {
+ WritingMode wm = mReflowInput.GetWritingMode();
+ return mContentArea.Size(wm).ConvertTo(aWM, wm);
+ }
+
+ // Amount of inset to apply during line-breaking, used by text-wrap:balance
+ // to adjust line-breaks for more consistent lengths throughout the block.
+ nscoord mInsetForBalance;
+
+ // Physical size. Use only for physical <-> logical coordinate conversion.
+ //
+ // Note: for vertical-rl writing-mode, if mContainerSize's width is
+ // initialized to zero due to unconstrained block-size, lines will be
+ // positioned (physically) incorrectly. We will fix them up at the end of
+ // nsBlockFrame::Reflow() after we know the total block-size of the frame.
+ nsSize mContainerSize;
+ const nsSize& ContainerSize() const { return mContainerSize; }
+
+ // Continuation out-of-flow float frames that need to move to our
+ // next in flow are placed here during reflow. It's a pointer to
+ // a frame list stored in the block's property table.
+ nsFrameList* mPushedFloats;
+ // This method makes sure pushed floats are accessible to
+ // StealFrame. Call it before adding any frames to mPushedFloats.
+ void SetupPushedFloatList();
+ /**
+ * Append aFloatCont and its next-in-flows within the same block to
+ * mPushedFloats. aFloatCont should not be on any child list when
+ * making this call. Its next-in-flows will be removed from
+ * mBlock using StealFrame() before being added to mPushedFloats.
+ * All appended frames will be marked NS_FRAME_IS_PUSHED_FLOAT.
+ */
+ void AppendPushedFloatChain(nsIFrame* aFloatCont);
+
+ // Track child overflow continuations.
+ nsOverflowContinuationTracker* mOverflowTracker;
+
+ //----------------------------------------
+
+ // This state is "running" state updated by the reflow of each line
+ // in the block. This same state is "recovered" when a line is not
+ // dirty and is passed over during incremental reflow.
+
+ // The current line being reflowed
+ // If it is mBlock->end_lines(), then it is invalid.
+ nsLineList::iterator mCurrentLine;
+
+ // When mHasLineAdjacentToTop is set, this refers to a line
+ // which we know is adjacent to the top of the block (in other words,
+ // all lines before it are empty and do not have clearance. This line is
+ // always before the current line.
+ nsLineList::iterator mLineAdjacentToTop;
+
+ // The current block-direction coordinate in the block
+ nscoord mBCoord;
+
+ // mBlock's computed logical border+padding with pre-reflow skip sides applied
+ // (See the constructor and nsIFrame::PreReflowBlockLevelLogicalSkipSides).
+ const LogicalMargin mBorderPadding;
+
+ // The overflow areas of all floats placed so far
+ OverflowAreas mFloatOverflowAreas;
+
+ // Previous child. This is used when pulling up a frame to update
+ // the sibling list.
+ nsIFrame* mPrevChild;
+
+ // The previous child frames collapsed bottom margin value.
+ nsCollapsingMargin mPrevBEndMargin;
+
+ // The current next-in-flow for the block. When lines are pulled
+ // from a next-in-flow, this is used to know which next-in-flow to
+ // pull from. When a next-in-flow is emptied of lines, we advance
+ // this to the next next-in-flow.
+ nsBlockFrame* mNextInFlow;
+
+ //----------------------------------------
+
+ // Temporary state, for line-reflow. This state is used during the reflow
+ // of a given line, but doesn't have meaning before or after.
+
+ // The list of floats that are "current-line" floats. These are
+ // added to the line after the line has been reflowed, to keep the
+ // list fiddling from being N^2.
+ nsTArray<nsIFrame*> mCurrentLineFloats;
+
+ // The list of floats which are "below current-line"
+ // floats. These are reflowed/placed after the line is reflowed
+ // and placed. Again, this is done to keep the list fiddling from
+ // being N^2.
+ nsTArray<nsIFrame*> mBelowCurrentLineFloats;
+
+ // The list of floats that are waiting on a break opportunity in order to be
+ // placed, since we're on a nowrap context.
+ nsTArray<nsIFrame*> mNoWrapFloats;
+
+ const nscoord mMinLineHeight;
+
+ int32_t mLineNumber;
+
+ Flags mFlags;
+
+ // Cache the result of nsBlockFrame::FindTrailingClear() from mBlock's
+ // prev-in-flows. See nsBlockFrame::ReflowPushedFloats().
+ StyleClear mTrailingClearFromPIF;
+
+ // The amount of computed content block-size "consumed" by our previous
+ // continuations.
+ const nscoord mConsumedBSize;
+
+ // Cache the current line's BSize if nsBlockFrame::PlaceLine() fails to
+ // place the line. When redoing the line, it will be used to query the
+ // accurate float available space in AddFloat() and
+ // nsBlockFrame::PlaceLine().
+ Maybe<nscoord> mLineBSize;
+
+ private:
+ bool CanPlaceFloat(nscoord aFloatISize,
+ const nsFlowAreaRect& aFloatAvailableSpace);
+
+ void PushFloatPastBreak(nsIFrame* aFloat);
+
+ void RecoverFloats(nsLineList::iterator aLine, nscoord aDeltaBCoord);
+};
+
+}; // namespace mozilla
+
+#endif // BlockReflowState_h
diff --git a/layout/generic/CSSAlignUtils.cpp b/layout/generic/CSSAlignUtils.cpp
new file mode 100644
index 0000000000..b88ec8bfa4
--- /dev/null
+++ b/layout/generic/CSSAlignUtils.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Utility code for performing CSS Box Alignment */
+
+#include "CSSAlignUtils.h"
+#include "ReflowInput.h"
+
+namespace mozilla {
+
+static nscoord SpaceToFill(WritingMode aWM, const LogicalSize& aSize,
+ nscoord aMargin, LogicalAxis aAxis,
+ nscoord aCBSize) {
+ nscoord size = aSize.Size(aAxis, aWM);
+ return aCBSize - (size + aMargin);
+}
+
+nscoord CSSAlignUtils::AlignJustifySelf(const StyleAlignFlags& aAlignment,
+ LogicalAxis aAxis,
+ AlignJustifyFlags aFlags,
+ nscoord aBaselineAdjust,
+ nscoord aCBSize, const ReflowInput& aRI,
+ const LogicalSize& aChildSize) {
+ MOZ_ASSERT(aAlignment != StyleAlignFlags::AUTO,
+ "auto values should have resolved already");
+ MOZ_ASSERT(aAlignment != StyleAlignFlags::LEFT &&
+ aAlignment != StyleAlignFlags::RIGHT,
+ "caller should map that to the corresponding START/END");
+
+ // Promote aFlags to convenience bools:
+ const bool isOverflowSafe = !!(aFlags & AlignJustifyFlags::OverflowSafe);
+ const bool isSameSide = !!(aFlags & AlignJustifyFlags::SameSide);
+
+ StyleAlignFlags alignment = aAlignment;
+ // Map some alignment values to 'start' / 'end'.
+ if (alignment == StyleAlignFlags::SELF_START) {
+ // align/justify-self: self-start
+ alignment =
+ MOZ_LIKELY(isSameSide) ? StyleAlignFlags::START : StyleAlignFlags::END;
+ } else if (alignment == StyleAlignFlags::SELF_END) {
+ alignment =
+ MOZ_LIKELY(isSameSide) ? StyleAlignFlags::END : StyleAlignFlags::START;
+ // flex-start/flex-end are the same as start/end, in most contexts.
+ // (They have special behavior in flex containers, so flex containers
+ // should map them to some other value before calling this method.)
+ } else if (alignment == StyleAlignFlags::FLEX_START) {
+ alignment = StyleAlignFlags::START;
+ } else if (alignment == StyleAlignFlags::FLEX_END) {
+ alignment = StyleAlignFlags::END;
+ }
+
+ // Get the item's margin corresponding to the container's start/end side.
+ WritingMode wm = aRI.GetWritingMode();
+ const LogicalMargin margin = aRI.ComputedLogicalMargin(wm);
+ const auto startSide = MakeLogicalSide(
+ aAxis, MOZ_LIKELY(isSameSide) ? eLogicalEdgeStart : eLogicalEdgeEnd);
+ const nscoord marginStart = margin.Side(startSide, wm);
+ const auto endSide = GetOppositeSide(startSide);
+ const nscoord marginEnd = margin.Side(endSide, wm);
+
+ const auto& styleMargin = aRI.mStyleMargin->mMargin;
+ bool hasAutoMarginStart;
+ bool hasAutoMarginEnd;
+ if (aFlags & AlignJustifyFlags::IgnoreAutoMargins) {
+ // (Note: ReflowInput will have treated "auto" margins as 0, so we
+ // don't need to do anything special to avoid expanding them.)
+ hasAutoMarginStart = hasAutoMarginEnd = false;
+ } else if (aAxis == eLogicalAxisBlock) {
+ hasAutoMarginStart = styleMargin.GetBStart(wm).IsAuto();
+ hasAutoMarginEnd = styleMargin.GetBEnd(wm).IsAuto();
+ } else { /* aAxis == eLogicalAxisInline */
+ hasAutoMarginStart = styleMargin.GetIStart(wm).IsAuto();
+ hasAutoMarginEnd = styleMargin.GetIEnd(wm).IsAuto();
+ }
+
+ // https://drafts.csswg.org/css-align-3/#overflow-values
+ // This implements <overflow-position> = 'safe'.
+ // And auto-margins: https://drafts.csswg.org/css-grid/#auto-margins
+ if ((MOZ_UNLIKELY(isOverflowSafe) && alignment != StyleAlignFlags::START) ||
+ hasAutoMarginStart || hasAutoMarginEnd) {
+ nscoord space =
+ SpaceToFill(wm, aChildSize, marginStart + marginEnd, aAxis, aCBSize);
+ // XXX we might want to include == 0 here as an optimization -
+ // I need to see what the baseline/last baseline code looks like first.
+ if (space < 0) {
+ // "Overflowing elements ignore their auto margins and overflow
+ // in the end directions"
+ alignment = StyleAlignFlags::START;
+ } else if (hasAutoMarginEnd) {
+ alignment = hasAutoMarginStart ? StyleAlignFlags::CENTER
+ : (isSameSide ? StyleAlignFlags::START
+ : StyleAlignFlags::END);
+ } else if (hasAutoMarginStart) {
+ alignment = isSameSide ? StyleAlignFlags::END : StyleAlignFlags::START;
+ }
+ }
+
+ // Determine the offset for the child frame (its border-box) which will
+ // achieve the requested alignment.
+ nscoord offset = 0;
+ if (alignment == StyleAlignFlags::BASELINE ||
+ alignment == StyleAlignFlags::LAST_BASELINE) {
+ if (MOZ_LIKELY(isSameSide == (alignment == StyleAlignFlags::BASELINE))) {
+ offset = marginStart + aBaselineAdjust;
+ } else {
+ nscoord size = aChildSize.Size(aAxis, wm);
+ offset = aCBSize - (size + marginEnd) - aBaselineAdjust;
+ }
+ } else if (alignment == StyleAlignFlags::STRETCH ||
+ alignment == StyleAlignFlags::START) {
+ // ComputeSize() deals with stretch
+ offset = marginStart;
+ } else if (alignment == StyleAlignFlags::END) {
+ nscoord size = aChildSize.Size(aAxis, wm);
+ offset = aCBSize - (size + marginEnd);
+ } else if (alignment == StyleAlignFlags::CENTER) {
+ nscoord size = aChildSize.Size(aAxis, wm);
+ offset = (aCBSize - size + marginStart - marginEnd) / 2;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unknown align-/justify-self value");
+ }
+
+ return offset;
+}
+
+} // namespace mozilla
diff --git a/layout/generic/CSSAlignUtils.h b/layout/generic/CSSAlignUtils.h
new file mode 100644
index 0000000000..ae715f4081
--- /dev/null
+++ b/layout/generic/CSSAlignUtils.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Utility code for performing CSS Box Alignment */
+
+#ifndef mozilla_CSSAlignUtils_h
+#define mozilla_CSSAlignUtils_h
+
+#include "mozilla/WritingModes.h"
+
+namespace mozilla {
+
+struct ReflowInput;
+struct StyleAlignFlags;
+
+class CSSAlignUtils {
+ public:
+ /**
+ * Flags to customize the behavior of AlignJustifySelf:
+ */
+ enum class AlignJustifyFlags {
+ NoFlags = 0,
+ // Indicates that we have <overflow-position> = safe.
+ OverflowSafe = 1 << 0,
+ // Indicates that the container's start side in aAxis is the same
+ // as the child's start side in the child's parallel axis.
+ SameSide = 1 << 1,
+ // Indicates that AlignJustifySelf() shouldn't expand "auto" margins.
+ // (By default, AlignJustifySelf() *will* expand such margins, to fill the
+ // available space before any alignment is done.)
+ IgnoreAutoMargins = 1 << 2,
+ };
+
+ /**
+ * This computes the aligned offset of a CSS-aligned child within its
+ * alignment container. The returned offset is distance between the
+ * logical "start" edge of the alignment container & the logical "start" edge
+ * of the aligned child (in terms of the alignment container's writing mode).
+ *
+ * @param aAlignment An enumerated value representing a keyword for
+ * "align-self" or "justify-self". The values
+ * StyleAlignFlags::{AUTO,LEFT,RIGHT} must *not* be
+ * passed here; this method expects the caller to have
+ * already resolved those to 'start', 'end', or 'stretch'.
+ * @param aAxis The container's axis in which we're doing alignment.
+ * @param aBaselineAdjust The amount to offset baseline-aligned children.
+ * @param aCBSize The size of the alignment container, in its aAxis.
+ * @param aRI A ReflowInput for the child.
+ * @param aChildSize The child's LogicalSize (in its own writing mode).
+ */
+ static nscoord AlignJustifySelf(const StyleAlignFlags& aAlignment,
+ LogicalAxis aAxis, AlignJustifyFlags aFlags,
+ nscoord aBaselineAdjust, nscoord aCBSize,
+ const ReflowInput& aRI,
+ const LogicalSize& aChildSize);
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSAlignUtils::AlignJustifyFlags)
+
+} // namespace mozilla
+
+#endif // mozilla_CSSAlignUtils_h
diff --git a/layout/generic/CSSOrderAwareFrameIterator.cpp b/layout/generic/CSSOrderAwareFrameIterator.cpp
new file mode 100644
index 0000000000..0c9520f378
--- /dev/null
+++ b/layout/generic/CSSOrderAwareFrameIterator.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Iterator class for frame lists that respect CSS "order" during layout */
+
+#include "CSSOrderAwareFrameIterator.h"
+#include "nsIFrameInlines.h"
+
+static bool CanUse(const nsIFrame* aFrame) {
+ return aFrame->IsFlexOrGridContainer() ||
+ (aFrame->GetContent() && aFrame->GetContent()->IsAnyOfXULElements(
+ nsGkAtoms::treecols, nsGkAtoms::treecol));
+}
+
+namespace mozilla {
+
+template <>
+bool CSSOrderAwareFrameIterator::CanUse(const nsIFrame* aFrame) {
+ return ::CanUse(aFrame);
+}
+
+template <>
+bool ReverseCSSOrderAwareFrameIterator::CanUse(const nsIFrame* aFrame) {
+ return ::CanUse(aFrame);
+}
+
+template <>
+int CSSOrderAwareFrameIterator::CSSOrderComparator(nsIFrame* const& a,
+ nsIFrame* const& b) {
+ return a->StylePosition()->mOrder - b->StylePosition()->mOrder;
+}
+
+template <>
+int CSSOrderAwareFrameIterator::CSSBoxOrdinalGroupComparator(
+ nsIFrame* const& a, nsIFrame* const& b) {
+ return a->StyleXUL()->mBoxOrdinal - b->StyleXUL()->mBoxOrdinal;
+}
+
+template <>
+bool CSSOrderAwareFrameIterator::IsForward() const {
+ return true;
+}
+
+template <>
+nsFrameList::iterator CSSOrderAwareFrameIterator::begin(
+ const nsFrameList& aList) {
+ return aList.begin();
+}
+
+template <>
+nsFrameList::iterator CSSOrderAwareFrameIterator::end(
+ const nsFrameList& aList) {
+ return aList.end();
+}
+
+template <>
+int ReverseCSSOrderAwareFrameIterator::CSSOrderComparator(nsIFrame* const& a,
+ nsIFrame* const& b) {
+ return b->StylePosition()->mOrder - a->StylePosition()->mOrder;
+}
+
+template <>
+int ReverseCSSOrderAwareFrameIterator::CSSBoxOrdinalGroupComparator(
+ nsIFrame* const& a, nsIFrame* const& b) {
+ return b->StyleXUL()->mBoxOrdinal - a->StyleXUL()->mBoxOrdinal;
+}
+
+template <>
+bool ReverseCSSOrderAwareFrameIterator::IsForward() const {
+ return false;
+}
+
+template <>
+nsFrameList::reverse_iterator ReverseCSSOrderAwareFrameIterator::begin(
+ const nsFrameList& aList) {
+ return aList.rbegin();
+}
+
+template <>
+nsFrameList::reverse_iterator ReverseCSSOrderAwareFrameIterator::end(
+ const nsFrameList& aList) {
+ return aList.rend();
+}
+
+} // namespace mozilla
diff --git a/layout/generic/CSSOrderAwareFrameIterator.h b/layout/generic/CSSOrderAwareFrameIterator.h
new file mode 100644
index 0000000000..8ad5cb65b2
--- /dev/null
+++ b/layout/generic/CSSOrderAwareFrameIterator.h
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Iterator class for frame lists that respect CSS "order" during layout */
+
+#ifndef mozilla_CSSOrderAwareFrameIterator_h
+#define mozilla_CSSOrderAwareFrameIterator_h
+
+#include <limits>
+#include "nsFrameList.h"
+#include "nsIFrame.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+/**
+ * CSSOrderAwareFrameIteratorT is a base class for iterators that traverse
+ * child frame lists in a way that respects their CSS "order" property.
+ * https://drafts.csswg.org/css-flexbox-1/#order-property
+ * This class isn't meant to be directly used; instead, use its specializations
+ * CSSOrderAwareFrameIterator and ReverseCSSOrderAwareFrameIterator.
+ *
+ * Client code can use a CSSOrderAwareFrameIterator to traverse lower-"order"
+ * frames before higher-"order" ones (as required for correct flex/grid
+ * layout), without modifying the frames' actual ordering within the frame
+ * tree. Any frames with equal "order" values will be traversed consecutively,
+ * in frametree order (which is generally equivalent to DOM order).
+ *
+ * By default, the iterator will skip past placeholder frames during
+ * iteration. You can adjust this behavior via the ChildFilter constructor arg.
+ *
+ * By default, the iterator will use the frames' CSS "order" property to
+ * determine its traversal order. However, it can be customized to instead use
+ * the (prefixed) legacy "box-ordinal-group" CSS property instead, as part of
+ * emulating "display:-webkit-box" containers. This behavior can be customized
+ * using the OrderingProperty constructor arg.
+ *
+ * A few notes on performance:
+ * - If you're iterating multiple times in a row, it's a good idea to reuse
+ * the same iterator (calling Reset() to start each new iteration), rather than
+ * instantiating a new one each time.
+ * - If you have foreknowledge of the list's orderedness, you can save some
+ * time by passing eKnownOrdered or eKnownUnordered to the constructor (which
+ * will skip some checks during construction).
+ *
+ * Warning: if the given frame list changes, it makes the iterator invalid and
+ * bad things will happen if it's used further.
+ */
+template <typename Iterator>
+class CSSOrderAwareFrameIteratorT {
+ public:
+ enum class OrderState { Unknown, Ordered, Unordered };
+ enum class ChildFilter { SkipPlaceholders, IncludeAll };
+ enum class OrderingProperty {
+ Order, // Default behavior: use "order".
+ BoxOrdinalGroup // Legacy behavior: use prefixed "box-ordinal-group".
+ };
+ CSSOrderAwareFrameIteratorT(
+ nsIFrame* aContainer, FrameChildListID aListID,
+ ChildFilter aFilter = ChildFilter::SkipPlaceholders,
+ OrderState aState = OrderState::Unknown,
+ OrderingProperty aOrderProp = OrderingProperty::Order)
+ : mChildren(aContainer->GetChildList(aListID)),
+ mArrayIndex(0),
+ mItemIndex(0),
+ mSkipPlaceholders(aFilter == ChildFilter::SkipPlaceholders)
+#ifdef DEBUG
+ ,
+ mContainer(aContainer),
+ mListID(aListID)
+#endif
+ {
+ MOZ_ASSERT(CanUse(aContainer),
+ "Only use this iterator in a container that honors 'order'");
+
+ size_t count = 0;
+ bool isOrdered = aState != OrderState::Unordered;
+ if (aState == OrderState::Unknown) {
+ auto maxOrder = std::numeric_limits<int32_t>::min();
+ for (auto* child : mChildren) {
+ ++count;
+
+ int32_t order = aOrderProp == OrderingProperty::BoxOrdinalGroup
+ ? child->StyleXUL()->mBoxOrdinal
+ : child->StylePosition()->mOrder;
+
+ if (order < maxOrder) {
+ isOrdered = false;
+ break;
+ }
+ maxOrder = order;
+ }
+ }
+ if (isOrdered) {
+ mIter.emplace(begin(mChildren));
+ mIterEnd.emplace(end(mChildren));
+ } else {
+ count *= 2; // XXX somewhat arbitrary estimate for now...
+ mArray.emplace(count);
+ for (Iterator i(begin(mChildren)), iEnd(end(mChildren)); i != iEnd; ++i) {
+ mArray->AppendElement(*i);
+ }
+ auto comparator = aOrderProp == OrderingProperty::BoxOrdinalGroup
+ ? CSSBoxOrdinalGroupComparator
+ : CSSOrderComparator;
+ mArray->StableSort(comparator);
+ }
+
+ if (mSkipPlaceholders) {
+ SkipPlaceholders();
+ }
+ }
+
+ CSSOrderAwareFrameIteratorT(CSSOrderAwareFrameIteratorT&&) = default;
+
+ ~CSSOrderAwareFrameIteratorT() {
+ MOZ_ASSERT(IsForward() == mItemCount.isNothing());
+ }
+
+ bool IsForward() const;
+
+ nsIFrame* get() const {
+ MOZ_ASSERT(!AtEnd());
+ if (mIter.isSome()) {
+ return **mIter;
+ }
+ return (*mArray)[mArrayIndex];
+ }
+
+ nsIFrame* operator*() const { return get(); }
+
+ /**
+ * Return the child index of the current item, placeholders not counted.
+ * It's forbidden to call this method when the current frame is placeholder.
+ */
+ size_t ItemIndex() const {
+ MOZ_ASSERT(!AtEnd());
+ MOZ_ASSERT(!(**this)->IsPlaceholderFrame(),
+ "MUST not call this when at a placeholder");
+ MOZ_ASSERT(IsForward() || mItemIndex < *mItemCount,
+ "Returning an out-of-range mItemIndex...");
+ return mItemIndex;
+ }
+
+ void SetItemCount(size_t aItemCount) {
+ MOZ_ASSERT(mIter.isSome() || aItemCount <= mArray->Length(),
+ "item count mismatch");
+ mItemCount.emplace(aItemCount);
+ // Note: it's OK if mItemIndex underflows -- ItemIndex()
+ // will not be called unless there is at least one item.
+ mItemIndex = IsForward() ? 0 : *mItemCount - 1;
+ }
+
+ /**
+ * Skip over placeholder children.
+ */
+ void SkipPlaceholders() {
+ if (mIter.isSome()) {
+ for (; *mIter != *mIterEnd; ++*mIter) {
+ nsIFrame* child = **mIter;
+ if (!child->IsPlaceholderFrame()) {
+ return;
+ }
+ }
+ } else {
+ for (; mArrayIndex < mArray->Length(); ++mArrayIndex) {
+ nsIFrame* child = (*mArray)[mArrayIndex];
+ if (!child->IsPlaceholderFrame()) {
+ return;
+ }
+ }
+ }
+ }
+
+ bool AtEnd() const {
+ MOZ_ASSERT(mIter.isSome() || mArrayIndex <= mArray->Length());
+ return mIter ? (*mIter == *mIterEnd) : mArrayIndex >= mArray->Length();
+ }
+
+ void Next() {
+#ifdef DEBUG
+ MOZ_ASSERT(!AtEnd());
+ const nsFrameList& list = mContainer->GetChildList(mListID);
+ MOZ_ASSERT(list.FirstChild() == mChildren.FirstChild() &&
+ list.LastChild() == mChildren.LastChild(),
+ "the list of child frames must not change while iterating!");
+#endif
+ if (mSkipPlaceholders || !(**this)->IsPlaceholderFrame()) {
+ IsForward() ? ++mItemIndex : --mItemIndex;
+ }
+ if (mIter.isSome()) {
+ ++*mIter;
+ } else {
+ ++mArrayIndex;
+ }
+ if (mSkipPlaceholders) {
+ SkipPlaceholders();
+ }
+ }
+
+ void Reset(ChildFilter aFilter = ChildFilter::SkipPlaceholders) {
+ if (mIter.isSome()) {
+ mIter.reset();
+ mIter.emplace(begin(mChildren));
+ mIterEnd.reset();
+ mIterEnd.emplace(end(mChildren));
+ } else {
+ mArrayIndex = 0;
+ }
+ mItemIndex = IsForward() ? 0 : *mItemCount - 1;
+ mSkipPlaceholders = aFilter == ChildFilter::SkipPlaceholders;
+ if (mSkipPlaceholders) {
+ SkipPlaceholders();
+ }
+ }
+
+ bool IsValid() const { return mIter.isSome() || mArray.isSome(); }
+
+ void Invalidate() {
+ mIter.reset();
+ mArray.reset();
+ }
+
+ bool ItemsAreAlreadyInOrder() const { return mIter.isSome(); }
+
+ private:
+ static bool CanUse(const nsIFrame*);
+
+ Iterator begin(const nsFrameList& aList);
+ Iterator end(const nsFrameList& aList);
+
+ static int CSSOrderComparator(nsIFrame* const& a, nsIFrame* const& b);
+ static int CSSBoxOrdinalGroupComparator(nsIFrame* const& a,
+ nsIFrame* const& b);
+
+ const nsFrameList& mChildren;
+ // Used if child list is already in ascending 'order'.
+ Maybe<Iterator> mIter;
+ Maybe<Iterator> mIterEnd;
+ // Used if child list is *not* in ascending 'order'.
+ // This array is pre-sorted in reverse order for a reverse iterator.
+ Maybe<nsTArray<nsIFrame*>> mArray;
+ size_t mArrayIndex;
+ // The index of the current item (placeholders excluded).
+ size_t mItemIndex;
+ // The number of items (placeholders excluded).
+ // It's only initialized and used in a reverse iterator.
+ Maybe<size_t> mItemCount;
+ // Skip placeholder children in the iteration?
+ bool mSkipPlaceholders;
+#ifdef DEBUG
+ nsIFrame* mContainer;
+ FrameChildListID mListID;
+#endif
+};
+
+using CSSOrderAwareFrameIterator =
+ CSSOrderAwareFrameIteratorT<nsFrameList::iterator>;
+using ReverseCSSOrderAwareFrameIterator =
+ CSSOrderAwareFrameIteratorT<nsFrameList::reverse_iterator>;
+
+} // namespace mozilla
+
+#endif // mozilla_CSSOrderAwareFrameIterator_h
diff --git a/layout/generic/ColumnSetWrapperFrame.cpp b/layout/generic/ColumnSetWrapperFrame.cpp
new file mode 100644
index 0000000000..7717f14df8
--- /dev/null
+++ b/layout/generic/ColumnSetWrapperFrame.cpp
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "ColumnSetWrapperFrame.h"
+
+#include "mozilla/ColumnUtils.h"
+#include "mozilla/PresShell.h"
+#include "nsContentUtils.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+
+using namespace mozilla;
+
+nsBlockFrame* NS_NewColumnSetWrapperFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle,
+ nsFrameState aStateFlags) {
+ ColumnSetWrapperFrame* frame = new (aPresShell)
+ ColumnSetWrapperFrame(aStyle, aPresShell->GetPresContext());
+ frame->AddStateBits(aStateFlags);
+ return frame;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(ColumnSetWrapperFrame)
+
+NS_QUERYFRAME_HEAD(ColumnSetWrapperFrame)
+ NS_QUERYFRAME_ENTRY(ColumnSetWrapperFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+ColumnSetWrapperFrame::ColumnSetWrapperFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsBlockFrame(aStyle, aPresContext, kClassID) {}
+
+void ColumnSetWrapperFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // ColumnSetWrapperFrame doesn't need to call ResolveBidi().
+ RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+}
+
+nsContainerFrame* ColumnSetWrapperFrame::GetContentInsertionFrame() {
+ nsIFrame* columnSet = PrincipalChildList().OnlyChild();
+ if (columnSet) {
+ // We have only one child, which means we don't have any column-span
+ // descendants. Thus we can safely return our only ColumnSet child's
+ // insertion frame as ours.
+ MOZ_ASSERT(columnSet->IsColumnSetFrame());
+ return columnSet->GetContentInsertionFrame();
+ }
+
+ // We have column-span descendants. Return ourselves as the insertion
+ // frame to let nsCSSFrameConstructor::WipeContainingBlock() figure out
+ // what to do.
+ return this;
+}
+
+void ColumnSetWrapperFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ MOZ_ASSERT(!GetPrevContinuation(),
+ "Who set NS_FRAME_OWNS_ANON_BOXES on our continuations?");
+
+ // It's sufficient to append the first ColumnSet child, which is the first
+ // continuation of all the other ColumnSets.
+ //
+ // We don't need to append -moz-column-span-wrapper children because
+ // they're non-inheriting anon boxes, and they cannot have any directly
+ // owned anon boxes nor generate any native anonymous content themselves.
+ // Thus, no need to restyle them. AssertColumnSpanWrapperSubtreeIsSane()
+ // asserts all the conditions above which allow us to skip appending
+ // -moz-column-span-wrappers.
+ auto FindFirstChildInChildLists = [this]() -> nsIFrame* {
+ const ChildListID listIDs[] = {FrameChildListID::Principal,
+ FrameChildListID::Overflow};
+ for (nsIFrame* frag = this; frag; frag = frag->GetNextInFlow()) {
+ for (ChildListID id : listIDs) {
+ const nsFrameList& list = frag->GetChildList(id);
+ if (nsIFrame* firstChild = list.FirstChild()) {
+ return firstChild;
+ }
+ }
+ }
+ return nullptr;
+ };
+
+ nsIFrame* columnSet = FindFirstChildInChildLists();
+ MOZ_ASSERT(columnSet && columnSet->IsColumnSetFrame(),
+ "The first child should always be ColumnSet!");
+ aResult.AppendElement(OwnedAnonBox(columnSet));
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult ColumnSetWrapperFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"ColumnSetWrapper"_ns, aResult);
+}
+#endif
+
+// Disallow any append, insert, or remove operations after building the
+// column hierarchy since any change to the column hierarchy in the column
+// sub-tree need to be re-created.
+void ColumnSetWrapperFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+#ifdef DEBUG
+ MOZ_ASSERT(!mFinishedBuildingColumns, "Should only call once!");
+ mFinishedBuildingColumns = true;
+#endif
+
+ nsBlockFrame::AppendFrames(aListID, std::move(aFrameList));
+
+#ifdef DEBUG
+ nsIFrame* firstColumnSet = PrincipalChildList().FirstChild();
+ for (nsIFrame* child : PrincipalChildList()) {
+ if (child->IsColumnSpan()) {
+ AssertColumnSpanWrapperSubtreeIsSane(child);
+ } else if (child != firstColumnSet) {
+ // All the other ColumnSets are the continuation of the first ColumnSet.
+ MOZ_ASSERT(child->IsColumnSetFrame() && child->GetPrevContinuation(),
+ "ColumnSet's prev-continuation is not set properly?");
+ }
+ }
+#endif
+}
+
+void ColumnSetWrapperFrame::InsertFrames(
+ ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
+ MOZ_ASSERT_UNREACHABLE("Unsupported operation!");
+ nsBlockFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+}
+
+void ColumnSetWrapperFrame::RemoveFrame(DestroyContext& aContext,
+ ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ MOZ_ASSERT_UNREACHABLE("Unsupported operation!");
+ nsBlockFrame::RemoveFrame(aContext, aListID, aOldFrame);
+}
+
+void ColumnSetWrapperFrame::MarkIntrinsicISizesDirty() {
+ nsBlockFrame::MarkIntrinsicISizesDirty();
+
+ // The parent's method adds NS_BLOCK_NEEDS_BIDI_RESOLUTION to all our
+ // continuations. Clear the bit because we don't want to call ResolveBidi().
+ for (nsIFrame* f = FirstContinuation(); f; f = f->GetNextContinuation()) {
+ f->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ }
+}
+
+nscoord ColumnSetWrapperFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord iSize = 0;
+ DISPLAY_MIN_INLINE_SIZE(this, iSize);
+
+ if (Maybe<nscoord> containISize =
+ ContainIntrinsicISize(NS_UNCONSTRAINEDSIZE)) {
+ // If we're size-contained in inline axis and contain-intrinsic-inline-size
+ // is not 'none', then use that size.
+ if (*containISize != NS_UNCONSTRAINEDSIZE) {
+ return *containISize;
+ }
+
+ // In the 'none' case, we determine our minimum intrinsic size purely from
+ // our column styling, as if we had no descendants. This should match what
+ // happens in nsColumnSetFrame::GetMinISize in an actual no-descendants
+ // scenario.
+ const nsStyleColumn* colStyle = StyleColumn();
+ if (colStyle->mColumnWidth.IsLength()) {
+ // As available inline size reduces to zero, our number of columns reduces
+ // to one, so no column gaps contribute to our minimum intrinsic size.
+ // Also, column-width doesn't set a lower bound on our minimum intrinsic
+ // size, either. Just use 0 because we're size-contained.
+ iSize = 0;
+ } else {
+ MOZ_ASSERT(colStyle->mColumnCount != nsStyleColumn::kColumnCountAuto,
+ "column-count and column-width can't both be auto!");
+ // As available inline size reduces to zero, we still have mColumnCount
+ // columns, so compute our minimum intrinsic size based on N zero-width
+ // columns, with specified gap size between them.
+ const nscoord colGap =
+ ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
+ iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount, colGap, 0);
+ }
+ } else {
+ for (nsIFrame* f : PrincipalChildList()) {
+ iSize = std::max(iSize, f->GetMinISize(aRenderingContext));
+ }
+ }
+
+ return iSize;
+}
+
+nscoord ColumnSetWrapperFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord iSize = 0;
+ DISPLAY_PREF_INLINE_SIZE(this, iSize);
+
+ if (Maybe<nscoord> containISize =
+ ContainIntrinsicISize(NS_UNCONSTRAINEDSIZE)) {
+ if (*containISize != NS_UNCONSTRAINEDSIZE) {
+ return *containISize;
+ }
+
+ const nsStyleColumn* colStyle = StyleColumn();
+ nscoord colISize;
+ if (colStyle->mColumnWidth.IsLength()) {
+ colISize =
+ ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
+ } else {
+ MOZ_ASSERT(colStyle->mColumnCount != nsStyleColumn::kColumnCountAuto,
+ "column-count and column-width can't both be auto!");
+ colISize = 0;
+ }
+
+ // If column-count is auto, assume one column.
+ const uint32_t numColumns =
+ colStyle->mColumnCount == nsStyleColumn::kColumnCountAuto
+ ? 1
+ : colStyle->mColumnCount;
+ const nscoord colGap =
+ ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
+ iSize = ColumnUtils::IntrinsicISize(numColumns, colGap, colISize);
+ } else {
+ for (nsIFrame* f : PrincipalChildList()) {
+ iSize = std::max(iSize, f->GetPrefISize(aRenderingContext));
+ }
+ }
+
+ return iSize;
+}
+
+template <typename Iterator>
+Maybe<nscoord> ColumnSetWrapperFrame::GetBaselineBOffset(
+ Iterator aStart, Iterator aEnd, WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ // Either forward iterator + first baseline, or reverse iterator + last
+ // baseline
+ MOZ_ASSERT((*aStart == PrincipalChildList().FirstChild() &&
+ aBaselineGroup == BaselineSharingGroup::First) ||
+ (*aStart == PrincipalChildList().LastChild() &&
+ aBaselineGroup == BaselineSharingGroup::Last),
+ "Iterator direction must match baseline sharing group.");
+ if (StyleDisplay()->IsContainLayout()) {
+ return Nothing{};
+ }
+
+ // Start from start/end of principal child list, and use the first valid
+ // baseline.
+ for (auto itr = aStart; itr != aEnd; ++itr) {
+ const nsIFrame* kid = *itr;
+ auto kidBaseline =
+ kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext);
+ if (!kidBaseline) {
+ continue;
+ }
+ // Baseline is offset from the kid's rectangle, so find the offset to the
+ // kid's rectangle.
+ LogicalRect kidRect{aWM, kid->GetLogicalNormalPosition(aWM, GetSize()),
+ kid->GetLogicalSize(aWM)};
+ if (aBaselineGroup == BaselineSharingGroup::First) {
+ *kidBaseline += kidRect.BStart(aWM);
+ } else {
+ *kidBaseline += (GetLogicalSize().BSize(aWM) - kidRect.BEnd(aWM));
+ }
+ return kidBaseline;
+ }
+ return Nothing{};
+}
+
+Maybe<nscoord> ColumnSetWrapperFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ if (aBaselineGroup == BaselineSharingGroup::First) {
+ return GetBaselineBOffset(PrincipalChildList().cbegin(),
+ PrincipalChildList().cend(), aWM, aBaselineGroup,
+ aExportContext);
+ }
+ return GetBaselineBOffset(PrincipalChildList().crbegin(),
+ PrincipalChildList().crend(), aWM, aBaselineGroup,
+ aExportContext);
+}
+
+#ifdef DEBUG
+
+/* static */
+void ColumnSetWrapperFrame::AssertColumnSpanWrapperSubtreeIsSane(
+ const nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->IsColumnSpan(), "aFrame is not column-span?");
+
+ if (!nsLayoutUtils::GetStyleFrame(const_cast<nsIFrame*>(aFrame))
+ ->Style()
+ ->IsAnonBox()) {
+ // aFrame's style frame has "column-span: all". Traverse no further.
+ return;
+ }
+
+ MOZ_ASSERT(
+ aFrame->Style()->GetPseudoType() == PseudoStyleType::columnSpanWrapper,
+ "aFrame should be ::-moz-column-span-wrapper");
+
+ MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES),
+ "::-moz-column-span-wrapper anonymous blocks cannot own "
+ "other types of anonymous blocks!");
+
+ for (const nsIFrame* child : aFrame->PrincipalChildList()) {
+ AssertColumnSpanWrapperSubtreeIsSane(child);
+ }
+}
+
+#endif
diff --git a/layout/generic/ColumnSetWrapperFrame.h b/layout/generic/ColumnSetWrapperFrame.h
new file mode 100644
index 0000000000..e219348189
--- /dev/null
+++ b/layout/generic/ColumnSetWrapperFrame.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// A frame for CSS multi-column layout that wraps nsColumnSetFrames and
+// column-span frames.
+
+#ifndef mozilla_ColumnSetWrapperFrame_h
+#define mozilla_ColumnSetWrapperFrame_h
+
+#include "nsBlockFrame.h"
+
+namespace mozilla {
+
+class PresShell;
+
+// This class is a wrapper for nsColumnSetFrames and column-span frame.
+// Essentially, we divide the *original* nsColumnSetFrame into multiple
+// nsColumnSetFrames on the basis of the number and position of spanning
+// elements.
+//
+// This wrapper is necessary for implementing column-span as it allows us to
+// maintain each nsColumnSetFrame as an independent set of columns, and each
+// column-span element then becomes just a block level element.
+//
+class ColumnSetWrapperFrame final : public nsBlockFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(ColumnSetWrapperFrame)
+ NS_DECL_QUERYFRAME
+
+ friend nsBlockFrame* ::NS_NewColumnSetWrapperFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle,
+ nsFrameState aStateFlags);
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ nsContainerFrame* GetContentInsertionFrame() override;
+
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override;
+
+ void MarkIntrinsicISizesDirty() override;
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const override;
+
+ private:
+ explicit ColumnSetWrapperFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext);
+ ~ColumnSetWrapperFrame() override = default;
+
+#ifdef DEBUG
+ static void AssertColumnSpanWrapperSubtreeIsSane(const nsIFrame* aFrame);
+
+ // True if frame constructor has finished building this frame and all of
+ // its descendants.
+ bool mFinishedBuildingColumns = false;
+#endif
+
+ template <typename Iterator>
+ Maybe<nscoord> GetBaselineBOffset(Iterator aStart, Iterator aEnd,
+ WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ColumnSetWrapperFrame_h
diff --git a/layout/generic/ColumnUtils.cpp b/layout/generic/ColumnUtils.cpp
new file mode 100644
index 0000000000..6f1f54824f
--- /dev/null
+++ b/layout/generic/ColumnUtils.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A namespace class for static muti-column utilities. */
+
+#include "mozilla/ColumnUtils.h"
+
+#include <algorithm>
+
+#include "nsContainerFrame.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla {
+
+/* static */
+nscoord ColumnUtils::GetColumnGap(const nsContainerFrame* aFrame,
+ nscoord aPercentageBasis) {
+ const auto& columnGap = aFrame->StylePosition()->mColumnGap;
+ if (columnGap.IsNormal()) {
+ return aFrame->StyleFont()->mFont.size.ToAppUnits();
+ }
+ return nsLayoutUtils::ResolveGapToLength(columnGap, aPercentageBasis);
+}
+
+/* static */
+nscoord ColumnUtils::ClampUsedColumnWidth(const Length& aColumnWidth) {
+ // Per spec, used values will be clamped to a minimum of 1px.
+ return std::max(CSSPixel::ToAppUnits(1), aColumnWidth.ToAppUnits());
+}
+
+/* static */
+nscoord ColumnUtils::IntrinsicISize(uint32_t aColCount, nscoord aColGap,
+ nscoord aColISize) {
+ MOZ_ASSERT(aColCount > 0, "Cannot compute with zero columns!");
+
+ // Column box's inline-size times number of columns (n), plus n-1 column gaps.
+ nscoord iSize = aColISize * aColCount + aColGap * (aColCount - 1);
+
+ // The multiplication above can make 'iSize' negative (integer overflow),
+ // so use std::max to protect against that.
+ return std::max(iSize, aColISize);
+}
+
+} // namespace mozilla
diff --git a/layout/generic/ColumnUtils.h b/layout/generic/ColumnUtils.h
new file mode 100644
index 0000000000..15fa0efca5
--- /dev/null
+++ b/layout/generic/ColumnUtils.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A namespace class for static muti-column utilities. */
+
+#ifndef mozilla_ColumnUtils_h
+#define mozilla_ColumnUtils_h
+
+#include "nsCoord.h"
+#include "nsStyleConsts.h"
+
+class nsContainerFrame;
+
+namespace mozilla {
+
+// ColumnUtils is a namespace class containing utility functions used by
+// multi-column containers like ColumnSetWrapperFrame and nsColumnSetFrame.
+//
+class ColumnUtils final {
+ public:
+ // Compute used value of 'column-gap' for aFrame.
+ static nscoord GetColumnGap(const nsContainerFrame* aFrame,
+ nscoord aPercentageBasis);
+
+ // Clamp used column width to a minimum of 1px.
+ static nscoord ClampUsedColumnWidth(const Length& aColumnWidth);
+
+ // Compute the intrinsic inline-size of a column container, given a non-zero
+ // column-count, column gap, and column box's inline-size.
+ static nscoord IntrinsicISize(uint32_t aColCount, nscoord aColGap,
+ nscoord aColISize);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ColumnUtils_h
diff --git a/layout/generic/FrameClass.py b/layout/generic/FrameClass.py
new file mode 100644
index 0000000000..90ad3f9570
--- /dev/null
+++ b/layout/generic/FrameClass.py
@@ -0,0 +1,22 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+class FrameClass:
+ def __init__(self, cls):
+ self.cls = cls
+
+
+class Frame(FrameClass):
+ def __init__(self, cls, ty, flags):
+ FrameClass.__init__(self, cls)
+ self.ty = ty
+ self.flags = flags
+ self.is_concrete = True
+
+
+class AbstractFrame(FrameClass):
+ def __init__(self, cls):
+ FrameClass.__init__(self, cls)
+ self.is_concrete = False
diff --git a/layout/generic/FrameClasses.py b/layout/generic/FrameClasses.py
new file mode 100644
index 0000000000..d1d0a0b13b
--- /dev/null
+++ b/layout/generic/FrameClasses.py
@@ -0,0 +1,203 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+# Frame class definitions, used to generate FrameIdList.h and FrameTypeList.h
+
+from FrameClass import AbstractFrame, Frame
+
+# Most frames support these.
+COMMON = {
+ "SupportsCSSTransforms",
+ "SupportsContainLayoutAndPaint",
+ "SupportsAspectRatio",
+}
+LEAF = {"Leaf"}
+MATHML = {"MathML"}
+SVG = {"SVG"}
+
+BLOCK = COMMON | {"CanContainOverflowContainers"}
+
+REPLACED = COMMON | {"Replaced"}
+REPLACED_SIZING = REPLACED | {"ReplacedSizing"}
+REPLACED_WITH_BLOCK = REPLACED | {"ReplacedContainsBlock"}
+REPLACED_SIZING_WITH_BLOCK = REPLACED_SIZING | REPLACED_WITH_BLOCK
+
+TABLE = COMMON - {"SupportsCSSTransforms"}
+TABLE_PART = {"SupportsCSSTransforms", "TablePart"}
+TABLE_CELL = TABLE_PART | {"SupportsContainLayoutAndPaint"}
+MATHML_CONTAINER = (COMMON - {"SupportsContainLayoutAndPaint"}) | MATHML
+SVG_CONTENT = (COMMON - {"SupportsContainLayoutAndPaint"}) | SVG
+SVG_CONTAINER = SVG_CONTENT | {"SVGContainer"}
+
+# NOTE: Intentionally not including "COMMON" here.
+INLINE = {"BidiInlineContainer", "LineParticipant"}
+RUBY_CONTENT = {"LineParticipant"}
+# FIXME(bug 713387): Shouldn't be Replaced, probably.
+TEXT = COMMON | {"Replaced", "LineParticipant"} | LEAF
+
+# See FrameClass.py and GenerateFrameLists.py for implementation details.
+# The following is a list of all the frame classes, followed by the frame type,
+# and a set of flags.
+#
+# The frame type is somewhat arbitrary (could literally be anything) but for
+# new frame class implementations it's probably a good idea to make it a unique
+# string (maybe matching the frame name).
+#
+# See bug 1555477 for some related discussion about the whole Type() set-up.
+FRAME_CLASSES = [
+ Frame("BRFrame", "Br", REPLACED | LEAF | {"LineParticipant"}),
+ Frame("nsBCTableCellFrame", "TableCell", TABLE_CELL),
+ Frame("nsBackdropFrame", "Backdrop", COMMON | LEAF),
+ Frame("nsBlockFrame", "Block", BLOCK),
+ Frame("nsCanvasFrame", "Canvas", BLOCK),
+ # FIXME(emilio, bug 1866692): These don't have a block, wtf? Can we remove the "ReplacedContainsBlock" flag
+ Frame("nsCheckboxRadioFrame", "CheckboxRadio", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsColorControlFrame", "ColorControl", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsColumnSetFrame", "ColumnSet", COMMON),
+ Frame("ColumnSetWrapperFrame", "ColumnSetWrapper", BLOCK),
+ Frame("nsComboboxControlFrame", "ComboboxControl", BLOCK | REPLACED_WITH_BLOCK),
+ # FIXME(emilio, bug 1362907): Revisit these after that bug, this is the
+ # only frame that has ReplacedContainsBlock but not Replaced, which is
+ # sketchy.
+ Frame(
+ "nsComboboxDisplayFrame", "ComboboxDisplay", REPLACED_WITH_BLOCK - {"Replaced"}
+ ),
+ Frame("nsContinuingTextFrame", "Text", TEXT),
+ Frame("nsDateTimeControlFrame", "DateTimeControl", REPLACED_WITH_BLOCK),
+ Frame("nsFieldSetFrame", "FieldSet", BLOCK),
+ Frame("nsFileControlFrame", "Block", REPLACED_WITH_BLOCK | LEAF),
+ Frame("FileControlLabelFrame", "Block", BLOCK | LEAF),
+ Frame("nsFirstLetterFrame", "Letter", INLINE),
+ Frame("nsFloatingFirstLetterFrame", "Letter", INLINE - {"LineParticipant"}),
+ Frame("nsFirstLineFrame", "Line", INLINE),
+ Frame("nsFlexContainerFrame", "FlexContainer", BLOCK),
+ Frame("nsIFrame", "None", COMMON),
+ Frame("nsGfxButtonControlFrame", "GfxButtonControl", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsGridContainerFrame", "GridContainer", BLOCK),
+ Frame("nsHTMLButtonControlFrame", "HTMLButtonControl", REPLACED_WITH_BLOCK),
+ Frame("nsHTMLCanvasFrame", "HTMLCanvas", REPLACED_SIZING),
+ Frame("nsHTMLFramesetBlankFrame", "None", COMMON | LEAF),
+ Frame("nsHTMLFramesetBorderFrame", "None", COMMON | LEAF),
+ Frame("nsHTMLFramesetFrame", "FrameSet", COMMON | LEAF),
+ Frame("nsHTMLScrollFrame", "Scroll", COMMON),
+ Frame("nsImageControlFrame", "ImageControl", REPLACED_SIZING | LEAF),
+ Frame("nsImageFrame", "Image", REPLACED_SIZING | {"LeafDynamic"}),
+ Frame("nsInlineFrame", "Inline", INLINE),
+ Frame("nsListControlFrame", "ListControl", REPLACED_WITH_BLOCK),
+ Frame("nsMathMLmathBlockFrame", "Block", BLOCK | MATHML),
+ Frame("nsMathMLmathInlineFrame", "Inline", INLINE | MATHML),
+ Frame("nsMathMLmencloseFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmfracFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmmultiscriptsFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmoFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmpaddedFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmrootFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmrowFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmspaceFrame", "None", MATHML_CONTAINER | LEAF),
+ Frame("nsMathMLmsqrtFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLmtableFrame", "Table", TABLE | MATHML),
+ Frame("nsMathMLmtableWrapperFrame", "TableWrapper", BLOCK | MATHML),
+ Frame("nsMathMLmtdFrame", "TableCell", TABLE_CELL | MATHML),
+ Frame("nsMathMLmtdInnerFrame", "Block", BLOCK | MATHML),
+ Frame("nsMathMLmtrFrame", "TableRow", TABLE_PART | MATHML),
+ Frame("nsMathMLmunderoverFrame", "None", MATHML_CONTAINER),
+ Frame("nsMathMLTokenFrame", "None", MATHML_CONTAINER),
+ Frame("nsMenuPopupFrame", "MenuPopup", BLOCK),
+ Frame("nsMeterFrame", "Meter", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsNumberControlFrame", "TextInput", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsPageBreakFrame", "PageBreak", COMMON | LEAF),
+ Frame("nsPageContentFrame", "PageContent", BLOCK),
+ Frame("nsPageFrame", "Page", COMMON),
+ Frame("nsPlaceholderFrame", "Placeholder", COMMON | LEAF),
+ Frame("nsProgressFrame", "Progress", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsRangeFrame", "Range", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsRubyBaseContainerFrame", "RubyBaseContainer", RUBY_CONTENT),
+ Frame("nsRubyBaseFrame", "RubyBase", RUBY_CONTENT),
+ Frame("nsRubyFrame", "Ruby", RUBY_CONTENT),
+ Frame("nsRubyTextContainerFrame", "RubyTextContainer", {"None"}),
+ Frame("nsRubyTextFrame", "RubyText", RUBY_CONTENT),
+ Frame("SimpleXULLeafFrame", "SimpleXULLeaf", COMMON | LEAF),
+ Frame("nsScrollbarButtonFrame", "SimpleXULLeaf", COMMON | LEAF),
+ Frame("nsScrollbarFrame", "Scrollbar", COMMON),
+ Frame("nsSearchControlFrame", "SearchControl", LEAF),
+ Frame("nsSelectsAreaFrame", "Block", BLOCK),
+ Frame("nsPageSequenceFrame", "PageSequence", COMMON),
+ Frame("nsSliderFrame", "Slider", COMMON),
+ Frame("nsSplitterFrame", "SimpleXULLeaf", COMMON | LEAF),
+ Frame("nsSubDocumentFrame", "SubDocument", REPLACED_SIZING_WITH_BLOCK | LEAF),
+ Frame("PrintedSheetFrame", "PrintedSheet", COMMON),
+ Frame("SVGAFrame", "SVGA", SVG_CONTAINER),
+ Frame("SVGClipPathFrame", "SVGClipPath", SVG_CONTAINER),
+ Frame("SVGContainerFrame", "None", SVG_CONTAINER),
+ Frame("SVGFEContainerFrame", "SVGFEContainer", SVG_CONTENT),
+ Frame("SVGFEImageFrame", "SVGFEImage", SVG_CONTENT | LEAF),
+ Frame("SVGFELeafFrame", "SVGFELeaf", SVG_CONTENT | LEAF),
+ Frame("SVGFEUnstyledLeafFrame", "SVGFEUnstyledLeaf", SVG_CONTENT | LEAF),
+ Frame("SVGFilterFrame", "SVGFilter", SVG_CONTAINER),
+ Frame("SVGForeignObjectFrame", "SVGForeignObject", SVG_CONTENT),
+ Frame("SVGGeometryFrame", "SVGGeometry", SVG_CONTENT | LEAF),
+ Frame("SVGGFrame", "SVGG", SVG_CONTAINER),
+ Frame("SVGImageFrame", "SVGImage", SVG_CONTENT | LEAF),
+ Frame("SVGInnerSVGFrame", "SVGInnerSVG", SVG_CONTAINER),
+ Frame("SVGLinearGradientFrame", "SVGLinearGradient", SVG_CONTAINER),
+ Frame("SVGMarkerFrame", "SVGMarker", SVG_CONTAINER),
+ Frame("SVGMarkerAnonChildFrame", "SVGMarkerAnonChild", SVG_CONTAINER),
+ Frame("SVGMaskFrame", "SVGMask", SVG_CONTAINER),
+ Frame(
+ "SVGOuterSVGFrame",
+ "SVGOuterSVG",
+ SVG_CONTAINER | {"Replaced", "ReplacedSizing", "SupportsContainLayoutAndPaint"},
+ ),
+ Frame("SVGOuterSVGAnonChildFrame", "SVGOuterSVGAnonChild", SVG_CONTAINER),
+ Frame("SVGPatternFrame", "SVGPattern", SVG_CONTAINER),
+ Frame("SVGRadialGradientFrame", "SVGRadialGradient", SVG_CONTAINER),
+ Frame("SVGStopFrame", "SVGStop", SVG_CONTENT | LEAF),
+ Frame("SVGSwitchFrame", "SVGSwitch", SVG_CONTAINER),
+ Frame("SVGSymbolFrame", "SVGSymbol", SVG_CONTAINER),
+ Frame("SVGTextFrame", "SVGText", SVG_CONTAINER),
+ # Not a leaf, though it always has a ShadowRoot, so in practice light DOM
+ # children never render.
+ Frame("SVGUseFrame", "SVGUse", SVG_CONTAINER),
+ Frame("MiddleCroppingLabelFrame", "MiddleCroppingLabel", BLOCK | LEAF),
+ Frame("SVGViewFrame", "SVGView", SVG_CONTENT | LEAF),
+ Frame("nsTableCellFrame", "TableCell", TABLE_CELL),
+ Frame("nsTableColFrame", "TableCol", TABLE_PART),
+ Frame("nsTableColGroupFrame", "TableColGroup", TABLE_PART),
+ Frame("nsTableFrame", "Table", TABLE),
+ Frame("nsTableWrapperFrame", "TableWrapper", BLOCK),
+ Frame("nsTableRowFrame", "TableRow", TABLE_PART),
+ Frame("nsTableRowGroupFrame", "TableRowGroup", TABLE_PART),
+ Frame("nsTextControlFrame", "TextInput", REPLACED_WITH_BLOCK | LEAF),
+ Frame("nsTextFrame", "Text", TEXT),
+ Frame("nsTreeBodyFrame", "SimpleXULLeaf", COMMON | LEAF),
+ Frame("nsVideoFrame", "HTMLVideo", REPLACED_SIZING),
+ Frame("nsAudioFrame", "HTMLVideo", REPLACED_SIZING - {"SupportsAspectRatio"}),
+ Frame("ViewportFrame", "Viewport", COMMON),
+ Frame("WBRFrame", "Wbr", COMMON | LEAF),
+ # Non-concrete classes (for FrameIID use)
+ AbstractFrame("MiddleCroppingBlockFrame"),
+ AbstractFrame("nsContainerFrame"),
+ AbstractFrame("nsLeafFrame"),
+ AbstractFrame("nsMathMLFrame"),
+ AbstractFrame("nsMathMLContainerFrame"),
+ AbstractFrame("nsRubyContentFrame"),
+ AbstractFrame("nsSplittableFrame"),
+ AbstractFrame("SVGDisplayContainerFrame"),
+ AbstractFrame("SVGGradientFrame"),
+ AbstractFrame("SVGPaintServerFrame"),
+ # Interfaces (for FrameIID use)
+ AbstractFrame("nsIAnonymousContentCreator"),
+ AbstractFrame("nsIFormControlFrame"),
+ AbstractFrame("nsIMathMLFrame"),
+ AbstractFrame("nsIPercentBSizeObserver"),
+ AbstractFrame("nsIPopupContainer"),
+ AbstractFrame("nsIScrollableFrame"),
+ AbstractFrame("nsIScrollbarMediator"),
+ AbstractFrame("nsISelectControlFrame"),
+ AbstractFrame("nsIStatefulFrame"),
+ AbstractFrame("ISVGDisplayableFrame"),
+ AbstractFrame("ISVGSVGFrame"),
+ AbstractFrame("nsITableCellLayout"),
+ AbstractFrame("nsITableLayout"),
+ AbstractFrame("nsITextControlFrame"),
+]
diff --git a/layout/generic/GenerateFrameLists.py b/layout/generic/GenerateFrameLists.py
new file mode 100644
index 0000000000..7c16398e9d
--- /dev/null
+++ b/layout/generic/GenerateFrameLists.py
@@ -0,0 +1,46 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from FrameClasses import FRAME_CLASSES
+
+HEADER = "// THIS IS AUTOGENERATED BY GenerateFrameLists.py. DO NOT EDIT\n"
+
+
+# Returns a list of list of FrameClass objects. The outermost list groups
+# the FrameClasses by their frame type, and is sorted from the largest group
+# to the smallest, and otherwise sorted by the frame type or class name.
+def grouped_frame_classes():
+ groups = dict()
+ for frame in FRAME_CLASSES:
+ if frame.is_concrete:
+ groups.setdefault(frame.ty, []).append(frame)
+ groups = groups.values()
+ return sorted(groups, key=lambda x: (-len(x), x[0].ty if len(x) > 1 else x[0].cls))
+
+
+def generate_frame_id_list_h(output, *ignore):
+ groups = grouped_frame_classes()
+ output.write(HEADER)
+ for group in groups:
+ for frame in group:
+ output.write(
+ "FRAME_ID(%s, %s, %s)\n"
+ % (
+ frame.cls,
+ frame.ty,
+ "|".join("ClassFlags::%s" % flag for flag in sorted(frame.flags)),
+ )
+ )
+ for frame in FRAME_CLASSES:
+ if not frame.is_concrete:
+ output.write("ABSTRACT_FRAME_ID(%s)\n" % frame.cls)
+
+
+def generate_frame_type_list_h(output, *ignore):
+ groups = grouped_frame_classes()
+ output.write(HEADER)
+ for group in groups:
+ output.write(
+ "FRAME_TYPE(%s, %s, %s)\n" % (group[0].ty, group[0].cls, group[-1].cls)
+ )
diff --git a/layout/generic/JustificationUtils.h b/layout/generic/JustificationUtils.h
new file mode 100644
index 0000000000..346a8b6186
--- /dev/null
+++ b/layout/generic/JustificationUtils.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_JustificationUtils_h_
+#define mozilla_JustificationUtils_h_
+
+#include "mozilla/Attributes.h"
+#include "nsCoord.h"
+
+namespace mozilla {
+
+/**
+ * Jutification Algorithm
+ *
+ * The justification algorithm is based on expansion opportunities
+ * between justifiable clusters. By this algorithm, there is one
+ * expansion opportunity at each side of a justifiable cluster, and
+ * at most one opportunity between two clusters. For example, if there
+ * is a line in a Chinese document is: "你好世界hello world", then
+ * the expansion opportunities (marked as '*') would be:
+ *
+ * 你*好*世*界*hello*' '*world
+ *
+ * The spacing left in a line will then be distributed equally to each
+ * opportunities. Because we want that, only justifiable clusters get
+ * expanded, and the split point between two justifiable clusters would
+ * be at the middle of the spacing, each expansion opportunities will be
+ * filled by two justification gaps. The example above would be:
+ *
+ * 你 | 好 | 世 | 界 |hello| ' ' |world
+ *
+ * In the algorithm, information about expansion opportunities is stored
+ * in structure JustificationInfo, and the assignment of justification
+ * gaps is in structure JustificationAssignment.
+ */
+
+struct JustificationInfo {
+ // Number of expansion opportunities inside a span. It doesn't include
+ // any opportunities between this span and the one before or after.
+ int32_t mInnerOpportunities;
+ // The justifiability of the start and end sides of the span.
+ bool mIsStartJustifiable;
+ bool mIsEndJustifiable;
+
+ constexpr JustificationInfo()
+ : mInnerOpportunities(0),
+ mIsStartJustifiable(false),
+ mIsEndJustifiable(false) {}
+
+ // Claim that the last opportunity should be cancelled
+ // because the trailing space just gets trimmed.
+ void CancelOpportunityForTrimmedSpace() {
+ if (mInnerOpportunities > 0) {
+ mInnerOpportunities--;
+ } else {
+ // There is no inner opportunities, hence the whole frame must
+ // contain only the trimmed space, because any content before
+ // space would cause an inner opportunity. The space made each
+ // side justifiable, which should be cancelled now.
+ mIsStartJustifiable = false;
+ mIsEndJustifiable = false;
+ }
+ }
+};
+
+struct JustificationAssignment {
+ // There are at most 2 gaps per end, so it is enough to use 2 bits.
+ uint8_t mGapsAtStart : 2;
+ uint8_t mGapsAtEnd : 2;
+
+ constexpr JustificationAssignment() : mGapsAtStart(0), mGapsAtEnd(0) {}
+
+ int32_t TotalGaps() const { return mGapsAtStart + mGapsAtEnd; }
+};
+
+struct JustificationApplicationState {
+ struct {
+ // The total number of justification gaps to be processed.
+ int32_t mCount;
+ // The number of justification gaps which have been handled.
+ int32_t mHandled;
+ } mGaps;
+
+ struct {
+ // The total spacing left in a line before justification.
+ nscoord mAvailable;
+ // The spacing has been consumed by handled justification gaps.
+ nscoord mConsumed;
+ } mWidth;
+
+ JustificationApplicationState(int32_t aGaps, nscoord aWidth) {
+ mGaps.mCount = aGaps;
+ mGaps.mHandled = 0;
+ mWidth.mAvailable = aWidth;
+ mWidth.mConsumed = 0;
+ }
+
+ bool IsJustifiable() const {
+ return mGaps.mCount > 0 && mWidth.mAvailable > 0;
+ }
+
+ nscoord Consume(int32_t aGaps) {
+ mGaps.mHandled += aGaps;
+ nscoord newAllocate = (mWidth.mAvailable * mGaps.mHandled) / mGaps.mCount;
+ nscoord deltaWidth = newAllocate - mWidth.mConsumed;
+ mWidth.mConsumed = newAllocate;
+ return deltaWidth;
+ }
+};
+
+class JustificationUtils {
+ public:
+ // Compute justification gaps should be applied on a unit.
+ static int32_t CountGaps(const JustificationInfo& aInfo,
+ const JustificationAssignment& aAssign) {
+ // Justification gaps include two gaps for each inner opportunities
+ // and the gaps given assigned to the ends.
+ return aInfo.mInnerOpportunities * 2 + aAssign.TotalGaps();
+ }
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_JustificationUtils_h_) */
diff --git a/layout/generic/LayoutMessageUtils.h b/layout/generic/LayoutMessageUtils.h
new file mode 100644
index 0000000000..0cc694382f
--- /dev/null
+++ b/layout/generic/LayoutMessageUtils.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef LAYOUT_GENERIC_LAYOUTMESSAGEUTILS_H_
+#define LAYOUT_GENERIC_LAYOUTMESSAGEUTILS_H_
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "nsIFrame.h"
+#include "mozilla/AspectRatio.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::IntrinsicSize> {
+ using paramType = mozilla::IntrinsicSize;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.width);
+ WriteParam(aWriter, aParam.height);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->width) &&
+ ReadParam(aReader, &aResult->height);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::AspectRatio> {
+ using paramType = mozilla::AspectRatio;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mRatio);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mRatio);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::StyleImageRendering>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::StyleImageRendering, mozilla::StyleImageRendering::Auto,
+ mozilla::StyleImageRendering::Optimizequality> {};
+
+} // namespace IPC
+
+#endif // LAYOUT_GENERIC_LAYOUTMESSAGEUTILS_H_
diff --git a/layout/generic/MathMLTextRunFactory.cpp b/layout/generic/MathMLTextRunFactory.cpp
new file mode 100644
index 0000000000..e8cb53833e
--- /dev/null
+++ b/layout/generic/MathMLTextRunFactory.cpp
@@ -0,0 +1,683 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MathMLTextRunFactory.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "mozilla/intl/UnicodeScriptCodes.h"
+
+#include "nsStyleConsts.h"
+#include "nsTextFrameUtils.h"
+#include "nsFontMetrics.h"
+#include "nsDeviceContext.h"
+
+using namespace mozilla;
+
+/*
+ Entries for the mathvariant lookup tables. mKey represents the Unicode
+ character to be transformed and is used for searching the tables.
+ mReplacement represents the mapped mathvariant Unicode character.
+*/
+typedef struct {
+ uint32_t mKey;
+ uint32_t mReplacement;
+} MathVarMapping;
+
+/*
+ Lookup tables for use with mathvariant mappings to transform a unicode
+ character point to another unicode character that indicates the proper output.
+ mKey represents one of two concepts.
+ 1. In the Latin table it represents a hole in the mathematical alphanumeric
+ block, where the character that should occupy that position is located
+ elsewhere.
+ 2. It represents an Arabic letter.
+
+ As a replacement, 0 is reserved to indicate no mapping was found.
+*/
+static const MathVarMapping gArabicInitialMapTable[] = {
+ {0x628, 0x1EE21}, {0x62A, 0x1EE35}, {0x62B, 0x1EE36}, {0x62C, 0x1EE22},
+ {0x62D, 0x1EE27}, {0x62E, 0x1EE37}, {0x633, 0x1EE2E}, {0x634, 0x1EE34},
+ {0x635, 0x1EE31}, {0x636, 0x1EE39}, {0x639, 0x1EE2F}, {0x63A, 0x1EE3B},
+ {0x641, 0x1EE30}, {0x642, 0x1EE32}, {0x643, 0x1EE2A}, {0x644, 0x1EE2B},
+ {0x645, 0x1EE2C}, {0x646, 0x1EE2D}, {0x647, 0x1EE24}, {0x64A, 0x1EE29}};
+
+static const MathVarMapping gArabicTailedMapTable[] = {
+ {0x62C, 0x1EE42}, {0x62D, 0x1EE47}, {0x62E, 0x1EE57}, {0x633, 0x1EE4E},
+ {0x634, 0x1EE54}, {0x635, 0x1EE51}, {0x636, 0x1EE59}, {0x639, 0x1EE4F},
+ {0x63A, 0x1EE5B}, {0x642, 0x1EE52}, {0x644, 0x1EE4B}, {0x646, 0x1EE4D},
+ {0x64A, 0x1EE49}, {0x66F, 0x1EE5F}, {0x6BA, 0x1EE5D}};
+
+static const MathVarMapping gArabicStretchedMapTable[] = {
+ {0x628, 0x1EE61}, {0x62A, 0x1EE75}, {0x62B, 0x1EE76}, {0x62C, 0x1EE62},
+ {0x62D, 0x1EE67}, {0x62E, 0x1EE77}, {0x633, 0x1EE6E}, {0x634, 0x1EE74},
+ {0x635, 0x1EE71}, {0x636, 0x1EE79}, {0x637, 0x1EE68}, {0x638, 0x1EE7A},
+ {0x639, 0x1EE6F}, {0x63A, 0x1EE7B}, {0x641, 0x1EE70}, {0x642, 0x1EE72},
+ {0x643, 0x1EE6A}, {0x645, 0x1EE6C}, {0x646, 0x1EE6D}, {0x647, 0x1EE64},
+ {0x64A, 0x1EE69}, {0x66E, 0x1EE7C}, {0x6A1, 0x1EE7E}};
+
+static const MathVarMapping gArabicLoopedMapTable[] = {
+ {0x627, 0x1EE80}, {0x628, 0x1EE81}, {0x62A, 0x1EE95}, {0x62B, 0x1EE96},
+ {0x62C, 0x1EE82}, {0x62D, 0x1EE87}, {0x62E, 0x1EE97}, {0x62F, 0x1EE83},
+ {0x630, 0x1EE98}, {0x631, 0x1EE93}, {0x632, 0x1EE86}, {0x633, 0x1EE8E},
+ {0x634, 0x1EE94}, {0x635, 0x1EE91}, {0x636, 0x1EE99}, {0x637, 0x1EE88},
+ {0x638, 0x1EE9A}, {0x639, 0x1EE8F}, {0x63A, 0x1EE9B}, {0x641, 0x1EE90},
+ {0x642, 0x1EE92}, {0x644, 0x1EE8B}, {0x645, 0x1EE8C}, {0x646, 0x1EE8D},
+ {0x647, 0x1EE84}, {0x648, 0x1EE85}, {0x64A, 0x1EE89}};
+
+static const MathVarMapping gArabicDoubleMapTable[] = {
+ {0x628, 0x1EEA1}, {0x62A, 0x1EEB5}, {0x62B, 0x1EEB6}, {0x62C, 0x1EEA2},
+ {0x62D, 0x1EEA7}, {0x62E, 0x1EEB7}, {0x62F, 0x1EEA3}, {0x630, 0x1EEB8},
+ {0x631, 0x1EEB3}, {0x632, 0x1EEA6}, {0x633, 0x1EEAE}, {0x634, 0x1EEB4},
+ {0x635, 0x1EEB1}, {0x636, 0x1EEB9}, {0x637, 0x1EEA8}, {0x638, 0x1EEBA},
+ {0x639, 0x1EEAF}, {0x63A, 0x1EEBB}, {0x641, 0x1EEB0}, {0x642, 0x1EEB2},
+ {0x644, 0x1EEAB}, {0x645, 0x1EEAC}, {0x646, 0x1EEAD}, {0x648, 0x1EEA5},
+ {0x64A, 0x1EEA9}};
+
+static const MathVarMapping gLatinExceptionMapTable[] = {
+ {0x1D455, 0x210E}, {0x1D49D, 0x212C}, {0x1D4A0, 0x2130}, {0x1D4A1, 0x2131},
+ {0x1D4A3, 0x210B}, {0x1D4A4, 0x2110}, {0x1D4A7, 0x2112}, {0x1D4A8, 0x2133},
+ {0x1D4AD, 0x211B}, {0x1D4BA, 0x212F}, {0x1D4BC, 0x210A}, {0x1D4C4, 0x2134},
+ {0x1D506, 0x212D}, {0x1D50B, 0x210C}, {0x1D50C, 0x2111}, {0x1D515, 0x211C},
+ {0x1D51D, 0x2128}, {0x1D53A, 0x2102}, {0x1D53F, 0x210D}, {0x1D545, 0x2115},
+ {0x1D547, 0x2119}, {0x1D548, 0x211A}, {0x1D549, 0x211D}, {0x1D551, 0x2124}};
+
+namespace {
+
+struct MathVarMappingWrapper {
+ const MathVarMapping* const mTable;
+ explicit MathVarMappingWrapper(const MathVarMapping* aTable)
+ : mTable(aTable) {}
+ uint32_t operator[](size_t index) const { return mTable[index].mKey; }
+};
+
+} // namespace
+
+// Finds a MathVarMapping struct with the specified key (aKey) within aTable.
+// aTable must be an array, whose length is specified by aNumElements
+static uint32_t MathvarMappingSearch(uint32_t aKey,
+ const MathVarMapping* aTable,
+ uint32_t aNumElements) {
+ size_t index;
+ if (BinarySearch(MathVarMappingWrapper(aTable), 0, aNumElements, aKey,
+ &index)) {
+ return aTable[index].mReplacement;
+ }
+
+ return 0;
+}
+
+#define GREEK_UPPER_THETA 0x03F4
+#define HOLE_GREEK_UPPER_THETA 0x03A2
+#define NABLA 0x2207
+#define PARTIAL_DIFFERENTIAL 0x2202
+#define GREEK_UPPER_ALPHA 0x0391
+#define GREEK_UPPER_OMEGA 0x03A9
+#define GREEK_LOWER_ALPHA 0x03B1
+#define GREEK_LOWER_OMEGA 0x03C9
+#define GREEK_LUNATE_EPSILON_SYMBOL 0x03F5
+#define GREEK_THETA_SYMBOL 0x03D1
+#define GREEK_KAPPA_SYMBOL 0x03F0
+#define GREEK_PHI_SYMBOL 0x03D5
+#define GREEK_RHO_SYMBOL 0x03F1
+#define GREEK_PI_SYMBOL 0x03D6
+#define GREEK_LETTER_DIGAMMA 0x03DC
+#define GREEK_SMALL_LETTER_DIGAMMA 0x03DD
+#define MATH_BOLD_CAPITAL_DIGAMMA 0x1D7CA
+#define MATH_BOLD_SMALL_DIGAMMA 0x1D7CB
+
+#define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
+#define LATIN_SMALL_LETTER_DOTLESS_J 0x0237
+
+#define MATH_ITALIC_SMALL_DOTLESS_I 0x1D6A4
+#define MATH_ITALIC_SMALL_DOTLESS_J 0x1D6A5
+
+#define MATH_BOLD_UPPER_A 0x1D400
+#define MATH_ITALIC_UPPER_A 0x1D434
+#define MATH_BOLD_SMALL_A 0x1D41A
+#define MATH_BOLD_UPPER_ALPHA 0x1D6A8
+#define MATH_BOLD_SMALL_ALPHA 0x1D6C2
+#define MATH_ITALIC_UPPER_ALPHA 0x1D6E2
+#define MATH_BOLD_DIGIT_ZERO 0x1D7CE
+#define MATH_DOUBLE_STRUCK_ZERO 0x1D7D8
+
+#define MATH_BOLD_UPPER_THETA 0x1D6B9
+#define MATH_BOLD_NABLA 0x1D6C1
+#define MATH_BOLD_PARTIAL_DIFFERENTIAL 0x1D6DB
+#define MATH_BOLD_EPSILON_SYMBOL 0x1D6DC
+#define MATH_BOLD_THETA_SYMBOL 0x1D6DD
+#define MATH_BOLD_KAPPA_SYMBOL 0x1D6DE
+#define MATH_BOLD_PHI_SYMBOL 0x1D6DF
+#define MATH_BOLD_RHO_SYMBOL 0x1D6E0
+#define MATH_BOLD_PI_SYMBOL 0x1D6E1
+
+/*
+ Performs the character mapping needed to implement MathML's mathvariant
+ attribute. It takes a unicode character and maps it to its appropriate
+ mathvariant counterpart specified by aMathVar. The mapped character is
+ typically located within Unicode's mathematical blocks (0x1D***, 0x1EE**) but
+ there are exceptions which this function accounts for.
+ Characters without a valid mapping or valid aMathvar value are returned
+ unaltered. Characters already in the mathematical blocks (or are one of the
+ exceptions) are never transformed.
+ Acceptable values for aMathVar are specified in layout/style/nsStyleConsts.h.
+ The transformable characters can be found at:
+ http://lists.w3.org/Archives/Public/www-math/2013Sep/0012.html and
+ https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols
+*/
+/*static */ uint32_t MathMLTextRunFactory::MathVariant(
+ uint32_t aCh, StyleMathVariant aMathVar) {
+ uint32_t baseChar;
+ enum CharacterType {
+ kIsLatin,
+ kIsGreekish,
+ kIsNumber,
+ kIsArabic,
+ };
+ CharacterType varType;
+
+ int8_t multiplier;
+
+ if (aMathVar <= StyleMathVariant::Normal) {
+ // nothing to do here
+ return aCh;
+ }
+ if (aMathVar > StyleMathVariant::Stretched) {
+ NS_ASSERTION(false, "Illegal mathvariant value");
+ return aCh;
+ }
+
+ // Exceptional characters with at most one possible transformation
+ if (aCh == HOLE_GREEK_UPPER_THETA) {
+ // Nothing at this code point is transformed
+ return aCh;
+ }
+ if (aCh == GREEK_LETTER_DIGAMMA) {
+ if (aMathVar == StyleMathVariant::Bold) {
+ return MATH_BOLD_CAPITAL_DIGAMMA;
+ }
+ return aCh;
+ }
+ if (aCh == GREEK_SMALL_LETTER_DIGAMMA) {
+ if (aMathVar == StyleMathVariant::Bold) {
+ return MATH_BOLD_SMALL_DIGAMMA;
+ }
+ return aCh;
+ }
+ if (aCh == LATIN_SMALL_LETTER_DOTLESS_I) {
+ if (aMathVar == StyleMathVariant::Italic) {
+ return MATH_ITALIC_SMALL_DOTLESS_I;
+ }
+ return aCh;
+ }
+ if (aCh == LATIN_SMALL_LETTER_DOTLESS_J) {
+ if (aMathVar == StyleMathVariant::Italic) {
+ return MATH_ITALIC_SMALL_DOTLESS_J;
+ }
+ return aCh;
+ }
+
+ // The Unicode mathematical blocks are divided into four segments: Latin,
+ // Greek, numbers and Arabic. In the case of the first three
+ // baseChar represents the relative order in which the characters are
+ // encoded in the Unicode mathematical block, normalised to the first
+ // character of that sequence.
+ //
+ if ('A' <= aCh && aCh <= 'Z') {
+ baseChar = aCh - 'A';
+ varType = kIsLatin;
+ } else if ('a' <= aCh && aCh <= 'z') {
+ // Lowercase characters are placed immediately after the uppercase
+ // characters in the Unicode mathematical block. The constant subtraction
+ // represents the number of characters between the start of the sequence
+ // (capital A) and the first lowercase letter.
+ baseChar = MATH_BOLD_SMALL_A - MATH_BOLD_UPPER_A + aCh - 'a';
+ varType = kIsLatin;
+ } else if ('0' <= aCh && aCh <= '9') {
+ baseChar = aCh - '0';
+ varType = kIsNumber;
+ } else if (GREEK_UPPER_ALPHA <= aCh && aCh <= GREEK_UPPER_OMEGA) {
+ baseChar = aCh - GREEK_UPPER_ALPHA;
+ varType = kIsGreekish;
+ } else if (GREEK_LOWER_ALPHA <= aCh && aCh <= GREEK_LOWER_OMEGA) {
+ // Lowercase Greek comes after uppercase Greek.
+ // Note in this instance the presence of an additional character (Nabla)
+ // between the end of the uppercase Greek characters and the lowercase
+ // ones.
+ baseChar =
+ MATH_BOLD_SMALL_ALPHA - MATH_BOLD_UPPER_ALPHA + aCh - GREEK_LOWER_ALPHA;
+ varType = kIsGreekish;
+ } else if (0x0600 <= aCh && aCh <= 0x06FF) {
+ // Arabic characters are defined within this range
+ varType = kIsArabic;
+ } else {
+ switch (aCh) {
+ case GREEK_UPPER_THETA:
+ baseChar = MATH_BOLD_UPPER_THETA - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case NABLA:
+ baseChar = MATH_BOLD_NABLA - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case PARTIAL_DIFFERENTIAL:
+ baseChar = MATH_BOLD_PARTIAL_DIFFERENTIAL - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case GREEK_LUNATE_EPSILON_SYMBOL:
+ baseChar = MATH_BOLD_EPSILON_SYMBOL - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case GREEK_THETA_SYMBOL:
+ baseChar = MATH_BOLD_THETA_SYMBOL - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case GREEK_KAPPA_SYMBOL:
+ baseChar = MATH_BOLD_KAPPA_SYMBOL - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case GREEK_PHI_SYMBOL:
+ baseChar = MATH_BOLD_PHI_SYMBOL - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case GREEK_RHO_SYMBOL:
+ baseChar = MATH_BOLD_RHO_SYMBOL - MATH_BOLD_UPPER_ALPHA;
+ break;
+ case GREEK_PI_SYMBOL:
+ baseChar = MATH_BOLD_PI_SYMBOL - MATH_BOLD_UPPER_ALPHA;
+ break;
+ default:
+ return aCh;
+ }
+
+ varType = kIsGreekish;
+ }
+
+ if (varType == kIsNumber) {
+ switch (aMathVar) {
+ // Each possible number mathvariant is encoded in a single, contiguous
+ // block. For example the beginning of the double struck number range
+ // follows immediately after the end of the bold number range.
+ // multiplier represents the order of the sequences relative to the first
+ // one.
+ case StyleMathVariant::Bold:
+ multiplier = 0;
+ break;
+ case StyleMathVariant::DoubleStruck:
+ multiplier = 1;
+ break;
+ case StyleMathVariant::SansSerif:
+ multiplier = 2;
+ break;
+ case StyleMathVariant::BoldSansSerif:
+ multiplier = 3;
+ break;
+ case StyleMathVariant::Monospace:
+ multiplier = 4;
+ break;
+ default:
+ // This mathvariant isn't defined for numbers or is otherwise normal
+ return aCh;
+ }
+ // As the ranges are contiguous, to find the desired mathvariant range it
+ // is sufficient to multiply the position within the sequence order
+ // (multiplier) with the period of the sequence (which is constant for all
+ // number sequences) and to add the character point of the first character
+ // within the number mathvariant range.
+ // To this the baseChar calculated earlier is added to obtain the final
+ // code point.
+ return baseChar +
+ multiplier * (MATH_DOUBLE_STRUCK_ZERO - MATH_BOLD_DIGIT_ZERO) +
+ MATH_BOLD_DIGIT_ZERO;
+ } else if (varType == kIsGreekish) {
+ switch (aMathVar) {
+ case StyleMathVariant::Bold:
+ multiplier = 0;
+ break;
+ case StyleMathVariant::Italic:
+ multiplier = 1;
+ break;
+ case StyleMathVariant::BoldItalic:
+ multiplier = 2;
+ break;
+ case StyleMathVariant::BoldSansSerif:
+ multiplier = 3;
+ break;
+ case StyleMathVariant::SansSerifBoldItalic:
+ multiplier = 4;
+ break;
+ default:
+ // This mathvariant isn't defined for Greek or is otherwise normal
+ return aCh;
+ }
+ // See the kIsNumber case for an explanation of the following calculation
+ return baseChar + MATH_BOLD_UPPER_ALPHA +
+ multiplier * (MATH_ITALIC_UPPER_ALPHA - MATH_BOLD_UPPER_ALPHA);
+ }
+
+ uint32_t tempChar;
+ uint32_t newChar;
+ if (varType == kIsArabic) {
+ const MathVarMapping* mapTable;
+ uint32_t tableLength;
+ switch (aMathVar) {
+ /* The Arabic mathematical block is not continuous, nor does it have a
+ * monotonic mapping to the unencoded characters, requiring the use of a
+ * lookup table.
+ */
+ case StyleMathVariant::Initial:
+ mapTable = gArabicInitialMapTable;
+ tableLength = ArrayLength(gArabicInitialMapTable);
+ break;
+ case StyleMathVariant::Tailed:
+ mapTable = gArabicTailedMapTable;
+ tableLength = ArrayLength(gArabicTailedMapTable);
+ break;
+ case StyleMathVariant::Stretched:
+ mapTable = gArabicStretchedMapTable;
+ tableLength = ArrayLength(gArabicStretchedMapTable);
+ break;
+ case StyleMathVariant::Looped:
+ mapTable = gArabicLoopedMapTable;
+ tableLength = ArrayLength(gArabicLoopedMapTable);
+ break;
+ case StyleMathVariant::DoubleStruck:
+ mapTable = gArabicDoubleMapTable;
+ tableLength = ArrayLength(gArabicDoubleMapTable);
+ break;
+ default:
+ // No valid transformations exist
+ return aCh;
+ }
+ newChar = MathvarMappingSearch(aCh, mapTable, tableLength);
+ } else {
+ // Must be Latin
+ if (aMathVar > StyleMathVariant::Monospace) {
+ // Latin doesn't support the Arabic mathvariants
+ return aCh;
+ }
+ multiplier = uint8_t(aMathVar) - 2;
+ // This is possible because the values for StyleMathVariant::* are
+ // chosen to coincide with the order in which the encoded mathvariant
+ // characters are located within their unicode block (less an offset to
+ // avoid _NONE and _NORMAL variants)
+ // See the kIsNumber case for an explanation of the following calculation
+ tempChar = baseChar + MATH_BOLD_UPPER_A +
+ multiplier * (MATH_ITALIC_UPPER_A - MATH_BOLD_UPPER_A);
+ // There are roughly twenty characters that are located outside of the
+ // mathematical block, so the spaces where they ought to be are used
+ // as keys for a lookup table containing the correct character mappings.
+ newChar = MathvarMappingSearch(tempChar, gLatinExceptionMapTable,
+ ArrayLength(gLatinExceptionMapTable));
+ }
+
+ if (newChar) {
+ return newChar;
+ } else if (varType == kIsLatin) {
+ return tempChar;
+ } else {
+ // An Arabic character without a corresponding mapping
+ return aCh;
+ }
+}
+
+#define TT_SSTY TRUETYPE_TAG('s', 's', 't', 'y')
+#define TT_DTLS TRUETYPE_TAG('d', 't', 'l', 's')
+
+void MathMLTextRunFactory::RebuildTextRun(
+ nsTransformedTextRun* aTextRun, mozilla::gfx::DrawTarget* aRefDrawTarget,
+ gfxMissingFontRecorder* aMFR) {
+ gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
+
+ nsAutoString convertedString;
+ AutoTArray<bool, 50> charsToMergeArray;
+ AutoTArray<bool, 50> deletedCharsArray;
+ AutoTArray<RefPtr<nsTransformedCharStyle>, 50> styleArray;
+ AutoTArray<uint8_t, 50> canBreakBeforeArray;
+ bool mergeNeeded = false;
+
+ bool singleCharMI =
+ !!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSingleCharMi);
+
+ uint32_t length = aTextRun->GetLength();
+ const char16_t* str = aTextRun->mString.BeginReading();
+ const nsTArray<RefPtr<nsTransformedCharStyle>>& styles = aTextRun->mStyles;
+ nsFont font;
+ if (length) {
+ font = styles[0]->mFont;
+
+ if (mSSTYScriptLevel || (mFlags & MATH_FONT_FEATURE_DTLS)) {
+ bool foundSSTY = false;
+ bool foundDTLS = false;
+ // We respect ssty settings explicitly set by the user
+ for (uint32_t i = 0; i < font.fontFeatureSettings.Length(); i++) {
+ if (font.fontFeatureSettings[i].mTag == TT_SSTY) {
+ foundSSTY = true;
+ } else if (font.fontFeatureSettings[i].mTag == TT_DTLS) {
+ foundDTLS = true;
+ }
+ }
+ if (mSSTYScriptLevel && !foundSSTY) {
+ uint8_t sstyLevel = 0;
+ // FIXME: Use the same logic as scale_factor_for_math_depth_change?
+ float scriptScaling =
+ pow(kMathMLDefaultScriptSizeMultiplier, mSSTYScriptLevel);
+ static_assert(kMathMLDefaultScriptSizeMultiplier < 1,
+ "Shouldn't it make things smaller?");
+ /*
+ An SSTY level of 2 is set if the scaling factor is less than or equal
+ to halfway between that for a scriptlevel of 1 (0.71) and that of a
+ scriptlevel of 2 (0.71^2), assuming the default script size
+ multiplier. An SSTY level of 1 is set if the script scaling factor is
+ less than or equal that for a scriptlevel of 1 assuming the default
+ script size multiplier.
+
+ User specified values of script size multiplier will change the
+ scaling factor which mSSTYScriptLevel values correspond to.
+
+ In the event that the script size multiplier actually makes things
+ larger, no change is made.
+
+ To opt out of this change, add the following to the stylesheet:
+ "font-feature-settings: 'ssty' 0"
+ */
+ if (scriptScaling <= (kMathMLDefaultScriptSizeMultiplier +
+ (kMathMLDefaultScriptSizeMultiplier *
+ kMathMLDefaultScriptSizeMultiplier)) /
+ 2) {
+ // Currently only the first two ssty settings are used, so two is
+ // large as we go
+ sstyLevel = 2;
+ } else if (scriptScaling <= kMathMLDefaultScriptSizeMultiplier) {
+ sstyLevel = 1;
+ }
+ if (sstyLevel) {
+ gfxFontFeature settingSSTY;
+ settingSSTY.mTag = TT_SSTY;
+ settingSSTY.mValue = sstyLevel;
+ font.fontFeatureSettings.AppendElement(settingSSTY);
+ }
+ }
+ /*
+ Apply the dtls font feature setting (dotless).
+ This gets applied to the base frame and all descendants of the base
+ frame of certain <mover> and <munderover> frames.
+
+ See nsMathMLmunderoverFrame.cpp for a full description.
+
+ To opt out of this change, add the following to the stylesheet:
+ "font-feature-settings: 'dtls' 0"
+ */
+ if ((mFlags & MATH_FONT_FEATURE_DTLS) && !foundDTLS) {
+ gfxFontFeature settingDTLS;
+ settingDTLS.mTag = TT_DTLS;
+ settingDTLS.mValue = 1;
+ font.fontFeatureSettings.AppendElement(settingDTLS);
+ }
+ }
+ }
+
+ StyleMathVariant mathVar = StyleMathVariant::None;
+ bool doMathvariantStyling = true;
+
+ // Ensure it will be safe to call FindFontForChar in the loop below.
+ fontGroup->CheckForUpdatedPlatformList();
+
+ for (uint32_t i = 0; i < length; ++i) {
+ int extraChars = 0;
+ mathVar = styles[i]->mMathVariant;
+
+ if (singleCharMI && mathVar == StyleMathVariant::None &&
+ (!StaticPrefs::mathml_legacy_mathvariant_attribute_disabled() ||
+ styles[i]->mTextTransform.case_ == StyleTextTransformCase::MathAuto)) {
+ mathVar = StyleMathVariant::Italic;
+ }
+
+ uint32_t ch = str[i];
+ if (i < length - 1 && NS_IS_SURROGATE_PAIR(ch, str[i + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
+ }
+ uint32_t ch2 = MathVariant(ch, mathVar);
+
+ if (!StaticPrefs::mathml_mathvariant_styling_fallback_disabled() &&
+ (mathVar == StyleMathVariant::Bold ||
+ mathVar == StyleMathVariant::BoldItalic ||
+ mathVar == StyleMathVariant::Italic)) {
+ if (ch == ch2 && ch != 0x20 && ch != 0xA0) {
+ // Don't apply the CSS style if a character cannot be
+ // transformed. There is an exception for whitespace as it is both
+ // common and innocuous.
+ doMathvariantStyling = false;
+ }
+ if (ch2 != ch) {
+ // Bug 930504. Some platforms do not have fonts for Mathematical
+ // Alphanumeric Symbols. Hence we check whether the transformed
+ // character is actually available.
+ FontMatchType matchType;
+ RefPtr<gfxFont> mathFont = fontGroup->FindFontForChar(
+ ch2, 0, 0, intl::Script::COMMON, nullptr, &matchType);
+ if (mathFont) {
+ // Don't apply the CSS style if there is a math font for at least one
+ // of the transformed character in this text run.
+ doMathvariantStyling = false;
+ } else {
+ // We fallback to the original character.
+ ch2 = ch;
+ if (aMFR) {
+ aMFR->RecordScript(intl::Script::MATHEMATICAL_NOTATION);
+ }
+ }
+ }
+ }
+
+ deletedCharsArray.AppendElement(false);
+ charsToMergeArray.AppendElement(false);
+ styleArray.AppendElement(styles[i]);
+ canBreakBeforeArray.AppendElement(aTextRun->CanBreakLineBefore(i));
+
+ if (IS_IN_BMP(ch2)) {
+ convertedString.Append(ch2);
+ } else {
+ convertedString.Append(H_SURROGATE(ch2));
+ convertedString.Append(L_SURROGATE(ch2));
+ ++extraChars;
+ if (!IS_IN_BMP(ch)) {
+ deletedCharsArray.AppendElement(
+ true); // not exactly deleted, but
+ // the trailing surrogate is skipped
+ ++i;
+ }
+ }
+
+ while (extraChars-- > 0) {
+ mergeNeeded = true;
+ charsToMergeArray.AppendElement(true);
+ styleArray.AppendElement(styles[i]);
+ canBreakBeforeArray.AppendElement(false);
+ }
+ }
+
+ gfx::ShapedTextFlags flags;
+ gfxTextRunFactory::Parameters innerParams =
+ GetParametersForInner(aTextRun, &flags, aRefDrawTarget);
+
+ RefPtr<nsTransformedTextRun> transformedChild;
+ RefPtr<gfxTextRun> cachedChild;
+ gfxTextRun* child;
+
+ if (!StaticPrefs::mathml_mathvariant_styling_fallback_disabled() &&
+ doMathvariantStyling) {
+ if (mathVar == StyleMathVariant::Bold) {
+ font.style = FontSlantStyle::NORMAL;
+ font.weight = FontWeight::BOLD;
+ } else if (mathVar == StyleMathVariant::Italic) {
+ font.style = FontSlantStyle::ITALIC;
+ font.weight = FontWeight::NORMAL;
+ } else if (mathVar == StyleMathVariant::BoldItalic) {
+ font.style = FontSlantStyle::ITALIC;
+ font.weight = FontWeight::BOLD;
+ }
+ }
+ gfxFontGroup* newFontGroup = nullptr;
+
+ // Get the correct gfxFontGroup that corresponds to the earlier font changes.
+ if (length) {
+ font.size = font.size.ScaledBy(mFontInflation);
+ nsPresContext* pc = styles[0]->mPresContext;
+ nsFontMetrics::Params params;
+ params.language = styles[0]->mLanguage;
+ params.explicitLanguage = styles[0]->mExplicitLanguage;
+ params.userFontSet = pc->GetUserFontSet();
+ params.textPerf = pc->GetTextPerfMetrics();
+ params.featureValueLookup = pc->GetFontFeatureValuesLookup();
+ RefPtr<nsFontMetrics> metrics = pc->GetMetricsFor(font, params);
+ newFontGroup = metrics->GetThebesFontGroup();
+ }
+
+ if (!newFontGroup) {
+ // If we can't get a new font group, fall back to the old one. Rendering
+ // will be incorrect, but not significantly so.
+ newFontGroup = fontGroup;
+ }
+
+ if (mInnerTransformingTextRunFactory) {
+ transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
+ convertedString.BeginReading(), convertedString.Length(), &innerParams,
+ newFontGroup, flags, nsTextFrameUtils::Flags(), std::move(styleArray),
+ false);
+ child = transformedChild.get();
+ } else {
+ cachedChild = newFontGroup->MakeTextRun(
+ convertedString.BeginReading(), convertedString.Length(), &innerParams,
+ flags, nsTextFrameUtils::Flags(), aMFR);
+ child = cachedChild.get();
+ }
+ if (!child) return;
+
+ typedef gfxTextRun::Range Range;
+
+ // Copy potential linebreaks into child so they're preserved
+ // (and also child will be shaped appropriately)
+ NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
+ "Dropped characters or break-before values somewhere!");
+ Range range(0, uint32_t(canBreakBeforeArray.Length()));
+ child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
+ if (transformedChild) {
+ transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
+ }
+
+ aTextRun->ResetGlyphRuns();
+ if (mergeNeeded) {
+ // Now merge multiple characters into one multi-glyph character as required
+ NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
+ "source length mismatch");
+ NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
+ "destination length mismatch");
+ MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
+ deletedCharsArray.Elements());
+ } else {
+ // No merging to do, so just copy; this produces a more optimized textrun.
+ // We can't steal the data because the child may be cached and stealing
+ // the data would break the cache.
+ aTextRun->CopyGlyphDataFrom(child, Range(child), 0);
+ }
+}
diff --git a/layout/generic/MathMLTextRunFactory.h b/layout/generic/MathMLTextRunFactory.h
new file mode 100644
index 0000000000..2be3223f91
--- /dev/null
+++ b/layout/generic/MathMLTextRunFactory.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MATHMLTEXTRUNFACTORY_H_
+#define MATHMLTEXTRUNFACTORY_H_
+
+#include "mozilla/UniquePtr.h"
+#include "nsTextRunTransformations.h"
+
+/**
+ * Builds textruns that render their text with MathML specific renderings.
+ */
+class MathMLTextRunFactory : public nsTransformingTextRunFactory {
+ public:
+ MathMLTextRunFactory(mozilla::UniquePtr<nsTransformingTextRunFactory>
+ aInnerTransformingTextRunFactory,
+ uint32_t aFlags, uint8_t aSSTYScriptLevel,
+ float aFontInflation)
+ : mInnerTransformingTextRunFactory(
+ std::move(aInnerTransformingTextRunFactory)),
+ mFlags(aFlags),
+ mFontInflation(aFontInflation),
+ mSSTYScriptLevel(aSSTYScriptLevel) {}
+
+ static uint32_t MathVariant(uint32_t aCh, mozilla::StyleMathVariant aMathVar);
+ virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
+ mozilla::gfx::DrawTarget* aRefDrawTarget,
+ gfxMissingFontRecorder* aMFR) override;
+ enum {
+ // Style effects which may override single character <mi> behaviour
+ MATH_FONT_FEATURE_DTLS = 0x4, // font feature dtls should be set
+ };
+
+ protected:
+ mozilla::UniquePtr<nsTransformingTextRunFactory>
+ mInnerTransformingTextRunFactory;
+ uint32_t mFlags;
+ float mFontInflation;
+ uint8_t mSSTYScriptLevel;
+};
+
+#endif /*MATHMLTEXTRUNFACTORY_H_*/
diff --git a/layout/generic/MiddleCroppingBlockFrame.cpp b/layout/generic/MiddleCroppingBlockFrame.cpp
new file mode 100644
index 0000000000..4cbbb684aa
--- /dev/null
+++ b/layout/generic/MiddleCroppingBlockFrame.cpp
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MiddleCroppingBlockFrame.h"
+#include "nsTextFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsTextNode.h"
+#include "nsLineLayout.h"
+#include "gfxContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/intl/Segmenter.h"
+#include "mozilla/ReflowInput.h"
+#include "mozilla/ReflowOutput.h"
+
+namespace mozilla {
+
+NS_QUERYFRAME_HEAD(MiddleCroppingBlockFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+ NS_QUERYFRAME_ENTRY(MiddleCroppingBlockFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+MiddleCroppingBlockFrame::MiddleCroppingBlockFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext,
+ ClassID aClassID)
+ : nsBlockFrame(aStyle, aPresContext, aClassID) {}
+
+MiddleCroppingBlockFrame::~MiddleCroppingBlockFrame() = default;
+
+void MiddleCroppingBlockFrame::UpdateDisplayedValue(const nsAString& aValue,
+ bool aIsCropped,
+ bool aNotify) {
+ auto* text = mTextNode.get();
+ uint32_t oldLength = aNotify ? 0 : text->TextLength();
+ text->SetText(aValue, aNotify);
+ if (!aNotify) {
+ // We can't notify during Reflow so we need to tell the text frame about the
+ // text content change we just did.
+ if (auto* textFrame = static_cast<nsTextFrame*>(text->GetPrimaryFrame())) {
+ textFrame->NotifyNativeAnonymousTextnodeChange(oldLength);
+ }
+ if (LinesBegin() != LinesEnd()) {
+ AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ LinesBegin()->MarkDirty();
+ }
+ }
+ mCropped = aIsCropped;
+}
+
+void MiddleCroppingBlockFrame::UpdateDisplayedValueToUncroppedValue(
+ bool aNotify) {
+ nsAutoString value;
+ GetUncroppedValue(value);
+ UpdateDisplayedValue(value, /* aIsCropped = */ false, aNotify);
+}
+
+nscoord MiddleCroppingBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+
+ // Our min inline size is our pref inline size
+ result = GetPrefISize(aRenderingContext);
+ return result;
+}
+
+nscoord MiddleCroppingBlockFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+
+ nsAutoString prevValue;
+ bool restoreOldValue = false;
+
+ // Make sure we measure with the uncropped value.
+ if (mCropped && mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
+ mTextNode->GetNodeValue(prevValue);
+ restoreOldValue = true;
+ UpdateDisplayedValueToUncroppedValue(false);
+ }
+
+ result = nsBlockFrame::GetPrefISize(aRenderingContext);
+
+ if (restoreOldValue) {
+ UpdateDisplayedValue(prevValue, /* aIsCropped = */ true, false);
+ }
+
+ return result;
+}
+
+bool MiddleCroppingBlockFrame::CropTextToWidth(gfxContext& aRenderingContext,
+ nscoord aWidth,
+ nsString& aText) const {
+ if (aText.IsEmpty()) {
+ return false;
+ }
+
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+
+ // see if the text will completely fit in the width given
+ if (nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, *fm,
+ aRenderingContext) <= aWidth) {
+ return false;
+ }
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
+
+ // see if the width is even smaller than the ellipsis
+ fm->SetTextRunRTL(false);
+ const nscoord ellipsisWidth =
+ nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, drawTarget);
+ if (ellipsisWidth >= aWidth) {
+ aText = kEllipsis;
+ return true;
+ }
+
+ // determine how much of the string will fit in the max width
+ nscoord totalWidth = ellipsisWidth;
+ const Span text(aText);
+ intl::GraphemeClusterBreakIteratorUtf16 leftIter(text);
+ intl::GraphemeClusterBreakReverseIteratorUtf16 rightIter(text);
+ uint32_t leftPos = 0;
+ uint32_t rightPos = aText.Length();
+ nsAutoString leftString, rightString;
+
+ while (leftPos < rightPos) {
+ Maybe<uint32_t> pos = leftIter.Next();
+ Span chars = text.FromTo(leftPos, *pos);
+ nscoord charWidth =
+ nsLayoutUtils::AppUnitWidthOfString(chars, *fm, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ leftString.Append(chars);
+ leftPos = *pos;
+ totalWidth += charWidth;
+
+ if (leftPos >= rightPos) {
+ break;
+ }
+
+ pos = rightIter.Next();
+ chars = text.FromTo(*pos, rightPos);
+ charWidth = nsLayoutUtils::AppUnitWidthOfString(chars, *fm, drawTarget);
+ if (totalWidth + charWidth > aWidth) {
+ break;
+ }
+
+ rightString.Insert(chars, 0);
+ rightPos = *pos;
+ totalWidth += charWidth;
+ }
+
+ aText = leftString + kEllipsis + rightString;
+ return true;
+}
+
+void MiddleCroppingBlockFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ // Restore the uncropped value.
+ nsAutoString value;
+ GetUncroppedValue(value);
+ bool cropped = false;
+ while (true) {
+ UpdateDisplayedValue(value, cropped, false); // update the text node
+ AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ LinesBegin()->MarkDirty();
+ nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+ if (cropped) {
+ break;
+ }
+ nscoord currentICoord = aReflowInput.mLineLayout
+ ? aReflowInput.mLineLayout->GetCurrentICoord()
+ : 0;
+ const nscoord availSize = aReflowInput.AvailableISize() - currentICoord;
+ const nscoord sizeToFit = std::min(aReflowInput.ComputedISize(), availSize);
+ if (LinesBegin()->ISize() > sizeToFit) {
+ // The value overflows - crop it and reflow again (once).
+ if (CropTextToWidth(*aReflowInput.mRenderingContext, sizeToFit, value)) {
+ nsBlockFrame::DidReflow(aPresContext, &aReflowInput);
+ aStatus.Reset();
+ MarkSubtreeDirty();
+ AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ cropped = true;
+ continue;
+ }
+ }
+ break;
+ }
+}
+
+nsresult MiddleCroppingBlockFrame::CreateAnonymousContent(
+ nsTArray<ContentInfo>& aContent) {
+ auto* doc = PresContext()->Document();
+ mTextNode = new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager());
+ // Update the displayed text to reflect the current element's value.
+ UpdateDisplayedValueToUncroppedValue(false);
+ aContent.AppendElement(mTextNode);
+ return NS_OK;
+}
+
+void MiddleCroppingBlockFrame::AppendAnonymousContentTo(
+ nsTArray<nsIContent*>& aContent, uint32_t aFilter) {
+ aContent.AppendElement(mTextNode);
+}
+
+void MiddleCroppingBlockFrame::Destroy(DestroyContext& aContext) {
+ aContext.AddAnonymousContent(mTextNode.forget());
+ nsBlockFrame::Destroy(aContext);
+}
+
+} // namespace mozilla
diff --git a/layout/generic/MiddleCroppingBlockFrame.h b/layout/generic/MiddleCroppingBlockFrame.h
new file mode 100644
index 0000000000..666b73440e
--- /dev/null
+++ b/layout/generic/MiddleCroppingBlockFrame.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MiddleCroppingBlockFrame_h
+#define mozilla_MiddleCroppingBlockFrame_h
+
+#include "nsBlockFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsQueryFrame.h"
+
+namespace mozilla {
+
+// A block frame that implements a simplistic version of middle-cropping. Note
+// that this is not fully l10n aware or complex-writing-system-friendly, so it's
+// generally mostly useful for filenames / urls / etc.
+class MiddleCroppingBlockFrame : public nsBlockFrame,
+ public nsIAnonymousContentCreator {
+ public:
+ virtual void GetUncroppedValue(nsAString&) = 0;
+ void UpdateDisplayedValueToUncroppedValue(bool aNotify);
+
+ NS_DECL_QUERYFRAME_TARGET(MiddleCroppingBlockFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_ABSTRACT_FRAME(MiddleCroppingBlockFrame)
+
+ protected:
+ MiddleCroppingBlockFrame(ComputedStyle*, nsPresContext*, ClassID);
+
+ ~MiddleCroppingBlockFrame();
+
+ void Reflow(nsPresContext*, ReflowOutput&, const ReflowInput&,
+ nsReflowStatus&) override;
+
+ nscoord GetMinISize(gfxContext*) override;
+ nscoord GetPrefISize(gfxContext*) override;
+
+ /**
+ * Crop aText to fit inside aWidth using the styles of aFrame.
+ * @return true if aText was modified
+ */
+ bool CropTextToWidth(gfxContext& aRenderingContext, nscoord aWidth,
+ nsString& aText) const;
+
+ nsresult CreateAnonymousContent(nsTArray<ContentInfo>&) override;
+ void AppendAnonymousContentTo(nsTArray<nsIContent*>&,
+ uint32_t aFilter) override;
+
+ /**
+ * Updates the displayed value by using aValue.
+ */
+ void UpdateDisplayedValue(const nsAString& aValue, bool aIsCropped,
+ bool aNotify);
+ void Destroy(DestroyContext&) override;
+
+ RefPtr<dom::Text> mTextNode;
+ bool mCropped = false;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/generic/PrintedSheetFrame.cpp b/layout/generic/PrintedSheetFrame.cpp
new file mode 100644
index 0000000000..5b8151bbd0
--- /dev/null
+++ b/layout/generic/PrintedSheetFrame.cpp
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/* Rendering object for a printed or print-previewed sheet of paper */
+
+#include "mozilla/PrintedSheetFrame.h"
+
+#include <tuple>
+
+#include "mozilla/StaticPrefs_print.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsPageContentFrame.h"
+#include "nsPageFrame.h"
+#include "nsPageSequenceFrame.h"
+
+using namespace mozilla;
+
+PrintedSheetFrame* NS_NewPrintedSheetFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ PrintedSheetFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_QUERYFRAME_HEAD(PrintedSheetFrame)
+ NS_QUERYFRAME_ENTRY(PrintedSheetFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(PrintedSheetFrame)
+
+void PrintedSheetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (PresContext()->IsScreen()) {
+ // Draw the background/shadow/etc. of a blank sheet of paper, for
+ // print-preview.
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+ }
+
+ for (auto* frame : mFrames) {
+ if (!frame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE)) {
+ BuildDisplayListForChild(aBuilder, frame, aLists);
+ }
+ }
+}
+
+// If the given page is included in the user's page range, this function
+// returns false. Otherwise, it tags the page with the
+// NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state bit and returns true.
+static bool TagIfSkippedByCustomRange(nsPageFrame* aPageFrame, int32_t aPageNum,
+ nsSharedPageData* aPD) {
+ if (!nsIPrintSettings::IsPageSkipped(aPageNum, aPD->mPageRanges)) {
+ MOZ_ASSERT(!aPageFrame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE),
+ "page frames NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state should "
+ "only be set if we actually want to skip the page");
+ return false;
+ }
+
+ aPageFrame->AddStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE);
+ return true;
+}
+
+void PrintedSheetFrame::ClaimPageFrameFromPrevInFlow() {
+ MoveOverflowToChildList();
+ if (!GetPrevContinuation()) {
+ // The first page content frame of each document will not yet have its page
+ // style set yet. This is because normally page style is set either from
+ // the previous page content frame, or using the new page name when named
+ // pages cause a page break in block reflow. Ensure that, for the first
+ // page, it is set here so that all nsPageContentFrames have their page
+ // style set before reflow.
+ auto* firstChild = PrincipalChildList().FirstChild();
+ MOZ_ASSERT(firstChild && firstChild->IsPageFrame(),
+ "PrintedSheetFrame only has nsPageFrame children");
+ auto* pageFrame = static_cast<nsPageFrame*>(firstChild);
+ pageFrame->PageContentFrame()->EnsurePageName();
+ }
+}
+
+void PrintedSheetFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("PrintedSheetFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ // If we have a prev-in-flow, take its overflowing content:
+ MoveOverflowToChildList();
+
+ const WritingMode wm = aReflowInput.GetWritingMode();
+
+ // See the comments for GetSizeForChildren.
+ // Note that nsPageFrame::ComputeSinglePPSPageSizeScale depends on this value
+ // and is currently called while reflowing a single nsPageFrame child (i.e.
+ // before we've finished reflowing ourself). Ideally our children wouldn't be
+ // accessing our dimensions until after we've finished reflowing ourself -
+ // see bug 1835782.
+ mSizeForChildren =
+ nsSize(aReflowInput.AvailableISize(), aReflowInput.AvailableBSize());
+ if (mPD->PagesPerSheetInfo()->mNumPages == 1) {
+ auto* firstChild = PrincipalChildList().FirstChild();
+ MOZ_ASSERT(firstChild && firstChild->IsPageFrame(),
+ "PrintedSheetFrame only has nsPageFrame children");
+ if (static_cast<nsPageFrame*>(firstChild)
+ ->GetPageOrientationRotation(mPD) != 0.0) {
+ std::swap(mSizeForChildren.width, mSizeForChildren.height);
+ }
+ }
+
+ // Count the number of pages that are displayed on this sheet (i.e. how many
+ // child frames we end up laying out, excluding any pages that are skipped
+ // due to not being in the user's page-range selection).
+ uint32_t numPagesOnThisSheet = 0;
+
+ // Target for numPagesOnThisSheet.
+ const uint32_t desiredPagesPerSheet = mPD->PagesPerSheetInfo()->mNumPages;
+
+ if (desiredPagesPerSheet > 1) {
+ ComputePagesPerSheetGridMetrics(mSizeForChildren);
+ }
+
+ // NOTE: I'm intentionally *not* using a range-based 'for' loop here, since
+ // we potentially mutate the frame list (appending to the end) during the
+ // list, which is not generally safe with range-based 'for' loops.
+ for (auto* childFrame = mFrames.FirstChild(); childFrame;
+ childFrame = childFrame->GetNextSibling()) {
+ MOZ_ASSERT(childFrame->IsPageFrame(),
+ "we're only expecting page frames as children");
+ auto* pageFrame = static_cast<nsPageFrame*>(childFrame);
+
+ // Be sure our child has a pointer to the nsSharedPageData and knows its
+ // page number:
+ pageFrame->SetSharedPageData(mPD);
+ pageFrame->DeterminePageNum();
+
+ if (!TagIfSkippedByCustomRange(pageFrame, pageFrame->GetPageNum(), mPD)) {
+ // The page is going to be displayed on this sheet. Tell it its index
+ // among the displayed pages, so we can use that to compute its "cell"
+ // when painting.
+ pageFrame->SetIndexOnSheet(numPagesOnThisSheet);
+ numPagesOnThisSheet++;
+ }
+
+ // This is the app-unit size of the page (in physical & logical units).
+ // Note: The page sizes come from CSS or else from the user selected size;
+ // pages are never reflowed to fit their sheet - if/when necessary they are
+ // scaled to fit their sheet. Hence why we get the page's own dimensions to
+ // use as its "available space"/"container size" here.
+ const nsSize physPageSize = pageFrame->ComputePageSize();
+ const LogicalSize pageSize(wm, physPageSize);
+
+ ReflowInput pageReflowInput(aPresContext, aReflowInput, pageFrame,
+ pageSize);
+
+ // For layout purposes, we position *all* our nsPageFrame children at our
+ // origin. Then, if we have multiple pages-per-sheet, we'll shrink & shift
+ // each one into the right position as a paint-time effect, in
+ // BuildDisplayList.
+ LogicalPoint pagePos(wm);
+
+ // Outparams for reflow:
+ ReflowOutput pageReflowOutput(pageReflowInput);
+ nsReflowStatus status;
+
+ ReflowChild(pageFrame, aPresContext, pageReflowOutput, pageReflowInput, wm,
+ pagePos, physPageSize, ReflowChildFlags::Default, status);
+
+ FinishReflowChild(pageFrame, aPresContext, pageReflowOutput,
+ &pageReflowInput, wm, pagePos, physPageSize,
+ ReflowChildFlags::Default);
+
+ // Since we don't support incremental reflow in printed documents (see the
+ // early-return in nsPageSequenceFrame::Reflow), we can assume that this
+ // was the first time that pageFrame has been reflowed, and so there's no
+ // way that it could already have a next-in-flow. If it *did* have a
+ // next-in-flow, we would need to handle it in the 'status' logic below.
+ NS_ASSERTION(!pageFrame->GetNextInFlow(), "bad child flow list");
+
+ // Did this page complete the document, or do we need to generate
+ // another page frame?
+ if (status.IsFullyComplete()) {
+ // The page we just reflowed is the final page! Record its page number
+ // as the number of pages:
+ mPD->mRawNumPages = pageFrame->GetPageNum();
+ } else {
+ // Create a continuation for our page frame. We add the continuation to
+ // our child list, and then potentially push it to our overflow list, if
+ // it really belongs on the next sheet.
+ nsIFrame* continuingPage =
+ PresShell()->FrameConstructor()->CreateContinuingFrame(pageFrame,
+ this);
+ mFrames.InsertFrame(nullptr, pageFrame, continuingPage);
+ const bool isContinuingPageSkipped =
+ TagIfSkippedByCustomRange(static_cast<nsPageFrame*>(continuingPage),
+ pageFrame->GetPageNum() + 1, mPD);
+
+ // If we've already reached the target number of pages for this sheet,
+ // and this continuation page that we just created is meant to be
+ // displayed (i.e. it's in the chosen page range), then we need to push it
+ // to our overflow list so that it'll go onto a subsequent sheet.
+ // Otherwise we leave it on this sheet. This ensures we *only* generate
+ // another sheet IFF there's a displayable page that will end up on it.
+ if (numPagesOnThisSheet >= desiredPagesPerSheet &&
+ !isContinuingPageSkipped) {
+ PushChildrenToOverflow(continuingPage, pageFrame);
+ aStatus.SetIncomplete();
+ }
+ }
+ }
+
+ // This should hold for the first sheet, because our UI should prevent the
+ // user from creating a 0-length page range; and it should hold for
+ // subsequent sheets because we should only create an additional sheet when
+ // we discover a displayable (i.e. non-skipped) page that we need to push
+ // to that new sheet.
+
+ // XXXdholbert In certain edge cases (e.g. after a page-orientation-flip that
+ // reduces the page count), it's possible for us to be given a page range
+ // that is *entirely out-of-bounds* (with "from" & "to" both being larger
+ // than our actual page-number count). This scenario produces a single
+ // PrintedSheetFrame with zero displayable pages on it, which is a weird
+ // state to be in. This is hopefully a scenario that the frontend code can
+ // detect and recover from (e.g. by clamping the range to our reported
+ // `rawNumPages`), but it can't do that until *after* we've completed this
+ // problematic reflow and can reported an up-to-date `rawNumPages` to the
+ // frontend. So: to give the frontend a chance to intervene and apply some
+ // correction/clamping to its print-range parameters, we soften this
+ // assertion *specifically for the first printed sheet*.
+ if (!GetPrevContinuation()) {
+ NS_WARNING_ASSERTION(numPagesOnThisSheet > 0,
+ "Shouldn't create a sheet with no displayable pages "
+ "on it");
+ } else {
+ MOZ_ASSERT(numPagesOnThisSheet > 0,
+ "Shouldn't create a sheet with no displayable pages on it");
+ }
+
+ MOZ_ASSERT(numPagesOnThisSheet <= desiredPagesPerSheet,
+ "Shouldn't have more than desired number of displayable pages "
+ "on this sheet");
+ mNumPages = numPagesOnThisSheet;
+
+ // Populate our ReflowOutput outparam -- just use up all the
+ // available space, for both our desired size & overflow areas.
+ aReflowOutput.ISize(wm) = aReflowInput.AvailableISize();
+ if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
+ aReflowOutput.BSize(wm) = aReflowInput.AvailableBSize();
+ }
+ aReflowOutput.SetOverflowAreasToDesiredBounds();
+
+ FinishAndStoreOverflow(&aReflowOutput);
+}
+
+nsSize PrintedSheetFrame::ComputeSheetSize(const nsPresContext* aPresContext) {
+ // We use the user selected page (sheet) dimensions, and default to the
+ // orientation as specified by the user.
+ nsSize sheetSize = aPresContext->GetPageSize();
+
+ // Don't waste cycles changing the orientation of a square.
+ if (sheetSize.width == sheetSize.height) {
+ return sheetSize;
+ }
+
+ if (!StaticPrefs::
+ print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) {
+ if (mPD->mPrintSettings->HasOrthogonalPagesPerSheet()) {
+ std::swap(sheetSize.width, sheetSize.height);
+ }
+ return sheetSize;
+ }
+
+ auto* firstChild = PrincipalChildList().FirstChild();
+ MOZ_ASSERT(firstChild->IsPageFrame(),
+ "PrintedSheetFrame only has nsPageFrame children");
+ auto* sheetsFirstPageFrame = static_cast<nsPageFrame*>(firstChild);
+
+ nsSize pageSize = sheetsFirstPageFrame->ComputePageSize();
+
+ // Don't waste cycles changing the orientation of a square.
+ if (pageSize.width == pageSize.height) {
+ return sheetSize;
+ }
+
+ const bool pageIsRotated =
+ sheetsFirstPageFrame->GetPageOrientationRotation(mPD) != 0.0;
+
+ if (pageIsRotated && pageSize.width == pageSize.height) {
+ // Straighforward rotation without needing sheet orientation optimization.
+ std::swap(sheetSize.width, sheetSize.height);
+ return sheetSize;
+ }
+
+ // Try to orient the sheet optimally based on the physical orientation of the
+ // first/sole page on the sheet. (In the multiple pages-per-sheet case, the
+ // first page is the only one that exists at this point in the code, so it is
+ // the only one we can reason about. Any other pages may, or may not, have
+ // the same physical orientation.)
+
+ if (pageIsRotated) {
+ // Fix up for its physical orientation:
+ std::swap(pageSize.width, pageSize.height);
+ }
+
+ const bool pageIsPortrait = pageSize.width < pageSize.height;
+ const bool sheetIsPortrait = sheetSize.width < sheetSize.height;
+
+ // Switch the sheet orientation if the page orientation is different, or
+ // if we need to switch it because the number of pages-per-sheet demands
+ // orthogonal sheet layout, but not if both are true since then we'd
+ // actually need to double switch.
+ if ((sheetIsPortrait != pageIsPortrait) !=
+ mPD->mPrintSettings->HasOrthogonalPagesPerSheet()) {
+ std::swap(sheetSize.width, sheetSize.height);
+ }
+
+ return sheetSize;
+}
+
+void PrintedSheetFrame::ComputePagesPerSheetGridMetrics(
+ const nsSize& aSheetSize) {
+ MOZ_ASSERT(mPD->PagesPerSheetInfo()->mNumPages > 1,
+ "Unnecessary to call this in a regular 1-page-per-sheet scenario; "
+ "the computed values won't ever be used in that case");
+
+ // Compute the space available for the pages-per-sheet "page grid" (just
+ // subtract the sheet's unwriteable margin area):
+ nsSize availSpaceOnSheet = aSheetSize;
+ nsMargin uwm = mPD->mPrintSettings->GetIgnoreUnwriteableMargins()
+ ? nsMargin{}
+ : nsPresContext::CSSTwipsToAppUnits(
+ mPD->mPrintSettings->GetUnwriteableMarginInTwips());
+
+ // XXXjwatt Once we support heterogeneous sheet orientations, we'll also need
+ // to rotate uwm if this sheet is not the primary orientation.
+ if (mPD->mPrintSettings->HasOrthogonalPagesPerSheet()) {
+ // aSheetSize already takes account of the switch of *sheet* orientation
+ // that we do in this case (the orientation implied by the page size
+ // dimensions in the nsIPrintSettings applies to *pages*). That is not the
+ // case for the unwriteable margins since we got them from the
+ // nsIPrintSettings object ourself, so we need to adjust `uwm` here.
+ //
+ // Note: In practice, sheets with an orientation that is orthogonal to the
+ // physical orientation of sheets output by a printer must be rotated 90
+ // degrees for/by the printer. In that case the convention seems to be that
+ // the "left" edge of the orthogonally oriented sheet becomes the "top",
+ // and so forth. The rotation direction will matter in the case that the
+ // top and bottom unwriteable margins are different, or the left and right
+ // unwriteable margins are different. So we need to match this behavior,
+ // which means we must rotate the `uwm` 90 degrees *counter-clockwise*.
+ nsMargin rotated(uwm.right, uwm.bottom, uwm.left, uwm.top);
+ uwm = rotated;
+ }
+
+ availSpaceOnSheet.width -= uwm.LeftRight();
+ availSpaceOnSheet.height -= uwm.TopBottom();
+
+ if (MOZ_UNLIKELY(availSpaceOnSheet.IsEmpty())) {
+ // This sort of thing should be rare, but it can happen if there are
+ // bizarre page sizes, and/or if there's an unexpectedly large unwriteable
+ // margin area.
+ NS_WARNING("Zero area for pages-per-sheet grid, or zero-sized grid");
+ mGridOrigin = nsPoint(0, 0);
+ mGridNumCols = 1;
+ return;
+ }
+
+ // If there are a different number of rows vs. cols, we'll aim to put
+ // the larger number of items in the longer axis.
+ const auto* ppsInfo = mPD->PagesPerSheetInfo();
+ uint32_t smallerNumTracks = ppsInfo->mNumPages / ppsInfo->mLargerNumTracks;
+ bool sheetIsPortraitLike = aSheetSize.width < aSheetSize.height;
+ auto numCols =
+ sheetIsPortraitLike ? smallerNumTracks : ppsInfo->mLargerNumTracks;
+ auto numRows =
+ sheetIsPortraitLike ? ppsInfo->mLargerNumTracks : smallerNumTracks;
+
+ mGridOrigin = nsPoint(uwm.left, uwm.top);
+ mGridNumCols = numCols;
+ mGridCellWidth = availSpaceOnSheet.width / nscoord(numCols);
+ mGridCellHeight = availSpaceOnSheet.height / nscoord(numRows);
+}
+
+gfx::IntSize PrintedSheetFrame::GetPrintTargetSizeInPoints(
+ const int32_t aAppUnitsPerPhysicalInch) const {
+ const auto size = GetSize();
+ MOZ_ASSERT(size.width > 0 && size.height > 0);
+ const float pointsPerAppUnit =
+ POINTS_PER_INCH_FLOAT / float(aAppUnitsPerPhysicalInch);
+ return IntSize::Ceil(float(size.width) * pointsPerAppUnit,
+ float(size.height) * pointsPerAppUnit);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult PrintedSheetFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"PrintedSheet"_ns, aResult);
+}
+#endif
+
+} // namespace mozilla
diff --git a/layout/generic/PrintedSheetFrame.h b/layout/generic/PrintedSheetFrame.h
new file mode 100644
index 0000000000..c914e374c1
--- /dev/null
+++ b/layout/generic/PrintedSheetFrame.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/* Rendering object for a printed or print-previewed sheet of paper */
+
+#ifndef LAYOUT_GENERIC_PRINTEDSHEETFRAME_H_
+#define LAYOUT_GENERIC_PRINTEDSHEETFRAME_H_
+
+#include "mozilla/gfx/Point.h"
+#include "nsContainerFrame.h"
+#include "nsHTMLParts.h"
+
+class nsSharedPageData;
+
+namespace mozilla {
+
+class PrintedSheetFrame final : public nsContainerFrame {
+ public:
+ using IntSize = mozilla::gfx::IntSize;
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(PrintedSheetFrame)
+
+ friend PrintedSheetFrame* ::NS_NewPrintedSheetFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ void SetSharedPageData(nsSharedPageData* aPD) { mPD = aPD; }
+
+ // XXX: this needs a better name, since it also updates style.
+ // Invokes MoveOverflowToChildList.
+ // This is intended for use by callers that need to be able to get our first/
+ // only nsPageFrame from our child list to examine its computed style just
+ // **prior** to us being reflowed. (If our first nsPageFrame will come from
+ // our prev-in-flow, we won't otherwise take ownership of it until we are
+ // reflowed.)
+ void ClaimPageFrameFromPrevInFlow();
+
+ // nsIFrame overrides
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ uint32_t GetNumPages() const { return mNumPages; }
+
+ // These methods provide information about the grid that pages should be
+ // placed into in the case that there are multiple pages-per-sheet.
+ uint32_t GetGridNumCols() const { return mGridNumCols; }
+ nsPoint GetGridOrigin() const { return mGridOrigin; }
+ nscoord GetGridCellWidth() const { return mGridCellWidth; }
+ nscoord GetGridCellHeight() const { return mGridCellHeight; }
+
+ nsSize ComputeSheetSize(const nsPresContext* aPresContext);
+
+ /**
+ * When we're printing one page-per-sheet and `page-orientation` on our
+ * single nsPageFrame child should cause the page to rotate, then we want to
+ * essentially rotate the sheet. We implement that by switching the
+ * dimensions of this sheet (changing its orientation), sizing the
+ * nsPageFrame to the original dimensions, and then applying the rotation to
+ * the nsPageFrame child.
+ *
+ * This returns the dimensions that this frame would have without any
+ * dimension swap we may have done to implement `page-orientation`. If
+ * there is no rotation caused by `page-orientation`, then the value returned
+ * and mRect.Size() are identical.
+ */
+ nsSize GetSizeForChildren() const { return mSizeForChildren; }
+
+ /**
+ * This method returns the dimensions of the physical page that the target
+ * [pseudo-]printer should create. This may be different from our own
+ * dimensions in the case where CSS `page-orientation` causes us to be
+ * rotated, but we only support that if the PrintTarget backend supports
+ * different page sizes/orientations. That's only the case for our Save-to-PDF
+ * backends (possibly other save-to-file outputs in future).
+ *
+ * The dimensions returned are expected to be passed to
+ * nsDeviceContext::BeginPage, which will pass them on to
+ * PrintTarget::BeginPage to use as the physical dimensions of the page.
+ */
+ IntSize GetPrintTargetSizeInPoints(
+ const int32_t aAppUnitsPerPhysicalInch) const;
+
+ private:
+ // Private construtor & destructor, to avoid accidental (non-FrameArena)
+ // instantiation/deletion:
+ PrintedSheetFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {}
+ ~PrintedSheetFrame() = default;
+
+ // Helper function to populate some pages-per-sheet metrics in our
+ // nsSharedPageData.
+ // XXXjwatt: We should investigate sharing this function for the single
+ // page-per-sheet case (bug 1835782). The logic for that case
+ // (nsPageFrame::ComputePageSizeScale) is somewhat different though, since
+ // that case uses no sheet margins and uses the user/CSS specified margins on
+ // the page, with any page scaling reverted to keep the margins unchanged.
+ // We, on the other hand, use the unwriteable margins for the sheet, unscaled,
+ // and use the user/CSS margins on the pages and allow them to be scaled
+ // along with any pages-per-sheet scaling. (This behavior makes maximum use
+ // of the sheet and, by scaling the default on the pages, results in a
+ // a sensible amount of spacing between pages.)
+ void ComputePagesPerSheetGridMetrics(const nsSize& aSheetSize);
+
+ // See GetSizeForChildren.
+ nsSize mSizeForChildren;
+
+ // Note: this will be set before reflow, and it's strongly owned by our
+ // nsPageSequenceFrame, which outlives us.
+ nsSharedPageData* mPD = nullptr;
+
+ // The number of visible pages in this sheet.
+ uint32_t mNumPages = 0;
+
+ // Number of "columns" in our pages-per-sheet layout. For example: if we're
+ // printing with 6 pages-per-sheet, then this could be either 3 or 2,
+ // depending on whether we're printing portrait-oriented pages onto a
+ // landscape-oriented sheet (3 cols) vs. if we're printing landscape-oriented
+ // pages onto a portrait-oriented sheet (2 cols).
+ uint32_t mGridNumCols = 1;
+
+ // The offset of the start of the multiple pages-per-sheet grid from the
+ // top-left of the sheet.
+ nsPoint mGridOrigin;
+
+ // The size of each cell on the sheet into which pages are to be placed.
+ // (The default values are arbitrary.)
+ nscoord mGridCellWidth = 1;
+ nscoord mGridCellHeight = 1;
+};
+
+} // namespace mozilla
+
+#endif /* LAYOUT_GENERIC_PRINTEDSHEETFRAME_H_ */
diff --git a/layout/generic/ReflowInput.cpp b/layout/generic/ReflowInput.cpp
new file mode 100644
index 0000000000..ed7dbd2656
--- /dev/null
+++ b/layout/generic/ReflowInput.cpp
@@ -0,0 +1,3042 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* struct containing the input to nsIFrame::Reflow */
+
+#include "mozilla/ReflowInput.h"
+
+#include <algorithm>
+
+#include "CounterStyleManager.h"
+#include "LayoutLogging.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/WritingModes.h"
+#include "nsBlockFrame.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsFlexContainerFrame.h"
+#include "nsFontInflationData.h"
+#include "nsFontMetrics.h"
+#include "nsGkAtoms.h"
+#include "nsGridContainerFrame.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsImageFrame.h"
+#include "nsIPercentBSizeObserver.h"
+#include "nsLayoutUtils.h"
+#include "nsLineBox.h"
+#include "nsPresContext.h"
+#include "nsStyleConsts.h"
+#include "nsTableCellFrame.h"
+#include "nsTableFrame.h"
+#include "StickyScrollContainer.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+
+static bool CheckNextInFlowParenthood(nsIFrame* aFrame, nsIFrame* aParent) {
+ nsIFrame* frameNext = aFrame->GetNextInFlow();
+ nsIFrame* parentNext = aParent->GetNextInFlow();
+ return frameNext && parentNext && frameNext->GetParent() == parentNext;
+}
+
+/**
+ * Adjusts the margin for a list (ol, ul), if necessary, depending on
+ * font inflation settings. Unfortunately, because bullets from a list are
+ * placed in the margin area, we only have ~40px in which to place the
+ * bullets. When they are inflated, however, this causes problems, since
+ * the text takes up more space than is available in the margin.
+ *
+ * This method will return a small amount (in app units) by which the
+ * margin can be adjusted, so that the space is available for list
+ * bullets to be rendered with font inflation enabled.
+ */
+static nscoord FontSizeInflationListMarginAdjustment(const nsIFrame* aFrame) {
+ if (!aFrame->IsBlockFrameOrSubclass()) {
+ return 0;
+ }
+
+ // We only want to adjust the margins if we're dealing with an ordered list.
+ const nsBlockFrame* blockFrame = static_cast<const nsBlockFrame*>(aFrame);
+ if (!blockFrame->HasMarker()) {
+ return 0;
+ }
+
+ float inflation = nsLayoutUtils::FontSizeInflationFor(aFrame);
+ if (inflation <= 1.0f) {
+ return 0;
+ }
+
+ // The HTML spec states that the default padding for ordered lists
+ // begins at 40px, indicating that we have 40px of space to place a
+ // bullet. When performing font inflation calculations, we add space
+ // equivalent to this, but simply inflated at the same amount as the
+ // text, in app units.
+ auto margin = nsPresContext::CSSPixelsToAppUnits(40) * (inflation - 1);
+
+ auto* list = aFrame->StyleList();
+ if (!list->mCounterStyle.IsAtom()) {
+ return margin;
+ }
+
+ nsAtom* type = list->mCounterStyle.AsAtom();
+ if (type != nsGkAtoms::none && type != nsGkAtoms::disc &&
+ type != nsGkAtoms::circle && type != nsGkAtoms::square &&
+ type != nsGkAtoms::disclosure_closed &&
+ type != nsGkAtoms::disclosure_open) {
+ return margin;
+ }
+
+ return 0;
+}
+
+SizeComputationInput::SizeComputationInput(nsIFrame* aFrame,
+ gfxContext* aRenderingContext)
+ : mFrame(aFrame),
+ mRenderingContext(aRenderingContext),
+ mWritingMode(aFrame->GetWritingMode()),
+ mIsThemed(aFrame->IsThemed()),
+ mComputedMargin(mWritingMode),
+ mComputedBorderPadding(mWritingMode),
+ mComputedPadding(mWritingMode) {
+ MOZ_ASSERT(mFrame);
+}
+
+SizeComputationInput::SizeComputationInput(
+ nsIFrame* aFrame, gfxContext* aRenderingContext,
+ WritingMode aContainingBlockWritingMode, nscoord aContainingBlockISize,
+ const Maybe<LogicalMargin>& aBorder, const Maybe<LogicalMargin>& aPadding)
+ : SizeComputationInput(aFrame, aRenderingContext) {
+ MOZ_ASSERT(!mFrame->IsTableColFrame());
+ InitOffsets(aContainingBlockWritingMode, aContainingBlockISize,
+ mFrame->Type(), {}, aBorder, aPadding);
+}
+
+// Initialize a <b>root</b> reflow input with a rendering context to
+// use for measuring things.
+ReflowInput::ReflowInput(nsPresContext* aPresContext, nsIFrame* aFrame,
+ gfxContext* aRenderingContext,
+ const LogicalSize& aAvailableSpace, InitFlags aFlags)
+ : SizeComputationInput(aFrame, aRenderingContext),
+ mAvailableSize(aAvailableSpace) {
+ MOZ_ASSERT(aRenderingContext, "no rendering context");
+ MOZ_ASSERT(aPresContext, "no pres context");
+ MOZ_ASSERT(aFrame, "no frame");
+ MOZ_ASSERT(aPresContext == aFrame->PresContext(), "wrong pres context");
+
+ if (aFlags.contains(InitFlag::DummyParentReflowInput)) {
+ mFlags.mDummyParentReflowInput = true;
+ }
+ if (aFlags.contains(InitFlag::StaticPosIsCBOrigin)) {
+ mFlags.mStaticPosIsCBOrigin = true;
+ }
+
+ if (!aFlags.contains(InitFlag::CallerWillInit)) {
+ Init(aPresContext);
+ }
+ // When we encounter a PageContent frame this will be set to true.
+ mFlags.mCanHaveClassABreakpoints = false;
+}
+
+// Initialize a reflow input for a child frame's reflow. Some state
+// is copied from the parent reflow input; the remaining state is
+// computed.
+ReflowInput::ReflowInput(nsPresContext* aPresContext,
+ const ReflowInput& aParentReflowInput,
+ nsIFrame* aFrame, const LogicalSize& aAvailableSpace,
+ const Maybe<LogicalSize>& aContainingBlockSize,
+ InitFlags aFlags,
+ const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aComputeSizeFlags)
+ : SizeComputationInput(aFrame, aParentReflowInput.mRenderingContext),
+ mParentReflowInput(&aParentReflowInput),
+ mFloatManager(aParentReflowInput.mFloatManager),
+ mLineLayout(mFrame->IsLineParticipant() ? aParentReflowInput.mLineLayout
+ : nullptr),
+ mBreakType(aParentReflowInput.mBreakType),
+ mPercentBSizeObserver(
+ (aParentReflowInput.mPercentBSizeObserver &&
+ aParentReflowInput.mPercentBSizeObserver->NeedsToObserve(*this))
+ ? aParentReflowInput.mPercentBSizeObserver
+ : nullptr),
+ mFlags(aParentReflowInput.mFlags),
+ mStyleSizeOverrides(aSizeOverrides),
+ mComputeSizeFlags(aComputeSizeFlags),
+ mReflowDepth(aParentReflowInput.mReflowDepth + 1),
+ mAvailableSize(aAvailableSpace) {
+ MOZ_ASSERT(aPresContext, "no pres context");
+ MOZ_ASSERT(aFrame, "no frame");
+ MOZ_ASSERT(aPresContext == aFrame->PresContext(), "wrong pres context");
+ MOZ_ASSERT(!mFlags.mSpecialBSizeReflow || !aFrame->IsSubtreeDirty(),
+ "frame should be clean when getting special bsize reflow");
+
+ if (mWritingMode.IsOrthogonalTo(aParentReflowInput.GetWritingMode())) {
+ // If we're setting up for an orthogonal flow, and the parent reflow input
+ // had a constrained ComputedBSize, we can use that as our AvailableISize
+ // in preference to leaving it unconstrained.
+ if (AvailableISize() == NS_UNCONSTRAINEDSIZE &&
+ aParentReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
+ SetAvailableISize(aParentReflowInput.ComputedBSize());
+ }
+ }
+
+ // Note: mFlags was initialized as a copy of aParentReflowInput.mFlags up in
+ // this constructor's init list, so the only flags that we need to explicitly
+ // initialize here are those that may need a value other than our parent's.
+ mFlags.mNextInFlowUntouched =
+ aParentReflowInput.mFlags.mNextInFlowUntouched &&
+ CheckNextInFlowParenthood(aFrame, aParentReflowInput.mFrame);
+ mFlags.mAssumingHScrollbar = mFlags.mAssumingVScrollbar = false;
+ mFlags.mIsColumnBalancing = false;
+ mFlags.mColumnSetWrapperHasNoBSizeLeft = false;
+ mFlags.mTreatBSizeAsIndefinite = false;
+ mFlags.mDummyParentReflowInput = false;
+ mFlags.mStaticPosIsCBOrigin = aFlags.contains(InitFlag::StaticPosIsCBOrigin);
+ mFlags.mIOffsetsNeedCSSAlign = mFlags.mBOffsetsNeedCSSAlign = false;
+
+ // aPresContext->IsPaginated() and the named pages pref should have been
+ // checked when constructing the root ReflowInput.
+ if (aParentReflowInput.mFlags.mCanHaveClassABreakpoints) {
+ MOZ_ASSERT(aPresContext->IsPaginated(),
+ "mCanHaveClassABreakpoints set during non-paginated reflow.");
+ }
+
+ {
+ using mozilla::LayoutFrameType;
+ switch (mFrame->Type()) {
+ case LayoutFrameType::PageContent:
+ // PageContent requires paginated reflow.
+ MOZ_ASSERT(aPresContext->IsPaginated(),
+ "nsPageContentFrame should not be in non-paginated reflow");
+ MOZ_ASSERT(!mFlags.mCanHaveClassABreakpoints,
+ "mFlags.mCanHaveClassABreakpoints should have been "
+ "initalized to false before we found nsPageContentFrame");
+ mFlags.mCanHaveClassABreakpoints = true;
+ break;
+ case LayoutFrameType::Block: // FALLTHROUGH
+ case LayoutFrameType::Canvas: // FALLTHROUGH
+ case LayoutFrameType::FlexContainer: // FALLTHROUGH
+ case LayoutFrameType::GridContainer:
+ if (mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ // Never allow breakpoints inside of out-of-flow frames.
+ mFlags.mCanHaveClassABreakpoints = false;
+ break;
+ }
+ // This frame type can have class A breakpoints, inherit this flag
+ // from the parent (this is done for all flags during construction).
+ // This also includes Canvas frames, as each PageContent frame always
+ // has exactly one child which is a Canvas frame.
+ // Do NOT include the subclasses of BlockFrame here, as the ones for
+ // which this could be applicable (ColumnSetWrapper and the MathML
+ // frames) cannot have class A breakpoints.
+ MOZ_ASSERT(mFlags.mCanHaveClassABreakpoints ==
+ aParentReflowInput.mFlags.mCanHaveClassABreakpoints);
+ break;
+ default:
+ mFlags.mCanHaveClassABreakpoints = false;
+ break;
+ }
+ }
+
+ if (aFlags.contains(InitFlag::DummyParentReflowInput) ||
+ (mParentReflowInput->mFlags.mDummyParentReflowInput &&
+ mFrame->IsTableFrame())) {
+ mFlags.mDummyParentReflowInput = true;
+ }
+
+ if (!aFlags.contains(InitFlag::CallerWillInit)) {
+ Init(aPresContext, aContainingBlockSize);
+ }
+}
+
+template <typename SizeOrMaxSize>
+inline nscoord SizeComputationInput::ComputeISizeValue(
+ const WritingMode aWM, const LogicalSize& aContainingBlockSize,
+ const LogicalSize& aContentEdgeToBoxSizing, nscoord aBoxSizingToMarginEdge,
+ const SizeOrMaxSize& aSize) const {
+ return mFrame
+ ->ComputeISizeValue(mRenderingContext, aWM, aContainingBlockSize,
+ aContentEdgeToBoxSizing, aBoxSizingToMarginEdge,
+ aSize)
+ .mISize;
+}
+
+template <typename SizeOrMaxSize>
+nscoord SizeComputationInput::ComputeISizeValue(
+ const LogicalSize& aContainingBlockSize, StyleBoxSizing aBoxSizing,
+ const SizeOrMaxSize& aSize) const {
+ WritingMode wm = GetWritingMode();
+ const auto borderPadding = ComputedLogicalBorderPadding(wm);
+ LogicalSize inside = aBoxSizing == StyleBoxSizing::Border
+ ? borderPadding.Size(wm)
+ : LogicalSize(wm);
+ nscoord outside =
+ borderPadding.IStartEnd(wm) + ComputedLogicalMargin(wm).IStartEnd(wm);
+ outside -= inside.ISize(wm);
+
+ return ComputeISizeValue(wm, aContainingBlockSize, inside, outside, aSize);
+}
+
+nscoord SizeComputationInput::ComputeBSizeValue(
+ nscoord aContainingBlockBSize, StyleBoxSizing aBoxSizing,
+ const LengthPercentage& aSize) const {
+ WritingMode wm = GetWritingMode();
+ nscoord inside = 0;
+ if (aBoxSizing == StyleBoxSizing::Border) {
+ inside = ComputedLogicalBorderPadding(wm).BStartEnd(wm);
+ }
+ return nsLayoutUtils::ComputeBSizeValue(aContainingBlockBSize, inside, aSize);
+}
+
+nsSize ReflowInput::ComputedSizeAsContainerIfConstrained() const {
+ LogicalSize size = ComputedSize();
+ if (size.ISize(mWritingMode) == NS_UNCONSTRAINEDSIZE) {
+ size.ISize(mWritingMode) = 0;
+ } else {
+ size.ISize(mWritingMode) += mComputedBorderPadding.IStartEnd(mWritingMode);
+ }
+ if (size.BSize(mWritingMode) == NS_UNCONSTRAINEDSIZE) {
+ size.BSize(mWritingMode) = 0;
+ } else {
+ size.BSize(mWritingMode) += mComputedBorderPadding.BStartEnd(mWritingMode);
+ }
+ return size.GetPhysicalSize(mWritingMode);
+}
+
+bool ReflowInput::ShouldReflowAllKids() const {
+ // Note that we could make a stronger optimization for IsBResize if
+ // we use it in a ShouldReflowChild test that replaces the current
+ // checks of NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN, if it
+ // were tested there along with NS_FRAME_CONTAINS_RELATIVE_BSIZE.
+ // This would need to be combined with a slight change in which
+ // frames NS_FRAME_CONTAINS_RELATIVE_BSIZE is marked on.
+ return mFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY) || IsIResize() ||
+ (IsBResize() &&
+ mFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) ||
+ mFlags.mIsInLastColumnBalancingReflow;
+}
+
+void ReflowInput::SetComputedISize(nscoord aComputedISize,
+ ResetResizeFlags aFlags) {
+ // It'd be nice to assert that |frame| is not in reflow, but this fails
+ // because viewport frames reset the computed isize on a copy of their reflow
+ // input when reflowing fixed-pos kids. In that case we actually don't want
+ // to mess with the resize flags, because comparing the frame's rect to the
+ // munged computed isize is pointless.
+ NS_WARNING_ASSERTION(aComputedISize >= 0, "Invalid computed inline-size!");
+ if (ComputedISize() != aComputedISize) {
+ mComputedSize.ISize(mWritingMode) = std::max(0, aComputedISize);
+ if (aFlags == ResetResizeFlags::Yes) {
+ InitResizeFlags(mFrame->PresContext(), mFrame->Type());
+ }
+ }
+}
+
+void ReflowInput::SetComputedBSize(nscoord aComputedBSize,
+ ResetResizeFlags aFlags) {
+ // It'd be nice to assert that |frame| is not in reflow, but this fails
+ // for the same reason as above.
+ NS_WARNING_ASSERTION(aComputedBSize >= 0, "Invalid computed block-size!");
+ if (ComputedBSize() != aComputedBSize) {
+ mComputedSize.BSize(mWritingMode) = std::max(0, aComputedBSize);
+ if (aFlags == ResetResizeFlags::Yes) {
+ InitResizeFlags(mFrame->PresContext(), mFrame->Type());
+ }
+ }
+}
+
+void ReflowInput::Init(nsPresContext* aPresContext,
+ const Maybe<LogicalSize>& aContainingBlockSize,
+ const Maybe<LogicalMargin>& aBorder,
+ const Maybe<LogicalMargin>& aPadding) {
+ if (AvailableISize() == NS_UNCONSTRAINEDSIZE) {
+ // Look up the parent chain for an orthogonal inline limit,
+ // and reset AvailableISize() if found.
+ for (const ReflowInput* parent = mParentReflowInput; parent != nullptr;
+ parent = parent->mParentReflowInput) {
+ if (parent->GetWritingMode().IsOrthogonalTo(mWritingMode) &&
+ parent->mOrthogonalLimit != NS_UNCONSTRAINEDSIZE) {
+ SetAvailableISize(parent->mOrthogonalLimit);
+ break;
+ }
+ }
+ }
+
+ LAYOUT_WARN_IF_FALSE(AvailableISize() != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained inline-size; this should only "
+ "result from very large sizes, not attempts at "
+ "intrinsic inline-size calculation");
+
+ mStylePosition = mFrame->StylePosition();
+ mStyleDisplay = mFrame->StyleDisplay();
+ mStyleBorder = mFrame->StyleBorder();
+ mStyleMargin = mFrame->StyleMargin();
+
+ InitCBReflowInput();
+
+ LayoutFrameType type = mFrame->Type();
+ if (type == mozilla::LayoutFrameType::Placeholder) {
+ // Placeholders have a no-op Reflow method that doesn't need the rest of
+ // this initialization, so we bail out early.
+ mComputedSize.SizeTo(mWritingMode, 0, 0);
+ return;
+ }
+
+ mFlags.mIsReplaced = mFrame->IsReplaced() || mFrame->IsReplacedWithBlock();
+
+ InitConstraints(aPresContext, aContainingBlockSize, aBorder, aPadding, type);
+
+ InitResizeFlags(aPresContext, type);
+ InitDynamicReflowRoot();
+
+ nsIFrame* parent = mFrame->GetParent();
+ if (parent && parent->HasAnyStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE) &&
+ !(parent->IsScrollFrame() &&
+ parent->StyleDisplay()->mOverflowY != StyleOverflow::Hidden)) {
+ mFrame->AddStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE);
+ } else if (type == LayoutFrameType::SVGForeignObject) {
+ // An SVG foreignObject frame is inherently constrained block-size.
+ mFrame->AddStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE);
+ } else {
+ const auto& bSizeCoord = mStylePosition->BSize(mWritingMode);
+ const auto& maxBSizeCoord = mStylePosition->MaxBSize(mWritingMode);
+ if ((!bSizeCoord.BehavesLikeInitialValueOnBlockAxis() ||
+ !maxBSizeCoord.BehavesLikeInitialValueOnBlockAxis()) &&
+ // Don't set NS_FRAME_IN_CONSTRAINED_BSIZE on body or html elements.
+ (mFrame->GetContent() && !(mFrame->GetContent()->IsAnyOfHTMLElements(
+ nsGkAtoms::body, nsGkAtoms::html)))) {
+ // If our block-size was specified as a percentage, then this could
+ // actually resolve to 'auto', based on:
+ // http://www.w3.org/TR/CSS21/visudet.html#the-height-property
+ nsIFrame* containingBlk = mFrame;
+ while (containingBlk) {
+ const nsStylePosition* stylePos = containingBlk->StylePosition();
+ const auto& bSizeCoord = stylePos->BSize(mWritingMode);
+ const auto& maxBSizeCoord = stylePos->MaxBSize(mWritingMode);
+ if ((bSizeCoord.IsLengthPercentage() && !bSizeCoord.HasPercent()) ||
+ (maxBSizeCoord.IsLengthPercentage() &&
+ !maxBSizeCoord.HasPercent())) {
+ mFrame->AddStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE);
+ break;
+ } else if (bSizeCoord.HasPercent() || maxBSizeCoord.HasPercent()) {
+ if (!(containingBlk = containingBlk->GetContainingBlock())) {
+ // If we've reached the top of the tree, then we don't have
+ // a constrained block-size.
+ mFrame->RemoveStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE);
+ break;
+ }
+
+ continue;
+ } else {
+ mFrame->RemoveStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE);
+ break;
+ }
+ }
+ } else {
+ mFrame->RemoveStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE);
+ }
+ }
+
+ if (mParentReflowInput &&
+ mParentReflowInput->GetWritingMode().IsOrthogonalTo(mWritingMode)) {
+ // Orthogonal frames are always reflowed with an unconstrained
+ // dimension to avoid incomplete reflow across an orthogonal
+ // boundary. Normally this is the block-size, but for column sets
+ // with auto-height it's the inline-size, so that they can add
+ // columns in the container's block direction
+ if (type == LayoutFrameType::ColumnSet &&
+ mStylePosition->ISize(mWritingMode).IsAuto()) {
+ SetComputedISize(NS_UNCONSTRAINEDSIZE, ResetResizeFlags::No);
+ } else {
+ SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
+ }
+ }
+
+ if (mFrame->GetContainSizeAxes().mBContained) {
+ // In the case that a box is size contained in block axis, we want to ensure
+ // that it is also monolithic. We do this by setting AvailableBSize() to an
+ // unconstrained size to avoid fragmentation.
+ SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
+ }
+
+ LAYOUT_WARN_IF_FALSE(
+ (mStyleDisplay->IsInlineOutsideStyle() && !mFrame->IsReplaced()) ||
+ type == LayoutFrameType::Text ||
+ ComputedISize() != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained inline-size; this should only "
+ "result from very large sizes, not attempts at "
+ "intrinsic inline-size calculation");
+}
+
+static bool MightBeContainingBlockFor(nsIFrame* aMaybeContainingBlock,
+ nsIFrame* aFrame,
+ const nsStyleDisplay* aStyleDisplay) {
+ // Keep this in sync with nsIFrame::GetContainingBlock.
+ if (aFrame->IsAbsolutelyPositioned(aStyleDisplay) &&
+ aMaybeContainingBlock == aFrame->GetParent()) {
+ return true;
+ }
+ return aMaybeContainingBlock->IsBlockContainer();
+}
+
+void ReflowInput::InitCBReflowInput() {
+ if (!mParentReflowInput) {
+ mCBReflowInput = nullptr;
+ return;
+ }
+ if (mParentReflowInput->mFlags.mDummyParentReflowInput) {
+ mCBReflowInput = mParentReflowInput;
+ return;
+ }
+
+ // To avoid a long walk up the frame tree check if the parent frame can be a
+ // containing block for mFrame.
+ if (MightBeContainingBlockFor(mParentReflowInput->mFrame, mFrame,
+ mStyleDisplay) &&
+ mParentReflowInput->mFrame ==
+ mFrame->GetContainingBlock(0, mStyleDisplay)) {
+ // Inner table frames need to use the containing block of the outer
+ // table frame.
+ if (mFrame->IsTableFrame()) {
+ mCBReflowInput = mParentReflowInput->mCBReflowInput;
+ } else {
+ mCBReflowInput = mParentReflowInput;
+ }
+ } else {
+ mCBReflowInput = mParentReflowInput->mCBReflowInput;
+ }
+}
+
+/* Check whether CalcQuirkContainingBlockHeight would stop on the
+ * given reflow input, using its block as a height. (essentially
+ * returns false for any case in which CalcQuirkContainingBlockHeight
+ * has a "continue" in its main loop.)
+ *
+ * XXX Maybe refactor CalcQuirkContainingBlockHeight so it uses
+ * this function as well
+ */
+static bool IsQuirkContainingBlockHeight(const ReflowInput* rs,
+ LayoutFrameType aFrameType) {
+ if (LayoutFrameType::Block == aFrameType ||
+ LayoutFrameType::Scroll == aFrameType) {
+ // Note: This next condition could change due to a style change,
+ // but that would cause a style reflow anyway, which means we're ok.
+ if (NS_UNCONSTRAINEDSIZE == rs->ComputedHeight()) {
+ if (!rs->mFrame->IsAbsolutelyPositioned(rs->mStyleDisplay)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void ReflowInput::InitResizeFlags(nsPresContext* aPresContext,
+ LayoutFrameType aFrameType) {
+ SetBResize(false);
+ SetIResize(false);
+ mFlags.mIsBResizeForPercentages = false;
+
+ const WritingMode wm = mWritingMode; // just a shorthand
+ // We should report that we have a resize in the inline dimension if
+ // *either* the border-box size or the content-box size in that
+ // dimension has changed. It might not actually be necessary to do
+ // this if the border-box size has changed and the content-box size
+ // has not changed, but since we've historically used the flag to mean
+ // border-box size change, continue to do that. It's possible for
+ // the content-box size to change without a border-box size change or
+ // a style change given (1) a fixed width (possibly fixed by max-width
+ // or min-width), box-sizing:border-box, and percentage padding;
+ // (2) box-sizing:content-box, M% width, and calc(Npx - M%) padding.
+ //
+ // However, we don't actually have the information at this point to tell
+ // whether the content-box size has changed, since both style data and the
+ // UsedPaddingProperty() have already been updated in
+ // SizeComputationInput::InitOffsets(). So, we check the HasPaddingChange()
+ // bit for the cases where it's possible for the content-box size to have
+ // changed without either (a) a change in the border-box size or (b) an
+ // nsChangeHint_NeedDirtyReflow change hint due to change in border or
+ // padding.
+ //
+ // We don't clear the HasPaddingChange() bit here, since sometimes we
+ // construct reflow input (e.g. in nsBlockFrame::ReflowBlockFrame to compute
+ // margin collapsing) without reflowing the frame. Instead, we clear it in
+ // nsIFrame::DidReflow().
+ bool isIResize =
+ // is the border-box resizing?
+ mFrame->ISize(wm) !=
+ ComputedISize() + ComputedLogicalBorderPadding(wm).IStartEnd(wm) ||
+ // or is the content-box resizing? (see comment above)
+ mFrame->HasPaddingChange();
+
+ if (mFrame->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT) &&
+ nsLayoutUtils::FontSizeInflationEnabled(aPresContext)) {
+ // Create our font inflation data if we don't have it already, and
+ // give it our current width information.
+ bool dirty = nsFontInflationData::UpdateFontInflationDataISizeFor(*this) &&
+ // Avoid running this at the box-to-block interface
+ // (where we shouldn't be inflating anyway, and where
+ // reflow input construction is probably to construct a
+ // dummy parent reflow input anyway).
+ !mFlags.mDummyParentReflowInput;
+
+ if (dirty || (!mFrame->GetParent() && isIResize)) {
+ // When font size inflation is enabled, a change in either:
+ // * the effective width of a font inflation flow root
+ // * the width of the frame
+ // needs to cause a dirty reflow since they change the font size
+ // inflation calculations, which in turn change the size of text,
+ // line-heights, etc. This is relatively similar to a classic
+ // case of style change reflow, except that because inflation
+ // doesn't affect the intrinsic sizing codepath, there's no need
+ // to invalidate intrinsic sizes.
+ //
+ // Note that this makes horizontal resizing a good bit more
+ // expensive. However, font size inflation is targeted at a set of
+ // devices (zoom-and-pan devices) where the main use case for
+ // horizontal resizing needing to be efficient (window resizing) is
+ // not present. It does still increase the cost of dynamic changes
+ // caused by script where a style or content change in one place
+ // causes a resize in another (e.g., rebalancing a table).
+
+ // FIXME: This isn't so great for the cases where
+ // ReflowInput::SetComputedWidth is called, if the first time
+ // we go through InitResizeFlags we set IsHResize() to true, and then
+ // the second time we'd set it to false even without the
+ // NS_FRAME_IS_DIRTY bit already set.
+ if (mFrame->IsSVGForeignObjectFrame()) {
+ // Foreign object frames use dirty bits in a special way.
+ mFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ nsIFrame* kid = mFrame->PrincipalChildList().FirstChild();
+ if (kid) {
+ kid->MarkSubtreeDirty();
+ }
+ } else {
+ mFrame->MarkSubtreeDirty();
+ }
+
+ // Mark intrinsic widths on all descendants dirty. We need to do
+ // this (1) since we're changing the size of text and need to
+ // clear text runs on text frames and (2) since we actually are
+ // changing some intrinsic widths, but only those that live inside
+ // of containers.
+
+ // It makes sense to do this for descendants but not ancestors
+ // (which is unusual) because we're only changing the unusual
+ // inflation-dependent intrinsic widths (i.e., ones computed with
+ // nsPresContext::mInflationDisabledForShrinkWrap set to false),
+ // which should never affect anything outside of their inflation
+ // flow root (or, for that matter, even their inflation
+ // container).
+
+ // This is also different from what PresShell::FrameNeedsReflow
+ // does because it doesn't go through placeholders. It doesn't
+ // need to because we're actually doing something that cares about
+ // frame tree geometry (the width on an ancestor) rather than
+ // style.
+
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(mFrame);
+
+ do {
+ nsIFrame* f = stack.PopLastElement();
+ for (const auto& childList : f->ChildLists()) {
+ for (nsIFrame* kid : childList.mList) {
+ kid->MarkIntrinsicISizesDirty();
+ stack.AppendElement(kid);
+ }
+ }
+ } while (stack.Length() != 0);
+ }
+ }
+
+ SetIResize(!mFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY) && isIResize);
+
+ // XXX Should we really need to null check mCBReflowInput? (We do for
+ // at least nsBoxFrame).
+ if (mFrame->HasBSizeChange()) {
+ // When we have an nsChangeHint_UpdateComputedBSize, we'll set a bit
+ // on the frame to indicate we're resizing. This might catch cases,
+ // such as a change between auto and a length, where the box doesn't
+ // actually resize but children with percentages resize (since those
+ // percentages become auto if their containing block is auto).
+ SetBResize(true);
+ mFlags.mIsBResizeForPercentages = true;
+ // We don't clear the HasBSizeChange state here, since sometimes we
+ // construct a ReflowInput (e.g. in nsBlockFrame::ReflowBlockFrame to
+ // compute margin collapsing) without reflowing the frame. Instead, we
+ // clear it in nsIFrame::DidReflow.
+ } else if (mCBReflowInput &&
+ mCBReflowInput->IsBResizeForPercentagesForWM(wm) &&
+ (mStylePosition->BSize(wm).HasPercent() ||
+ mStylePosition->MinBSize(wm).HasPercent() ||
+ mStylePosition->MaxBSize(wm).HasPercent())) {
+ // We have a percentage (or calc-with-percentage) block-size, and the
+ // value it's relative to has changed.
+ SetBResize(true);
+ mFlags.mIsBResizeForPercentages = true;
+ } else if (aFrameType == LayoutFrameType::TableCell &&
+ (mFlags.mSpecialBSizeReflow ||
+ mFrame->FirstInFlow()->HasAnyStateBits(
+ NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) &&
+ mFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ // Need to set the bit on the cell so that
+ // mCBReflowInput->IsBResize() is set correctly below when
+ // reflowing descendant.
+ SetBResize(true);
+ mFlags.mIsBResizeForPercentages = true;
+ } else if (mCBReflowInput && mFrame->IsBlockWrapper()) {
+ // XXX Is this problematic for relatively positioned inlines acting
+ // as containing block for absolutely positioned elements?
+ // Possibly; in that case we should at least be checking
+ // IsSubtreeDirty(), I'd think.
+ SetBResize(mCBReflowInput->IsBResizeForWM(wm));
+ mFlags.mIsBResizeForPercentages =
+ mCBReflowInput->IsBResizeForPercentagesForWM(wm);
+ } else if (ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
+ // We have an 'auto' block-size.
+ if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() &&
+ mCBReflowInput) {
+ // FIXME: This should probably also check IsIResize().
+ SetBResize(mCBReflowInput->IsBResizeForWM(wm));
+ } else {
+ SetBResize(IsIResize());
+ }
+ SetBResize(IsBResize() || mFrame->IsSubtreeDirty());
+ } else {
+ // We have a non-'auto' block-size, i.e., a length. Set the BResize
+ // flag to whether the size is actually different.
+ SetBResize(mFrame->BSize(wm) !=
+ ComputedBSize() +
+ ComputedLogicalBorderPadding(wm).BStartEnd(wm));
+ }
+
+ bool dependsOnCBBSize = (mStylePosition->BSizeDependsOnContainer(wm) &&
+ // FIXME: condition this on not-abspos?
+ !mStylePosition->BSize(wm).IsAuto()) ||
+ mStylePosition->MinBSizeDependsOnContainer(wm) ||
+ mStylePosition->MaxBSizeDependsOnContainer(wm) ||
+ mStylePosition->mOffset.GetBStart(wm).HasPercent() ||
+ !mStylePosition->mOffset.GetBEnd(wm).IsAuto();
+
+ // If mFrame is a flex item, and mFrame's block axis is the flex container's
+ // main axis (e.g. in a column-oriented flex container with same
+ // writing-mode), then its block-size depends on its CB size, if its
+ // flex-basis has a percentage.
+ if (mFrame->IsFlexItem() &&
+ !nsFlexContainerFrame::IsItemInlineAxisMainAxis(mFrame)) {
+ const auto& flexBasis = mStylePosition->mFlexBasis;
+ dependsOnCBBSize |= (flexBasis.IsSize() && flexBasis.AsSize().HasPercent());
+ }
+
+ if (mFrame->StyleFont()->mLineHeight.IsMozBlockHeight()) {
+ // line-height depends on block bsize
+ mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ // but only on containing blocks if this frame is not a suitable block
+ dependsOnCBBSize |= !nsLayoutUtils::IsNonWrapperBlock(mFrame);
+ }
+
+ // If we're the descendant of a table cell that performs special bsize
+ // reflows and we could be the child that requires them, always set
+ // the block-axis resize in case this is the first pass before the
+ // special bsize reflow. However, don't do this if it actually is
+ // the special bsize reflow, since in that case it will already be
+ // set correctly above if we need it set.
+ if (!IsBResize() && mCBReflowInput &&
+ (mCBReflowInput->mFrame->IsTableCellFrame() ||
+ mCBReflowInput->mFlags.mHeightDependsOnAncestorCell) &&
+ !mCBReflowInput->mFlags.mSpecialBSizeReflow && dependsOnCBBSize) {
+ SetBResize(true);
+ mFlags.mHeightDependsOnAncestorCell = true;
+ }
+
+ // Set NS_FRAME_CONTAINS_RELATIVE_BSIZE if it's needed.
+
+ // It would be nice to check that |ComputedBSize != NS_UNCONSTRAINEDSIZE|
+ // &&ed with the percentage bsize check. However, this doesn't get
+ // along with table special bsize reflows, since a special bsize
+ // reflow (a quirk that makes such percentage height work on children
+ // of table cells) can cause not just a single percentage height to
+ // become fixed, but an entire descendant chain of percentage height
+ // to become fixed.
+ if (dependsOnCBBSize && mCBReflowInput) {
+ const ReflowInput* rs = this;
+ bool hitCBReflowInput = false;
+ do {
+ rs = rs->mParentReflowInput;
+ if (!rs) {
+ break;
+ }
+
+ if (rs->mFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ break; // no need to go further
+ }
+ rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+
+ // Keep track of whether we've hit the containing block, because
+ // we need to go at least that far.
+ if (rs == mCBReflowInput) {
+ hitCBReflowInput = true;
+ }
+
+ // XXX What about orthogonal flows? It doesn't make sense to
+ // keep propagating this bit across an orthogonal boundary,
+ // where the meaning of BSize changes. Bug 1175517.
+ } while (!hitCBReflowInput ||
+ (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() &&
+ !IsQuirkContainingBlockHeight(rs, rs->mFrame->Type())));
+ // Note: We actually don't need to set the
+ // NS_FRAME_CONTAINS_RELATIVE_BSIZE bit for the cases
+ // where we hit the early break statements in
+ // CalcQuirkContainingBlockHeight. But it doesn't hurt
+ // us to set the bit in these cases.
+ }
+ if (mFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ // If we're reflowing everything, then we'll find out if we need
+ // to re-set this.
+ mFrame->RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+}
+
+void ReflowInput::InitDynamicReflowRoot() {
+ if (mFrame->CanBeDynamicReflowRoot()) {
+ mFrame->AddStateBits(NS_FRAME_DYNAMIC_REFLOW_ROOT);
+ } else {
+ mFrame->RemoveStateBits(NS_FRAME_DYNAMIC_REFLOW_ROOT);
+ }
+}
+
+bool ReflowInput::ShouldApplyAutomaticMinimumOnBlockAxis() const {
+ MOZ_ASSERT(!mFrame->HasReplacedSizing());
+ return mFlags.mIsBSizeSetByAspectRatio &&
+ !mStyleDisplay->IsScrollableOverflow() &&
+ mStylePosition->MinBSize(GetWritingMode()).IsAuto();
+}
+
+bool ReflowInput::IsInFragmentedContext() const {
+ // We consider mFrame with a prev-in-flow being in a fragmented context
+ // because nsColumnSetFrame can reflow its last column with an unconstrained
+ // available block-size.
+ return AvailableBSize() != NS_UNCONSTRAINEDSIZE || mFrame->GetPrevInFlow();
+}
+
+/* static */
+LogicalMargin ReflowInput::ComputeRelativeOffsets(WritingMode aWM,
+ nsIFrame* aFrame,
+ const LogicalSize& aCBSize) {
+ LogicalMargin offsets(aWM);
+ const nsStylePosition* position = aFrame->StylePosition();
+
+ // Compute the 'inlineStart' and 'inlineEnd' values. 'inlineStart'
+ // moves the boxes to the end of the line, and 'inlineEnd' moves the
+ // boxes to the start of the line. The computed values are always:
+ // inlineStart=-inlineEnd
+ const auto& inlineStart = position->mOffset.GetIStart(aWM);
+ const auto& inlineEnd = position->mOffset.GetIEnd(aWM);
+ bool inlineStartIsAuto = inlineStart.IsAuto();
+ bool inlineEndIsAuto = inlineEnd.IsAuto();
+
+ // If neither 'inlineStart' nor 'inlineEnd' is auto, then we're
+ // over-constrained and we ignore one of them
+ if (!inlineStartIsAuto && !inlineEndIsAuto) {
+ inlineEndIsAuto = true;
+ }
+
+ if (inlineStartIsAuto) {
+ if (inlineEndIsAuto) {
+ // If both are 'auto' (their initial values), the computed values are 0
+ offsets.IStart(aWM) = offsets.IEnd(aWM) = 0;
+ } else {
+ // 'inlineEnd' isn't 'auto' so compute its value
+ offsets.IEnd(aWM) =
+ nsLayoutUtils::ComputeCBDependentValue(aCBSize.ISize(aWM), inlineEnd);
+
+ // Computed value for 'inlineStart' is minus the value of 'inlineEnd'
+ offsets.IStart(aWM) = -offsets.IEnd(aWM);
+ }
+
+ } else {
+ NS_ASSERTION(inlineEndIsAuto, "unexpected specified constraint");
+
+ // 'InlineStart' isn't 'auto' so compute its value
+ offsets.IStart(aWM) =
+ nsLayoutUtils::ComputeCBDependentValue(aCBSize.ISize(aWM), inlineStart);
+
+ // Computed value for 'inlineEnd' is minus the value of 'inlineStart'
+ offsets.IEnd(aWM) = -offsets.IStart(aWM);
+ }
+
+ // Compute the 'blockStart' and 'blockEnd' values. The 'blockStart'
+ // and 'blockEnd' properties move relatively positioned elements in
+ // the block progression direction. They also must be each other's
+ // negative
+ const auto& blockStart = position->mOffset.GetBStart(aWM);
+ const auto& blockEnd = position->mOffset.GetBEnd(aWM);
+ bool blockStartIsAuto = blockStart.IsAuto();
+ bool blockEndIsAuto = blockEnd.IsAuto();
+
+ // Check for percentage based values and a containing block block-size
+ // that depends on the content block-size. Treat them like 'auto'
+ if (NS_UNCONSTRAINEDSIZE == aCBSize.BSize(aWM)) {
+ if (blockStart.HasPercent()) {
+ blockStartIsAuto = true;
+ }
+ if (blockEnd.HasPercent()) {
+ blockEndIsAuto = true;
+ }
+ }
+
+ // If neither is 'auto', 'block-end' is ignored
+ if (!blockStartIsAuto && !blockEndIsAuto) {
+ blockEndIsAuto = true;
+ }
+
+ if (blockStartIsAuto) {
+ if (blockEndIsAuto) {
+ // If both are 'auto' (their initial values), the computed values are 0
+ offsets.BStart(aWM) = offsets.BEnd(aWM) = 0;
+ } else {
+ // 'blockEnd' isn't 'auto' so compute its value
+ offsets.BEnd(aWM) = nsLayoutUtils::ComputeBSizeDependentValue(
+ aCBSize.BSize(aWM), blockEnd);
+
+ // Computed value for 'blockStart' is minus the value of 'blockEnd'
+ offsets.BStart(aWM) = -offsets.BEnd(aWM);
+ }
+
+ } else {
+ NS_ASSERTION(blockEndIsAuto, "unexpected specified constraint");
+
+ // 'blockStart' isn't 'auto' so compute its value
+ offsets.BStart(aWM) = nsLayoutUtils::ComputeBSizeDependentValue(
+ aCBSize.BSize(aWM), blockStart);
+
+ // Computed value for 'blockEnd' is minus the value of 'blockStart'
+ offsets.BEnd(aWM) = -offsets.BStart(aWM);
+ }
+
+ // Convert the offsets to physical coordinates and store them on the frame
+ const nsMargin physicalOffsets = offsets.GetPhysicalMargin(aWM);
+ if (nsMargin* prop =
+ aFrame->GetProperty(nsIFrame::ComputedOffsetProperty())) {
+ *prop = physicalOffsets;
+ } else {
+ aFrame->AddProperty(nsIFrame::ComputedOffsetProperty(),
+ new nsMargin(physicalOffsets));
+ }
+
+ NS_ASSERTION(offsets.IStart(aWM) == -offsets.IEnd(aWM) &&
+ offsets.BStart(aWM) == -offsets.BEnd(aWM),
+ "ComputeRelativeOffsets should return valid results!");
+
+ return offsets;
+}
+
+/* static */
+void ReflowInput::ApplyRelativePositioning(nsIFrame* aFrame,
+ const nsMargin& aComputedOffsets,
+ nsPoint* aPosition) {
+ if (!aFrame->IsRelativelyOrStickyPositioned()) {
+ NS_ASSERTION(!aFrame->HasProperty(nsIFrame::NormalPositionProperty()),
+ "We assume that changing the 'position' property causes "
+ "frame reconstruction. If that ever changes, this code "
+ "should call "
+ "aFrame->RemoveProperty(nsIFrame::NormalPositionProperty())");
+ return;
+ }
+
+ // Store the normal position
+ aFrame->SetProperty(nsIFrame::NormalPositionProperty(), *aPosition);
+
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+ if (StylePositionProperty::Relative == display->mPosition) {
+ *aPosition += nsPoint(aComputedOffsets.left, aComputedOffsets.top);
+ } else if (StylePositionProperty::Sticky == display->mPosition &&
+ !aFrame->GetNextContinuation() && !aFrame->GetPrevContinuation() &&
+ !aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // Sticky positioning for elements with multiple frames needs to be
+ // computed all at once. We can't safely do that here because we might be
+ // partway through (re)positioning the frames, so leave it until the scroll
+ // container reflows and calls StickyScrollContainer::UpdatePositions.
+ // For single-frame sticky positioned elements, though, go ahead and apply
+ // it now to avoid unnecessary overflow updates later.
+ StickyScrollContainer* ssc =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(aFrame);
+ if (ssc) {
+ *aPosition = ssc->ComputePosition(aFrame);
+ }
+ }
+}
+
+// static
+void ReflowInput::ComputeAbsPosInlineAutoMargin(nscoord aAvailMarginSpace,
+ WritingMode aContainingBlockWM,
+ bool aIsMarginIStartAuto,
+ bool aIsMarginIEndAuto,
+ LogicalMargin& aMargin,
+ LogicalMargin& aOffsets) {
+ if (aIsMarginIStartAuto) {
+ if (aIsMarginIEndAuto) {
+ if (aAvailMarginSpace < 0) {
+ // Note that this case is different from the neither-'auto'
+ // case below, where the spec says to ignore 'left'/'right'.
+ // Ignore the specified value for 'margin-right'.
+ aMargin.IEnd(aContainingBlockWM) = aAvailMarginSpace;
+ } else {
+ // Both 'margin-left' and 'margin-right' are 'auto', so they get
+ // equal values
+ aMargin.IStart(aContainingBlockWM) = aAvailMarginSpace / 2;
+ aMargin.IEnd(aContainingBlockWM) =
+ aAvailMarginSpace - aMargin.IStart(aContainingBlockWM);
+ }
+ } else {
+ // Just 'margin-left' is 'auto'
+ aMargin.IStart(aContainingBlockWM) = aAvailMarginSpace;
+ }
+ } else {
+ if (aIsMarginIEndAuto) {
+ // Just 'margin-right' is 'auto'
+ aMargin.IEnd(aContainingBlockWM) = aAvailMarginSpace;
+ } else {
+ // We're over-constrained so use the direction of the containing
+ // block to dictate which value to ignore. (And note that the
+ // spec says to ignore 'left' or 'right' rather than
+ // 'margin-left' or 'margin-right'.)
+ // Note that this case is different from the both-'auto' case
+ // above, where the spec says to ignore
+ // 'margin-left'/'margin-right'.
+ // Ignore the specified value for 'right'.
+ aOffsets.IEnd(aContainingBlockWM) += aAvailMarginSpace;
+ }
+ }
+}
+
+// static
+void ReflowInput::ComputeAbsPosBlockAutoMargin(nscoord aAvailMarginSpace,
+ WritingMode aContainingBlockWM,
+ bool aIsMarginBStartAuto,
+ bool aIsMarginBEndAuto,
+ LogicalMargin& aMargin,
+ LogicalMargin& aOffsets) {
+ if (aIsMarginBStartAuto) {
+ if (aIsMarginBEndAuto) {
+ // Both 'margin-top' and 'margin-bottom' are 'auto', so they get
+ // equal values
+ aMargin.BStart(aContainingBlockWM) = aAvailMarginSpace / 2;
+ aMargin.BEnd(aContainingBlockWM) =
+ aAvailMarginSpace - aMargin.BStart(aContainingBlockWM);
+ } else {
+ // Just margin-block-start is 'auto'
+ aMargin.BStart(aContainingBlockWM) = aAvailMarginSpace;
+ }
+ } else {
+ if (aIsMarginBEndAuto) {
+ // Just margin-block-end is 'auto'
+ aMargin.BEnd(aContainingBlockWM) = aAvailMarginSpace;
+ } else {
+ // We're over-constrained so ignore the specified value for
+ // block-end. (And note that the spec says to ignore 'bottom'
+ // rather than 'margin-bottom'.)
+ aOffsets.BEnd(aContainingBlockWM) += aAvailMarginSpace;
+ }
+ }
+}
+
+void ReflowInput::ApplyRelativePositioning(
+ nsIFrame* aFrame, mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalMargin& aComputedOffsets,
+ mozilla::LogicalPoint* aPosition, const nsSize& aContainerSize) {
+ // Subtract the size of the frame from the container size that we
+ // use for converting between the logical and physical origins of
+ // the frame. This accounts for the fact that logical origins in RTL
+ // coordinate systems are at the top right of the frame instead of
+ // the top left.
+ nsSize frameSize = aFrame->GetSize();
+ nsPoint pos =
+ aPosition->GetPhysicalPoint(aWritingMode, aContainerSize - frameSize);
+ ApplyRelativePositioning(
+ aFrame, aComputedOffsets.GetPhysicalMargin(aWritingMode), &pos);
+ *aPosition =
+ mozilla::LogicalPoint(aWritingMode, pos, aContainerSize - frameSize);
+}
+
+nsIFrame* ReflowInput::GetHypotheticalBoxContainer(nsIFrame* aFrame,
+ nscoord& aCBIStartEdge,
+ LogicalSize& aCBSize) const {
+ aFrame = aFrame->GetContainingBlock();
+ NS_ASSERTION(aFrame != mFrame, "How did that happen?");
+
+ /* Now aFrame is the containing block we want */
+
+ /* Check whether the containing block is currently being reflowed.
+ If so, use the info from the reflow input. */
+ const ReflowInput* reflowInput;
+ if (aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
+ for (reflowInput = mParentReflowInput;
+ reflowInput && reflowInput->mFrame != aFrame;
+ reflowInput = reflowInput->mParentReflowInput) {
+ /* do nothing */
+ }
+ } else {
+ reflowInput = nullptr;
+ }
+
+ if (reflowInput) {
+ WritingMode wm = reflowInput->GetWritingMode();
+ NS_ASSERTION(wm == aFrame->GetWritingMode(), "unexpected writing mode");
+ aCBIStartEdge = reflowInput->ComputedLogicalBorderPadding(wm).IStart(wm);
+ aCBSize = reflowInput->ComputedSize(wm);
+ } else {
+ /* Didn't find a reflow reflowInput for aFrame. Just compute the
+ information we want, on the assumption that aFrame already knows its
+ size. This really ought to be true by now. */
+ NS_ASSERTION(!aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
+ "aFrame shouldn't be in reflow; we'll lie if it is");
+ WritingMode wm = aFrame->GetWritingMode();
+ // Compute CB's offset & content-box size by subtracting borderpadding from
+ // frame size.
+ const auto& bp = aFrame->GetLogicalUsedBorderAndPadding(wm);
+ aCBIStartEdge = bp.IStart(wm);
+ aCBSize = aFrame->GetLogicalSize(wm) - bp.Size(wm);
+ }
+
+ return aFrame;
+}
+
+struct nsHypotheticalPosition {
+ // offset from inline-start edge of containing block (which is a padding edge)
+ nscoord mIStart;
+ // offset from block-start edge of containing block (which is a padding edge)
+ nscoord mBStart;
+ WritingMode mWritingMode;
+};
+
+/**
+ * aInsideBoxSizing returns the part of the padding, border, and margin
+ * in the aAxis dimension that goes inside the edge given by box-sizing;
+ * aOutsideBoxSizing returns the rest.
+ */
+void ReflowInput::CalculateBorderPaddingMargin(
+ LogicalAxis aAxis, nscoord aContainingBlockSize, nscoord* aInsideBoxSizing,
+ nscoord* aOutsideBoxSizing) const {
+ WritingMode wm = GetWritingMode();
+ mozilla::Side startSide =
+ wm.PhysicalSide(MakeLogicalSide(aAxis, eLogicalEdgeStart));
+ mozilla::Side endSide =
+ wm.PhysicalSide(MakeLogicalSide(aAxis, eLogicalEdgeEnd));
+
+ nsMargin styleBorder = mStyleBorder->GetComputedBorder();
+ nscoord borderStartEnd =
+ styleBorder.Side(startSide) + styleBorder.Side(endSide);
+
+ nscoord paddingStartEnd, marginStartEnd;
+
+ // See if the style system can provide us the padding directly
+ const auto* stylePadding = mFrame->StylePadding();
+ if (nsMargin padding; stylePadding->GetPadding(padding)) {
+ paddingStartEnd = padding.Side(startSide) + padding.Side(endSide);
+ } else {
+ // We have to compute the start and end values
+ nscoord start, end;
+ start = nsLayoutUtils::ComputeCBDependentValue(
+ aContainingBlockSize, stylePadding->mPadding.Get(startSide));
+ end = nsLayoutUtils::ComputeCBDependentValue(
+ aContainingBlockSize, stylePadding->mPadding.Get(endSide));
+ paddingStartEnd = start + end;
+ }
+
+ // See if the style system can provide us the margin directly
+ if (nsMargin margin; mStyleMargin->GetMargin(margin)) {
+ marginStartEnd = margin.Side(startSide) + margin.Side(endSide);
+ } else {
+ nscoord start, end;
+ // We have to compute the start and end values
+ if (mStyleMargin->mMargin.Get(startSide).IsAuto()) {
+ // We set this to 0 for now, and fix it up later in
+ // InitAbsoluteConstraints (which is caller of this function, via
+ // CalculateHypotheticalPosition).
+ start = 0;
+ } else {
+ start = nsLayoutUtils::ComputeCBDependentValue(
+ aContainingBlockSize, mStyleMargin->mMargin.Get(startSide));
+ }
+ if (mStyleMargin->mMargin.Get(endSide).IsAuto()) {
+ // We set this to 0 for now, and fix it up later in
+ // InitAbsoluteConstraints (which is caller of this function, via
+ // CalculateHypotheticalPosition).
+ end = 0;
+ } else {
+ end = nsLayoutUtils::ComputeCBDependentValue(
+ aContainingBlockSize, mStyleMargin->mMargin.Get(endSide));
+ }
+ marginStartEnd = start + end;
+ }
+
+ nscoord outside = paddingStartEnd + borderStartEnd + marginStartEnd;
+ nscoord inside = 0;
+ if (mStylePosition->mBoxSizing == StyleBoxSizing::Border) {
+ inside = borderStartEnd + paddingStartEnd;
+ }
+ outside -= inside;
+ *aInsideBoxSizing = inside;
+ *aOutsideBoxSizing = outside;
+}
+
+/**
+ * Returns true iff a pre-order traversal of the normal child
+ * frames rooted at aFrame finds no non-empty frame before aDescendant.
+ */
+static bool AreAllEarlierInFlowFramesEmpty(nsIFrame* aFrame,
+ nsIFrame* aDescendant,
+ bool* aFound) {
+ if (aFrame == aDescendant) {
+ *aFound = true;
+ return true;
+ }
+ if (aFrame->IsPlaceholderFrame()) {
+ auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
+ MOZ_ASSERT(ph->IsSelfEmpty() && ph->PrincipalChildList().IsEmpty());
+ ph->SetLineIsEmptySoFar(true);
+ } else {
+ if (!aFrame->IsSelfEmpty()) {
+ *aFound = false;
+ return false;
+ }
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ bool allEmpty = AreAllEarlierInFlowFramesEmpty(f, aDescendant, aFound);
+ if (*aFound || !allEmpty) {
+ return allEmpty;
+ }
+ }
+ }
+ *aFound = false;
+ return true;
+}
+
+static bool AxisPolarityFlipped(LogicalAxis aThisAxis, WritingMode aThisWm,
+ WritingMode aOtherWm) {
+ if (MOZ_LIKELY(aThisWm == aOtherWm)) {
+ // Dedicated short circuit for the common case.
+ return false;
+ }
+ LogicalAxis otherAxis = aThisWm.IsOrthogonalTo(aOtherWm)
+ ? GetOrthogonalAxis(aThisAxis)
+ : aThisAxis;
+ NS_ASSERTION(
+ aThisWm.PhysicalAxis(aThisAxis) == aOtherWm.PhysicalAxis(otherAxis),
+ "Physical axes must match!");
+ Side thisStartSide =
+ aThisWm.PhysicalSide(MakeLogicalSide(aThisAxis, eLogicalEdgeStart));
+ Side otherStartSide =
+ aOtherWm.PhysicalSide(MakeLogicalSide(otherAxis, eLogicalEdgeStart));
+ return thisStartSide != otherStartSide;
+}
+
+static bool InlinePolarityFlipped(WritingMode aThisWm, WritingMode aOtherWm) {
+ return AxisPolarityFlipped(eLogicalAxisInline, aThisWm, aOtherWm);
+}
+
+static bool BlockPolarityFlipped(WritingMode aThisWm, WritingMode aOtherWm) {
+ return AxisPolarityFlipped(eLogicalAxisBlock, aThisWm, aOtherWm);
+}
+
+// Calculate the position of the hypothetical box that the element would have
+// if it were in the flow.
+// The values returned are relative to the padding edge of the absolute
+// containing block. The writing-mode of the hypothetical box position will
+// have the same block direction as the absolute containing block, but may
+// differ in inline-bidi direction.
+// In the code below, |aCBReflowInput->frame| is the absolute containing block,
+// while |containingBlock| is the nearest block container of the placeholder
+// frame, which may be different from the absolute containing block.
+void ReflowInput::CalculateHypotheticalPosition(
+ nsPresContext* aPresContext, nsPlaceholderFrame* aPlaceholderFrame,
+ const ReflowInput* aCBReflowInput, nsHypotheticalPosition& aHypotheticalPos,
+ LayoutFrameType aFrameType) const {
+ NS_ASSERTION(mStyleDisplay->mOriginalDisplay != StyleDisplay::None,
+ "mOriginalDisplay has not been properly initialized");
+
+ // Find the nearest containing block frame to the placeholder frame,
+ // and its inline-start edge and width.
+ nscoord blockIStartContentEdge;
+ // Dummy writing mode for blockContentSize, will be changed as needed by
+ // GetHypotheticalBoxContainer.
+ WritingMode cbwm = aCBReflowInput->GetWritingMode();
+ LogicalSize blockContentSize(cbwm);
+ nsIFrame* containingBlock = GetHypotheticalBoxContainer(
+ aPlaceholderFrame, blockIStartContentEdge, blockContentSize);
+ // Now blockContentSize is in containingBlock's writing mode.
+
+ // If it's a replaced element and it has a 'auto' value for
+ //'inline size', see if we can get the intrinsic size. This will allow
+ // us to exactly determine both the inline edges
+ WritingMode wm = containingBlock->GetWritingMode();
+
+ const auto& styleISize = mStylePosition->ISize(wm);
+ bool isAutoISize = styleISize.IsAuto();
+ Maybe<nsSize> intrinsicSize;
+ if (mFlags.mIsReplaced && isAutoISize) {
+ // See if we can get the intrinsic size of the element
+ intrinsicSize = mFrame->GetIntrinsicSize().ToSize();
+ }
+
+ // See if we can calculate what the box inline size would have been if
+ // the element had been in the flow
+ Maybe<nscoord> boxISize;
+ if (mStyleDisplay->IsOriginalDisplayInlineOutside() && !mFlags.mIsReplaced) {
+ // For non-replaced inline-level elements the 'inline size' property
+ // doesn't apply, so we don't know what the inline size would have
+ // been without reflowing it
+
+ } else {
+ // It's either a replaced inline-level element or a block-level element
+
+ // Determine the total amount of inline direction
+ // border/padding/margin that the element would have had if it had
+ // been in the flow. Note that we ignore any 'auto' and 'inherit'
+ // values
+ nscoord insideBoxISizing, outsideBoxISizing;
+ CalculateBorderPaddingMargin(eLogicalAxisInline, blockContentSize.ISize(wm),
+ &insideBoxISizing, &outsideBoxISizing);
+
+ if (mFlags.mIsReplaced && isAutoISize) {
+ // It's a replaced element with an 'auto' inline size so the box
+ // inline size is its intrinsic size plus any border/padding/margin
+ if (intrinsicSize) {
+ boxISize.emplace(LogicalSize(wm, *intrinsicSize).ISize(wm) +
+ outsideBoxISizing + insideBoxISizing);
+ }
+
+ } else if (isAutoISize) {
+ // The box inline size is the containing block inline size
+ boxISize.emplace(blockContentSize.ISize(wm));
+ } else {
+ // We need to compute it. It's important we do this, because if it's
+ // percentage based this computed value may be different from the computed
+ // value calculated using the absolute containing block width
+ nscoord insideBoxBSizing, dummy;
+ CalculateBorderPaddingMargin(eLogicalAxisBlock,
+ blockContentSize.ISize(wm),
+ &insideBoxBSizing, &dummy);
+ boxISize.emplace(
+ ComputeISizeValue(wm, blockContentSize,
+ LogicalSize(wm, insideBoxISizing, insideBoxBSizing),
+ outsideBoxISizing, styleISize) +
+ insideBoxISizing + outsideBoxISizing);
+ }
+ }
+
+ // Get the placeholder x-offset and y-offset in the coordinate
+ // space of its containing block
+ // XXXbz the placeholder is not fully reflowed yet if our containing block is
+ // relatively positioned...
+ nsSize containerSize =
+ containingBlock->HasAnyStateBits(NS_FRAME_IN_REFLOW)
+ ? aCBReflowInput->ComputedSizeAsContainerIfConstrained()
+ : containingBlock->GetSize();
+ LogicalPoint placeholderOffset(
+ wm, aPlaceholderFrame->GetOffsetToIgnoringScrolling(containingBlock),
+ containerSize);
+
+ // First, determine the hypothetical box's mBStart. We want to check the
+ // content insertion frame of containingBlock for block-ness, but make
+ // sure to compute all coordinates in the coordinate system of
+ // containingBlock.
+ nsBlockFrame* blockFrame =
+ do_QueryFrame(containingBlock->GetContentInsertionFrame());
+ if (blockFrame) {
+ // Use a null containerSize to convert a LogicalPoint functioning as a
+ // vector into a physical nsPoint vector.
+ const nsSize nullContainerSize;
+ LogicalPoint blockOffset(
+ wm, blockFrame->GetOffsetToIgnoringScrolling(containingBlock),
+ nullContainerSize);
+ bool isValid;
+ nsBlockInFlowLineIterator iter(blockFrame, aPlaceholderFrame, &isValid);
+ if (!isValid) {
+ // Give up. We're probably dealing with somebody using
+ // position:absolute inside native-anonymous content anyway.
+ aHypotheticalPos.mBStart = placeholderOffset.B(wm);
+ } else {
+ NS_ASSERTION(iter.GetContainer() == blockFrame,
+ "Found placeholder in wrong block!");
+ nsBlockFrame::LineIterator lineBox = iter.GetLine();
+
+ // How we determine the hypothetical box depends on whether the element
+ // would have been inline-level or block-level
+ LogicalRect lineBounds = lineBox->GetBounds().ConvertTo(
+ wm, lineBox->mWritingMode, lineBox->mContainerSize);
+ if (mStyleDisplay->IsOriginalDisplayInlineOutside()) {
+ // Use the block-start of the inline box which the placeholder lives in
+ // as the hypothetical box's block-start.
+ aHypotheticalPos.mBStart = lineBounds.BStart(wm) + blockOffset.B(wm);
+ } else {
+ // The element would have been block-level which means it would
+ // be below the line containing the placeholder frame, unless
+ // all the frames before it are empty. In that case, it would
+ // have been just before this line.
+ // XXXbz the line box is not fully reflowed yet if our
+ // containing block is relatively positioned...
+ if (lineBox != iter.End()) {
+ nsIFrame* firstFrame = lineBox->mFirstChild;
+ bool allEmpty = false;
+ if (firstFrame == aPlaceholderFrame) {
+ aPlaceholderFrame->SetLineIsEmptySoFar(true);
+ allEmpty = true;
+ } else {
+ auto prev = aPlaceholderFrame->GetPrevSibling();
+ if (prev && prev->IsPlaceholderFrame()) {
+ auto ph = static_cast<nsPlaceholderFrame*>(prev);
+ if (ph->GetLineIsEmptySoFar(&allEmpty)) {
+ aPlaceholderFrame->SetLineIsEmptySoFar(allEmpty);
+ }
+ }
+ }
+ if (!allEmpty) {
+ bool found = false;
+ while (firstFrame) { // See bug 223064
+ allEmpty = AreAllEarlierInFlowFramesEmpty(
+ firstFrame, aPlaceholderFrame, &found);
+ if (found || !allEmpty) {
+ break;
+ }
+ firstFrame = firstFrame->GetNextSibling();
+ }
+ aPlaceholderFrame->SetLineIsEmptySoFar(allEmpty);
+ }
+ NS_ASSERTION(firstFrame, "Couldn't find placeholder!");
+
+ if (allEmpty) {
+ // The top of the hypothetical box is the top of the line
+ // containing the placeholder, since there is nothing in the
+ // line before our placeholder except empty frames.
+ aHypotheticalPos.mBStart =
+ lineBounds.BStart(wm) + blockOffset.B(wm);
+ } else {
+ // The top of the hypothetical box is just below the line
+ // containing the placeholder.
+ aHypotheticalPos.mBStart = lineBounds.BEnd(wm) + blockOffset.B(wm);
+ }
+ } else {
+ // Just use the placeholder's block-offset wrt the containing block
+ aHypotheticalPos.mBStart = placeholderOffset.B(wm);
+ }
+ }
+ }
+ } else {
+ // The containing block is not a block, so it's probably something
+ // like a XUL box, etc.
+ // Just use the placeholder's block-offset
+ aHypotheticalPos.mBStart = placeholderOffset.B(wm);
+ }
+
+ // Second, determine the hypothetical box's mIStart.
+ // How we determine the hypothetical box depends on whether the element
+ // would have been inline-level or block-level
+ if (mStyleDisplay->IsOriginalDisplayInlineOutside() ||
+ mFlags.mIOffsetsNeedCSSAlign) {
+ // The placeholder represents the IStart edge of the hypothetical box.
+ // (Or if mFlags.mIOffsetsNeedCSSAlign is set, it represents the IStart
+ // edge of the Alignment Container.)
+ aHypotheticalPos.mIStart = placeholderOffset.I(wm);
+ } else {
+ aHypotheticalPos.mIStart = blockIStartContentEdge;
+ }
+
+ // The current coordinate space is that of the nearest block to the
+ // placeholder. Convert to the coordinate space of the absolute containing
+ // block.
+ nsPoint cbOffset =
+ containingBlock->GetOffsetToIgnoringScrolling(aCBReflowInput->mFrame);
+
+ nsSize reflowSize = aCBReflowInput->ComputedSizeAsContainerIfConstrained();
+ LogicalPoint logCBOffs(wm, cbOffset, reflowSize - containerSize);
+ aHypotheticalPos.mIStart += logCBOffs.I(wm);
+ aHypotheticalPos.mBStart += logCBOffs.B(wm);
+
+ // If block direction doesn't match (whether orthogonal or antiparallel),
+ // we'll have to convert aHypotheticalPos to be in terms of cbwm.
+ // This upcoming conversion must be taken into account for border offsets.
+ const bool hypotheticalPosWillUseCbwm =
+ cbwm.GetBlockDir() != wm.GetBlockDir();
+ // The specified offsets are relative to the absolute containing block's
+ // padding edge and our current values are relative to the border edge, so
+ // translate.
+ const LogicalMargin border = aCBReflowInput->ComputedLogicalBorder(wm);
+ if (hypotheticalPosWillUseCbwm && InlinePolarityFlipped(wm, cbwm)) {
+ aHypotheticalPos.mIStart += border.IEnd(wm);
+ } else {
+ aHypotheticalPos.mIStart -= border.IStart(wm);
+ }
+
+ if (hypotheticalPosWillUseCbwm && BlockPolarityFlipped(wm, cbwm)) {
+ aHypotheticalPos.mBStart += border.BEnd(wm);
+ } else {
+ aHypotheticalPos.mBStart -= border.BStart(wm);
+ }
+ // At this point, we have computed aHypotheticalPos using the writing mode
+ // of the placeholder's containing block.
+
+ if (hypotheticalPosWillUseCbwm) {
+ // If the block direction we used in calculating aHypotheticalPos does not
+ // match the absolute containing block's, we need to convert here so that
+ // aHypotheticalPos is usable in relation to the absolute containing block.
+ // This requires computing or measuring the abspos frame's block-size,
+ // which is not otherwise required/used here (as aHypotheticalPos
+ // records only the block-start coordinate).
+
+ // This is similar to the inline-size calculation for a replaced
+ // inline-level element or a block-level element (above), except that
+ // 'auto' sizing is handled differently in the block direction for non-
+ // replaced elements and replaced elements lacking an intrinsic size.
+
+ // Determine the total amount of block direction
+ // border/padding/margin that the element would have had if it had
+ // been in the flow. Note that we ignore any 'auto' and 'inherit'
+ // values.
+ nscoord insideBoxSizing, outsideBoxSizing;
+ CalculateBorderPaddingMargin(eLogicalAxisBlock, blockContentSize.BSize(wm),
+ &insideBoxSizing, &outsideBoxSizing);
+
+ nscoord boxBSize;
+ const auto& styleBSize = mStylePosition->BSize(wm);
+ if (styleBSize.BehavesLikeInitialValueOnBlockAxis()) {
+ if (mFlags.mIsReplaced && intrinsicSize) {
+ // It's a replaced element with an 'auto' block size so the box
+ // block size is its intrinsic size plus any border/padding/margin
+ boxBSize = LogicalSize(wm, *intrinsicSize).BSize(wm) +
+ outsideBoxSizing + insideBoxSizing;
+ } else {
+ // XXX Bug 1191801
+ // Figure out how to get the correct boxBSize here (need to reflow the
+ // positioned frame?)
+ boxBSize = 0;
+ }
+ } else {
+ // We need to compute it. It's important we do this, because if it's
+ // percentage-based this computed value may be different from the
+ // computed value calculated using the absolute containing block height.
+ boxBSize = nsLayoutUtils::ComputeBSizeValue(
+ blockContentSize.BSize(wm), insideBoxSizing,
+ styleBSize.AsLengthPercentage()) +
+ insideBoxSizing + outsideBoxSizing;
+ }
+
+ LogicalSize boxSize(wm, boxISize.valueOr(0), boxBSize);
+
+ LogicalPoint origin(wm, aHypotheticalPos.mIStart, aHypotheticalPos.mBStart);
+ origin =
+ origin.ConvertTo(cbwm, wm, reflowSize - boxSize.GetPhysicalSize(wm));
+
+ aHypotheticalPos.mIStart = origin.I(cbwm);
+ aHypotheticalPos.mBStart = origin.B(cbwm);
+ aHypotheticalPos.mWritingMode = cbwm;
+ } else {
+ aHypotheticalPos.mWritingMode = wm;
+ }
+}
+
+bool ReflowInput::IsInlineSizeComputableByBlockSizeAndAspectRatio(
+ nscoord aBlockSize) const {
+ WritingMode wm = GetWritingMode();
+ MOZ_ASSERT(!mStylePosition->mOffset.GetBStart(wm).IsAuto() &&
+ !mStylePosition->mOffset.GetBEnd(wm).IsAuto(),
+ "If any of the block-start and block-end are auto, aBlockSize "
+ "doesn't make sense");
+ NS_WARNING_ASSERTION(
+ aBlockSize >= 0 && aBlockSize != NS_UNCONSTRAINEDSIZE,
+ "The caller shouldn't give us an unresolved or invalid block size");
+
+ if (!mStylePosition->mAspectRatio.HasFiniteRatio()) {
+ return false;
+ }
+
+ // We don't have to compute the inline size by aspect-ratio and the resolved
+ // block size (from insets) for replaced elements.
+ if (mFrame->IsReplaced()) {
+ return false;
+ }
+
+ // If inline size is specified, we should have it by mFrame->ComputeSize()
+ // already.
+ if (mStylePosition->ISize(wm).IsLengthPercentage()) {
+ return false;
+ }
+
+ // If both inline insets are non-auto, mFrame->ComputeSize() should get a
+ // possible inline size by those insets, so we don't rely on aspect-ratio.
+ if (!mStylePosition->mOffset.GetIStart(wm).IsAuto() &&
+ !mStylePosition->mOffset.GetIEnd(wm).IsAuto()) {
+ return false;
+ }
+
+ // Just an error handling. If |aBlockSize| is NS_UNCONSTRAINEDSIZE, there must
+ // be something wrong, and we don't want to continue the calculation for
+ // aspect-ratio. So we return false if this happens.
+ return aBlockSize != NS_UNCONSTRAINEDSIZE;
+}
+
+// FIXME: Move this into nsIFrame::ComputeSize() if possible, so most of the
+// if-checks can be simplier.
+LogicalSize ReflowInput::CalculateAbsoluteSizeWithResolvedAutoBlockSize(
+ nscoord aAutoBSize, const LogicalSize& aTentativeComputedSize) {
+ LogicalSize resultSize = aTentativeComputedSize;
+ WritingMode wm = GetWritingMode();
+
+ // Two cases we don't want to early return:
+ // 1. If the block size behaves as initial value and we haven't resolved it in
+ // ComputeSize() yet, we need to apply |aAutoBSize|.
+ // Also, we check both computed style and |resultSize.BSize(wm)| to avoid
+ // applying |aAutoBSize| when the resolved block size is saturated at
+ // nscoord_MAX, and wrongly treated as NS_UNCONSTRAINEDSIZE because of a
+ // giant specified block-size.
+ // 2. If the block size needs to be computed via aspect-ratio and
+ // |aAutoBSize|, we need to apply |aAutoBSize|. In this case,
+ // |resultSize.BSize(wm)| may not be NS_UNCONSTRAINEDSIZE because we apply
+ // aspect-ratio in ComputeSize() for block axis by default, so we have to
+ // check its computed style.
+ const bool bSizeBehavesAsInitial =
+ mStylePosition->BSize(wm).BehavesLikeInitialValueOnBlockAxis();
+ const bool bSizeIsStillUnconstrained =
+ bSizeBehavesAsInitial && resultSize.BSize(wm) == NS_UNCONSTRAINEDSIZE;
+ const bool needsComputeInlineSizeByAspectRatio =
+ bSizeBehavesAsInitial &&
+ IsInlineSizeComputableByBlockSizeAndAspectRatio(aAutoBSize);
+ if (!bSizeIsStillUnconstrained && !needsComputeInlineSizeByAspectRatio) {
+ return resultSize;
+ }
+
+ // For non-replaced elements with block-size auto, the block-size
+ // fills the remaining space, and we clamp it by min/max size constraints.
+ resultSize.BSize(wm) = ApplyMinMaxBSize(aAutoBSize);
+
+ if (!needsComputeInlineSizeByAspectRatio) {
+ return resultSize;
+ }
+
+ // Calculate transferred inline size through aspect-ratio.
+ // For non-replaced elements, we always take box-sizing into account.
+ const auto boxSizingAdjust =
+ mStylePosition->mBoxSizing == StyleBoxSizing::Border
+ ? ComputedLogicalBorderPadding(wm).Size(wm)
+ : LogicalSize(wm);
+ auto transferredISize =
+ mStylePosition->mAspectRatio.ToLayoutRatio().ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, wm, aAutoBSize, boxSizingAdjust);
+ resultSize.ISize(wm) = ApplyMinMaxISize(transferredISize);
+
+ MOZ_ASSERT(mFlags.mIsBSizeSetByAspectRatio,
+ "This flag should have been set because nsIFrame::ComputeSize() "
+ "returns AspectRatioUsage::ToComputeBSize unconditionally for "
+ "auto block-size");
+ mFlags.mIsBSizeSetByAspectRatio = false;
+
+ return resultSize;
+}
+
+void ReflowInput::InitAbsoluteConstraints(nsPresContext* aPresContext,
+ const ReflowInput* aCBReflowInput,
+ const LogicalSize& aCBSize,
+ LayoutFrameType aFrameType) {
+ WritingMode wm = GetWritingMode();
+ WritingMode cbwm = aCBReflowInput->GetWritingMode();
+ NS_WARNING_ASSERTION(aCBSize.BSize(cbwm) != NS_UNCONSTRAINEDSIZE,
+ "containing block bsize must be constrained");
+
+ NS_ASSERTION(aFrameType != LayoutFrameType::Table,
+ "InitAbsoluteConstraints should not be called on table frames");
+ NS_ASSERTION(mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "Why are we here?");
+
+ const auto& styleOffset = mStylePosition->mOffset;
+ bool iStartIsAuto = styleOffset.GetIStart(cbwm).IsAuto();
+ bool iEndIsAuto = styleOffset.GetIEnd(cbwm).IsAuto();
+ bool bStartIsAuto = styleOffset.GetBStart(cbwm).IsAuto();
+ bool bEndIsAuto = styleOffset.GetBEnd(cbwm).IsAuto();
+
+ // If both 'left' and 'right' are 'auto' or both 'top' and 'bottom' are
+ // 'auto', then compute the hypothetical box position where the element would
+ // have been if it had been in the flow
+ nsHypotheticalPosition hypotheticalPos;
+ if ((iStartIsAuto && iEndIsAuto) || (bStartIsAuto && bEndIsAuto)) {
+ nsPlaceholderFrame* placeholderFrame = mFrame->GetPlaceholderFrame();
+ MOZ_ASSERT(placeholderFrame, "no placeholder frame");
+ nsIFrame* placeholderParent = placeholderFrame->GetParent();
+ MOZ_ASSERT(placeholderParent, "shouldn't have unparented placeholders");
+
+ if (placeholderFrame->HasAnyStateBits(
+ PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) {
+ MOZ_ASSERT(placeholderParent->IsFlexOrGridContainer(),
+ "This flag should only be set on grid/flex children");
+ // If the (as-yet unknown) static position will determine the inline
+ // and/or block offsets, set flags to note those offsets aren't valid
+ // until we can do CSS Box Alignment on the OOF frame.
+ mFlags.mIOffsetsNeedCSSAlign = (iStartIsAuto && iEndIsAuto);
+ mFlags.mBOffsetsNeedCSSAlign = (bStartIsAuto && bEndIsAuto);
+ }
+
+ if (mFlags.mStaticPosIsCBOrigin) {
+ hypotheticalPos.mWritingMode = cbwm;
+ hypotheticalPos.mIStart = nscoord(0);
+ hypotheticalPos.mBStart = nscoord(0);
+ if (placeholderParent->IsGridContainerFrame() &&
+ placeholderParent->HasAnyStateBits(NS_STATE_GRID_IS_COL_MASONRY |
+ NS_STATE_GRID_IS_ROW_MASONRY)) {
+ // Disable CSS alignment in Masonry layout since we don't have real grid
+ // areas in that axis. We'll use the placeholder position instead as it
+ // was calculated by nsGridContainerFrame::MasonryLayout.
+ auto cbsz = aCBSize.GetPhysicalSize(cbwm);
+ LogicalPoint pos = placeholderFrame->GetLogicalPosition(cbwm, cbsz);
+ if (placeholderParent->HasAnyStateBits(NS_STATE_GRID_IS_COL_MASONRY)) {
+ mFlags.mIOffsetsNeedCSSAlign = false;
+ hypotheticalPos.mIStart = pos.I(cbwm);
+ } else {
+ mFlags.mBOffsetsNeedCSSAlign = false;
+ hypotheticalPos.mBStart = pos.B(cbwm);
+ }
+ }
+ } else {
+ // XXXmats all this is broken for orthogonal writing-modes: bug 1521988.
+ CalculateHypotheticalPosition(aPresContext, placeholderFrame,
+ aCBReflowInput, hypotheticalPos,
+ aFrameType);
+ if (aCBReflowInput->mFrame->IsGridContainerFrame()) {
+ // 'hypotheticalPos' is relative to the padding rect of the CB *frame*.
+ // In grid layout the CB is the grid area rectangle, so we translate
+ // 'hypotheticalPos' to be relative that rectangle here.
+ nsRect cb = nsGridContainerFrame::GridItemCB(mFrame);
+ nscoord left(0);
+ nscoord right(0);
+ if (cbwm.IsBidiLTR()) {
+ left = cb.X();
+ } else {
+ right = aCBReflowInput->ComputedWidth() +
+ aCBReflowInput->ComputedPhysicalPadding().LeftRight() -
+ cb.XMost();
+ }
+ LogicalMargin offsets(cbwm, nsMargin(cb.Y(), right, nscoord(0), left));
+ hypotheticalPos.mIStart -= offsets.IStart(cbwm);
+ hypotheticalPos.mBStart -= offsets.BStart(cbwm);
+ }
+ }
+ }
+
+ // Initialize the 'left' and 'right' computed offsets
+ // XXX Handle new 'static-position' value...
+
+ // Size of the containing block in its writing mode
+ LogicalSize cbSize = aCBSize;
+ LogicalMargin offsets = ComputedLogicalOffsets(cbwm);
+
+ if (iStartIsAuto) {
+ offsets.IStart(cbwm) = 0;
+ } else {
+ offsets.IStart(cbwm) = nsLayoutUtils::ComputeCBDependentValue(
+ cbSize.ISize(cbwm), styleOffset.GetIStart(cbwm));
+ }
+ if (iEndIsAuto) {
+ offsets.IEnd(cbwm) = 0;
+ } else {
+ offsets.IEnd(cbwm) = nsLayoutUtils::ComputeCBDependentValue(
+ cbSize.ISize(cbwm), styleOffset.GetIEnd(cbwm));
+ }
+
+ if (iStartIsAuto && iEndIsAuto) {
+ if (cbwm.IsBidiLTR() != hypotheticalPos.mWritingMode.IsBidiLTR()) {
+ offsets.IEnd(cbwm) = hypotheticalPos.mIStart;
+ iEndIsAuto = false;
+ } else {
+ offsets.IStart(cbwm) = hypotheticalPos.mIStart;
+ iStartIsAuto = false;
+ }
+ }
+
+ if (bStartIsAuto) {
+ offsets.BStart(cbwm) = 0;
+ } else {
+ offsets.BStart(cbwm) = nsLayoutUtils::ComputeBSizeDependentValue(
+ cbSize.BSize(cbwm), styleOffset.GetBStart(cbwm));
+ }
+ if (bEndIsAuto) {
+ offsets.BEnd(cbwm) = 0;
+ } else {
+ offsets.BEnd(cbwm) = nsLayoutUtils::ComputeBSizeDependentValue(
+ cbSize.BSize(cbwm), styleOffset.GetBEnd(cbwm));
+ }
+
+ if (bStartIsAuto && bEndIsAuto) {
+ // Treat 'top' like 'static-position'
+ offsets.BStart(cbwm) = hypotheticalPos.mBStart;
+ bStartIsAuto = false;
+ }
+
+ SetComputedLogicalOffsets(cbwm, offsets);
+
+ if (wm.IsOrthogonalTo(cbwm)) {
+ if (bStartIsAuto || bEndIsAuto) {
+ mComputeSizeFlags += ComputeSizeFlag::ShrinkWrap;
+ }
+ } else {
+ if (iStartIsAuto || iEndIsAuto) {
+ mComputeSizeFlags += ComputeSizeFlag::ShrinkWrap;
+ }
+ }
+
+ nsIFrame::SizeComputationResult sizeResult = {
+ LogicalSize(wm), nsIFrame::AspectRatioUsage::None};
+ {
+ AutoMaybeDisableFontInflation an(mFrame);
+
+ sizeResult = mFrame->ComputeSize(
+ mRenderingContext, wm, cbSize.ConvertTo(wm, cbwm),
+ cbSize.ConvertTo(wm, cbwm).ISize(wm), // XXX or AvailableISize()?
+ ComputedLogicalMargin(wm).Size(wm) +
+ ComputedLogicalOffsets(wm).Size(wm),
+ ComputedLogicalBorderPadding(wm).Size(wm), {}, mComputeSizeFlags);
+ mComputedSize = sizeResult.mLogicalSize;
+ NS_ASSERTION(ComputedISize() >= 0, "Bogus inline-size");
+ NS_ASSERTION(
+ ComputedBSize() == NS_UNCONSTRAINEDSIZE || ComputedBSize() >= 0,
+ "Bogus block-size");
+ }
+
+ LogicalSize& computedSize = sizeResult.mLogicalSize;
+ computedSize = computedSize.ConvertTo(cbwm, wm);
+
+ mFlags.mIsBSizeSetByAspectRatio = sizeResult.mAspectRatioUsage ==
+ nsIFrame::AspectRatioUsage::ToComputeBSize;
+
+ // XXX Now that we have ComputeSize, can we condense many of the
+ // branches off of widthIsAuto?
+
+ LogicalMargin margin = ComputedLogicalMargin(cbwm);
+ const LogicalMargin borderPadding = ComputedLogicalBorderPadding(cbwm);
+
+ bool iSizeIsAuto = mStylePosition->ISize(cbwm).IsAuto();
+ bool marginIStartIsAuto = false;
+ bool marginIEndIsAuto = false;
+ bool marginBStartIsAuto = false;
+ bool marginBEndIsAuto = false;
+ if (iStartIsAuto) {
+ // We know 'right' is not 'auto' anymore thanks to the hypothetical
+ // box code above.
+ // Solve for 'left'.
+ if (iSizeIsAuto) {
+ // XXXldb This, and the corresponding code in
+ // nsAbsoluteContainingBlock.cpp, could probably go away now that
+ // we always compute widths.
+ offsets.IStart(cbwm) = NS_AUTOOFFSET;
+ } else {
+ offsets.IStart(cbwm) = cbSize.ISize(cbwm) - offsets.IEnd(cbwm) -
+ computedSize.ISize(cbwm) - margin.IStartEnd(cbwm) -
+ borderPadding.IStartEnd(cbwm);
+ }
+ } else if (iEndIsAuto) {
+ // We know 'left' is not 'auto' anymore thanks to the hypothetical
+ // box code above.
+ // Solve for 'right'.
+ if (iSizeIsAuto) {
+ // XXXldb This, and the corresponding code in
+ // nsAbsoluteContainingBlock.cpp, could probably go away now that
+ // we always compute widths.
+ offsets.IEnd(cbwm) = NS_AUTOOFFSET;
+ } else {
+ offsets.IEnd(cbwm) = cbSize.ISize(cbwm) - offsets.IStart(cbwm) -
+ computedSize.ISize(cbwm) - margin.IStartEnd(cbwm) -
+ borderPadding.IStartEnd(cbwm);
+ }
+ } else if (!mFrame->HasIntrinsicKeywordForBSize() ||
+ !wm.IsOrthogonalTo(cbwm)) {
+ // Neither 'inline-start' nor 'inline-end' is 'auto'.
+ if (wm.IsOrthogonalTo(cbwm)) {
+ // For orthogonal blocks, we need to handle the case where the block had
+ // unconstrained block-size, which mapped to unconstrained inline-size
+ // in the containing block's writing mode.
+ nscoord autoISize = cbSize.ISize(cbwm) - margin.IStartEnd(cbwm) -
+ borderPadding.IStartEnd(cbwm) -
+ offsets.IStartEnd(cbwm);
+ autoISize = std::max(autoISize, 0);
+ // FIXME: Bug 1602669: if |autoISize| happens to be numerically equal to
+ // NS_UNCONSTRAINEDSIZE, we may get some unexpected behavior. We need a
+ // better way to distinguish between unconstrained size and resolved
+ // size.
+ NS_WARNING_ASSERTION(autoISize != NS_UNCONSTRAINEDSIZE,
+ "Unexpected size from inline-start and inline-end");
+
+ nscoord autoBSizeInWM = autoISize;
+ LogicalSize computedSizeInWM =
+ CalculateAbsoluteSizeWithResolvedAutoBlockSize(
+ autoBSizeInWM, computedSize.ConvertTo(wm, cbwm));
+ computedSize = computedSizeInWM.ConvertTo(cbwm, wm);
+ }
+
+ // However, the inline-size might
+ // still not fill all the available space (even though we didn't
+ // shrink-wrap) in case:
+ // * inline-size was specified
+ // * we're dealing with a replaced element
+ // * width was constrained by min- or max-inline-size.
+
+ nscoord availMarginSpace =
+ aCBSize.ISize(cbwm) - offsets.IStartEnd(cbwm) - margin.IStartEnd(cbwm) -
+ borderPadding.IStartEnd(cbwm) - computedSize.ISize(cbwm);
+ marginIStartIsAuto = mStyleMargin->mMargin.GetIStart(cbwm).IsAuto();
+ marginIEndIsAuto = mStyleMargin->mMargin.GetIEnd(cbwm).IsAuto();
+ ComputeAbsPosInlineAutoMargin(availMarginSpace, cbwm, marginIStartIsAuto,
+ marginIEndIsAuto, margin, offsets);
+ }
+
+ bool bSizeIsAuto =
+ mStylePosition->BSize(cbwm).BehavesLikeInitialValueOnBlockAxis();
+ if (bStartIsAuto) {
+ // solve for block-start
+ if (bSizeIsAuto) {
+ offsets.BStart(cbwm) = NS_AUTOOFFSET;
+ } else {
+ offsets.BStart(cbwm) = cbSize.BSize(cbwm) - margin.BStartEnd(cbwm) -
+ borderPadding.BStartEnd(cbwm) -
+ computedSize.BSize(cbwm) - offsets.BEnd(cbwm);
+ }
+ } else if (bEndIsAuto) {
+ // solve for block-end
+ if (bSizeIsAuto) {
+ offsets.BEnd(cbwm) = NS_AUTOOFFSET;
+ } else {
+ offsets.BEnd(cbwm) = cbSize.BSize(cbwm) - margin.BStartEnd(cbwm) -
+ borderPadding.BStartEnd(cbwm) -
+ computedSize.BSize(cbwm) - offsets.BStart(cbwm);
+ }
+ } else if (!mFrame->HasIntrinsicKeywordForBSize() ||
+ wm.IsOrthogonalTo(cbwm)) {
+ // Neither block-start nor -end is 'auto'.
+ nscoord autoBSize = cbSize.BSize(cbwm) - margin.BStartEnd(cbwm) -
+ borderPadding.BStartEnd(cbwm) - offsets.BStartEnd(cbwm);
+ autoBSize = std::max(autoBSize, 0);
+ // FIXME: Bug 1602669: if |autoBSize| happens to be numerically equal to
+ // NS_UNCONSTRAINEDSIZE, we may get some unexpected behavior. We need a
+ // better way to distinguish between unconstrained size and resolved size.
+ NS_WARNING_ASSERTION(autoBSize != NS_UNCONSTRAINEDSIZE,
+ "Unexpected size from block-start and block-end");
+
+ // For orthogonal case, the inline size in |wm| should have been handled by
+ // ComputeSize(). In other words, we only have to apply |autoBSize| to
+ // the computed size if this value can represent the block size in |wm|.
+ if (!wm.IsOrthogonalTo(cbwm)) {
+ // We handle the unconstrained block-size in current block's writing
+ // mode 'wm'.
+ LogicalSize computedSizeInWM =
+ CalculateAbsoluteSizeWithResolvedAutoBlockSize(
+ autoBSize, computedSize.ConvertTo(wm, cbwm));
+ computedSize = computedSizeInWM.ConvertTo(cbwm, wm);
+ }
+
+ // The block-size might still not fill all the available space in case:
+ // * bsize was specified
+ // * we're dealing with a replaced element
+ // * bsize was constrained by min- or max-bsize.
+ nscoord availMarginSpace = autoBSize - computedSize.BSize(cbwm);
+ marginBStartIsAuto = mStyleMargin->mMargin.GetBStart(cbwm).IsAuto();
+ marginBEndIsAuto = mStyleMargin->mMargin.GetBEnd(cbwm).IsAuto();
+
+ ComputeAbsPosBlockAutoMargin(availMarginSpace, cbwm, marginBStartIsAuto,
+ marginBEndIsAuto, margin, offsets);
+ }
+ mComputedSize = computedSize.ConvertTo(wm, cbwm);
+
+ SetComputedLogicalOffsets(cbwm, offsets);
+ SetComputedLogicalMargin(cbwm, margin);
+
+ // If we have auto margins, update our UsedMarginProperty. The property
+ // will have already been created by InitOffsets if it is needed.
+ if (marginIStartIsAuto || marginIEndIsAuto || marginBStartIsAuto ||
+ marginBEndIsAuto) {
+ nsMargin* propValue = mFrame->GetProperty(nsIFrame::UsedMarginProperty());
+ MOZ_ASSERT(propValue,
+ "UsedMarginProperty should have been created "
+ "by InitOffsets.");
+ *propValue = margin.GetPhysicalMargin(cbwm);
+ }
+}
+
+// This will not be converted to abstract coordinates because it's only
+// used in CalcQuirkContainingBlockHeight
+static nscoord GetBlockMarginBorderPadding(const ReflowInput* aReflowInput) {
+ nscoord result = 0;
+ if (!aReflowInput) return result;
+
+ // zero auto margins
+ nsMargin margin = aReflowInput->ComputedPhysicalMargin();
+ if (NS_AUTOMARGIN == margin.top) margin.top = 0;
+ if (NS_AUTOMARGIN == margin.bottom) margin.bottom = 0;
+
+ result += margin.top + margin.bottom;
+ result += aReflowInput->ComputedPhysicalBorderPadding().top +
+ aReflowInput->ComputedPhysicalBorderPadding().bottom;
+
+ return result;
+}
+
+/* Get the height based on the viewport of the containing block specified
+ * in aReflowInput when the containing block has mComputedHeight ==
+ * NS_UNCONSTRAINEDSIZE This will walk up the chain of containing blocks looking
+ * for a computed height until it finds the canvas frame, or it encounters a
+ * frame that is not a block, area, or scroll frame. This handles compatibility
+ * with IE (see bug 85016 and bug 219693)
+ *
+ * When we encounter scrolledContent block frames, we skip over them,
+ * since they are guaranteed to not be useful for computing the containing
+ * block.
+ *
+ * See also IsQuirkContainingBlockHeight.
+ */
+static nscoord CalcQuirkContainingBlockHeight(
+ const ReflowInput* aCBReflowInput) {
+ const ReflowInput* firstAncestorRI = nullptr; // a candidate for html frame
+ const ReflowInput* secondAncestorRI = nullptr; // a candidate for body frame
+
+ // initialize the default to NS_UNCONSTRAINEDSIZE as this is the containings
+ // block computed height when this function is called. It is possible that we
+ // don't alter this height especially if we are restricted to one level
+ nscoord result = NS_UNCONSTRAINEDSIZE;
+
+ const ReflowInput* ri = aCBReflowInput;
+ for (; ri; ri = ri->mParentReflowInput) {
+ LayoutFrameType frameType = ri->mFrame->Type();
+ // if the ancestor is auto height then skip it and continue up if it
+ // is the first block frame and possibly the body/html
+ if (LayoutFrameType::Block == frameType ||
+ LayoutFrameType::Scroll == frameType) {
+ secondAncestorRI = firstAncestorRI;
+ firstAncestorRI = ri;
+
+ // If the current frame we're looking at is positioned, we don't want to
+ // go any further (see bug 221784). The behavior we want here is: 1) If
+ // not auto-height, use this as the percentage base. 2) If auto-height,
+ // keep looking, unless the frame is positioned.
+ if (NS_UNCONSTRAINEDSIZE == ri->ComputedHeight()) {
+ if (ri->mFrame->IsAbsolutelyPositioned(ri->mStyleDisplay)) {
+ break;
+ } else {
+ continue;
+ }
+ }
+ } else if (LayoutFrameType::Canvas == frameType) {
+ // Always continue on to the height calculation
+ } else if (LayoutFrameType::PageContent == frameType) {
+ nsIFrame* prevInFlow = ri->mFrame->GetPrevInFlow();
+ // only use the page content frame for a height basis if it is the first
+ // in flow
+ if (prevInFlow) break;
+ } else {
+ break;
+ }
+
+ // if the ancestor is the page content frame then the percent base is
+ // the avail height, otherwise it is the computed height
+ result = (LayoutFrameType::PageContent == frameType) ? ri->AvailableHeight()
+ : ri->ComputedHeight();
+ // if unconstrained - don't sutract borders - would result in huge height
+ if (NS_UNCONSTRAINEDSIZE == result) return result;
+
+ // if we got to the canvas or page content frame, then subtract out
+ // margin/border/padding for the BODY and HTML elements
+ if ((LayoutFrameType::Canvas == frameType) ||
+ (LayoutFrameType::PageContent == frameType)) {
+ result -= GetBlockMarginBorderPadding(firstAncestorRI);
+ result -= GetBlockMarginBorderPadding(secondAncestorRI);
+
+#ifdef DEBUG
+ // make sure the first ancestor is the HTML and the second is the BODY
+ if (firstAncestorRI) {
+ nsIContent* frameContent = firstAncestorRI->mFrame->GetContent();
+ if (frameContent) {
+ NS_ASSERTION(frameContent->IsHTMLElement(nsGkAtoms::html),
+ "First ancestor is not HTML");
+ }
+ }
+ if (secondAncestorRI) {
+ nsIContent* frameContent = secondAncestorRI->mFrame->GetContent();
+ if (frameContent) {
+ NS_ASSERTION(frameContent->IsHTMLElement(nsGkAtoms::body),
+ "Second ancestor is not BODY");
+ }
+ }
+#endif
+
+ }
+ // if we got to the html frame (a block child of the canvas) ...
+ else if (LayoutFrameType::Block == frameType && ri->mParentReflowInput &&
+ ri->mParentReflowInput->mFrame->IsCanvasFrame()) {
+ // ... then subtract out margin/border/padding for the BODY element
+ result -= GetBlockMarginBorderPadding(secondAncestorRI);
+ }
+ break;
+ }
+
+ // Make sure not to return a negative height here!
+ return std::max(result, 0);
+}
+
+// Called by InitConstraints() to compute the containing block rectangle for
+// the element. Handles the special logic for absolutely positioned elements
+LogicalSize ReflowInput::ComputeContainingBlockRectangle(
+ nsPresContext* aPresContext, const ReflowInput* aContainingBlockRI) const {
+ // Unless the element is absolutely positioned, the containing block is
+ // formed by the content edge of the nearest block-level ancestor
+ LogicalSize cbSize = aContainingBlockRI->ComputedSize();
+
+ WritingMode wm = aContainingBlockRI->GetWritingMode();
+
+ if (aContainingBlockRI->mFlags.mTreatBSizeAsIndefinite) {
+ cbSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ } else if (aContainingBlockRI->mPercentageBasisInBlockAxis) {
+ MOZ_ASSERT(cbSize.BSize(wm) == NS_UNCONSTRAINEDSIZE,
+ "Why provide a percentage basis when the containing block's "
+ "block-size is definite?");
+ cbSize.BSize(wm) = *aContainingBlockRI->mPercentageBasisInBlockAxis;
+ }
+
+ if (((mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ // XXXfr hack for making frames behave properly when in overflow
+ // container lists, see bug 154892; need to revisit later
+ !mFrame->GetPrevInFlow()) ||
+ (mFrame->IsTableFrame() &&
+ mFrame->GetParent()->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) &&
+ mStyleDisplay->IsAbsolutelyPositioned(mFrame)) {
+ // See if the ancestor is block-level or inline-level
+ const auto computedPadding = aContainingBlockRI->ComputedLogicalPadding(wm);
+ if (aContainingBlockRI->mStyleDisplay->IsInlineOutsideStyle()) {
+ // Base our size on the actual size of the frame. In cases when this is
+ // completely bogus (eg initial reflow), this code shouldn't even be
+ // called, since the code in nsInlineFrame::Reflow will pass in
+ // the containing block dimensions to our constructor.
+ // XXXbz we should be taking the in-flows into account too, but
+ // that's very hard.
+
+ LogicalMargin computedBorder =
+ aContainingBlockRI->ComputedLogicalBorderPadding(wm) -
+ computedPadding;
+ cbSize.ISize(wm) =
+ aContainingBlockRI->mFrame->ISize(wm) - computedBorder.IStartEnd(wm);
+ NS_ASSERTION(cbSize.ISize(wm) >= 0, "Negative containing block isize!");
+ cbSize.BSize(wm) =
+ aContainingBlockRI->mFrame->BSize(wm) - computedBorder.BStartEnd(wm);
+ NS_ASSERTION(cbSize.BSize(wm) >= 0, "Negative containing block bsize!");
+ } else {
+ // If the ancestor is block-level, the containing block is formed by the
+ // padding edge of the ancestor
+ cbSize += computedPadding.Size(wm);
+ }
+ } else {
+ auto IsQuirky = [](const StyleSize& aSize) -> bool {
+ return aSize.ConvertsToPercentage();
+ };
+ // an element in quirks mode gets a containing block based on looking for a
+ // parent with a non-auto height if the element has a percent height.
+ // Note: We don't emulate this quirk for percents in calc(), or in vertical
+ // writing modes, or if the containing block is a flex or grid item.
+ if (!wm.IsVertical() && NS_UNCONSTRAINEDSIZE == cbSize.BSize(wm)) {
+ if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() &&
+ !aContainingBlockRI->mFrame->IsFlexOrGridItem() &&
+ (IsQuirky(mStylePosition->mHeight) ||
+ (mFrame->IsTableWrapperFrame() &&
+ IsQuirky(mFrame->PrincipalChildList()
+ .FirstChild()
+ ->StylePosition()
+ ->mHeight)))) {
+ cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(aContainingBlockRI);
+ }
+ }
+ }
+
+ return cbSize.ConvertTo(GetWritingMode(), wm);
+}
+
+// XXX refactor this code to have methods for each set of properties
+// we are computing: width,height,line-height; margin; offsets
+
+void ReflowInput::InitConstraints(
+ nsPresContext* aPresContext, const Maybe<LogicalSize>& aContainingBlockSize,
+ const Maybe<LogicalMargin>& aBorder, const Maybe<LogicalMargin>& aPadding,
+ LayoutFrameType aFrameType) {
+ WritingMode wm = GetWritingMode();
+ LogicalSize cbSize = aContainingBlockSize.valueOr(
+ LogicalSize(mWritingMode, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE));
+ DISPLAY_INIT_CONSTRAINTS(mFrame, this, cbSize.ISize(wm), cbSize.BSize(wm),
+ aBorder, aPadding);
+
+ // If this is a reflow root, then set the computed width and
+ // height equal to the available space
+ if (nullptr == mParentReflowInput || mFlags.mDummyParentReflowInput) {
+ // XXXldb This doesn't mean what it used to!
+ InitOffsets(wm, cbSize.ISize(wm), aFrameType, mComputeSizeFlags, aBorder,
+ aPadding, mStyleDisplay);
+ // Override mComputedMargin since reflow roots start from the
+ // frame's boundary, which is inside the margin.
+ SetComputedLogicalMargin(wm, LogicalMargin(wm));
+ SetComputedLogicalOffsets(wm, LogicalMargin(wm));
+
+ const auto borderPadding = ComputedLogicalBorderPadding(wm);
+ SetComputedISize(
+ std::max(0, AvailableISize() - borderPadding.IStartEnd(wm)),
+ ResetResizeFlags::No);
+ SetComputedBSize(
+ AvailableBSize() != NS_UNCONSTRAINEDSIZE
+ ? std::max(0, AvailableBSize() - borderPadding.BStartEnd(wm))
+ : NS_UNCONSTRAINEDSIZE,
+ ResetResizeFlags::No);
+
+ mComputedMinSize.SizeTo(mWritingMode, 0, 0);
+ mComputedMaxSize.SizeTo(mWritingMode, NS_UNCONSTRAINEDSIZE,
+ NS_UNCONSTRAINEDSIZE);
+ } else {
+ // Get the containing block's reflow input
+ const ReflowInput* cbri = mCBReflowInput;
+ MOZ_ASSERT(cbri, "no containing block");
+ MOZ_ASSERT(mFrame->GetParent());
+
+ // If we weren't given a containing block size, then compute one.
+ if (aContainingBlockSize.isNothing()) {
+ cbSize = ComputeContainingBlockRectangle(aPresContext, cbri);
+ }
+
+ // See if the containing block height is based on the size of its
+ // content
+ if (NS_UNCONSTRAINEDSIZE == cbSize.BSize(wm)) {
+ // See if the containing block is a cell frame which needs
+ // to use the mComputedHeight of the cell instead of what the cell block
+ // passed in.
+ // XXX It seems like this could lead to bugs with min-height and friends
+ if (cbri->mParentReflowInput && cbri->mFrame->IsTableCellFrame()) {
+ cbSize.BSize(wm) = cbri->ComputedSize(wm).BSize(wm);
+ }
+ }
+
+ // XXX Might need to also pass the CB height (not width) for page boxes,
+ // too, if we implement them.
+
+ // For calculating positioning offsets, margins, borders and
+ // padding, we use the writing mode of the containing block
+ WritingMode cbwm = cbri->GetWritingMode();
+ InitOffsets(cbwm, cbSize.ConvertTo(cbwm, wm).ISize(cbwm), aFrameType,
+ mComputeSizeFlags, aBorder, aPadding, mStyleDisplay);
+
+ // For calculating the size of this box, we use its own writing mode
+ const auto& blockSize = mStylePosition->BSize(wm);
+ bool isAutoBSize = blockSize.BehavesLikeInitialValueOnBlockAxis();
+
+ // Check for a percentage based block size and a containing block
+ // block size that depends on the content block size
+ if (blockSize.HasPercent()) {
+ if (NS_UNCONSTRAINEDSIZE == cbSize.BSize(wm)) {
+ // this if clause enables %-blockSize on replaced inline frames,
+ // such as images. See bug 54119. The else clause "blockSizeUnit =
+ // eStyleUnit_Auto;" used to be called exclusively.
+ if (mFlags.mIsReplaced && mStyleDisplay->IsInlineOutsideStyle()) {
+ // Get the containing block's reflow input
+ NS_ASSERTION(nullptr != cbri, "no containing block");
+ // in quirks mode, get the cb height using the special quirk method
+ if (!wm.IsVertical() &&
+ eCompatibility_NavQuirks == aPresContext->CompatibilityMode()) {
+ if (!cbri->mFrame->IsTableCellFrame() &&
+ !cbri->mFrame->IsFlexOrGridItem()) {
+ cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(cbri);
+ if (cbSize.BSize(wm) == NS_UNCONSTRAINEDSIZE) {
+ isAutoBSize = true;
+ }
+ } else {
+ isAutoBSize = true;
+ }
+ }
+ // in standard mode, use the cb block size. if it's "auto",
+ // as will be the case by default in BODY, use auto block size
+ // as per CSS2 spec.
+ else {
+ nscoord computedBSize = cbri->ComputedSize(wm).BSize(wm);
+ if (NS_UNCONSTRAINEDSIZE != computedBSize) {
+ cbSize.BSize(wm) = computedBSize;
+ } else {
+ isAutoBSize = true;
+ }
+ }
+ } else {
+ // default to interpreting the blockSize like 'auto'
+ isAutoBSize = true;
+ }
+ }
+ }
+
+ // Compute our offsets if the element is relatively positioned. We
+ // need the correct containing block inline-size and block-size
+ // here, which is why we need to do it after all the quirks-n-such
+ // above. (If the element is sticky positioned, we need to wait
+ // until the scroll container knows its size, so we compute offsets
+ // from StickyScrollContainer::UpdatePositions.)
+ if (mStyleDisplay->IsRelativelyPositioned(mFrame)) {
+ const LogicalMargin offsets =
+ ComputeRelativeOffsets(cbwm, mFrame, cbSize.ConvertTo(cbwm, wm));
+ SetComputedLogicalOffsets(cbwm, offsets);
+ } else {
+ // Initialize offsets to 0
+ SetComputedLogicalOffsets(wm, LogicalMargin(wm));
+ }
+
+ // Calculate the computed values for min and max properties. Note that
+ // this MUST come after we've computed our border and padding.
+ ComputeMinMaxValues(cbSize);
+
+ // Calculate the computed inlineSize and blockSize.
+ // This varies by frame type.
+
+ if (IsInternalTableFrame()) {
+ // Internal table elements. The rules vary depending on the type.
+ // Calculate the computed isize
+ bool rowOrRowGroup = false;
+ const auto& inlineSize = mStylePosition->ISize(wm);
+ bool isAutoISize = inlineSize.IsAuto();
+ if ((StyleDisplay::TableRow == mStyleDisplay->mDisplay) ||
+ (StyleDisplay::TableRowGroup == mStyleDisplay->mDisplay)) {
+ // 'inlineSize' property doesn't apply to table rows and row groups
+ isAutoISize = true;
+ rowOrRowGroup = true;
+ }
+
+ // calc() with both percentages and lengths act like auto on internal
+ // table elements
+ if (isAutoISize || inlineSize.HasLengthAndPercentage()) {
+ if (AvailableISize() != NS_UNCONSTRAINEDSIZE && !rowOrRowGroup) {
+ // Internal table elements don't have margins. Only tables and
+ // cells have border and padding
+ SetComputedISize(
+ std::max(0, AvailableISize() -
+ ComputedLogicalBorderPadding(wm).IStartEnd(wm)),
+ ResetResizeFlags::No);
+ } else {
+ SetComputedISize(AvailableISize(), ResetResizeFlags::No);
+ }
+ NS_ASSERTION(ComputedISize() >= 0, "Bogus computed isize");
+
+ } else {
+ SetComputedISize(
+ ComputeISizeValue(cbSize, mStylePosition->mBoxSizing, inlineSize),
+ ResetResizeFlags::No);
+ }
+
+ // Calculate the computed block size
+ if (StyleDisplay::TableColumn == mStyleDisplay->mDisplay ||
+ StyleDisplay::TableColumnGroup == mStyleDisplay->mDisplay) {
+ // 'blockSize' property doesn't apply to table columns and column groups
+ isAutoBSize = true;
+ }
+ // calc() with both percentages and lengths acts like 'auto' on internal
+ // table elements
+ if (isAutoBSize || blockSize.HasLengthAndPercentage()) {
+ SetComputedBSize(NS_UNCONSTRAINEDSIZE, ResetResizeFlags::No);
+ } else {
+ SetComputedBSize(
+ ComputeBSizeValue(cbSize.BSize(wm), mStylePosition->mBoxSizing,
+ blockSize.AsLengthPercentage()),
+ ResetResizeFlags::No);
+ }
+
+ // Doesn't apply to internal table elements
+ mComputedMinSize.SizeTo(mWritingMode, 0, 0);
+ mComputedMaxSize.SizeTo(mWritingMode, NS_UNCONSTRAINEDSIZE,
+ NS_UNCONSTRAINEDSIZE);
+ } else if (mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ mStyleDisplay->IsAbsolutelyPositionedStyle() &&
+ // XXXfr hack for making frames behave properly when in overflow
+ // container lists, see bug 154892; need to revisit later
+ !mFrame->GetPrevInFlow()) {
+ InitAbsoluteConstraints(aPresContext, cbri,
+ cbSize.ConvertTo(cbri->GetWritingMode(), wm),
+ aFrameType);
+ } else {
+ AutoMaybeDisableFontInflation an(mFrame);
+
+ const bool isBlockLevel =
+ ((!mStyleDisplay->IsInlineOutsideStyle() &&
+ // internal table values on replaced elements behaves as inline
+ // https://drafts.csswg.org/css-tables-3/#table-structure
+ // "... it is handled instead as though the author had declared
+ // either 'block' (for 'table' display) or 'inline' (for all
+ // other values)"
+ !(mFlags.mIsReplaced && (mStyleDisplay->IsInnerTableStyle() ||
+ mStyleDisplay->DisplayOutside() ==
+ StyleDisplayOutside::TableCaption))) ||
+ // The inner table frame always fills its outer wrapper table frame,
+ // even for 'inline-table'.
+ mFrame->IsTableFrame()) &&
+ // XXX abs.pos. continuations treated like blocks, see comment in
+ // the else-if condition above.
+ (!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) ||
+ mStyleDisplay->IsAbsolutelyPositionedStyle());
+
+ if (!isBlockLevel) {
+ mComputeSizeFlags += ComputeSizeFlag::ShrinkWrap;
+ }
+
+ nsIFrame* alignCB = mFrame->GetParent();
+ if (alignCB->IsTableWrapperFrame()) {
+ nsIFrame* alignCBParent = alignCB->GetParent();
+ if (alignCBParent && alignCBParent->IsGridContainerFrame()) {
+ alignCB = alignCBParent;
+ }
+ }
+ if (!alignCB->IsGridContainerFrame()) {
+ // Shrink-wrap blocks that are orthogonal to their container.
+ if (isBlockLevel && mCBReflowInput &&
+ mCBReflowInput->GetWritingMode().IsOrthogonalTo(mWritingMode)) {
+ mComputeSizeFlags += ComputeSizeFlag::ShrinkWrap;
+ }
+ }
+
+ if (cbSize.ISize(wm) == NS_UNCONSTRAINEDSIZE) {
+ // For orthogonal flows, where we found a parent orthogonal-limit
+ // for AvailableISize() in Init(), we'll use the same here as well.
+ cbSize.ISize(wm) = AvailableISize();
+ }
+
+ auto size =
+ mFrame->ComputeSize(mRenderingContext, wm, cbSize, AvailableISize(),
+ ComputedLogicalMargin(wm).Size(wm),
+ ComputedLogicalBorderPadding(wm).Size(wm),
+ mStyleSizeOverrides, mComputeSizeFlags);
+
+ mComputedSize = size.mLogicalSize;
+ NS_ASSERTION(ComputedISize() >= 0, "Bogus inline-size");
+ NS_ASSERTION(
+ ComputedBSize() == NS_UNCONSTRAINEDSIZE || ComputedBSize() >= 0,
+ "Bogus block-size");
+
+ mFlags.mIsBSizeSetByAspectRatio =
+ size.mAspectRatioUsage == nsIFrame::AspectRatioUsage::ToComputeBSize;
+
+ const bool shouldCalculateBlockSideMargins = [&]() {
+ if (!isBlockLevel) {
+ return false;
+ }
+ if (mStyleDisplay->mDisplay == StyleDisplay::InlineTable) {
+ return false;
+ }
+ if (mFrame->IsTableFrame()) {
+ return false;
+ }
+ if (alignCB->IsFlexOrGridContainer()) {
+ // Exclude flex and grid items.
+ return false;
+ }
+ const auto pseudoType = mFrame->Style()->GetPseudoType();
+ if (pseudoType == PseudoStyleType::marker &&
+ mFrame->GetParent()->StyleList()->mListStylePosition ==
+ StyleListStylePosition::Outside) {
+ // Exclude outside ::markers.
+ return false;
+ }
+ if (pseudoType == PseudoStyleType::columnContent) {
+ // Exclude -moz-column-content since it cannot have any margin.
+ return false;
+ }
+ return true;
+ }();
+
+ if (shouldCalculateBlockSideMargins) {
+ CalculateBlockSideMargins();
+ }
+ }
+ }
+
+ // Save our containing block dimensions
+ mContainingBlockSize = cbSize;
+}
+
+static void UpdateProp(nsIFrame* aFrame,
+ const FramePropertyDescriptor<nsMargin>* aProperty,
+ bool aNeeded, const nsMargin& aNewValue) {
+ if (aNeeded) {
+ if (nsMargin* propValue = aFrame->GetProperty(aProperty)) {
+ *propValue = aNewValue;
+ } else {
+ aFrame->AddProperty(aProperty, new nsMargin(aNewValue));
+ }
+ } else {
+ aFrame->RemoveProperty(aProperty);
+ }
+}
+
+void SizeComputationInput::InitOffsets(WritingMode aCBWM, nscoord aPercentBasis,
+ LayoutFrameType aFrameType,
+ ComputeSizeFlags aFlags,
+ const Maybe<LogicalMargin>& aBorder,
+ const Maybe<LogicalMargin>& aPadding,
+ const nsStyleDisplay* aDisplay) {
+ DISPLAY_INIT_OFFSETS(mFrame, this, aPercentBasis, aCBWM, aBorder, aPadding);
+ nsPresContext* presContext = mFrame->PresContext();
+
+ // Compute margins from the specified margin style information. These
+ // become the default computed values, and may be adjusted below
+ // XXX fix to provide 0,0 for the top&bottom margins for
+ // inline-non-replaced elements
+ bool needMarginProp = ComputeMargin(aCBWM, aPercentBasis, aFrameType);
+ // Note that ComputeMargin() simplistically resolves 'auto' margins to 0.
+ // In formatting contexts where this isn't correct, some later code will
+ // need to update the UsedMargin() property with the actual resolved value.
+ // One example of this is ::CalculateBlockSideMargins().
+ ::UpdateProp(mFrame, nsIFrame::UsedMarginProperty(), needMarginProp,
+ ComputedPhysicalMargin());
+
+ const WritingMode wm = GetWritingMode();
+ const nsStyleDisplay* disp = mFrame->StyleDisplayWithOptionalParam(aDisplay);
+ bool needPaddingProp;
+ LayoutDeviceIntMargin widgetPadding;
+ if (mIsThemed && presContext->Theme()->GetWidgetPadding(
+ presContext->DeviceContext(), mFrame,
+ disp->EffectiveAppearance(), &widgetPadding)) {
+ const nsMargin padding = LayoutDevicePixel::ToAppUnits(
+ widgetPadding, presContext->AppUnitsPerDevPixel());
+ SetComputedLogicalPadding(wm, LogicalMargin(wm, padding));
+ needPaddingProp = false;
+ } else if (mFrame->IsInSVGTextSubtree()) {
+ SetComputedLogicalPadding(wm, LogicalMargin(wm));
+ needPaddingProp = false;
+ } else if (aPadding) { // padding is an input arg
+ SetComputedLogicalPadding(wm, *aPadding);
+ nsMargin stylePadding;
+ // If the caller passes a padding that doesn't match our style (like
+ // nsTextControlFrame might due due to theming), then we also need a
+ // padding prop.
+ needPaddingProp = !mFrame->StylePadding()->GetPadding(stylePadding) ||
+ aPadding->GetPhysicalMargin(wm) != stylePadding;
+ } else {
+ needPaddingProp = ComputePadding(aCBWM, aPercentBasis, aFrameType);
+ }
+
+ // Add [align|justify]-content:baseline padding contribution.
+ typedef const FramePropertyDescriptor<SmallValueHolder<nscoord>>* Prop;
+ auto ApplyBaselinePadding = [this, wm, &needPaddingProp](LogicalAxis aAxis,
+ Prop aProp) {
+ bool found;
+ nscoord val = mFrame->GetProperty(aProp, &found);
+ if (found) {
+ NS_ASSERTION(val != nscoord(0), "zero in this property is useless");
+ LogicalSide side;
+ if (val > 0) {
+ side = MakeLogicalSide(aAxis, eLogicalEdgeStart);
+ } else {
+ side = MakeLogicalSide(aAxis, eLogicalEdgeEnd);
+ val = -val;
+ }
+ mComputedPadding.Side(side, wm) += val;
+ needPaddingProp = true;
+ if (aAxis == eLogicalAxisBlock && val > 0) {
+ // We have a baseline-adjusted block-axis start padding, so
+ // we need this to mark lines dirty when mIsBResize is true:
+ this->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+ }
+ };
+ if (!aFlags.contains(ComputeSizeFlag::IsGridMeasuringReflow)) {
+ ApplyBaselinePadding(eLogicalAxisBlock, nsIFrame::BBaselinePadProperty());
+ }
+ if (!aFlags.contains(ComputeSizeFlag::ShrinkWrap)) {
+ ApplyBaselinePadding(eLogicalAxisInline, nsIFrame::IBaselinePadProperty());
+ }
+
+ LogicalMargin border(wm);
+ if (mIsThemed) {
+ const LayoutDeviceIntMargin widgetBorder =
+ presContext->Theme()->GetWidgetBorder(
+ presContext->DeviceContext(), mFrame, disp->EffectiveAppearance());
+ border = LogicalMargin(
+ wm, LayoutDevicePixel::ToAppUnits(widgetBorder,
+ presContext->AppUnitsPerDevPixel()));
+ } else if (mFrame->IsInSVGTextSubtree()) {
+ // Do nothing since the border local variable is initialized all zero.
+ } else if (aBorder) { // border is an input arg
+ border = *aBorder;
+ } else {
+ border = LogicalMargin(wm, mFrame->StyleBorder()->GetComputedBorder());
+ }
+ SetComputedLogicalBorderPadding(wm, border + ComputedLogicalPadding(wm));
+
+ if (aFrameType == LayoutFrameType::Scrollbar) {
+ // scrollbars may have had their width or height smashed to zero
+ // by the associated scrollframe, in which case we must not report
+ // any padding or border.
+ nsSize size(mFrame->GetSize());
+ if (size.width == 0 || size.height == 0) {
+ SetComputedLogicalPadding(wm, LogicalMargin(wm));
+ SetComputedLogicalBorderPadding(wm, LogicalMargin(wm));
+ }
+ }
+
+ bool hasPaddingChange;
+ if (nsMargin* oldPadding =
+ mFrame->GetProperty(nsIFrame::UsedPaddingProperty())) {
+ // Note: If a padding change is already detectable without resolving the
+ // percentage, e.g. a padding is changing from 50px to 50%,
+ // nsIFrame::DidSetComputedStyle() will cache the old padding in
+ // UsedPaddingProperty().
+ hasPaddingChange = *oldPadding != ComputedPhysicalPadding();
+ } else {
+ // Our padding may have changed, but we can't tell at this point.
+ hasPaddingChange = needPaddingProp;
+ }
+ // Keep mHasPaddingChange bit set until we've done reflow. We'll clear it in
+ // nsIFrame::DidReflow()
+ mFrame->SetHasPaddingChange(mFrame->HasPaddingChange() || hasPaddingChange);
+
+ ::UpdateProp(mFrame, nsIFrame::UsedPaddingProperty(), needPaddingProp,
+ ComputedPhysicalPadding());
+}
+
+// This code enforces section 10.3.3 of the CSS2 spec for this formula:
+//
+// 'margin-left' + 'border-left-width' + 'padding-left' + 'width' +
+// 'padding-right' + 'border-right-width' + 'margin-right'
+// = width of containing block
+//
+// Note: the width unit is not auto when this is called
+void ReflowInput::CalculateBlockSideMargins() {
+ MOZ_ASSERT(!mFrame->IsTableFrame(),
+ "Inner table frame cannot have computed margins!");
+
+ // Calculations here are done in the containing block's writing mode,
+ // which is where margins will eventually be applied: we're calculating
+ // margins that will be used by the container in its inline direction,
+ // which in the case of an orthogonal contained block will correspond to
+ // the block direction of this reflow input. So in the orthogonal-flow
+ // case, "CalculateBlock*Side*Margins" will actually end up adjusting
+ // the BStart/BEnd margins; those are the "sides" of the block from its
+ // container's point of view.
+ WritingMode cbWM =
+ mCBReflowInput ? mCBReflowInput->GetWritingMode() : GetWritingMode();
+
+ nscoord availISizeCBWM = AvailableSize(cbWM).ISize(cbWM);
+ nscoord computedISizeCBWM = ComputedSize(cbWM).ISize(cbWM);
+ if (computedISizeCBWM == NS_UNCONSTRAINEDSIZE) {
+ // For orthogonal flows, where we found a parent orthogonal-limit
+ // for AvailableISize() in Init(), we don't have meaningful sizes to
+ // adjust. Act like the sum is already correct (below).
+ return;
+ }
+
+ LAYOUT_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != computedISizeCBWM &&
+ NS_UNCONSTRAINEDSIZE != availISizeCBWM,
+ "have unconstrained inline-size; this should only "
+ "result from very large sizes, not attempts at "
+ "intrinsic inline-size calculation");
+
+ LogicalMargin margin = ComputedLogicalMargin(cbWM);
+ LogicalMargin borderPadding = ComputedLogicalBorderPadding(cbWM);
+ nscoord sum = margin.IStartEnd(cbWM) + borderPadding.IStartEnd(cbWM) +
+ computedISizeCBWM;
+ if (sum == availISizeCBWM) {
+ // The sum is already correct
+ return;
+ }
+
+ // Determine the start and end margin values. The isize value
+ // remains constant while we do this.
+
+ // Calculate how much space is available for margins
+ nscoord availMarginSpace = availISizeCBWM - sum;
+
+ // If the available margin space is negative, then don't follow the
+ // usual overconstraint rules.
+ if (availMarginSpace < 0) {
+ margin.IEnd(cbWM) += availMarginSpace;
+ SetComputedLogicalMargin(cbWM, margin);
+ return;
+ }
+
+ // The css2 spec clearly defines how block elements should behave
+ // in section 10.3.3.
+ const auto& styleSides = mStyleMargin->mMargin;
+ bool isAutoStartMargin = styleSides.GetIStart(cbWM).IsAuto();
+ bool isAutoEndMargin = styleSides.GetIEnd(cbWM).IsAuto();
+ if (!isAutoStartMargin && !isAutoEndMargin) {
+ // Neither margin is 'auto' so we're over constrained. Use the
+ // 'direction' property of the parent to tell which margin to
+ // ignore
+ // First check if there is an HTML alignment that we should honor
+ const StyleTextAlign* textAlign =
+ mParentReflowInput
+ ? &mParentReflowInput->mFrame->StyleText()->mTextAlign
+ : nullptr;
+ if (textAlign && (*textAlign == StyleTextAlign::MozLeft ||
+ *textAlign == StyleTextAlign::MozCenter ||
+ *textAlign == StyleTextAlign::MozRight)) {
+ if (mParentReflowInput->mWritingMode.IsBidiLTR()) {
+ isAutoStartMargin = *textAlign != StyleTextAlign::MozLeft;
+ isAutoEndMargin = *textAlign != StyleTextAlign::MozRight;
+ } else {
+ isAutoStartMargin = *textAlign != StyleTextAlign::MozRight;
+ isAutoEndMargin = *textAlign != StyleTextAlign::MozLeft;
+ }
+ }
+ // Otherwise apply the CSS rules, and ignore one margin by forcing
+ // it to 'auto', depending on 'direction'.
+ else {
+ isAutoEndMargin = true;
+ }
+ }
+
+ // Logic which is common to blocks and tables
+ // The computed margins need not be zero because the 'auto' could come from
+ // overconstraint or from HTML alignment so values need to be accumulated
+
+ if (isAutoStartMargin) {
+ if (isAutoEndMargin) {
+ // Both margins are 'auto' so the computed addition should be equal
+ nscoord forStart = availMarginSpace / 2;
+ margin.IStart(cbWM) += forStart;
+ margin.IEnd(cbWM) += availMarginSpace - forStart;
+ } else {
+ margin.IStart(cbWM) += availMarginSpace;
+ }
+ } else if (isAutoEndMargin) {
+ margin.IEnd(cbWM) += availMarginSpace;
+ }
+ SetComputedLogicalMargin(cbWM, margin);
+
+ if (isAutoStartMargin || isAutoEndMargin) {
+ // Update the UsedMargin property if we were tracking it already.
+ nsMargin* propValue = mFrame->GetProperty(nsIFrame::UsedMarginProperty());
+ if (propValue) {
+ *propValue = margin.GetPhysicalMargin(cbWM);
+ }
+ }
+}
+
+// For "normal" we use the font's normal line height (em height + leading).
+// If both internal leading and external leading specified by font itself are
+// zeros, we should compensate this by creating extra (external) leading.
+// This is necessary because without this compensation, normal line height might
+// look too tight.
+constexpr float kNormalLineHeightFactor = 1.2f;
+static nscoord GetNormalLineHeight(nsFontMetrics* aFontMetrics) {
+ MOZ_ASSERT(aFontMetrics, "no font metrics");
+ nscoord externalLeading = aFontMetrics->ExternalLeading();
+ nscoord internalLeading = aFontMetrics->InternalLeading();
+ nscoord emHeight = aFontMetrics->EmHeight();
+ if (!internalLeading && !externalLeading) {
+ return NSToCoordRound(emHeight * kNormalLineHeightFactor);
+ }
+ return emHeight + internalLeading + externalLeading;
+}
+
+static inline nscoord ComputeLineHeight(const StyleLineHeight& aLh,
+ const nsStyleFont& aRelativeToFont,
+ nsPresContext* aPresContext,
+ bool aIsVertical, nscoord aBlockBSize,
+ float aFontSizeInflation) {
+ if (aLh.IsLength()) {
+ nscoord result = aLh.AsLength().ToAppUnits();
+ if (aFontSizeInflation != 1.0f) {
+ result = NSToCoordRound(result * aFontSizeInflation);
+ }
+ return result;
+ }
+
+ if (aLh.IsNumber()) {
+ // For factor units the computed value of the line-height property
+ // is found by multiplying the factor by the font's computed size
+ // (adjusted for min-size prefs and text zoom).
+ return aRelativeToFont.mFont.size
+ .ScaledBy(aLh.AsNumber() * aFontSizeInflation)
+ .ToAppUnits();
+ }
+
+ MOZ_ASSERT(aLh.IsNormal() || aLh.IsMozBlockHeight());
+ if (aLh.IsMozBlockHeight() && aBlockBSize != NS_UNCONSTRAINEDSIZE) {
+ return aBlockBSize;
+ }
+
+ auto size = aRelativeToFont.mFont.size;
+ size.ScaleBy(aFontSizeInflation);
+
+ if (aPresContext) {
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetMetricsFor(
+ aPresContext, aIsVertical, &aRelativeToFont, size,
+ /* aUseUserFontSet = */ true);
+ return GetNormalLineHeight(fm);
+ }
+ // If we don't have a pres context, use a 1.2em fallback.
+ size.ScaleBy(kNormalLineHeightFactor);
+ return size.ToAppUnits();
+}
+
+nscoord ReflowInput::GetLineHeight() const {
+ if (mLineHeight != NS_UNCONSTRAINEDSIZE) {
+ return mLineHeight;
+ }
+
+ nscoord blockBSize = nsLayoutUtils::IsNonWrapperBlock(mFrame)
+ ? ComputedBSize()
+ : (mCBReflowInput ? mCBReflowInput->ComputedBSize()
+ : NS_UNCONSTRAINEDSIZE);
+ mLineHeight = CalcLineHeight(*mFrame->Style(), mFrame->PresContext(),
+ mFrame->GetContent(), blockBSize,
+ nsLayoutUtils::FontSizeInflationFor(mFrame));
+ return mLineHeight;
+}
+
+void ReflowInput::SetLineHeight(nscoord aLineHeight) {
+ MOZ_ASSERT(aLineHeight >= 0, "aLineHeight must be >= 0!");
+
+ if (mLineHeight != aLineHeight) {
+ mLineHeight = aLineHeight;
+ // Setting used line height can change a frame's block-size if mFrame's
+ // block-size behaves as auto.
+ InitResizeFlags(mFrame->PresContext(), mFrame->Type());
+ }
+}
+
+/* static */
+nscoord ReflowInput::CalcLineHeight(const ComputedStyle& aStyle,
+ nsPresContext* aPresContext,
+ const nsIContent* aContent,
+ nscoord aBlockBSize,
+ float aFontSizeInflation) {
+ const StyleLineHeight& lh = aStyle.StyleFont()->mLineHeight;
+ WritingMode wm(&aStyle);
+ const bool vertical = wm.IsVertical() && !wm.IsSideways();
+ return CalcLineHeight(lh, *aStyle.StyleFont(), aPresContext, vertical,
+ aContent, aBlockBSize, aFontSizeInflation);
+}
+
+nscoord ReflowInput::CalcLineHeight(
+ const StyleLineHeight& aLh, const nsStyleFont& aRelativeToFont,
+ nsPresContext* aPresContext, bool aIsVertical, const nsIContent* aContent,
+ nscoord aBlockBSize, float aFontSizeInflation) {
+ nscoord lineHeight =
+ ComputeLineHeight(aLh, aRelativeToFont, aPresContext, aIsVertical,
+ aBlockBSize, aFontSizeInflation);
+
+ NS_ASSERTION(lineHeight >= 0, "ComputeLineHeight screwed up");
+
+ const auto* input = HTMLInputElement::FromNodeOrNull(aContent);
+ if (input && input->IsSingleLineTextControl()) {
+ // For Web-compatibility, single-line text input elements cannot
+ // have a line-height smaller than 'normal'.
+ if (!aLh.IsNormal()) {
+ nscoord normal = ComputeLineHeight(
+ StyleLineHeight::Normal(), aRelativeToFont, aPresContext, aIsVertical,
+ aBlockBSize, aFontSizeInflation);
+ if (lineHeight < normal) {
+ lineHeight = normal;
+ }
+ }
+ }
+
+ return lineHeight;
+}
+
+bool SizeComputationInput::ComputeMargin(WritingMode aCBWM,
+ nscoord aPercentBasis,
+ LayoutFrameType aFrameType) {
+ // SVG text frames have no margin.
+ if (mFrame->IsInSVGTextSubtree()) {
+ return false;
+ }
+
+ if (aFrameType == LayoutFrameType::Table) {
+ // Table frame's margin is inherited to the table wrapper frame via the
+ // ::-moz-table-wrapper rule in ua.css, so don't set any margins for it.
+ SetComputedLogicalMargin(mWritingMode, LogicalMargin(mWritingMode));
+ return false;
+ }
+
+ // If style style can provide us the margin directly, then use it.
+ const nsStyleMargin* styleMargin = mFrame->StyleMargin();
+
+ nsMargin margin;
+ const bool isCBDependent = !styleMargin->GetMargin(margin);
+ if (isCBDependent) {
+ // We have to compute the value. Note that this calculation is
+ // performed according to the writing mode of the containing block
+ // (http://dev.w3.org/csswg/css-writing-modes-3/#orthogonal-flows)
+ if (aPercentBasis == NS_UNCONSTRAINEDSIZE) {
+ aPercentBasis = 0;
+ }
+ LogicalMargin m(aCBWM);
+ m.IStart(aCBWM) = nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, styleMargin->mMargin.GetIStart(aCBWM));
+ m.IEnd(aCBWM) = nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, styleMargin->mMargin.GetIEnd(aCBWM));
+
+ m.BStart(aCBWM) = nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, styleMargin->mMargin.GetBStart(aCBWM));
+ m.BEnd(aCBWM) = nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, styleMargin->mMargin.GetBEnd(aCBWM));
+
+ SetComputedLogicalMargin(aCBWM, m);
+ } else {
+ SetComputedLogicalMargin(mWritingMode, LogicalMargin(mWritingMode, margin));
+ }
+
+ // ... but font-size-inflation-based margin adjustment uses the
+ // frame's writing mode
+ nscoord marginAdjustment = FontSizeInflationListMarginAdjustment(mFrame);
+
+ if (marginAdjustment > 0) {
+ LogicalMargin m = ComputedLogicalMargin(mWritingMode);
+ m.IStart(mWritingMode) += marginAdjustment;
+ SetComputedLogicalMargin(mWritingMode, m);
+ }
+
+ return isCBDependent;
+}
+
+bool SizeComputationInput::ComputePadding(WritingMode aCBWM,
+ nscoord aPercentBasis,
+ LayoutFrameType aFrameType) {
+ // If style can provide us the padding directly, then use it.
+ const nsStylePadding* stylePadding = mFrame->StylePadding();
+ nsMargin padding;
+ bool isCBDependent = !stylePadding->GetPadding(padding);
+ // a table row/col group, row/col doesn't have padding
+ // XXXldb Neither do border-collapse tables.
+ if (LayoutFrameType::TableRowGroup == aFrameType ||
+ LayoutFrameType::TableColGroup == aFrameType ||
+ LayoutFrameType::TableRow == aFrameType ||
+ LayoutFrameType::TableCol == aFrameType) {
+ SetComputedLogicalPadding(mWritingMode, LogicalMargin(mWritingMode));
+ } else if (isCBDependent) {
+ // We have to compute the value. This calculation is performed
+ // according to the writing mode of the containing block
+ // (http://dev.w3.org/csswg/css-writing-modes-3/#orthogonal-flows)
+ // clamp negative calc() results to 0
+ if (aPercentBasis == NS_UNCONSTRAINEDSIZE) {
+ aPercentBasis = 0;
+ }
+ LogicalMargin p(aCBWM);
+ p.IStart(aCBWM) = std::max(
+ 0, nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, stylePadding->mPadding.GetIStart(aCBWM)));
+ p.IEnd(aCBWM) =
+ std::max(0, nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, stylePadding->mPadding.GetIEnd(aCBWM)));
+
+ p.BStart(aCBWM) = std::max(
+ 0, nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, stylePadding->mPadding.GetBStart(aCBWM)));
+ p.BEnd(aCBWM) =
+ std::max(0, nsLayoutUtils::ComputeCBDependentValue(
+ aPercentBasis, stylePadding->mPadding.GetBEnd(aCBWM)));
+
+ SetComputedLogicalPadding(aCBWM, p);
+ } else {
+ SetComputedLogicalPadding(mWritingMode,
+ LogicalMargin(mWritingMode, padding));
+ }
+ return isCBDependent;
+}
+
+void ReflowInput::ComputeMinMaxValues(const LogicalSize& aCBSize) {
+ WritingMode wm = GetWritingMode();
+
+ const auto& minISize = mStylePosition->MinISize(wm);
+ const auto& maxISize = mStylePosition->MaxISize(wm);
+ const auto& minBSize = mStylePosition->MinBSize(wm);
+ const auto& maxBSize = mStylePosition->MaxBSize(wm);
+
+ LogicalSize minWidgetSize(wm);
+ if (mIsThemed) {
+ nsPresContext* pc = mFrame->PresContext();
+ const LayoutDeviceIntSize widget = pc->Theme()->GetMinimumWidgetSize(
+ pc, mFrame, mStyleDisplay->EffectiveAppearance());
+
+ // Convert themed widget's physical dimensions to logical coords.
+ minWidgetSize = {
+ wm, LayoutDeviceIntSize::ToAppUnits(widget, pc->AppUnitsPerDevPixel())};
+
+ // GetMinimumWidgetSize() returns border-box; we need content-box.
+ minWidgetSize -= ComputedLogicalBorderPadding(wm).Size(wm);
+ }
+
+ // NOTE: min-width:auto resolves to 0, except on a flex item. (But
+ // even there, it's supposed to be ignored (i.e. treated as 0) until
+ // the flex container explicitly resolves & considers it.)
+ if (minISize.IsAuto()) {
+ SetComputedMinISize(0);
+ } else {
+ SetComputedMinISize(
+ ComputeISizeValue(aCBSize, mStylePosition->mBoxSizing, minISize));
+ }
+
+ if (mIsThemed) {
+ SetComputedMinISize(std::max(ComputedMinISize(), minWidgetSize.ISize(wm)));
+ }
+
+ if (maxISize.IsNone()) {
+ // Specified value of 'none'
+ SetComputedMaxISize(NS_UNCONSTRAINEDSIZE);
+ } else {
+ SetComputedMaxISize(
+ ComputeISizeValue(aCBSize, mStylePosition->mBoxSizing, maxISize));
+ }
+
+ // If the computed value of 'min-width' is greater than the value of
+ // 'max-width', 'max-width' is set to the value of 'min-width'
+ if (ComputedMinISize() > ComputedMaxISize()) {
+ SetComputedMaxISize(ComputedMinISize());
+ }
+
+ // Check for percentage based values and a containing block height that
+ // depends on the content height. Treat them like the initial value.
+ // Likewise, check for calc() with percentages on internal table elements;
+ // that's treated as the initial value too.
+ const bool isInternalTableFrame = IsInternalTableFrame();
+ const nscoord& bPercentageBasis = aCBSize.BSize(wm);
+ auto BSizeBehavesAsInitialValue = [&](const auto& aBSize) {
+ if (nsLayoutUtils::IsAutoBSize(aBSize, bPercentageBasis)) {
+ return true;
+ }
+ if (isInternalTableFrame) {
+ return aBSize.HasLengthAndPercentage();
+ }
+ return false;
+ };
+
+ // NOTE: min-height:auto resolves to 0, except on a flex item. (But
+ // even there, it's supposed to be ignored (i.e. treated as 0) until
+ // the flex container explicitly resolves & considers it.)
+ if (BSizeBehavesAsInitialValue(minBSize)) {
+ SetComputedMinBSize(0);
+ } else {
+ SetComputedMinBSize(ComputeBSizeValue(bPercentageBasis,
+ mStylePosition->mBoxSizing,
+ minBSize.AsLengthPercentage()));
+ }
+
+ if (mIsThemed) {
+ SetComputedMinBSize(std::max(ComputedMinBSize(), minWidgetSize.BSize(wm)));
+ }
+
+ if (BSizeBehavesAsInitialValue(maxBSize)) {
+ // Specified value of 'none'
+ SetComputedMaxBSize(NS_UNCONSTRAINEDSIZE);
+ } else {
+ SetComputedMaxBSize(ComputeBSizeValue(bPercentageBasis,
+ mStylePosition->mBoxSizing,
+ maxBSize.AsLengthPercentage()));
+ }
+
+ // If the computed value of 'min-height' is greater than the value of
+ // 'max-height', 'max-height' is set to the value of 'min-height'
+ if (ComputedMinBSize() > ComputedMaxBSize()) {
+ SetComputedMaxBSize(ComputedMinBSize());
+ }
+}
+
+bool ReflowInput::IsInternalTableFrame() const {
+ return mFrame->IsTableRowGroupFrame() || mFrame->IsTableColGroupFrame() ||
+ mFrame->IsTableRowFrame() || mFrame->IsTableCellFrame();
+}
diff --git a/layout/generic/ReflowInput.h b/layout/generic/ReflowInput.h
new file mode 100644
index 0000000000..620ad0f413
--- /dev/null
+++ b/layout/generic/ReflowInput.h
@@ -0,0 +1,1045 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* struct containing the input to nsIFrame::Reflow */
+
+#ifndef mozilla_ReflowInput_h
+#define mozilla_ReflowInput_h
+
+#include "nsMargin.h"
+#include "nsStyleConsts.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/WritingModes.h"
+#include "LayoutConstants.h"
+#include "ReflowOutput.h"
+#include <algorithm>
+
+class gfxContext;
+class nsFloatManager;
+struct nsHypotheticalPosition;
+class nsIPercentBSizeObserver;
+class nsLineLayout;
+class nsPlaceholderFrame;
+class nsPresContext;
+class nsReflowStatus;
+
+namespace mozilla {
+enum class LayoutFrameType : uint8_t;
+
+/**
+ * A set of StyleSizes used as an input parameter to various functions that
+ * compute sizes like nsIFrame::ComputeSize(). If any of the member fields has a
+ * value, the function may use the value instead of retrieving it from the
+ * frame's style.
+ *
+ * The logical sizes are assumed to be in the associated frame's writing-mode.
+ */
+struct StyleSizeOverrides {
+ Maybe<StyleSize> mStyleISize;
+ Maybe<StyleSize> mStyleBSize;
+ Maybe<AspectRatio> mAspectRatio;
+
+ bool HasAnyOverrides() const { return mStyleISize || mStyleBSize; }
+ bool HasAnyLengthOverrides() const {
+ return (mStyleISize && mStyleISize->ConvertsToLength()) ||
+ (mStyleBSize && mStyleBSize->ConvertsToLength());
+ }
+
+ // By default, table wrapper frame considers the size overrides applied to
+ // itself, so it creates any length size overrides for inner table frame by
+ // subtracting the area occupied by the caption and border & padding according
+ // to box-sizing.
+ //
+ // When this flag is true, table wrapper frame is required to apply the size
+ // overrides to the inner table frame directly, without any modification,
+ // which is useful for flex container to override the inner table frame's
+ // preferred main size with 'flex-basis'.
+ //
+ // Note: if mStyleISize is a LengthPercentage, the inner table frame will
+ // comply with the inline-size override without enforcing its min-content
+ // inline-size in nsTableFrame::ComputeSize(). This is necessary so that small
+ // flex-basis values like 'flex-basis:1%' can be resolved correctly; the
+ // flexbox layout algorithm does still explicitly clamp to min-sizes *at a
+ // later step*, after the flex-basis has been resolved -- so this flag won't
+ // actually produce any user-visible tables whose final inline size is smaller
+ // than their min-content inline size.
+ bool mApplyOverridesVerbatim = false;
+};
+} // namespace mozilla
+
+/**
+ * @return aValue clamped to [aMinValue, aMaxValue].
+ *
+ * @note This function needs to handle aMinValue > aMaxValue. In that case,
+ * aMinValue is returned. That's why we cannot use std::clamp() and
+ * mozilla::clamped() since they both assert max >= min.
+ * @note If aMinValue and aMaxValue are computed min block-size and max
+ * block-size, it is simpler to use ReflowInput::ApplyMinMaxBSize().
+ * Similarly, there is ReflowInput::ApplyMinMaxISize() for clamping an
+ * inline-size.
+ * @see http://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ * @see http://www.w3.org/TR/CSS21/visudet.html#min-max-heights
+ */
+template <class NumericType>
+NumericType NS_CSS_MINMAX(NumericType aValue, NumericType aMinValue,
+ NumericType aMaxValue) {
+ NumericType result = aValue;
+ if (aMaxValue < result) result = aMaxValue;
+ if (aMinValue > result) result = aMinValue;
+ return result;
+}
+
+namespace mozilla {
+
+// A base class of ReflowInput that computes only the padding,
+// border, and margin, since those values are needed more often.
+struct SizeComputationInput {
+ public:
+ // The frame being reflowed.
+ nsIFrame* const mFrame;
+
+ // Rendering context to use for measurement.
+ gfxContext* mRenderingContext;
+
+ nsMargin ComputedPhysicalMargin() const {
+ return mComputedMargin.GetPhysicalMargin(mWritingMode);
+ }
+ nsMargin ComputedPhysicalBorderPadding() const {
+ return mComputedBorderPadding.GetPhysicalMargin(mWritingMode);
+ }
+ nsMargin ComputedPhysicalBorder() const {
+ return ComputedLogicalBorder(mWritingMode).GetPhysicalMargin(mWritingMode);
+ }
+ nsMargin ComputedPhysicalPadding() const {
+ return mComputedPadding.GetPhysicalMargin(mWritingMode);
+ }
+
+ mozilla::LogicalMargin ComputedLogicalMargin(mozilla::WritingMode aWM) const {
+ return mComputedMargin.ConvertTo(aWM, mWritingMode);
+ }
+ mozilla::LogicalMargin ComputedLogicalBorderPadding(
+ mozilla::WritingMode aWM) const {
+ return mComputedBorderPadding.ConvertTo(aWM, mWritingMode);
+ }
+ mozilla::LogicalMargin ComputedLogicalPadding(
+ mozilla::WritingMode aWM) const {
+ return mComputedPadding.ConvertTo(aWM, mWritingMode);
+ }
+ mozilla::LogicalMargin ComputedLogicalBorder(mozilla::WritingMode aWM) const {
+ return (mComputedBorderPadding - mComputedPadding)
+ .ConvertTo(aWM, mWritingMode);
+ }
+
+ void SetComputedLogicalMargin(mozilla::WritingMode aWM,
+ const mozilla::LogicalMargin& aMargin) {
+ mComputedMargin = aMargin.ConvertTo(mWritingMode, aWM);
+ }
+ void SetComputedLogicalBorderPadding(
+ mozilla::WritingMode aWM, const mozilla::LogicalMargin& aBorderPadding) {
+ mComputedBorderPadding = aBorderPadding.ConvertTo(mWritingMode, aWM);
+ }
+ void SetComputedLogicalPadding(mozilla::WritingMode aWM,
+ const mozilla::LogicalMargin& aPadding) {
+ mComputedPadding = aPadding.ConvertTo(mWritingMode, aWM);
+ }
+
+ mozilla::WritingMode GetWritingMode() const { return mWritingMode; }
+
+ protected:
+ // cached copy of the frame's writing-mode, for logical coordinates
+ const mozilla::WritingMode mWritingMode;
+
+ // Cached mFrame->IsThemed().
+ const bool mIsThemed = false;
+
+ // Computed margin values
+ mozilla::LogicalMargin mComputedMargin;
+
+ // Cached copy of the border + padding values
+ mozilla::LogicalMargin mComputedBorderPadding;
+
+ // Computed padding values
+ mozilla::LogicalMargin mComputedPadding;
+
+ public:
+ // Callers using this constructor must call InitOffsets on their own.
+ SizeComputationInput(nsIFrame* aFrame, gfxContext* aRenderingContext);
+
+ SizeComputationInput(nsIFrame* aFrame, gfxContext* aRenderingContext,
+ mozilla::WritingMode aContainingBlockWritingMode,
+ nscoord aContainingBlockISize,
+ const mozilla::Maybe<mozilla::LogicalMargin>& aBorder =
+ mozilla::Nothing(),
+ const mozilla::Maybe<mozilla::LogicalMargin>& aPadding =
+ mozilla::Nothing());
+
+#ifdef DEBUG
+ // Reflow trace methods. Defined in nsFrame.cpp so they have access
+ // to the display-reflow infrastructure.
+ static void* DisplayInitOffsetsEnter(nsIFrame* aFrame,
+ SizeComputationInput* aState,
+ nscoord aPercentBasis,
+ mozilla::WritingMode aCBWritingMode,
+ const nsMargin* aBorder,
+ const nsMargin* aPadding);
+ static void DisplayInitOffsetsExit(nsIFrame* aFrame,
+ SizeComputationInput* aState,
+ void* aValue);
+#endif
+
+ private:
+ /**
+ * Computes margin values from the specified margin style information, and
+ * fills in the mComputedMargin member.
+ *
+ * @param aCBWM Writing mode of the containing block
+ * @param aPercentBasis
+ * Inline size of the containing block (in its own writing mode), to use
+ * for resolving percentage margin values in the inline and block axes.
+ * @return true if the margin is dependent on the containing block size.
+ */
+ bool ComputeMargin(mozilla::WritingMode aCBWM, nscoord aPercentBasis,
+ mozilla::LayoutFrameType aFrameType);
+
+ /**
+ * Computes padding values from the specified padding style information, and
+ * fills in the mComputedPadding member.
+ *
+ * @param aCBWM Writing mode of the containing block
+ * @param aPercentBasis
+ * Inline size of the containing block (in its own writing mode), to use
+ * for resolving percentage padding values in the inline and block axes.
+ * @return true if the padding is dependent on the containing block size.
+ */
+ bool ComputePadding(mozilla::WritingMode aCBWM, nscoord aPercentBasis,
+ mozilla::LayoutFrameType aFrameType);
+
+ protected:
+ void InitOffsets(mozilla::WritingMode aCBWM, nscoord aPercentBasis,
+ mozilla::LayoutFrameType aFrameType,
+ mozilla::ComputeSizeFlags aFlags,
+ const mozilla::Maybe<mozilla::LogicalMargin>& aBorder,
+ const mozilla::Maybe<mozilla::LogicalMargin>& aPadding,
+ const nsStyleDisplay* aDisplay = nullptr);
+
+ /*
+ * Convert StyleSize or StyleMaxSize to nscoord when percentages depend on the
+ * inline size of the containing block, and enumerated values are for inline
+ * size, min-inline-size, or max-inline-size. Does not handle auto inline
+ * sizes.
+ */
+ template <typename SizeOrMaxSize>
+ inline nscoord ComputeISizeValue(const WritingMode aWM,
+ const LogicalSize& aContainingBlockSize,
+ const LogicalSize& aContentEdgeToBoxSizing,
+ nscoord aBoxSizingToMarginEdge,
+ const SizeOrMaxSize&) const;
+ // same as previous, but using mComputedBorderPadding, mComputedPadding,
+ // and mComputedMargin
+ template <typename SizeOrMaxSize>
+ inline nscoord ComputeISizeValue(const LogicalSize& aContainingBlockSize,
+ mozilla::StyleBoxSizing aBoxSizing,
+ const SizeOrMaxSize&) const;
+
+ nscoord ComputeBSizeValue(nscoord aContainingBlockBSize,
+ mozilla::StyleBoxSizing aBoxSizing,
+ const mozilla::LengthPercentage& aCoord) const;
+};
+
+/**
+ * State passed to a frame during reflow or intrinsic size calculation.
+ *
+ * XXX Refactor so only a base class (nsSizingState?) is used for intrinsic
+ * size calculation.
+ *
+ * @see nsIFrame#Reflow()
+ */
+struct ReflowInput : public SizeComputationInput {
+ // the reflow inputs are linked together. this is the pointer to the
+ // parent's reflow input
+ const ReflowInput* mParentReflowInput = nullptr;
+
+ // A non-owning pointer to the float manager associated with this area,
+ // which points to the object owned by nsAutoFloatManager::mNew.
+ nsFloatManager* mFloatManager = nullptr;
+
+ // LineLayout object (only for inline reflow; set to nullptr otherwise)
+ nsLineLayout* mLineLayout = nullptr;
+
+ // The appropriate reflow input for the containing block (for
+ // percentage widths, etc.) of this reflow input's frame. It will be setup
+ // properly in InitCBReflowInput().
+ const ReflowInput* mCBReflowInput = nullptr;
+
+ // The amount the in-flow position of the block is moving vertically relative
+ // to its previous in-flow position (i.e. the amount the line containing the
+ // block is moving).
+ // This should be zero for anything which is not a block outside, and it
+ // should be zero for anything which has a non-block parent.
+ // The intended use of this value is to allow the accurate determination
+ // of the potential impact of a float
+ // This takes on an arbitrary value the first time a block is reflowed
+ nscoord mBlockDelta = 0;
+
+ // If a ReflowInput finds itself initialized with an unconstrained
+ // inline-size, it will look up its parentReflowInput chain for a reflow input
+ // with an orthogonal writing mode and a non-NS_UNCONSTRAINEDSIZE value for
+ // orthogonal limit; when it finds such a reflow input, it will use its
+ // orthogonal-limit value to constrain inline-size.
+ // This is initialized to NS_UNCONSTRAINEDSIZE (so it will be ignored),
+ // but reset to a suitable value for the reflow root by PresShell.
+ nscoord mOrthogonalLimit = NS_UNCONSTRAINEDSIZE;
+
+ // Physical accessors for the private fields. They are needed for
+ // compatibility with not-yet-updated code. New code should use the accessors
+ // for logical coordinates, unless the code really works on physical
+ // coordinates.
+ nscoord AvailableWidth() const { return mAvailableSize.Width(mWritingMode); }
+ nscoord AvailableHeight() const {
+ return mAvailableSize.Height(mWritingMode);
+ }
+ nscoord ComputedWidth() const { return mComputedSize.Width(mWritingMode); }
+ nscoord ComputedHeight() const { return mComputedSize.Height(mWritingMode); }
+ nscoord ComputedMinWidth() const {
+ return mComputedMinSize.Width(mWritingMode);
+ }
+ nscoord ComputedMaxWidth() const {
+ return mComputedMaxSize.Width(mWritingMode);
+ }
+ nscoord ComputedMinHeight() const {
+ return mComputedMinSize.Height(mWritingMode);
+ }
+ nscoord ComputedMaxHeight() const {
+ return mComputedMaxSize.Height(mWritingMode);
+ }
+
+ // Logical accessors for private fields in mWritingMode.
+ nscoord AvailableISize() const { return mAvailableSize.ISize(mWritingMode); }
+ nscoord AvailableBSize() const { return mAvailableSize.BSize(mWritingMode); }
+ nscoord ComputedISize() const { return mComputedSize.ISize(mWritingMode); }
+ nscoord ComputedBSize() const { return mComputedSize.BSize(mWritingMode); }
+ nscoord ComputedMinISize() const {
+ return mComputedMinSize.ISize(mWritingMode);
+ }
+ nscoord ComputedMaxISize() const {
+ return mComputedMaxSize.ISize(mWritingMode);
+ }
+ nscoord ComputedMinBSize() const {
+ return mComputedMinSize.BSize(mWritingMode);
+ }
+ nscoord ComputedMaxBSize() const {
+ return mComputedMaxSize.BSize(mWritingMode);
+ }
+
+ // WARNING: In general, adjusting available inline-size or block-size is not
+ // safe because ReflowInput has members whose values depend on the available
+ // size passing through the constructor. For example,
+ // CalculateBlockSideMargins() is called during initialization, and uses
+ // AvailableSize(). Make sure your use case doesn't lead to stale member
+ // values in ReflowInput!
+ void SetAvailableISize(nscoord aAvailableISize) {
+ mAvailableSize.ISize(mWritingMode) = aAvailableISize;
+ }
+ void SetAvailableBSize(nscoord aAvailableBSize) {
+ mAvailableSize.BSize(mWritingMode) = aAvailableBSize;
+ }
+
+ void SetComputedMinISize(nscoord aMinISize) {
+ mComputedMinSize.ISize(mWritingMode) = aMinISize;
+ }
+ void SetComputedMaxISize(nscoord aMaxISize) {
+ mComputedMaxSize.ISize(mWritingMode) = aMaxISize;
+ }
+ void SetComputedMinBSize(nscoord aMinBSize) {
+ mComputedMinSize.BSize(mWritingMode) = aMinBSize;
+ }
+ void SetComputedMaxBSize(nscoord aMaxBSize) {
+ mComputedMaxSize.BSize(mWritingMode) = aMaxBSize;
+ }
+ void SetPercentageBasisInBlockAxis(nscoord aBSize) {
+ mPercentageBasisInBlockAxis = Some(aBSize);
+ }
+
+ mozilla::LogicalSize AvailableSize() const { return mAvailableSize; }
+ mozilla::LogicalSize ComputedSize() const { return mComputedSize; }
+ mozilla::LogicalSize ComputedMinSize() const { return mComputedMinSize; }
+ mozilla::LogicalSize ComputedMaxSize() const { return mComputedMaxSize; }
+
+ mozilla::LogicalSize AvailableSize(mozilla::WritingMode aWM) const {
+ return AvailableSize().ConvertTo(aWM, mWritingMode);
+ }
+ mozilla::LogicalSize ComputedSize(mozilla::WritingMode aWM) const {
+ return ComputedSize().ConvertTo(aWM, mWritingMode);
+ }
+ mozilla::LogicalSize ComputedMinSize(mozilla::WritingMode aWM) const {
+ return ComputedMinSize().ConvertTo(aWM, mWritingMode);
+ }
+ mozilla::LogicalSize ComputedMaxSize(mozilla::WritingMode aWM) const {
+ return ComputedMaxSize().ConvertTo(aWM, mWritingMode);
+ }
+
+ mozilla::LogicalSize ComputedSizeWithPadding(mozilla::WritingMode aWM) const {
+ return ComputedSize(aWM) + ComputedLogicalPadding(aWM).Size(aWM);
+ }
+
+ mozilla::LogicalSize ComputedSizeWithBorderPadding(
+ mozilla::WritingMode aWM) const {
+ return ComputedSize(aWM) + ComputedLogicalBorderPadding(aWM).Size(aWM);
+ }
+
+ mozilla::LogicalSize ComputedSizeWithMarginBorderPadding(
+ mozilla::WritingMode aWM) const {
+ return ComputedSizeWithBorderPadding(aWM) +
+ ComputedLogicalMargin(aWM).Size(aWM);
+ }
+
+ nsSize ComputedPhysicalSize() const {
+ return mComputedSize.GetPhysicalSize(mWritingMode);
+ }
+
+ nsMargin ComputedPhysicalOffsets() const {
+ return mComputedOffsets.GetPhysicalMargin(mWritingMode);
+ }
+
+ LogicalMargin ComputedLogicalOffsets(mozilla::WritingMode aWM) const {
+ return mComputedOffsets.ConvertTo(aWM, mWritingMode);
+ }
+
+ void SetComputedLogicalOffsets(mozilla::WritingMode aWM,
+ const LogicalMargin& aOffsets) {
+ mComputedOffsets = aOffsets.ConvertTo(mWritingMode, aWM);
+ }
+
+ // Return ReflowInput's computed size including border-padding, with
+ // unconstrained dimensions replaced by zero.
+ nsSize ComputedSizeAsContainerIfConstrained() const;
+
+ // Our saved containing block dimensions.
+ LogicalSize mContainingBlockSize{mWritingMode};
+
+ // Cached pointers to the various style structs used during initialization.
+ const nsStyleDisplay* mStyleDisplay = nullptr;
+ const nsStylePosition* mStylePosition = nullptr;
+ const nsStyleBorder* mStyleBorder = nullptr;
+ const nsStyleMargin* mStyleMargin = nullptr;
+
+ enum class BreakType : uint8_t {
+ Auto,
+ Column,
+ Page,
+ };
+ BreakType mBreakType = BreakType::Auto;
+
+ // a frame (e.g. nsTableCellFrame) which may need to generate a special
+ // reflow for percent bsize calculations
+ nsIPercentBSizeObserver* mPercentBSizeObserver = nullptr;
+
+ // CSS margin collapsing sometimes requires us to reflow
+ // optimistically assuming that margins collapse to see if clearance
+ // is required. When we discover that clearance is required, we
+ // store the frame in which clearance was discovered to the location
+ // requested here.
+ nsIFrame** mDiscoveredClearance = nullptr;
+
+ struct Flags {
+ Flags() { memset(this, 0, sizeof(*this)); }
+
+ // cached mFrame->IsReplaced() || mFrame->IsReplacedWithBlock()
+ bool mIsReplaced : 1;
+
+ // used by tables to communicate special reflow (in process) to handle
+ // percent bsize frames inside cells which may not have computed bsizes
+ bool mSpecialBSizeReflow : 1;
+
+ // nothing in the frame's next-in-flow (or its descendants) is changing
+ bool mNextInFlowUntouched : 1;
+
+ // Is the current context at the top of a page? When true, we force
+ // something that's too tall for a page/column to fit anyway to avoid
+ // infinite loops.
+ bool mIsTopOfPage : 1;
+
+ // parent frame is an nsIScrollableFrame and it is assuming a horizontal
+ // scrollbar
+ bool mAssumingHScrollbar : 1;
+
+ // parent frame is an nsIScrollableFrame and it is assuming a vertical
+ // scrollbar
+ bool mAssumingVScrollbar : 1;
+
+ // Is frame a different inline-size than before?
+ bool mIsIResize : 1;
+
+ // Is frame (potentially) a different block-size than before?
+ // This includes cases where the block-size is 'auto' and the
+ // contents or width have changed.
+ bool mIsBResize : 1;
+
+ // Has this frame changed block-size in a way that affects
+ // block-size percentages on frames for which it is the containing
+ // block? This includes a change between 'auto' and a length that
+ // doesn't actually change the frame's block-size. It does not
+ // include cases where the block-size is 'auto' and the frame's
+ // contents have changed.
+ //
+ // In the current code, this is only true when mIsBResize is also
+ // true, although it doesn't necessarily need to be that way (e.g.,
+ // in the case of a frame changing from 'auto' to a length that
+ // produces the same height).
+ bool mIsBResizeForPercentages : 1;
+
+ // tables are splittable, this should happen only inside a page and never
+ // insider a column frame
+ bool mTableIsSplittable : 1;
+
+ // Does frame height depend on an ancestor table-cell?
+ bool mHeightDependsOnAncestorCell : 1;
+
+ // nsColumnSetFrame is balancing columns
+ bool mIsColumnBalancing : 1;
+
+ // We have an ancestor nsColumnSetFrame performing the last column balancing
+ // reflow. The available block-size of the last column might become
+ // unconstrained.
+ bool mIsInLastColumnBalancingReflow : 1;
+
+ // True if ColumnSetWrapperFrame has a constrained block-size, and is going
+ // to consume all of its block-size in this fragment. This bit is passed to
+ // nsColumnSetFrame to determine whether to give up balancing and create
+ // overflow columns.
+ bool mColumnSetWrapperHasNoBSizeLeft : 1;
+
+ // If this flag is set, the BSize of this frame should be considered
+ // indefinite for the purposes of percent resolution on child frames (we
+ // should behave as if ComputedBSize() were NS_UNCONSTRAINEDSIZE when doing
+ // percent resolution against this.ComputedBSize()). For example: flex
+ // items may have their ComputedBSize() resolved ahead-of-time by their
+ // flex container, and yet their BSize might have to be considered
+ // indefinite per https://drafts.csswg.org/css-flexbox/#definite-sizes
+ bool mTreatBSizeAsIndefinite : 1;
+
+ // a "fake" reflow input made in order to be the parent of a real one
+ bool mDummyParentReflowInput : 1;
+
+ // Should this frame reflow its place-holder children? If the available
+ // height of this frame didn't change, but its in a paginated environment
+ // (e.g. columns), it should always reflow its placeholder children.
+ bool mMustReflowPlaceholders : 1;
+
+ // the STATIC_POS_IS_CB_ORIGIN ctor flag
+ bool mStaticPosIsCBOrigin : 1;
+
+ // If set, the following two flags indicate that:
+ // (1) this frame is absolutely-positioned (or fixed-positioned).
+ // (2) this frame's static position depends on the CSS Box Alignment.
+ // (3) we do need to compute the static position, because the frame's
+ // {Inline and/or Block} offsets actually depend on it.
+ // When these bits are set, the offset values (IStart/IEnd, BStart/BEnd)
+ // represent the "start" edge of the frame's CSS Box Alignment container
+ // area, in that axis -- and these offsets need to be further-resolved
+ // (with CSS Box Alignment) after we know the OOF frame's size.
+ // NOTE: The "I" and "B" (for "Inline" and "Block") refer the axes of the
+ // *containing block's writing-mode*, NOT mFrame's own writing-mode. This
+ // is purely for convenience, since that's the writing-mode we're dealing
+ // with when we set & react to these bits.
+ bool mIOffsetsNeedCSSAlign : 1;
+ bool mBOffsetsNeedCSSAlign : 1;
+
+ // Is this frame or one of its ancestors being reflowed in a different
+ // continuation than the one in which it was previously reflowed? In
+ // other words, has it moved to a different column or page than it was in
+ // the previous reflow?
+ //
+ // FIXME: For now, we only ensure that this is set correctly for blocks.
+ // This is okay because the only thing that uses it only cares about
+ // whether there's been a fragment change within the same block formatting
+ // context.
+ bool mMovedBlockFragments : 1;
+
+ // Is the block-size computed by aspect-ratio and inline size (i.e. block
+ // axis is the ratio-dependent axis)? We set this flag so that we can check
+ // whether to apply automatic content-based minimum sizes once we know the
+ // children's block-size (after reflowing them).
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ bool mIsBSizeSetByAspectRatio : 1;
+
+ // If true, then children of this frame can generate class A breakpoints
+ // for paginated reflow.
+ bool mCanHaveClassABreakpoints : 1;
+ };
+ Flags mFlags;
+
+ mozilla::StyleSizeOverrides mStyleSizeOverrides;
+
+ mozilla::ComputeSizeFlags mComputeSizeFlags;
+
+ // This value keeps track of how deeply nested a given reflow input
+ // is from the top of the frame tree.
+ int16_t mReflowDepth = 0;
+
+ // Logical and physical accessors for the resize flags.
+ bool IsHResize() const {
+ return mWritingMode.IsVertical() ? mFlags.mIsBResize : mFlags.mIsIResize;
+ }
+ bool IsVResize() const {
+ return mWritingMode.IsVertical() ? mFlags.mIsIResize : mFlags.mIsBResize;
+ }
+ bool IsIResize() const { return mFlags.mIsIResize; }
+ bool IsBResize() const { return mFlags.mIsBResize; }
+ bool IsBResizeForWM(mozilla::WritingMode aWM) const {
+ return aWM.IsOrthogonalTo(mWritingMode) ? mFlags.mIsIResize
+ : mFlags.mIsBResize;
+ }
+ bool IsBResizeForPercentagesForWM(mozilla::WritingMode aWM) const {
+ // This uses the relatively-accurate mIsBResizeForPercentages flag
+ // when the writing modes are parallel, and is a bit more
+ // pessimistic when orthogonal.
+ return !aWM.IsOrthogonalTo(mWritingMode) ? mFlags.mIsBResizeForPercentages
+ : IsIResize();
+ }
+ void SetHResize(bool aValue) {
+ if (mWritingMode.IsVertical()) {
+ mFlags.mIsBResize = aValue;
+ } else {
+ mFlags.mIsIResize = aValue;
+ }
+ }
+ void SetVResize(bool aValue) {
+ if (mWritingMode.IsVertical()) {
+ mFlags.mIsIResize = aValue;
+ } else {
+ mFlags.mIsBResize = aValue;
+ }
+ }
+ void SetIResize(bool aValue) { mFlags.mIsIResize = aValue; }
+ void SetBResize(bool aValue) { mFlags.mIsBResize = aValue; }
+
+ // Values for |aFlags| passed to constructor
+ enum class InitFlag : uint8_t {
+ // Indicates that the parent of this reflow input is "fake" (see
+ // mDummyParentReflowInput in mFlags).
+ DummyParentReflowInput,
+
+ // Indicates that the calling function will initialize the reflow input, and
+ // that the constructor should not call Init().
+ CallerWillInit,
+
+ // The caller wants the abs.pos. static-position resolved at the origin of
+ // the containing block, i.e. at LogicalPoint(0, 0). (Note that this
+ // doesn't necessarily mean that (0, 0) is the *correct* static position
+ // for the frame in question.)
+ // @note In a Grid container's masonry axis we'll always use
+ // the placeholder's position in that axis regardless of this flag.
+ StaticPosIsCBOrigin,
+ };
+ using InitFlags = mozilla::EnumSet<InitFlag>;
+
+ // Note: The copy constructor is written by the compiler automatically. You
+ // can use that and then override specific values if you want, or you can
+ // call Init as desired...
+
+ /**
+ * Initialize a ROOT reflow input.
+ *
+ * @param aPresContext Must be equal to aFrame->PresContext().
+ * @param aFrame The frame for whose reflow input is being constructed.
+ * @param aRenderingContext The rendering context to be used for measurements.
+ * @param aAvailableSpace The available space to reflow aFrame (in aFrame's
+ * writing-mode). See comments for mAvailableSize for more information.
+ * @param aFlags A set of flags used for additional boolean parameters (see
+ * InitFlags above).
+ */
+ ReflowInput(nsPresContext* aPresContext, nsIFrame* aFrame,
+ gfxContext* aRenderingContext,
+ const mozilla::LogicalSize& aAvailableSpace,
+ InitFlags aFlags = {});
+
+ /**
+ * Initialize a reflow input for a child frame's reflow. Some parts of the
+ * state are copied from the parent's reflow input. The remainder is computed.
+ *
+ * @param aPresContext Must be equal to aFrame->PresContext().
+ * @param aParentReflowInput A reference to an ReflowInput object that
+ * is to be the parent of this object.
+ * @param aFrame The frame for whose reflow input is being constructed.
+ * @param aAvailableSpace The available space to reflow aFrame (in aFrame's
+ * writing-mode). See comments for mAvailableSize for more information.
+ * @param aContainingBlockSize An optional size (in aFrame's writing mode),
+ * specifying the containing block size to use instead of the default
+ * size computed by ComputeContainingBlockRectangle(). If
+ * InitFlag::CallerWillInit is used, this is ignored. Pass it via
+ * Init() instead.
+ * @param aFlags A set of flags used for additional boolean parameters (see
+ * InitFlags above).
+ * @param aStyleSizeOverrides The style data used to override mFrame's when we
+ * call nsIFrame::ComputeSize() internally.
+ * @param aComputeSizeFlags A set of flags used when we call
+ * nsIFrame::ComputeSize() internally.
+ */
+ ReflowInput(nsPresContext* aPresContext,
+ const ReflowInput& aParentReflowInput, nsIFrame* aFrame,
+ const mozilla::LogicalSize& aAvailableSpace,
+ const mozilla::Maybe<mozilla::LogicalSize>& aContainingBlockSize =
+ mozilla::Nothing(),
+ InitFlags aFlags = {},
+ const mozilla::StyleSizeOverrides& aSizeOverrides = {},
+ mozilla::ComputeSizeFlags aComputeSizeFlags = {});
+
+ /**
+ * This method initializes various data members. It is automatically called by
+ * the constructors if InitFlags::CallerWillInit is *not* used.
+ *
+ * @param aContainingBlockSize An optional size (in mFrame's writing mode),
+ * specifying the containing block size to use instead of the default
+ * size computed by ComputeContainingBlockRectangle().
+ * @param aBorder An optional border (in mFrame's writing mode). If given, use
+ * it instead of the border computed from mFrame's StyleBorder.
+ * @param aPadding An optional padding (in mFrame's writing mode). If given,
+ * use it instead of the padding computing from mFrame's StylePadding.
+ */
+ void Init(nsPresContext* aPresContext,
+ const mozilla::Maybe<mozilla::LogicalSize>& aContainingBlockSize =
+ mozilla::Nothing(),
+ const mozilla::Maybe<mozilla::LogicalMargin>& aBorder =
+ mozilla::Nothing(),
+ const mozilla::Maybe<mozilla::LogicalMargin>& aPadding =
+ mozilla::Nothing());
+
+ /**
+ * Get the used line-height property. The return value will be >= 0.
+ */
+ nscoord GetLineHeight() const;
+
+ /**
+ * Set the used line-height. aLineHeight must be >= 0.
+ */
+ void SetLineHeight(nscoord aLineHeight);
+
+ /**
+ * Calculate the used line-height property without a reflow input instance.
+ * The return value will be >= 0.
+ *
+ * @param aBlockBSize The computed block size of the content rect of the block
+ * that the line should fill. Only used with
+ * line-height:-moz-block-height. NS_UNCONSTRAINEDSIZE
+ * results in a normal line-height for
+ * line-height:-moz-block-height.
+ * @param aFontSizeInflation The result of the appropriate
+ * nsLayoutUtils::FontSizeInflationFor call,
+ * or 1.0 if during intrinsic size
+ * calculation.
+ */
+ static nscoord CalcLineHeight(const ComputedStyle&,
+ nsPresContext* aPresContext,
+ const nsIContent* aContent, nscoord aBlockBSize,
+ float aFontSizeInflation);
+
+ static nscoord CalcLineHeight(const StyleLineHeight&,
+ const nsStyleFont& aRelativeToFont,
+ nsPresContext* aPresContext, bool aIsVertical,
+ const nsIContent* aContent, nscoord aBlockBSize,
+ float aFontSizeInflation);
+
+ mozilla::LogicalSize ComputeContainingBlockRectangle(
+ nsPresContext* aPresContext, const ReflowInput* aContainingBlockRI) const;
+
+ /**
+ * Apply the mComputed(Min/Max)ISize constraints to the content
+ * size computed so far.
+ */
+ nscoord ApplyMinMaxISize(nscoord aISize) const {
+ if (NS_UNCONSTRAINEDSIZE != ComputedMaxISize()) {
+ aISize = std::min(aISize, ComputedMaxISize());
+ }
+ return std::max(aISize, ComputedMinISize());
+ }
+
+ /**
+ * Apply the mComputed(Min/Max)BSize constraints to the content
+ * size computed so far.
+ *
+ * @param aBSize The block-size that we've computed an to which we want to
+ * apply min/max constraints.
+ * @param aConsumed The amount of the computed block-size that was consumed by
+ * our prev-in-flows.
+ */
+ nscoord ApplyMinMaxBSize(nscoord aBSize, nscoord aConsumed = 0) const {
+ aBSize += aConsumed;
+
+ if (NS_UNCONSTRAINEDSIZE != ComputedMaxBSize()) {
+ aBSize = std::min(aBSize, ComputedMaxBSize());
+ }
+
+ if (NS_UNCONSTRAINEDSIZE != ComputedMinBSize()) {
+ aBSize = std::max(aBSize, ComputedMinBSize());
+ }
+
+ return aBSize - aConsumed;
+ }
+
+ bool ShouldReflowAllKids() const;
+
+ // This method doesn't apply min/max computed widths to the value passed in.
+ void SetComputedWidth(nscoord aComputedWidth) {
+ if (mWritingMode.IsVertical()) {
+ SetComputedBSize(aComputedWidth);
+ } else {
+ SetComputedISize(aComputedWidth);
+ }
+ }
+
+ // This method doesn't apply min/max computed heights to the value passed in.
+ void SetComputedHeight(nscoord aComputedHeight) {
+ if (mWritingMode.IsVertical()) {
+ SetComputedISize(aComputedHeight);
+ } else {
+ SetComputedBSize(aComputedHeight);
+ }
+ }
+
+ // Use "No" to request SetComputedISize/SetComputedBSize not to reset resize
+ // flags.
+ enum class ResetResizeFlags : bool { No, Yes };
+
+ // This method doesn't apply min/max computed inline-sizes to the value passed
+ // in.
+ void SetComputedISize(nscoord aComputedISize,
+ ResetResizeFlags aFlags = ResetResizeFlags::Yes);
+
+ // These methods don't apply min/max computed block-sizes to the value passed
+ // in.
+ void SetComputedBSize(nscoord aComputedBSize,
+ ResetResizeFlags aFlags = ResetResizeFlags::Yes);
+
+ bool WillReflowAgainForClearance() const {
+ return mDiscoveredClearance && *mDiscoveredClearance;
+ }
+
+ // Returns true if we should apply automatic minimum on the block axis.
+ //
+ // The automatic minimum size in the ratio-dependent axis of a box with a
+ // preferred aspect ratio that is neither a replaced element nor a scroll
+ // container is its min-content size clamped from above by its maximum size.
+ //
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ bool ShouldApplyAutomaticMinimumOnBlockAxis() const;
+
+ // Returns true if mFrame has a constrained available block-size, or if mFrame
+ // is a continuation. When this method returns true, mFrame can be considered
+ // to be in a "fragmented context."
+ //
+ // Note: this method usually returns true when mFrame is in a paged
+ // environment (e.g. printing) or has a multi-column container ancestor.
+ // However, this doesn't include several cases when we're intentionally
+ // performing layout in a fragmentation-ignoring way, e.g. 1) mFrame is a flex
+ // or grid item, and this ReflowInput is for a measuring reflow with an
+ // unconstrained available block-size, or 2) mFrame is (or is inside of) an
+ // element that forms an orthogonal writing-mode.
+ bool IsInFragmentedContext() const;
+
+ // Compute the offsets for a relative position element
+ //
+ // @param aWM the writing mode of aCBSize and the returned offsets.
+ static mozilla::LogicalMargin ComputeRelativeOffsets(
+ mozilla::WritingMode aWM, nsIFrame* aFrame,
+ const mozilla::LogicalSize& aCBSize);
+
+ // If aFrame is a relatively or sticky positioned element, adjust aPosition
+ // appropriately.
+ //
+ // @param aComputedOffsets aFrame's relative offset, either from the cached
+ // nsIFrame::ComputedOffsetProperty() or ComputedPhysicalOffsets().
+ // Note: This parameter is used only when aFrame is relatively
+ // positioned, not sticky positioned.
+ // @param aPosition [in/out] Pass aFrame's normal position (pre-relative
+ // positioning), and this method will update it to indicate aFrame's
+ // actual position.
+ static void ApplyRelativePositioning(nsIFrame* aFrame,
+ const nsMargin& aComputedOffsets,
+ nsPoint* aPosition);
+
+ static void ApplyRelativePositioning(
+ nsIFrame* aFrame, mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalMargin& aComputedOffsets,
+ mozilla::LogicalPoint* aPosition, const nsSize& aContainerSize);
+
+ // Resolve any block-axis 'auto' margins (if any) for an absolutely positioned
+ // frame. aMargin and aOffsets are both outparams (though we only touch
+ // aOffsets if the position is overconstrained)
+ static void ComputeAbsPosBlockAutoMargin(nscoord aAvailMarginSpace,
+ WritingMode aContainingBlockWM,
+ bool aIsMarginBStartAuto,
+ bool aIsMarginBEndAuto,
+ LogicalMargin& aMargin,
+ LogicalMargin& aOffsets);
+
+ // Resolve any inline-axis 'auto' margins (if any) for an absolutely
+ // positioned frame. aMargin and aOffsets are both outparams (though we only
+ // touch aOffsets if the position is overconstrained)
+ static void ComputeAbsPosInlineAutoMargin(nscoord aAvailMarginSpace,
+ WritingMode aContainingBlockWM,
+ bool aIsMarginIStartAuto,
+ bool aIsMarginIEndAuto,
+ LogicalMargin& aMargin,
+ LogicalMargin& aOffsets);
+
+#ifdef DEBUG
+ // Reflow trace methods. Defined in nsFrame.cpp so they have access
+ // to the display-reflow infrastructure.
+ static void* DisplayInitConstraintsEnter(nsIFrame* aFrame,
+ ReflowInput* aState,
+ nscoord aCBISize, nscoord aCBBSize,
+ const nsMargin* aBorder,
+ const nsMargin* aPadding);
+ static void DisplayInitConstraintsExit(nsIFrame* aFrame, ReflowInput* aState,
+ void* aValue);
+ static void* DisplayInitFrameTypeEnter(nsIFrame* aFrame, ReflowInput* aState);
+ static void DisplayInitFrameTypeExit(nsIFrame* aFrame, ReflowInput* aState,
+ void* aValue);
+#endif
+
+ protected:
+ void InitCBReflowInput();
+ void InitResizeFlags(nsPresContext* aPresContext,
+ mozilla::LayoutFrameType aFrameType);
+ void InitDynamicReflowRoot();
+
+ void InitConstraints(
+ nsPresContext* aPresContext,
+ const mozilla::Maybe<mozilla::LogicalSize>& aContainingBlockSize,
+ const mozilla::Maybe<mozilla::LogicalMargin>& aBorder,
+ const mozilla::Maybe<mozilla::LogicalMargin>& aPadding,
+ mozilla::LayoutFrameType aFrameType);
+
+ // Returns the nearest containing block or block frame (whether or not
+ // it is a containing block) for the specified frame. Also returns
+ // the inline-start edge and logical size of the containing block's
+ // content area.
+ // These are returned in the coordinate space of the containing block.
+ nsIFrame* GetHypotheticalBoxContainer(nsIFrame* aFrame,
+ nscoord& aCBIStartEdge,
+ mozilla::LogicalSize& aCBSize) const;
+
+ // Calculate a "hypothetical box" position where the placeholder frame
+ // (for a position:fixed/absolute element) would have been placed if it were
+ // positioned statically. The hypothetical box position will have a writing
+ // mode with the same block direction as the absolute containing block
+ // (aCBReflowInput->frame), though it may differ in inline direction.
+ void CalculateHypotheticalPosition(nsPresContext* aPresContext,
+ nsPlaceholderFrame* aPlaceholderFrame,
+ const ReflowInput* aCBReflowInput,
+ nsHypotheticalPosition& aHypotheticalPos,
+ mozilla::LayoutFrameType aFrameType) const;
+
+ // Check if we can use the resolved auto block size (by insets) to compute
+ // the inline size through aspect-ratio on absolute-positioned elements.
+ // This is only needed for non-replaced elements.
+ // https://drafts.csswg.org/css-position/#abspos-auto-size
+ bool IsInlineSizeComputableByBlockSizeAndAspectRatio(
+ nscoord aBlockSize) const;
+
+ // This calculates the size by using the resolved auto block size (from
+ // non-auto block insets), according to the writing mode of current block.
+ LogicalSize CalculateAbsoluteSizeWithResolvedAutoBlockSize(
+ nscoord aAutoBSize, const LogicalSize& aTentativeComputedSize);
+
+ void InitAbsoluteConstraints(nsPresContext* aPresContext,
+ const ReflowInput* aCBReflowInput,
+ const mozilla::LogicalSize& aContainingBlockSize,
+ mozilla::LayoutFrameType aFrameType);
+
+ // Calculates the computed values for the 'min-inline-size',
+ // 'max-inline-size', 'min-block-size', and 'max-block-size' properties, and
+ // stores them in the assorted data members
+ void ComputeMinMaxValues(const mozilla::LogicalSize& aCBSize);
+
+ // aInsideBoxSizing returns the part of the padding, border, and margin
+ // in the aAxis dimension that goes inside the edge given by box-sizing;
+ // aOutsideBoxSizing returns the rest.
+ void CalculateBorderPaddingMargin(mozilla::LogicalAxis aAxis,
+ nscoord aContainingBlockSize,
+ nscoord* aInsideBoxSizing,
+ nscoord* aOutsideBoxSizing) const;
+
+ void CalculateBlockSideMargins();
+
+ /**
+ * @return true if mFrame is an internal table frame, i.e. an
+ * ns[RowGroup|ColGroup|Row|Cell]Frame. (We exclude nsTableColFrame
+ * here since we never setup a ReflowInput for those.)
+ */
+ bool IsInternalTableFrame() const;
+
+ private:
+ // The available size in which to reflow the frame. The space represents the
+ // amount of room for the frame's margin, border, padding, and content area.
+ //
+ // The available inline-size should be constrained. The frame's inline-size
+ // you choose should fit within it.
+
+ // In galley mode, the available block-size is always unconstrained, and only
+ // page mode or multi-column layout involves a constrained available
+ // block-size.
+ //
+ // An unconstrained available block-size means you can choose whatever size
+ // you want. If the value is constrained, the frame's block-start border,
+ // padding, and content, must fit. If a frame is fully-complete after reflow,
+ // then its block-end border, padding, and margin (and similar for its
+ // fully-complete ancestors) will need to fit within this available
+ // block-size. However, if a frame is monolithic, it may choose a block-size
+ // larger than the available block-size.
+ mozilla::LogicalSize mAvailableSize{mWritingMode};
+
+ // The computed size specifies the frame's content area, and it does not apply
+ // to inline non-replaced elements.
+ //
+ // For block-level frames, the computed inline-size is based on the
+ // inline-size of the containing block, the margin/border/padding areas, and
+ // the min/max inline-size.
+ //
+ // For non-replaced block-level frames in the flow and floated, if the
+ // computed block-size is NS_UNCONSTRAINEDSIZE, you should choose a block-size
+ // to shrink wrap around the normal flow child frames. The block-size must be
+ // within the limit of the min/max block-size if there is such a limit.
+ mozilla::LogicalSize mComputedSize{mWritingMode};
+
+ // Computed values for 'inset' properties. Only applies to 'positioned'
+ // elements.
+ mozilla::LogicalMargin mComputedOffsets{mWritingMode};
+
+ // Computed value for 'min-inline-size'/'min-block-size'.
+ mozilla::LogicalSize mComputedMinSize{mWritingMode};
+
+ // Computed value for 'max-inline-size'/'max-block-size'.
+ mozilla::LogicalSize mComputedMaxSize{mWritingMode, NS_UNCONSTRAINEDSIZE,
+ NS_UNCONSTRAINEDSIZE};
+
+ // Percentage basis in the block axis for the purpose of percentage resolution
+ // on children.
+ //
+ // This will be ignored when mTreatBSizeAsIndefinite flag is true, or when a
+ // customized containing block size is provided via ReflowInput's constructor
+ // or Init(). When this percentage basis exists, it will be used to replace
+ // the containing block's ComputedBSize() in
+ // ComputeContainingBlockRectangle().
+ //
+ // This is currently used in a special scenario where we treat certain
+ // sized-to-content flex items as having an 'auto' block-size for their final
+ // reflow to accomodate fragmentation-imposed block-size growth. This sort of
+ // flex item does nonetheless have a known block-size (from the flex layout
+ // algorithm) that it needs to use as a definite percentage-basis for its
+ // children during its final reflow; and we represent that here.
+ Maybe<nscoord> mPercentageBasisInBlockAxis;
+
+ // Cache the used line-height property.
+ mutable nscoord mLineHeight = NS_UNCONSTRAINEDSIZE;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ReflowInput_h
diff --git a/layout/generic/ReflowOutput.cpp b/layout/generic/ReflowOutput.cpp
new file mode 100644
index 0000000000..a0312dffda
--- /dev/null
+++ b/layout/generic/ReflowOutput.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* struct containing the output from nsIFrame::Reflow */
+
+#include "mozilla/ReflowOutput.h"
+#include "mozilla/ReflowInput.h"
+
+namespace mozilla {
+
+static bool IsValidOverflowRect(const nsRect& aRect) {
+ // `IsEmpty` in the context of `nsRect` means "width OR height is zero."
+ // However, in the context of overflow, the rect having one axis as zero is
+ // NOT considered empty.
+ if (MOZ_LIKELY(!aRect.IsEmpty())) {
+ return true;
+ }
+
+ // Be defensive and consider rects with any negative size as invalid.
+ return !aRect.IsEqualEdges(nsRect()) && aRect.Width() >= 0 &&
+ aRect.Height() >= 0;
+}
+
+/* static */
+nsRect OverflowAreas::GetOverflowClipRect(const nsRect& aRectToClip,
+ const nsRect& aBounds,
+ PhysicalAxes aClipAxes,
+ const nsSize& aOverflowMargin) {
+ auto inflatedBounds = aBounds;
+ inflatedBounds.Inflate(aOverflowMargin);
+ auto clip = aRectToClip;
+ if (aClipAxes & PhysicalAxes::Vertical) {
+ clip.y = inflatedBounds.y;
+ clip.height = inflatedBounds.height;
+ }
+ if (aClipAxes & PhysicalAxes::Horizontal) {
+ clip.x = inflatedBounds.x;
+ clip.width = inflatedBounds.width;
+ }
+ return clip;
+}
+
+/* static */
+void OverflowAreas::ApplyOverflowClippingOnRect(nsRect& aOverflowRect,
+ const nsRect& aBounds,
+ PhysicalAxes aClipAxes,
+ const nsSize& aOverflowMargin) {
+ aOverflowRect = aOverflowRect.Intersect(
+ GetOverflowClipRect(aOverflowRect, aBounds, aClipAxes, aOverflowMargin));
+}
+
+void OverflowAreas::UnionWith(const OverflowAreas& aOther) {
+ if (IsValidOverflowRect(aOther.InkOverflow())) {
+ InkOverflow().UnionRect(InkOverflow(), aOther.InkOverflow());
+ }
+ if (IsValidOverflowRect(aOther.ScrollableOverflow())) {
+ ScrollableOverflow().UnionRect(ScrollableOverflow(),
+ aOther.ScrollableOverflow());
+ }
+}
+
+void OverflowAreas::UnionAllWith(const nsRect& aRect) {
+ if (!IsValidOverflowRect(aRect)) {
+ // Same as `UnionWith()` - avoid losing information.
+ return;
+ }
+ InkOverflow().UnionRect(InkOverflow(), aRect);
+ ScrollableOverflow().UnionRect(ScrollableOverflow(), aRect);
+}
+
+void OverflowAreas::SetAllTo(const nsRect& aRect) {
+ InkOverflow() = aRect;
+ ScrollableOverflow() = aRect;
+}
+
+ReflowOutput::ReflowOutput(const ReflowInput& aReflowInput)
+ : ReflowOutput(aReflowInput.GetWritingMode()) {}
+
+void ReflowOutput::SetOverflowAreasToDesiredBounds() {
+ mOverflowAreas.SetAllTo(nsRect(0, 0, Width(), Height()));
+}
+
+void ReflowOutput::UnionOverflowAreasWithDesiredBounds() {
+ mOverflowAreas.UnionAllWith(nsRect(0, 0, Width(), Height()));
+}
+
+} // namespace mozilla
diff --git a/layout/generic/ReflowOutput.h b/layout/generic/ReflowOutput.h
new file mode 100644
index 0000000000..ab71fa75a4
--- /dev/null
+++ b/layout/generic/ReflowOutput.h
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* struct containing the output from nsIFrame::Reflow */
+
+#ifndef mozilla_ReflowOutput_h
+#define mozilla_ReflowOutput_h
+
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/WritingModes.h"
+#include "nsBoundingMetrics.h"
+#include "nsRect.h"
+
+//----------------------------------------------------------------------
+
+namespace mozilla {
+struct ReflowInput;
+
+enum class OverflowType : uint8_t { Ink, Scrollable };
+constexpr auto AllOverflowTypes() {
+ return mozilla::MakeInclusiveEnumeratedRange(OverflowType::Ink,
+ OverflowType::Scrollable);
+}
+
+struct OverflowAreas {
+ public:
+ nsRect& InkOverflow() { return mInk; }
+ const nsRect& InkOverflow() const { return mInk; }
+
+ nsRect& ScrollableOverflow() { return mScrollable; }
+ const nsRect& ScrollableOverflow() const { return mScrollable; }
+
+ nsRect& Overflow(OverflowType aType) {
+ return aType == OverflowType::Ink ? InkOverflow() : ScrollableOverflow();
+ }
+ const nsRect& Overflow(OverflowType aType) const {
+ return aType == OverflowType::Ink ? InkOverflow() : ScrollableOverflow();
+ }
+
+ OverflowAreas() = default;
+
+ OverflowAreas(const nsRect& aInkOverflow, const nsRect& aScrollableOverflow)
+ : mInk(aInkOverflow), mScrollable(aScrollableOverflow) {}
+
+ bool operator==(const OverflowAreas& aOther) const {
+ // Scrollable overflow is a point-set rectangle and ink overflow
+ // is a pixel-set rectangle.
+ return InkOverflow().IsEqualInterior(aOther.InkOverflow()) &&
+ ScrollableOverflow().IsEqualEdges(aOther.ScrollableOverflow());
+ }
+
+ bool operator!=(const OverflowAreas& aOther) const {
+ return !(*this == aOther);
+ }
+
+ OverflowAreas operator+(const nsPoint& aPoint) const {
+ OverflowAreas result(*this);
+ result += aPoint;
+ return result;
+ }
+
+ OverflowAreas& operator+=(const nsPoint& aPoint) {
+ mInk += aPoint;
+ mScrollable += aPoint;
+ return *this;
+ }
+
+ void Clear() { SetAllTo(nsRect()); }
+
+ // Mutates |this| by unioning both overflow areas with |aOther|.
+ void UnionWith(const OverflowAreas& aOther);
+
+ // Mutates |this| by unioning both overflow areas with |aRect|.
+ void UnionAllWith(const nsRect& aRect);
+
+ // Mutates |this| by setting both overflow areas to |aRect|.
+ void SetAllTo(const nsRect& aRect);
+
+ // Applies overflow clipping (for e.g. overflow: clip) as needed to both our
+ // overflow rects.
+ void ApplyClipping(const nsRect& aBounds, PhysicalAxes aClipAxes,
+ const nsSize& aOverflowMargin) {
+ ApplyOverflowClippingOnRect(InkOverflow(), aBounds, aClipAxes,
+ aOverflowMargin);
+ ApplyOverflowClippingOnRect(ScrollableOverflow(), aBounds, aClipAxes,
+ aOverflowMargin);
+ }
+
+ // Gets the overflow clipping rect for a given element given a rect to clip,
+ // the frame bounds, a set of axes, and the overflow margin.
+ static nsRect GetOverflowClipRect(const nsRect& aRectToClip,
+ const nsRect& aBounds,
+ PhysicalAxes aClipAxes,
+ const nsSize& aOverflowMargin);
+
+ // Applies the overflow clipping to a given overflow rect, given the frame
+ // bounds, and the physical axes on which to apply the overflow clip.
+ static void ApplyOverflowClippingOnRect(nsRect& aOverflowRect,
+ const nsRect& aBounds,
+ PhysicalAxes aClipAxes,
+ const nsSize& aOverflowMargin);
+
+ private:
+ nsRect mInk;
+ nsRect mScrollable;
+};
+
+} // namespace mozilla
+
+/**
+ * An nsCollapsingMargin represents a vertical collapsing margin between
+ * blocks as described in section 8.3.1 of CSS2,
+ * <URL: http://www.w3.org/TR/REC-CSS2/box.html#collapsing-margins >.
+ *
+ * All adjacent vertical margins collapse, and the resulting margin is
+ * the sum of the largest positive margin included and the smallest (most
+ * negative) negative margin included.
+ */
+struct nsCollapsingMargin {
+ private:
+ nscoord mMostPos; // the largest positive margin included
+ nscoord mMostNeg; // the smallest negative margin included
+
+ public:
+ nsCollapsingMargin() : mMostPos(0), mMostNeg(0) {}
+
+ nsCollapsingMargin(const nsCollapsingMargin& aOther) = default;
+
+ bool operator==(const nsCollapsingMargin& aOther) const {
+ return mMostPos == aOther.mMostPos && mMostNeg == aOther.mMostNeg;
+ }
+
+ bool operator!=(const nsCollapsingMargin& aOther) const {
+ return !(*this == aOther);
+ }
+
+ nsCollapsingMargin& operator=(const nsCollapsingMargin& aOther) = default;
+
+ void Include(nscoord aCoord) {
+ if (aCoord > mMostPos)
+ mMostPos = aCoord;
+ else if (aCoord < mMostNeg)
+ mMostNeg = aCoord;
+ }
+
+ void Include(const nsCollapsingMargin& aOther) {
+ if (aOther.mMostPos > mMostPos) mMostPos = aOther.mMostPos;
+ if (aOther.mMostNeg < mMostNeg) mMostNeg = aOther.mMostNeg;
+ }
+
+ void Zero() {
+ mMostPos = 0;
+ mMostNeg = 0;
+ }
+
+ bool IsZero() const { return (mMostPos == 0) && (mMostNeg == 0); }
+
+ nscoord get() const { return mMostPos + mMostNeg; }
+};
+
+namespace mozilla {
+
+/**
+ * ReflowOutput is initialized by a parent frame as a parameter passing to
+ * Reflow() to allow a child frame to return its desired size and alignment
+ * information.
+ *
+ * ReflowOutput's constructor usually takes a parent frame's WritingMode (or
+ * ReflowInput) because it is more convenient for the parent frame to use the
+ * stored Size() after reflowing the child frame. However, it can actually
+ * accept any WritingMode (or ReflowInput) because SetSize() knows how to
+ * convert a size in any writing mode to the stored writing mode.
+ *
+ * @see nsIFrame::Reflow() for more information.
+ */
+class ReflowOutput {
+ public:
+ explicit ReflowOutput(mozilla::WritingMode aWritingMode)
+ : mSize(aWritingMode), mWritingMode(aWritingMode) {}
+
+ // A convenient constructor to get WritingMode in ReflowInput.
+ explicit ReflowOutput(const ReflowInput& aReflowInput);
+
+ nscoord ISize(mozilla::WritingMode aWritingMode) const {
+ return mSize.ISize(aWritingMode);
+ }
+ nscoord BSize(mozilla::WritingMode aWritingMode) const {
+ return mSize.BSize(aWritingMode);
+ }
+ mozilla::LogicalSize Size(mozilla::WritingMode aWritingMode) const {
+ return mSize.ConvertTo(aWritingMode, mWritingMode);
+ }
+
+ nscoord& ISize(mozilla::WritingMode aWritingMode) {
+ return mSize.ISize(aWritingMode);
+ }
+ nscoord& BSize(mozilla::WritingMode aWritingMode) {
+ return mSize.BSize(aWritingMode);
+ }
+
+ // Set inline and block size from a LogicalSize, converting to our
+ // writing mode as necessary.
+ void SetSize(mozilla::WritingMode aWM, mozilla::LogicalSize aSize) {
+ mSize = aSize.ConvertTo(mWritingMode, aWM);
+ }
+
+ // Set both inline and block size to zero -- no need for a writing mode!
+ void ClearSize() { mSize.SizeTo(mWritingMode, 0, 0); }
+
+ // Width and Height are physical dimensions, independent of writing mode.
+ // Accessing these is slightly more expensive than accessing the logical
+ // dimensions; as far as possible, client code should work purely with logical
+ // dimensions.
+ nscoord Width() const { return mSize.Width(mWritingMode); }
+ nscoord Height() const { return mSize.Height(mWritingMode); }
+ nscoord& Width() {
+ return mWritingMode.IsVertical() ? mSize.BSize(mWritingMode)
+ : mSize.ISize(mWritingMode);
+ }
+ nscoord& Height() {
+ return mWritingMode.IsVertical() ? mSize.ISize(mWritingMode)
+ : mSize.BSize(mWritingMode);
+ }
+
+ nsSize PhysicalSize() const { return mSize.GetPhysicalSize(mWritingMode); }
+
+ // It's only meaningful to consider "ascent" on the block-start side of the
+ // frame, so no need to pass a writing mode argument
+ enum { ASK_FOR_BASELINE = nscoord_MAX };
+ nscoord BlockStartAscent() const { return mBlockStartAscent; }
+ void SetBlockStartAscent(nscoord aAscent) { mBlockStartAscent = aAscent; }
+
+ // Metrics that _exactly_ enclose the text to allow precise MathML placements.
+ nsBoundingMetrics mBoundingMetrics; // [OUT]
+
+ // Carried out block-end margin values. This is the collapsed
+ // (generational) block-end margin value.
+ nsCollapsingMargin mCarriedOutBEndMargin;
+
+ // For frames that have content that overflow their content area
+ // (HasOverflowAreas() is true) these rectangles represent the total
+ // area of the frame including visible overflow, i.e., don't include
+ // overflowing content that is hidden. The rects are in the local
+ // coordinate space of the frame, and should be at least as big as the
+ // desired size. If there is no content that overflows, then the
+ // overflow area is identical to the desired size and should be {0, 0,
+ // width, height}.
+ OverflowAreas mOverflowAreas;
+
+ nsRect& InkOverflow() { return mOverflowAreas.InkOverflow(); }
+ const nsRect& InkOverflow() const { return mOverflowAreas.InkOverflow(); }
+ nsRect& ScrollableOverflow() { return mOverflowAreas.ScrollableOverflow(); }
+ const nsRect& ScrollableOverflow() const {
+ return mOverflowAreas.ScrollableOverflow();
+ }
+
+ // Set all of mOverflowAreas to (0, 0, width, height).
+ void SetOverflowAreasToDesiredBounds();
+
+ // Union all of mOverflowAreas with (0, 0, width, height).
+ void UnionOverflowAreasWithDesiredBounds();
+
+ mozilla::WritingMode GetWritingMode() const { return mWritingMode; }
+
+ private:
+ // Desired size of a frame's border-box.
+ LogicalSize mSize;
+
+ // Baseline (in block direction), or the default value ASK_FOR_BASELINE.
+ nscoord mBlockStartAscent = ASK_FOR_BASELINE;
+
+ mozilla::WritingMode mWritingMode;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ReflowOutput_h
diff --git a/layout/generic/RubyUtils.cpp b/layout/generic/RubyUtils.cpp
new file mode 100644
index 0000000000..4cdd4b65b3
--- /dev/null
+++ b/layout/generic/RubyUtils.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RubyUtils.h"
+#include "nsRubyFrame.h"
+#include "nsRubyBaseFrame.h"
+#include "nsRubyTextFrame.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextContainerFrame.h"
+
+using namespace mozilla;
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ReservedISize, nscoord)
+
+/* static */
+void RubyUtils::SetReservedISize(nsIFrame* aFrame, nscoord aISize) {
+ MOZ_ASSERT(IsExpandableRubyBox(aFrame));
+ aFrame->SetProperty(ReservedISize(), aISize);
+}
+
+/* static */
+void RubyUtils::ClearReservedISize(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsExpandableRubyBox(aFrame));
+ aFrame->RemoveProperty(ReservedISize());
+}
+
+/* static */
+nscoord RubyUtils::GetReservedISize(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsExpandableRubyBox(aFrame));
+ return aFrame->GetProperty(ReservedISize());
+}
+
+AutoRubyTextContainerArray::AutoRubyTextContainerArray(
+ nsRubyBaseContainerFrame* aBaseContainer) {
+ for (nsIFrame* frame = aBaseContainer->GetNextSibling();
+ frame && frame->IsRubyTextContainerFrame();
+ frame = frame->GetNextSibling()) {
+ AppendElement(static_cast<nsRubyTextContainerFrame*>(frame));
+ }
+}
+
+nsIFrame* RubyColumn::Iterator::operator*() const {
+ nsIFrame* frame;
+ if (mIndex == -1) {
+ frame = mColumn.mBaseFrame;
+ } else {
+ frame = mColumn.mTextFrames[mIndex];
+ }
+ MOZ_ASSERT(frame, "Frame here cannot be null");
+ return frame;
+}
+
+void RubyColumn::Iterator::SkipUntilExistingFrame() {
+ if (mIndex == -1) {
+ if (mColumn.mBaseFrame) {
+ return;
+ }
+ ++mIndex;
+ }
+ int32_t numTextFrames = mColumn.mTextFrames.Length();
+ for (; mIndex < numTextFrames; ++mIndex) {
+ if (mColumn.mTextFrames[mIndex]) {
+ break;
+ }
+ }
+}
+
+RubySegmentEnumerator::RubySegmentEnumerator(nsRubyFrame* aRubyFrame) {
+ nsIFrame* frame = aRubyFrame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(!frame || frame->IsRubyBaseContainerFrame());
+ mBaseContainer = static_cast<nsRubyBaseContainerFrame*>(frame);
+}
+
+void RubySegmentEnumerator::Next() {
+ MOZ_ASSERT(mBaseContainer);
+ nsIFrame* frame = mBaseContainer->GetNextSibling();
+ while (frame && !frame->IsRubyBaseContainerFrame()) {
+ frame = frame->GetNextSibling();
+ }
+ mBaseContainer = static_cast<nsRubyBaseContainerFrame*>(frame);
+}
+
+RubyColumnEnumerator::RubyColumnEnumerator(
+ nsRubyBaseContainerFrame* aBaseContainer,
+ const AutoRubyTextContainerArray& aTextContainers)
+ : mAtIntraLevelWhitespace(false) {
+ const uint32_t rtcCount = aTextContainers.Length();
+ mFrames.SetCapacity(rtcCount + 1);
+
+ nsIFrame* rbFrame = aBaseContainer->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(!rbFrame || rbFrame->IsRubyBaseFrame());
+ mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rbFrame));
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsRubyTextContainerFrame* container = aTextContainers[i];
+ // If the container is for span, leave a nullptr here.
+ // Spans do not take part in pairing.
+ nsIFrame* rtFrame = !container->IsSpanContainer()
+ ? container->PrincipalChildList().FirstChild()
+ : nullptr;
+ MOZ_ASSERT(!rtFrame || rtFrame->IsRubyTextFrame());
+ mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rtFrame));
+ }
+
+ // We have to init mAtIntraLevelWhitespace to be correct for the
+ // first column. There are two ways we could end up with intra-level
+ // whitespace in our first colum:
+ // 1. The current segment itself is an inter-segment whitespace;
+ // 2. If our ruby segment is split across multiple lines, and some
+ // intra-level whitespace happens to fall right after a line-break.
+ // Each line will get its own nsRubyBaseContainerFrame, and the
+ // container right after the line-break will end up with its first
+ // column containing that intra-level whitespace.
+ for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
+ nsRubyContentFrame* frame = mFrames[i];
+ if (frame && frame->IsIntraLevelWhitespace()) {
+ mAtIntraLevelWhitespace = true;
+ break;
+ }
+ }
+}
+
+void RubyColumnEnumerator::Next() {
+ bool advancingToIntraLevelWhitespace = false;
+ for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
+ nsRubyContentFrame* frame = mFrames[i];
+ // If we've got intra-level whitespace frames at some levels in the
+ // current ruby column, we "faked" an anonymous box for all other
+ // levels for this column. So when we advance off this column, we
+ // don't advance any of the frames in those levels, because we're
+ // just advancing across the "fake" frames.
+ if (frame &&
+ (!mAtIntraLevelWhitespace || frame->IsIntraLevelWhitespace())) {
+ nsIFrame* nextSibling = frame->GetNextSibling();
+ MOZ_ASSERT(!nextSibling || nextSibling->Type() == frame->Type(),
+ "Frame type should be identical among a level");
+ mFrames[i] = frame = static_cast<nsRubyContentFrame*>(nextSibling);
+ if (!advancingToIntraLevelWhitespace && frame &&
+ frame->IsIntraLevelWhitespace()) {
+ advancingToIntraLevelWhitespace = true;
+ }
+ }
+ }
+ MOZ_ASSERT(!advancingToIntraLevelWhitespace || !mAtIntraLevelWhitespace,
+ "Should never have adjacent intra-level whitespace columns");
+ mAtIntraLevelWhitespace = advancingToIntraLevelWhitespace;
+}
+
+bool RubyColumnEnumerator::AtEnd() const {
+ for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
+ if (mFrames[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+nsRubyContentFrame* RubyColumnEnumerator::GetFrameAtLevel(
+ uint32_t aIndex) const {
+ // If the current ruby column is for intra-level whitespaces, we
+ // return nullptr for any levels that do not have an actual intra-
+ // level whitespace frame in this column. This nullptr represents
+ // an anonymous empty intra-level whitespace box. (In this case,
+ // it's important that we NOT return mFrames[aIndex], because it's
+ // really part of the next column, not the current one.)
+ nsRubyContentFrame* frame = mFrames[aIndex];
+ return !mAtIntraLevelWhitespace || (frame && frame->IsIntraLevelWhitespace())
+ ? frame
+ : nullptr;
+}
+
+void RubyColumnEnumerator::GetColumn(RubyColumn& aColumn) const {
+ nsRubyContentFrame* rbFrame = GetFrameAtLevel(0);
+ MOZ_ASSERT(!rbFrame || rbFrame->IsRubyBaseFrame());
+ aColumn.mBaseFrame = static_cast<nsRubyBaseFrame*>(rbFrame);
+ aColumn.mTextFrames.ClearAndRetainStorage();
+ for (uint32_t i = 1, iend = mFrames.Length(); i < iend; i++) {
+ nsRubyContentFrame* rtFrame = GetFrameAtLevel(i);
+ MOZ_ASSERT(!rtFrame || rtFrame->IsRubyTextFrame());
+ aColumn.mTextFrames.AppendElement(static_cast<nsRubyTextFrame*>(rtFrame));
+ }
+ aColumn.mIsIntraLevelWhitespace = mAtIntraLevelWhitespace;
+}
diff --git a/layout/generic/RubyUtils.h b/layout/generic/RubyUtils.h
new file mode 100644
index 0000000000..cd2019d100
--- /dev/null
+++ b/layout/generic/RubyUtils.h
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RubyUtils_h_
+#define mozilla_RubyUtils_h_
+
+#include "nsCSSAnonBoxes.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+#include "nsTArray.h"
+
+#define RTC_ARRAY_SIZE 1
+
+class nsRubyFrame;
+class nsRubyBaseFrame;
+class nsRubyTextFrame;
+class nsRubyContentFrame;
+class nsRubyBaseContainerFrame;
+class nsRubyTextContainerFrame;
+
+namespace mozilla {
+
+/**
+ * Reserved ISize
+ *
+ * With some exceptions, each ruby internal box has two isizes, which
+ * are the reflowed isize and the final isize. The reflowed isize is
+ * what a box itself needs. It is determined when the box gets reflowed.
+ *
+ * The final isize is what a box should be as the final result. For a
+ * ruby base/text box, the final isize is the size of its ruby column.
+ * For a ruby base/text container, the final isize is the size of its
+ * ruby segment. The final isize is never smaller than the reflowed
+ * isize. It is initially determined when a ruby column/segment gets
+ * fully reflowed, and may be advanced when a box is expanded, e.g.
+ * for justification.
+ *
+ * The difference between the reflowed isize and the final isize is
+ * reserved in the line layout after reflowing a box, hence it is called
+ * "Reserved ISize" here. It is used to expand the ruby boxes from their
+ * reflowed isize to the final isize during alignment of the line.
+ *
+ * There are three exceptions for the final isize:
+ * 1. A ruby text container has a larger final isize only if it is for
+ * a span or collapsed annotations.
+ * 2. A ruby base container has a larger final isize only if at least
+ * one of its ruby text containers does.
+ * 3. If a ruby text container has a larger final isize, its children
+ * must not have.
+ */
+
+class RubyUtils {
+ public:
+ static inline bool IsRubyContentBox(LayoutFrameType aFrameType) {
+ return aFrameType == mozilla::LayoutFrameType::RubyBase ||
+ aFrameType == mozilla::LayoutFrameType::RubyText;
+ }
+
+ static inline bool IsRubyContainerBox(LayoutFrameType aFrameType) {
+ return aFrameType == mozilla::LayoutFrameType::RubyBaseContainer ||
+ aFrameType == mozilla::LayoutFrameType::RubyTextContainer;
+ }
+
+ static inline bool IsRubyBox(LayoutFrameType aFrameType) {
+ return aFrameType == mozilla::LayoutFrameType::Ruby ||
+ IsRubyContentBox(aFrameType) || IsRubyContainerBox(aFrameType);
+ }
+
+ static inline bool IsExpandableRubyBox(nsIFrame* aFrame) {
+ mozilla::LayoutFrameType type = aFrame->Type();
+ return IsRubyContentBox(type) || IsRubyContainerBox(type);
+ }
+
+ static inline bool IsRubyPseudo(PseudoStyleType aPseudo) {
+ return aPseudo == PseudoStyleType::blockRubyContent ||
+ aPseudo == PseudoStyleType::ruby ||
+ aPseudo == PseudoStyleType::rubyBase ||
+ aPseudo == PseudoStyleType::rubyText ||
+ aPseudo == PseudoStyleType::rubyBaseContainer ||
+ aPseudo == PseudoStyleType::rubyTextContainer;
+ }
+
+ static void SetReservedISize(nsIFrame* aFrame, nscoord aISize);
+ static void ClearReservedISize(nsIFrame* aFrame);
+ static nscoord GetReservedISize(nsIFrame* aFrame);
+};
+
+/**
+ * This array stores all ruby text containers of the ruby segment
+ * of the given ruby base container.
+ */
+class MOZ_RAII AutoRubyTextContainerArray final
+ : public AutoTArray<nsRubyTextContainerFrame*, RTC_ARRAY_SIZE> {
+ public:
+ explicit AutoRubyTextContainerArray(nsRubyBaseContainerFrame* aBaseContainer);
+};
+
+/**
+ * This enumerator enumerates each ruby segment.
+ */
+class MOZ_STACK_CLASS RubySegmentEnumerator {
+ public:
+ explicit RubySegmentEnumerator(nsRubyFrame* aRubyFrame);
+
+ void Next();
+ bool AtEnd() const { return !mBaseContainer; }
+
+ nsRubyBaseContainerFrame* GetBaseContainer() const { return mBaseContainer; }
+
+ private:
+ nsRubyBaseContainerFrame* mBaseContainer;
+};
+
+/**
+ * Ruby column is a unit consists of one ruby base and all ruby
+ * annotations paired with it.
+ * See http://dev.w3.org/csswg/css-ruby/#ruby-pairing
+ */
+struct MOZ_STACK_CLASS RubyColumn {
+ nsRubyBaseFrame* mBaseFrame;
+ AutoTArray<nsRubyTextFrame*, RTC_ARRAY_SIZE> mTextFrames;
+ bool mIsIntraLevelWhitespace;
+
+ RubyColumn() : mBaseFrame(nullptr), mIsIntraLevelWhitespace(false) {}
+
+ // Helper class to support iteration across the frames within a single
+ // RubyColumn (the column's ruby base and its annotations).
+ class MOZ_STACK_CLASS Iterator {
+ public:
+ nsIFrame* operator*() const;
+
+ Iterator& operator++() {
+ ++mIndex;
+ SkipUntilExistingFrame();
+ return *this;
+ }
+ Iterator operator++(int) {
+ auto ret = *this;
+ ++*this;
+ return ret;
+ }
+
+ friend bool operator==(const Iterator& aIter1, const Iterator& aIter2) {
+ MOZ_ASSERT(&aIter1.mColumn == &aIter2.mColumn,
+ "Should only compare iterators of the same ruby column");
+ return aIter1.mIndex == aIter2.mIndex;
+ }
+ friend bool operator!=(const Iterator& aIter1, const Iterator& aIter2) {
+ return !(aIter1 == aIter2);
+ }
+
+ private:
+ Iterator(const RubyColumn& aColumn, int32_t aIndex)
+ : mColumn(aColumn), mIndex(aIndex) {
+ MOZ_ASSERT(
+ aIndex == -1 ||
+ (aIndex >= 0 && aIndex <= int32_t(aColumn.mTextFrames.Length())));
+ SkipUntilExistingFrame();
+ }
+ friend struct RubyColumn; // for the constructor
+
+ void SkipUntilExistingFrame();
+
+ const RubyColumn& mColumn;
+ // -1 means the ruby base frame,
+ // non-negative means the index of ruby text frame
+ // a value of mTextFrames.Length() means we're done iterating
+ int32_t mIndex = -1;
+ };
+
+ Iterator begin() const { return Iterator(*this, -1); }
+ Iterator end() const { return Iterator(*this, mTextFrames.Length()); }
+ Iterator cbegin() const { return begin(); }
+ Iterator cend() const { return end(); }
+};
+
+/**
+ * This enumerator enumerates ruby columns in a segment.
+ */
+class MOZ_STACK_CLASS RubyColumnEnumerator {
+ public:
+ RubyColumnEnumerator(nsRubyBaseContainerFrame* aRBCFrame,
+ const AutoRubyTextContainerArray& aRTCFrames);
+
+ void Next();
+ bool AtEnd() const;
+
+ uint32_t GetLevelCount() const { return mFrames.Length(); }
+ nsRubyContentFrame* GetFrameAtLevel(uint32_t aIndex) const;
+ void GetColumn(RubyColumn& aColumn) const;
+
+ private:
+ // Frames in this array are NOT necessary part of the current column.
+ // When in doubt, use GetFrameAtLevel to access it.
+ // See GetFrameAtLevel() and Next() for more info.
+ AutoTArray<nsRubyContentFrame*, RTC_ARRAY_SIZE + 1> mFrames;
+ // Whether we are on a column for intra-level whitespaces
+ bool mAtIntraLevelWhitespace;
+};
+
+/**
+ * Stores block-axis leadings produced from ruby annotations.
+ */
+struct RubyBlockLeadings {
+ nscoord mStart = 0;
+ nscoord mEnd = 0;
+
+ void Reset() { mStart = mEnd = 0; }
+ void Update(nscoord aStart, nscoord aEnd) {
+ mStart = std::max(mStart, aStart);
+ mEnd = std::max(mEnd, aEnd);
+ }
+ void Update(const RubyBlockLeadings& aOther) {
+ Update(aOther.mStart, aOther.mEnd);
+ }
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_RubyUtils_h_) */
diff --git a/layout/generic/ScrollAnchorContainer.cpp b/layout/generic/ScrollAnchorContainer.cpp
new file mode 100644
index 0000000000..5e1d8aa56d
--- /dev/null
+++ b/layout/generic/ScrollAnchorContainer.cpp
@@ -0,0 +1,797 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollAnchorContainer.h"
+#include <cstddef>
+
+#include "mozilla/dom/Text.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/ToString.h"
+#include "nsBlockFrame.h"
+#include "nsGfxScrollFrame.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+
+using namespace mozilla::dom;
+
+#ifdef DEBUG
+static mozilla::LazyLogModule sAnchorLog("scrollanchor");
+
+# define ANCHOR_LOG_WITH(anchor_, fmt, ...) \
+ MOZ_LOG(sAnchorLog, LogLevel::Debug, \
+ ("ANCHOR(%p, %s, root: %d): " fmt, (anchor_), \
+ (anchor_) \
+ ->Frame() \
+ ->PresContext() \
+ ->Document() \
+ ->GetDocumentURI() \
+ ->GetSpecOrDefault() \
+ .get(), \
+ (anchor_)->Frame()->mIsRoot, ##__VA_ARGS__));
+
+# define ANCHOR_LOG(fmt, ...) ANCHOR_LOG_WITH(this, fmt, ##__VA_ARGS__)
+#else
+# define ANCHOR_LOG(...)
+# define ANCHOR_LOG_WITH(...)
+#endif
+
+namespace mozilla::layout {
+
+nsHTMLScrollFrame* ScrollAnchorContainer::Frame() const {
+ return reinterpret_cast<nsHTMLScrollFrame*>(
+ ((char*)this) - offsetof(nsHTMLScrollFrame, mAnchor));
+}
+
+ScrollAnchorContainer::ScrollAnchorContainer(nsHTMLScrollFrame* aScrollFrame)
+ : mDisabled(false),
+ mAnchorMightBeSubOptimal(false),
+ mAnchorNodeIsDirty(true),
+ mApplyingAnchorAdjustment(false),
+ mSuppressAnchorAdjustment(false) {
+ MOZ_ASSERT(aScrollFrame == Frame());
+}
+
+ScrollAnchorContainer::~ScrollAnchorContainer() = default;
+
+ScrollAnchorContainer* ScrollAnchorContainer::FindFor(nsIFrame* aFrame) {
+ aFrame = aFrame->GetParent();
+ if (!aFrame) {
+ return nullptr;
+ }
+ nsIScrollableFrame* nearest = nsLayoutUtils::GetNearestScrollableFrame(
+ aFrame, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+ if (nearest) {
+ return nearest->Anchor();
+ }
+ return nullptr;
+}
+
+nsIScrollableFrame* ScrollAnchorContainer::ScrollableFrame() const {
+ return Frame()->GetScrollTargetFrame();
+}
+
+/**
+ * Set the appropriate frame flags for a frame that has become or is no longer
+ * an anchor node.
+ */
+static void SetAnchorFlags(const nsIFrame* aScrolledFrame,
+ nsIFrame* aAnchorNode, bool aInScrollAnchorChain) {
+ nsIFrame* frame = aAnchorNode;
+ while (frame && frame != aScrolledFrame) {
+ // TODO(emilio, bug 1629280): This commented out assertion below should
+ // hold, but it may not in the case of reparenting-during-reflow (due to
+ // inline fragmentation or such). That looks fishy!
+ //
+ // We should either invalidate the anchor when reparenting any frame on the
+ // chain, or fix up the chain flags.
+ //
+ // MOZ_DIAGNOSTIC_ASSERT(frame->IsInScrollAnchorChain() !=
+ // aInScrollAnchorChain);
+ frame->SetInScrollAnchorChain(aInScrollAnchorChain);
+ frame = frame->GetParent();
+ }
+ MOZ_ASSERT(frame,
+ "The anchor node should be a descendant of the scrolled frame");
+ // If needed, invalidate the frame so that we start/stop highlighting the
+ // anchor
+ if (StaticPrefs::layout_css_scroll_anchoring_highlight()) {
+ for (nsIFrame* frame = aAnchorNode->FirstContinuation(); !!frame;
+ frame = frame->GetNextContinuation()) {
+ frame->InvalidateFrame();
+ }
+ }
+}
+
+/**
+ * Compute the scrollable overflow rect [1] of aCandidate relative to
+ * aScrollFrame with all transforms applied.
+ *
+ * The specification is ambiguous about what can be selected as a scroll anchor,
+ * which makes the scroll anchoring bounding rect partially undefined [2]. This
+ * code attempts to match the implementation in Blink.
+ *
+ * An additional unspecified behavior is that any scrollable overflow before the
+ * border start edge in the block axis of aScrollFrame should be clamped. This
+ * is to prevent absolutely positioned descendant elements from being able to
+ * trigger scroll adjustments [3].
+ *
+ * [1]
+ * https://drafts.csswg.org/css-scroll-anchoring-1/#scroll-anchoring-bounding-rect
+ * [2] https://github.com/w3c/csswg-drafts/issues/3478
+ * [3] https://bugzilla.mozilla.org/show_bug.cgi?id=1519541
+ */
+static nsRect FindScrollAnchoringBoundingRect(const nsIFrame* aScrollFrame,
+ nsIFrame* aCandidate) {
+ MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrame(aScrollFrame, aCandidate));
+ if (!!Text::FromNodeOrNull(aCandidate->GetContent())) {
+ // This is a frame for a text node. The spec says we need to accumulate the
+ // union of all line boxes in the coordinate space of the scroll frame
+ // accounting for transforms.
+ //
+ // To do this, we translate and accumulate the overflow rect for each text
+ // continuation to the coordinate space of the nearest ancestor block
+ // frame. Then we transform the resulting rect into the coordinate space of
+ // the scroll frame.
+ //
+ // Transforms aren't allowed on non-replaced inline boxes, so we can assume
+ // that these text node continuations will have the same transform as their
+ // nearest block ancestor. And it should be faster to transform their union
+ // rather than individually transforming each overflow rect
+ //
+ // XXX for fragmented blocks, blockAncestor will be an ancestor only to the
+ // text continuations in the first block continuation. GetOffsetTo
+ // should continue to work, but is it correct with transforms or a
+ // performance hazard?
+ nsIFrame* blockAncestor =
+ nsLayoutUtils::FindNearestBlockAncestor(aCandidate);
+ MOZ_ASSERT(
+ nsLayoutUtils::IsProperAncestorFrame(aScrollFrame, blockAncestor));
+ nsRect bounding;
+ for (nsIFrame* continuation = aCandidate->FirstContinuation(); continuation;
+ continuation = continuation->GetNextContinuation()) {
+ nsRect overflowRect =
+ continuation->ScrollableOverflowRectRelativeToSelf();
+ overflowRect += continuation->GetOffsetTo(blockAncestor);
+ bounding = bounding.Union(overflowRect);
+ }
+ return nsLayoutUtils::TransformFrameRectToAncestor(blockAncestor, bounding,
+ aScrollFrame);
+ }
+
+ nsRect borderRect = aCandidate->GetRectRelativeToSelf();
+ nsRect overflowRect = aCandidate->ScrollableOverflowRectRelativeToSelf();
+
+ NS_ASSERTION(overflowRect.Contains(borderRect),
+ "overflow rect must include border rect, and the clamping logic "
+ "here depends on that");
+
+ // Clamp the scrollable overflow rect to the border start edge on the block
+ // axis of the scroll frame
+ WritingMode writingMode = aScrollFrame->GetWritingMode();
+ switch (writingMode.GetBlockDir()) {
+ case WritingMode::eBlockTB: {
+ overflowRect.SetBoxY(borderRect.Y(), overflowRect.YMost());
+ break;
+ }
+ case WritingMode::eBlockLR: {
+ overflowRect.SetBoxX(borderRect.X(), overflowRect.XMost());
+ break;
+ }
+ case WritingMode::eBlockRL: {
+ overflowRect.SetBoxX(overflowRect.X(), borderRect.XMost());
+ break;
+ }
+ }
+
+ nsRect transformed = nsLayoutUtils::TransformFrameRectToAncestor(
+ aCandidate, overflowRect, aScrollFrame);
+ return transformed;
+}
+
+/**
+ * Compute the offset between the scrollable overflow rect start edge of
+ * aCandidate and the scroll-port start edge of aScrollFrame, in the block axis
+ * of aScrollFrame.
+ */
+static nscoord FindScrollAnchoringBoundingOffset(
+ const nsHTMLScrollFrame* aScrollFrame, nsIFrame* aCandidate) {
+ WritingMode writingMode = aScrollFrame->GetWritingMode();
+ nsRect physicalBounding =
+ FindScrollAnchoringBoundingRect(aScrollFrame, aCandidate);
+ LogicalRect logicalBounding(writingMode, physicalBounding,
+ aScrollFrame->mScrolledFrame->GetSize());
+ return logicalBounding.BStart(writingMode);
+}
+
+bool ScrollAnchorContainer::CanMaintainAnchor() const {
+ if (!StaticPrefs::layout_css_scroll_anchoring_enabled()) {
+ return false;
+ }
+
+ // If we've been disabled due to heuristics, we don't anchor anymore.
+ if (mDisabled) {
+ return false;
+ }
+
+ const nsStyleDisplay& disp = *Frame()->StyleDisplay();
+ // Don't select a scroll anchor if the scroll frame has `overflow-anchor:
+ // none`.
+ if (disp.mOverflowAnchor != mozilla::StyleOverflowAnchor::Auto) {
+ return false;
+ }
+
+ // Or if the scroll frame has not been scrolled from the logical origin. This
+ // is not in the specification [1], but Blink does this.
+ //
+ // [1] https://github.com/w3c/csswg-drafts/issues/3319
+ if (Frame()->GetLogicalScrollPosition() == nsPoint()) {
+ return false;
+ }
+
+ // Or if there is perspective that could affect the scrollable overflow rect
+ // for descendant frames. This is not in the specification as Blink doesn't
+ // share this behavior with perspective [1].
+ //
+ // [1] https://github.com/w3c/csswg-drafts/issues/3322
+ if (Frame()->ChildrenHavePerspective()) {
+ return false;
+ }
+
+ return true;
+}
+
+void ScrollAnchorContainer::SelectAnchor() {
+ MOZ_ASSERT(Frame()->mScrolledFrame);
+ MOZ_ASSERT(mAnchorNodeIsDirty);
+
+ AUTO_PROFILER_LABEL("ScrollAnchorContainer::SelectAnchor", LAYOUT);
+ ANCHOR_LOG("Selecting anchor with scroll-port=%s.\n",
+ mozilla::ToString(Frame()->GetVisualOptimalViewingRect()).c_str());
+
+ // Select a new scroll anchor
+ nsIFrame* oldAnchor = mAnchorNode;
+ if (CanMaintainAnchor()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !Frame()->mScrolledFrame->IsInScrollAnchorChain(),
+ "Our scrolled frame can't serve as or contain an anchor for an "
+ "ancestor if it can maintain its own anchor");
+ ANCHOR_LOG("Beginning selection.\n");
+ mAnchorNode = FindAnchorIn(Frame()->mScrolledFrame);
+ } else {
+ ANCHOR_LOG("Skipping selection, doesn't maintain a scroll anchor.\n");
+ mAnchorNode = nullptr;
+ }
+ mAnchorMightBeSubOptimal =
+ mAnchorNode && mAnchorNode->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Update the anchor flags if needed
+ if (oldAnchor != mAnchorNode) {
+ ANCHOR_LOG("Anchor node has changed from (%p) to (%p).\n", oldAnchor,
+ mAnchorNode);
+
+ // Unset all flags for the old scroll anchor
+ if (oldAnchor) {
+ SetAnchorFlags(Frame()->mScrolledFrame, oldAnchor, false);
+ }
+
+ // Set all flags for the new scroll anchor
+ if (mAnchorNode) {
+ // Anchor selection will never select a descendant of a nested scroll
+ // frame which maintains an anchor, so we can set flags without
+ // conflicting with other scroll anchor containers.
+ SetAnchorFlags(Frame()->mScrolledFrame, mAnchorNode, true);
+ }
+ } else {
+ ANCHOR_LOG("Anchor node has remained (%p).\n", mAnchorNode);
+ }
+
+ // Calculate the position to use for scroll adjustments
+ if (mAnchorNode) {
+ mLastAnchorOffset = FindScrollAnchoringBoundingOffset(Frame(), mAnchorNode);
+ ANCHOR_LOG("Using last anchor offset = %d.\n", mLastAnchorOffset);
+ } else {
+ mLastAnchorOffset = 0;
+ }
+
+ mAnchorNodeIsDirty = false;
+}
+
+void ScrollAnchorContainer::UserScrolled() {
+ if (mApplyingAnchorAdjustment) {
+ return;
+ }
+ InvalidateAnchor();
+
+ if (!StaticPrefs::
+ layout_css_scroll_anchoring_reset_heuristic_during_animation() &&
+ Frame()->ScrollAnimationState().contains(
+ nsIScrollableFrame::AnimationState::APZInProgress)) {
+ // We'd want to skip resetting our heuristic while APZ is running an async
+ // scroll because this UserScrolled function gets called on every refresh
+ // driver's tick during running the async scroll, thus it will clobber the
+ // heuristic.
+ return;
+ }
+
+ mHeuristic.Reset();
+}
+
+void ScrollAnchorContainer::DisablingHeuristic::Reset() {
+ mConsecutiveScrollAnchoringAdjustments = SaturateUint32(0);
+ mConsecutiveScrollAnchoringAdjustmentLength = 0;
+ mTimeStamp = {};
+}
+
+void ScrollAnchorContainer::AdjustmentMade(nscoord aAdjustment) {
+ MOZ_ASSERT(!mDisabled, "How?");
+ mDisabled = mHeuristic.AdjustmentMade(*this, aAdjustment);
+}
+
+bool ScrollAnchorContainer::DisablingHeuristic::AdjustmentMade(
+ const ScrollAnchorContainer& aAnchor, nscoord aAdjustment) {
+ // A reasonably large number of times that we want to check for this. If we
+ // haven't hit this limit after these many attempts we assume we'll never hit
+ // it.
+ //
+ // This is to prevent the number getting too large and making the limit round
+ // to zero by mere precision error.
+ //
+ // 100k should be enough for anyone :)
+ static const uint32_t kAnchorCheckCountLimit = 100000;
+
+ // Zero-length adjustments are common & don't have side effects, so we don't
+ // want them to consider them here; they'd bias our average towards 0.
+ MOZ_ASSERT(aAdjustment, "Don't call this API for zero-length adjustments");
+
+ const uint32_t maxConsecutiveAdjustments =
+ StaticPrefs::layout_css_scroll_anchoring_max_consecutive_adjustments();
+
+ if (!maxConsecutiveAdjustments) {
+ return false;
+ }
+
+ // We don't high resolution for this timestamp.
+ const auto now = TimeStamp::NowLoRes();
+ if (mConsecutiveScrollAnchoringAdjustments++ == 0) {
+ MOZ_ASSERT(mTimeStamp.IsNull());
+ mTimeStamp = now;
+ } else if (
+ const auto timeoutMs = StaticPrefs::
+ layout_css_scroll_anchoring_max_consecutive_adjustments_timeout_ms();
+ timeoutMs && (now - mTimeStamp).ToMilliseconds() > timeoutMs) {
+ Reset();
+ return false;
+ }
+
+ mConsecutiveScrollAnchoringAdjustmentLength = NSCoordSaturatingAdd(
+ mConsecutiveScrollAnchoringAdjustmentLength, aAdjustment);
+
+ uint32_t consecutiveAdjustments =
+ mConsecutiveScrollAnchoringAdjustments.value();
+ if (consecutiveAdjustments < maxConsecutiveAdjustments ||
+ consecutiveAdjustments > kAnchorCheckCountLimit) {
+ return false;
+ }
+
+ auto cssPixels =
+ CSSPixel::FromAppUnits(mConsecutiveScrollAnchoringAdjustmentLength);
+ double average = double(cssPixels) / consecutiveAdjustments;
+ uint32_t minAverage = StaticPrefs::
+ layout_css_scroll_anchoring_min_average_adjustment_threshold();
+ if (MOZ_LIKELY(std::abs(average) >= double(minAverage))) {
+ return false;
+ }
+
+ ANCHOR_LOG_WITH(&aAnchor,
+ "Disabled scroll anchoring for container: "
+ "%f average, %f total out of %u consecutive adjustments\n",
+ average, float(cssPixels), consecutiveAdjustments);
+
+ AutoTArray<nsString, 3> arguments;
+ arguments.AppendElement()->AppendInt(consecutiveAdjustments);
+ arguments.AppendElement()->AppendFloat(average);
+ arguments.AppendElement()->AppendFloat(cssPixels);
+
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
+ aAnchor.Frame()->PresContext()->Document(),
+ nsContentUtils::eLAYOUT_PROPERTIES,
+ "ScrollAnchoringDisabledInContainer",
+ arguments);
+ return true;
+}
+
+void ScrollAnchorContainer::SuppressAdjustments() {
+ ANCHOR_LOG("Received a scroll anchor suppression for %p.\n", this);
+ mSuppressAnchorAdjustment = true;
+
+ // Forward to our parent if appropriate, that is, if we don't maintain an
+ // anchor, and we can't maintain one.
+ //
+ // Note that we need to check !CanMaintainAnchor(), instead of just whether
+ // our frame is in the anchor chain of our ancestor as InvalidateAnchor()
+ // does, given some suppression triggers apply even for nodes that are not in
+ // the anchor chain.
+ if (!mAnchorNode && !CanMaintainAnchor()) {
+ if (ScrollAnchorContainer* container = FindFor(Frame())) {
+ ANCHOR_LOG(" > Forwarding to parent anchor\n");
+ container->SuppressAdjustments();
+ }
+ }
+}
+
+void ScrollAnchorContainer::InvalidateAnchor(ScheduleSelection aSchedule) {
+ ANCHOR_LOG("Invalidating scroll anchor %p for %p.\n", mAnchorNode, this);
+
+ if (mAnchorNode) {
+ SetAnchorFlags(Frame()->mScrolledFrame, mAnchorNode, false);
+ } else if (Frame()->mScrolledFrame->IsInScrollAnchorChain()) {
+ ANCHOR_LOG(" > Forwarding to parent anchor\n");
+ // We don't maintain an anchor, and our scrolled frame is in the anchor
+ // chain of an ancestor. Invalidate that anchor.
+ //
+ // NOTE: Intentionally not forwarding aSchedule: Scheduling is always safe
+ // and not doing so is just an optimization.
+ FindFor(Frame())->InvalidateAnchor();
+ }
+ mAnchorNode = nullptr;
+ mAnchorMightBeSubOptimal = false;
+ mAnchorNodeIsDirty = true;
+ mLastAnchorOffset = 0;
+
+ if (!CanMaintainAnchor() || aSchedule == ScheduleSelection::No) {
+ return;
+ }
+
+ Frame()->PresShell()->PostPendingScrollAnchorSelection(this);
+}
+
+void ScrollAnchorContainer::Destroy() {
+ InvalidateAnchor(ScheduleSelection::No);
+}
+
+void ScrollAnchorContainer::ApplyAdjustments() {
+ if (!mAnchorNode || mAnchorNodeIsDirty || mDisabled ||
+ Frame()->HasPendingScrollRestoration() ||
+ (StaticPrefs::
+ layout_css_scroll_anchoring_reset_heuristic_during_animation() &&
+ Frame()->IsProcessingScrollEvent()) ||
+ Frame()->ScrollAnimationState().contains(
+ nsIScrollableFrame::AnimationState::TriggeredByScript) ||
+ Frame()->GetScrollPosition() == nsPoint()) {
+ ANCHOR_LOG(
+ "Ignoring post-reflow (anchor=%p, dirty=%d, disabled=%d, "
+ "pendingRestoration=%d, scrollevent=%d, scriptAnimating=%d, "
+ "zeroScrollPos=%d pendingSuppression=%d, "
+ "container=%p).\n",
+ mAnchorNode, mAnchorNodeIsDirty, mDisabled,
+ Frame()->HasPendingScrollRestoration(),
+ Frame()->IsProcessingScrollEvent(),
+ Frame()->ScrollAnimationState().contains(
+ nsIScrollableFrame::AnimationState::TriggeredByScript),
+ Frame()->GetScrollPosition() == nsPoint(), mSuppressAnchorAdjustment,
+ this);
+ if (mSuppressAnchorAdjustment) {
+ mSuppressAnchorAdjustment = false;
+ InvalidateAnchor();
+ }
+ return;
+ }
+
+ nscoord current = FindScrollAnchoringBoundingOffset(Frame(), mAnchorNode);
+ nscoord logicalAdjustment = current - mLastAnchorOffset;
+ WritingMode writingMode = Frame()->GetWritingMode();
+
+ ANCHOR_LOG("Anchor has moved from %d to %d.\n", mLastAnchorOffset, current);
+
+ auto maybeInvalidate = MakeScopeExit([&] {
+ if (mAnchorMightBeSubOptimal &&
+ StaticPrefs::layout_css_scroll_anchoring_reselect_if_suboptimal()) {
+ ANCHOR_LOG(
+ "Anchor might be suboptimal, invalidating to try finding a better "
+ "one\n");
+ InvalidateAnchor();
+ }
+ });
+
+ if (logicalAdjustment == 0) {
+ ANCHOR_LOG("Ignoring zero delta anchor adjustment for %p.\n", this);
+ mSuppressAnchorAdjustment = false;
+ return;
+ }
+
+ if (mSuppressAnchorAdjustment) {
+ ANCHOR_LOG("Applying anchor adjustment suppression for %p.\n", this);
+ mSuppressAnchorAdjustment = false;
+ InvalidateAnchor();
+ return;
+ }
+
+ ANCHOR_LOG("Applying anchor adjustment of %d in %s with anchor %p.\n",
+ logicalAdjustment, ToString(writingMode).c_str(), mAnchorNode);
+
+ AdjustmentMade(logicalAdjustment);
+
+ nsPoint physicalAdjustment;
+ switch (writingMode.GetBlockDir()) {
+ case WritingMode::eBlockTB: {
+ physicalAdjustment.y = logicalAdjustment;
+ break;
+ }
+ case WritingMode::eBlockLR: {
+ physicalAdjustment.x = logicalAdjustment;
+ break;
+ }
+ case WritingMode::eBlockRL: {
+ physicalAdjustment.x = -logicalAdjustment;
+ break;
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(!mApplyingAnchorAdjustment);
+ // We should use AutoRestore here, but that doesn't work with bitfields
+ mApplyingAnchorAdjustment = true;
+ Frame()->ScrollToInternal(
+ Frame()->GetScrollPosition() + physicalAdjustment, ScrollMode::Instant,
+ StaticPrefs::layout_css_scroll_anchoring_absolute_update()
+ ? ScrollOrigin::AnchorAdjustment
+ : ScrollOrigin::Relative);
+ mApplyingAnchorAdjustment = false;
+
+ if (Frame()->mIsRoot) {
+ Frame()->PresShell()->RootScrollFrameAdjusted(physicalAdjustment.y);
+ }
+
+ // The anchor position may not be in the same relative position after
+ // adjustment. Update ourselves so we have consistent state.
+ mLastAnchorOffset = FindScrollAnchoringBoundingOffset(Frame(), mAnchorNode);
+}
+
+ScrollAnchorContainer::ExamineResult
+ScrollAnchorContainer::ExamineAnchorCandidate(nsIFrame* aFrame) const {
+#ifdef DEBUG_FRAME_DUMP
+ nsCString tag = aFrame->ListTag();
+ ANCHOR_LOG("\tVisiting frame=%s (%p).\n", tag.get(), aFrame);
+#else
+ ANCHOR_LOG("\t\tVisiting frame=%p.\n", aFrame);
+#endif
+ bool isText = !!Text::FromNodeOrNull(aFrame->GetContent());
+ bool isContinuation = !!aFrame->GetPrevContinuation();
+
+ if (isText && isContinuation) {
+ ANCHOR_LOG("\t\tExcluding continuation text node.\n");
+ return ExamineResult::Exclude;
+ }
+
+ // Check if the author has opted out of scroll anchoring for this frame
+ // and its descendants.
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ if (disp->mOverflowAnchor == mozilla::StyleOverflowAnchor::None) {
+ ANCHOR_LOG("\t\tExcluding `overflow-anchor: none`.\n");
+ return ExamineResult::Exclude;
+ }
+
+ // Sticky positioned elements can move with the scroll frame, making them
+ // unsuitable scroll anchors. This isn't in the specification yet [1], but
+ // matches Blink's implementation.
+ //
+ // [1] https://github.com/w3c/csswg-drafts/issues/3319
+ if (aFrame->IsStickyPositioned()) {
+ ANCHOR_LOG("\t\tExcluding `position: sticky`.\n");
+ return ExamineResult::Exclude;
+ }
+
+ // The frame for a <br> element has a non-zero area, but Blink treats them
+ // as if they have no area, so exclude them specially.
+ if (aFrame->IsBrFrame()) {
+ ANCHOR_LOG("\t\tExcluding <br>.\n");
+ return ExamineResult::Exclude;
+ }
+
+ // Exclude frames that aren't accessible to content.
+ bool isChrome =
+ aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess();
+ bool isPseudo = aFrame->Style()->IsPseudoElement();
+ if (isChrome && !isPseudo) {
+ ANCHOR_LOG("\t\tExcluding chrome only content.\n");
+ return ExamineResult::Exclude;
+ }
+
+ const bool isReplaced = aFrame->IsReplaced();
+ const bool isNonReplacedInline =
+ aFrame->StyleDisplay()->IsInlineInsideStyle() && !isReplaced;
+
+ const bool isAnonBox = aFrame->Style()->IsAnonBox();
+
+ // See if this frame has or could maintain its own anchor node.
+ const bool isScrollableWithAnchor = [&] {
+ nsIScrollableFrame* scrollable = do_QueryFrame(aFrame);
+ if (!scrollable) {
+ return false;
+ }
+ auto* anchor = scrollable->Anchor();
+ return anchor->AnchorNode() || anchor->CanMaintainAnchor();
+ }();
+
+ // We don't allow scroll anchors to be selected inside of nested scrollable
+ // frames which maintain an anchor node as it's not clear how an anchor
+ // adjustment should apply to multiple scrollable frames.
+ //
+ // It is important to descend into _some_ scrollable frames, specially
+ // overflow: hidden, as those don't generally maintain their own anchors, and
+ // it is a common case in the wild where scroll anchoring ought to work.
+ //
+ // We also don't allow scroll anchors to be selected inside of replaced
+ // elements (like <img>, <video>, <svg>...) as they behave atomically. SVG
+ // uses a different layout model than CSS, and the specification doesn't say
+ // it should apply anyway.
+ //
+ // [1] https://github.com/w3c/csswg-drafts/issues/3477
+ const bool canDescend = !isScrollableWithAnchor && !isReplaced;
+
+ // Non-replaced inline boxes (including ruby frames) and anon boxes are not
+ // acceptable anchors, so we descend if possible, or otherwise exclude them
+ // altogether.
+ if (!isText && (isNonReplacedInline || isAnonBox)) {
+ ANCHOR_LOG(
+ "\t\tSearching descendants of anon or non-replaced inline box (a=%d, "
+ "i=%d).\n",
+ isAnonBox, isNonReplacedInline);
+ if (canDescend) {
+ return ExamineResult::PassThrough;
+ }
+ return ExamineResult::Exclude;
+ }
+
+ // Find the scroll anchoring bounding rect.
+ nsRect rect = FindScrollAnchoringBoundingRect(Frame(), aFrame);
+ ANCHOR_LOG("\t\trect = [%d %d x %d %d].\n", rect.x, rect.y, rect.width,
+ rect.height);
+
+ // Check if this frame is visible in the scroll port. This will exclude rects
+ // with zero sized area. The specification is ambiguous about this [1], but
+ // this matches Blink's implementation.
+ //
+ // [1] https://github.com/w3c/csswg-drafts/issues/3483
+ nsRect visibleRect;
+ if (!visibleRect.IntersectRect(rect,
+ Frame()->GetVisualOptimalViewingRect())) {
+ return ExamineResult::Exclude;
+ }
+
+ // It's not clear what the scroll anchoring bounding rect is, for elements
+ // fragmented in the block direction (e.g. across column or page breaks).
+ //
+ // Inline-fragmented elements other than text shouldn't get here because of
+ // the isNonReplacedInline check.
+ //
+ // For text nodes that are fragmented, it's specified that we need to consider
+ // the union of its line boxes.
+ //
+ // So for text nodes we handle them by including the union of line boxes in
+ // the bounding rect of the primary frame, and not selecting any
+ // continuations.
+ //
+ // For block-outside elements we choose to consider the bounding rect of each
+ // frame individually, allowing ourselves to descend into any frame, but only
+ // selecting a frame if it's not a continuation.
+ if (canDescend && isContinuation) {
+ ANCHOR_LOG("\t\tSearching descendants of a continuation.\n");
+ return ExamineResult::PassThrough;
+ }
+
+ // If this frame is fully visible, then select it as the scroll anchor.
+ if (visibleRect.IsEqualEdges(rect)) {
+ ANCHOR_LOG("\t\tFully visible, taking.\n");
+ return ExamineResult::Accept;
+ }
+
+ // If we can't descend into this frame, then select it as the scroll anchor.
+ if (!canDescend) {
+ ANCHOR_LOG("\t\tIntersects a frame that we can't descend into, taking.\n");
+ return ExamineResult::Accept;
+ }
+
+ // It must be partially visible and we can descend into this frame. Examine
+ // its children for a better scroll anchor or fall back to this one.
+ ANCHOR_LOG("\t\tIntersects valid candidate, checking descendants.\n");
+ return ExamineResult::Traverse;
+}
+
+nsIFrame* ScrollAnchorContainer::FindAnchorIn(nsIFrame* aFrame) const {
+ // Visit the child lists of this frame
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ // Skip child lists that contain out-of-flow frames, we'll visit them by
+ // following placeholders in the in-flow lists so that we visit these
+ // frames in DOM order.
+ // XXX do we actually need to exclude FrameChildListID::OverflowOutOfFlow
+ // too?
+ if (listID == FrameChildListID::Absolute ||
+ listID == FrameChildListID::Fixed ||
+ listID == FrameChildListID::Float ||
+ listID == FrameChildListID::OverflowOutOfFlow) {
+ continue;
+ }
+
+ // Search the child list, and return if we selected an anchor
+ if (nsIFrame* anchor = FindAnchorInList(list)) {
+ return anchor;
+ }
+ }
+
+ // The spec requires us to do an extra pass to visit absolutely positioned
+ // frames a second time after all the children of their containing block have
+ // been visited.
+ //
+ // It's not clear why this is needed [1], but it matches Blink's
+ // implementation, and is needed for a WPT test.
+ //
+ // [1] https://github.com/w3c/csswg-drafts/issues/3465
+ const nsFrameList& absPosList =
+ aFrame->GetChildList(FrameChildListID::Absolute);
+ if (nsIFrame* anchor = FindAnchorInList(absPosList)) {
+ return anchor;
+ }
+
+ return nullptr;
+}
+
+nsIFrame* ScrollAnchorContainer::FindAnchorInList(
+ const nsFrameList& aFrameList) const {
+ for (nsIFrame* child : aFrameList) {
+ // If this is a placeholder, try to follow it to the out of flow frame.
+ nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(child);
+ if (child != realFrame) {
+ // If the out of flow frame is not a descendant of our scroll frame,
+ // then it must have a different containing block and cannot be an
+ // anchor node.
+ if (!nsLayoutUtils::IsProperAncestorFrame(Frame(), realFrame)) {
+ ANCHOR_LOG(
+ "\t\tSkipping out of flow frame that is not a descendant of the "
+ "scroll frame.\n");
+ continue;
+ }
+ ANCHOR_LOG("\t\tFollowing placeholder to out of flow frame.\n");
+ child = realFrame;
+ }
+
+ // Perform the candidate examination algorithm
+ ExamineResult examine = ExamineAnchorCandidate(child);
+
+ // See the comment before the definition of `ExamineResult` in
+ // `ScrollAnchorContainer.h` for an explanation of this behavior.
+ switch (examine) {
+ case ExamineResult::Exclude: {
+ continue;
+ }
+ case ExamineResult::PassThrough: {
+ nsIFrame* candidate = FindAnchorIn(child);
+ if (!candidate) {
+ continue;
+ }
+ return candidate;
+ }
+ case ExamineResult::Traverse: {
+ nsIFrame* candidate = FindAnchorIn(child);
+ if (!candidate) {
+ return child;
+ }
+ return candidate;
+ }
+ case ExamineResult::Accept: {
+ return child;
+ }
+ }
+ }
+ return nullptr;
+}
+
+} // namespace mozilla::layout
diff --git a/layout/generic/ScrollAnchorContainer.h b/layout/generic/ScrollAnchorContainer.h
new file mode 100644
index 0000000000..e86a937da6
--- /dev/null
+++ b/layout/generic/ScrollAnchorContainer.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollAnchorContainer_h_
+#define mozilla_layout_ScrollAnchorContainer_h_
+
+#include "nsPoint.h"
+#include "mozilla/Saturate.h"
+#include "mozilla/TimeStamp.h"
+
+class nsFrameList;
+class nsHTMLScrollFrame;
+class nsIFrame;
+class nsIScrollableFrame;
+
+namespace mozilla::layout {
+
+/**
+ * A scroll anchor container finds a descendent element of a scrollable frame
+ * to be an anchor node. After every reflow, the scroll anchor will apply
+ * scroll adjustments to keep the anchor node in the same relative position.
+ *
+ * See: https://drafts.csswg.org/css-scroll-anchoring/
+ */
+class ScrollAnchorContainer final {
+ public:
+ explicit ScrollAnchorContainer(nsHTMLScrollFrame* aScrollFrame);
+ ~ScrollAnchorContainer();
+
+ /**
+ * Returns the nearest scroll anchor container that could select aFrame as an
+ * anchor node.
+ */
+ static ScrollAnchorContainer* FindFor(nsIFrame* aFrame);
+
+ /**
+ * Returns the frame that is the selected anchor node or null if no anchor
+ * is selected.
+ */
+ nsIFrame* AnchorNode() const { return mAnchorNode; }
+
+ // The owner of this scroll anchor container.
+ nsHTMLScrollFrame* Frame() const;
+
+ /**
+ * Returns the frame that owns this scroll anchor container as a scrollable
+ * frame. This is always non-null.
+ */
+ nsIScrollableFrame* ScrollableFrame() const;
+
+ /**
+ * Find a suitable anchor node among the descendants of the scrollable frame.
+ * This should only be called after the scroll anchor has been invalidated.
+ */
+ void SelectAnchor();
+
+ /**
+ * Whether this scroll frame can maintain an anchor node at the moment.
+ */
+ bool CanMaintainAnchor() const;
+
+ /**
+ * Notify the scroll anchor container that its scroll frame has been
+ * scrolled by a user and should invalidate itself.
+ */
+ void UserScrolled();
+
+ /**
+ * Notify the scroll anchor container that a reflow has happened and it
+ * should query its anchor to see if a scroll adjustment needs to occur.
+ */
+ void ApplyAdjustments();
+
+ /**
+ * Notify the scroll anchor container that it should suppress any scroll
+ * adjustment that may happen after the next layout flush.
+ */
+ void SuppressAdjustments();
+
+ /**
+ * Notify this scroll anchor container that its anchor node should be
+ * invalidated, and recomputed at the next available opportunity if
+ * ScheduleSelection is Yes.
+ */
+ enum class ScheduleSelection { No, Yes };
+ void InvalidateAnchor(ScheduleSelection = ScheduleSelection::Yes);
+
+ /**
+ * Notify this scroll anchor container that it will be destroyed along with
+ * its parent frame.
+ */
+ void Destroy();
+
+ private:
+ // Represents an assessment of a frame's suitability as a scroll anchor,
+ // from the scroll-anchoring spec's "candidate examination algorithm":
+ // https://drafts.csswg.org/css-scroll-anchoring-1/#candidate-examination
+ enum class ExamineResult {
+ // The frame is an excluded subtree or fully clipped and should be ignored.
+ // This corresponds with step 1 in the algorithm.
+ Exclude,
+ // This frame is an anonymous or inline box and its descendants should be
+ // searched to find an anchor node. If none are found, then continue
+ // searching. This is implied by the prologue of the algorithm, and
+ // should be made explicit in the spec [1].
+ //
+ // [1] https://github.com/w3c/csswg-drafts/issues/3489
+ PassThrough,
+ // The frame is partially visible and its descendants should be searched to
+ // find an anchor node. If none are found then this frame should be
+ // selected. This corresponds with step 3 in the algorithm.
+ Traverse,
+ // The frame is fully visible and should be selected as an anchor node. This
+ // corresponds with step 2 in the algorithm.
+ Accept,
+ };
+
+ ExamineResult ExamineAnchorCandidate(nsIFrame* aPrimaryFrame) const;
+
+ // Search a frame's children to find an anchor node. Returns the frame for a
+ // valid anchor node, if one was found in the frames descendants, or null
+ // otherwise.
+ nsIFrame* FindAnchorIn(nsIFrame* aFrame) const;
+
+ // Search a child list to find an anchor node. Returns the frame for a valid
+ // anchor node, if one was found in this child list, or null otherwise.
+ nsIFrame* FindAnchorInList(const nsFrameList& aFrameList) const;
+
+ // Notes that a given adjustment has happened, and maybe disables scroll
+ // anchoring on this scroller altogether based on various prefs.
+ void AdjustmentMade(nscoord aAdjustment);
+
+ // The anchor node that we will scroll to keep in the same relative position
+ // after reflows. This may be null if we were not able to select a valid
+ // scroll anchor
+ nsIFrame* mAnchorNode = nullptr;
+
+ // The last offset of the scroll anchor node's scrollable overflow rect start
+ // edge relative to the scroll-port start edge, in the block axis of the
+ // scroll frame. This is used for calculating the distance to scroll to keep
+ // the anchor node in the same relative position
+ nscoord mLastAnchorOffset = 0;
+
+ struct DisablingHeuristic {
+ // The number of consecutive scroll anchoring adjustments that have happened
+ // without a user scroll.
+ SaturateUint32 mConsecutiveScrollAnchoringAdjustments{0};
+
+ // The total length that has been adjusted by all the consecutive
+ // adjustments referenced above. Note that this is a sum, so that
+ // oscillating adjustments average towards zero.
+ nscoord mConsecutiveScrollAnchoringAdjustmentLength{0};
+
+ // The time we started checking for adjustments.
+ TimeStamp mTimeStamp;
+
+ // Returns whether anchoring should get disabled.
+ bool AdjustmentMade(const ScrollAnchorContainer&, nscoord aAdjustment);
+ void Reset();
+ } mHeuristic;
+
+ // True if we've been disabled by the heuristic controlled by
+ // layout.css.scroll-anchoring.max-consecutive-adjustments and
+ // layout.css.scroll-anchoring.min-adjustment-threshold.
+ bool mDisabled : 1;
+
+ // True if when we selected the current scroll anchor, there were unlaid out
+ // children that could be better anchor nodes after layout.
+ bool mAnchorMightBeSubOptimal : 1;
+ // True if we should recalculate our anchor node at the next chance
+ bool mAnchorNodeIsDirty : 1;
+ // True if we are applying a scroll anchor adjustment
+ bool mApplyingAnchorAdjustment : 1;
+ // True if we should suppress anchor adjustments
+ bool mSuppressAnchorAdjustment : 1;
+};
+
+} // namespace mozilla::layout
+
+#endif // mozilla_layout_ScrollAnchorContainer_h_
diff --git a/layout/generic/ScrollAnimationBezierPhysics.cpp b/layout/generic/ScrollAnimationBezierPhysics.cpp
new file mode 100644
index 0000000000..ffc69a9fa4
--- /dev/null
+++ b/layout/generic/ScrollAnimationBezierPhysics.cpp
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollAnimationBezierPhysics.h"
+#include "mozilla/StaticPrefs_general.h"
+
+using namespace mozilla;
+
+ScrollAnimationBezierPhysics::ScrollAnimationBezierPhysics(
+ const nsPoint& aStartPos,
+ const ScrollAnimationBezierPhysicsSettings& aSettings)
+ : mSettings(aSettings), mStartPos(aStartPos), mIsFirstIteration(true) {}
+
+void ScrollAnimationBezierPhysics::Update(const TimeStamp& aTime,
+ const nsPoint& aDestination,
+ const nsSize& aCurrentVelocity) {
+ if (mIsFirstIteration) {
+ InitializeHistory(aTime);
+ }
+
+ TimeDuration duration = ComputeDuration(aTime);
+ nsSize currentVelocity = aCurrentVelocity;
+
+ if (!mIsFirstIteration) {
+ // If an additional event has not changed the destination, then do not let
+ // another minimum duration reset slow things down. If it would then
+ // instead continue with the existing timing function.
+ if (aDestination == mDestination &&
+ aTime + duration > mStartTime + mDuration) {
+ return;
+ }
+
+ currentVelocity = VelocityAt(aTime);
+ mStartPos = PositionAt(aTime);
+ }
+
+ mStartTime = aTime;
+ mDuration = duration;
+ mDestination = aDestination;
+ InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
+ aDestination.x);
+ InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
+ aDestination.y);
+ mIsFirstIteration = false;
+}
+
+void ScrollAnimationBezierPhysics::ApplyContentShift(
+ const CSSPoint& aShiftDelta) {
+ nsPoint shiftDelta = CSSPoint::ToAppUnits(aShiftDelta);
+ mStartPos += shiftDelta;
+ mDestination += shiftDelta;
+}
+
+TimeDuration ScrollAnimationBezierPhysics::ComputeDuration(
+ const TimeStamp& aTime) {
+ // Average last 3 delta durations (rounding errors up to 2ms are negligible
+ // for us)
+ int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
+ mPrevEventTime[2] = mPrevEventTime[1];
+ mPrevEventTime[1] = mPrevEventTime[0];
+ mPrevEventTime[0] = aTime;
+
+ // Modulate duration according to events rate (quicker events -> shorter
+ // durations). The desired effect is to use longer duration when scrolling
+ // slowly, such that it's easier to follow, but reduce the duration to make it
+ // feel more snappy when scrolling quickly. To reduce fluctuations of the
+ // duration, we average event intervals using the recent 4 timestamps (now +
+ // three prev -> 3 intervals).
+ int32_t durationMS =
+ clamped<int32_t>(eventsDeltaMs * mSettings.mIntervalRatio,
+ mSettings.mMinMS, mSettings.mMaxMS);
+
+ return TimeDuration::FromMilliseconds(durationMS);
+}
+
+void ScrollAnimationBezierPhysics::InitializeHistory(const TimeStamp& aTime) {
+ // Starting a new scroll (i.e. not when extending an existing scroll
+ // animation), create imaginary prev timestamps with maximum relevant
+ // intervals between them.
+
+ // Longest relevant interval (which results in maximum duration)
+ TimeDuration maxDelta = TimeDuration::FromMilliseconds(
+ mSettings.mMaxMS / mSettings.mIntervalRatio);
+ mPrevEventTime[0] = aTime - maxDelta;
+ mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
+ mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
+}
+
+void ScrollAnimationBezierPhysics::InitTimingFunction(
+ SMILKeySpline& aTimingFunction, nscoord aCurrentPos,
+ nscoord aCurrentVelocity, nscoord aDestination) {
+ if (aDestination == aCurrentPos ||
+ StaticPrefs::general_smoothScroll_currentVelocityWeighting() == 0) {
+ aTimingFunction.Init(
+ 0, 0, 1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(),
+ 1);
+ return;
+ }
+
+ const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
+ double slope =
+ aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
+ double normalization = sqrt(1.0 + slope * slope);
+ double dt = 1.0 / normalization *
+ StaticPrefs::general_smoothScroll_currentVelocityWeighting();
+ double dxy = slope / normalization *
+ StaticPrefs::general_smoothScroll_currentVelocityWeighting();
+ aTimingFunction.Init(
+ dt, dxy,
+ 1 - StaticPrefs::general_smoothScroll_stopDecelerationWeighting(), 1);
+}
+
+nsPoint ScrollAnimationBezierPhysics::PositionAt(const TimeStamp& aTime) {
+ if (IsFinished(aTime)) {
+ return mDestination;
+ }
+
+ double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
+ double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
+ return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x +
+ progressX * mDestination.x),
+ NSToCoordRound((1 - progressY) * mStartPos.y +
+ progressY * mDestination.y));
+}
+
+nsSize ScrollAnimationBezierPhysics::VelocityAt(const TimeStamp& aTime) {
+ if (IsFinished(aTime)) {
+ return nsSize(0, 0);
+ }
+
+ double timeProgress = ProgressAt(aTime);
+ return nsSize(VelocityComponent(timeProgress, mTimingFunctionX, mStartPos.x,
+ mDestination.x),
+ VelocityComponent(timeProgress, mTimingFunctionY, mStartPos.y,
+ mDestination.y));
+}
+
+nscoord ScrollAnimationBezierPhysics::VelocityComponent(
+ double aTimeProgress, const SMILKeySpline& aTimingFunction, nscoord aStart,
+ nscoord aDestination) const {
+ double dt, dxy;
+ aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
+ if (dt == 0) return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
+
+ const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
+ double slope = dxy / dt;
+ return NSToCoordRound(slope * (aDestination - aStart) /
+ (mDuration / oneSecond));
+}
diff --git a/layout/generic/ScrollAnimationBezierPhysics.h b/layout/generic/ScrollAnimationBezierPhysics.h
new file mode 100644
index 0000000000..c3bf36ee71
--- /dev/null
+++ b/layout/generic/ScrollAnimationBezierPhysics.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollAnimationBezierPhysics_h_
+#define mozilla_layout_ScrollAnimationBezierPhysics_h_
+
+#include "ScrollAnimationPhysics.h"
+#include "mozilla/SMILKeySpline.h"
+
+namespace mozilla {
+
+struct ScrollAnimationBezierPhysicsSettings {
+ // These values are minimum and maximum animation duration per event,
+ // and a global ratio which defines how longer is the animation's duration
+ // compared to the average recent events intervals (such that for a relatively
+ // consistent events rate, the next event arrives before current animation
+ // ends)
+ int32_t mMinMS;
+ int32_t mMaxMS;
+ double mIntervalRatio;
+};
+
+// This class implements a cubic bezier timing function and automatically
+// adapts the animation duration based on the scrolling rate.
+class ScrollAnimationBezierPhysics final : public ScrollAnimationPhysics {
+ public:
+ explicit ScrollAnimationBezierPhysics(
+ const nsPoint& aStartPos,
+ const ScrollAnimationBezierPhysicsSettings& aSettings);
+
+ void Update(const TimeStamp& aTime, const nsPoint& aDestination,
+ const nsSize& aCurrentVelocity) override;
+
+ void ApplyContentShift(const CSSPoint& aShiftDelta) override;
+
+ // Get the velocity at a point in time in nscoords/sec.
+ nsSize VelocityAt(const TimeStamp& aTime) override;
+
+ // Returns the expected scroll position at a given point in time, in app
+ // units, relative to the scroll frame.
+ nsPoint PositionAt(const TimeStamp& aTime) override;
+
+ bool IsFinished(const TimeStamp& aTime) override {
+ return aTime > mStartTime + mDuration;
+ }
+
+ protected:
+ double ProgressAt(const TimeStamp& aTime) const {
+ return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
+ }
+
+ nscoord VelocityComponent(double aTimeProgress,
+ const SMILKeySpline& aTimingFunction,
+ nscoord aStart, nscoord aDestination) const;
+
+ // Calculate duration, possibly dynamically according to events rate and
+ // event origin. (also maintain previous timestamps - which are only used
+ // here).
+ TimeDuration ComputeDuration(const TimeStamp& aTime);
+
+ // Initializes the timing function in such a way that the current velocity is
+ // preserved.
+ void InitTimingFunction(SMILKeySpline& aTimingFunction, nscoord aCurrentPos,
+ nscoord aCurrentVelocity, nscoord aDestination);
+
+ // Initialize event history.
+ void InitializeHistory(const TimeStamp& aTime);
+
+ // Cached Preferences values.
+ ScrollAnimationBezierPhysicsSettings mSettings;
+
+ // mPrevEventTime holds previous 3 timestamps for intervals averaging (to
+ // reduce duration fluctuations). When AsyncScroll is constructed and no
+ // previous timestamps are available (indicated with mIsFirstIteration),
+ // initialize mPrevEventTime using imaginary previous timestamps with maximum
+ // relevant intervals between them.
+ TimeStamp mPrevEventTime[3];
+
+ TimeStamp mStartTime;
+
+ nsPoint mStartPos;
+ nsPoint mDestination;
+ TimeDuration mDuration;
+ SMILKeySpline mTimingFunctionX;
+ SMILKeySpline mTimingFunctionY;
+ bool mIsFirstIteration;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_layout_ScrollAnimationBezierPhysics_h_
diff --git a/layout/generic/ScrollAnimationMSDPhysics.cpp b/layout/generic/ScrollAnimationMSDPhysics.cpp
new file mode 100644
index 0000000000..de67f9c59a
--- /dev/null
+++ b/layout/generic/ScrollAnimationMSDPhysics.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollAnimationMSDPhysics.h"
+#include "mozilla/StaticPrefs_general.h"
+
+using namespace mozilla;
+
+ScrollAnimationMSDPhysics::ScrollAnimationMSDPhysics(const nsPoint& aStartPos)
+ : mStartPos(aStartPos),
+ mModelX(
+ 0, 0, 0,
+ StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant(),
+ 1),
+ mModelY(
+ 0, 0, 0,
+ StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant(),
+ 1),
+ mIsFirstIteration(true) {}
+
+void ScrollAnimationMSDPhysics::Update(const TimeStamp& aTime,
+ const nsPoint& aDestination,
+ const nsSize& aCurrentVelocity) {
+ double springConstant = ComputeSpringConstant(aTime);
+
+ // mLastSimulatedTime is the most recent time that this animation has been
+ // "observed" at. We don't want to update back to a state in the past, so we
+ // set mStartTime to the more recent of mLastSimulatedTime and aTime.
+ // aTime can be in the past if we're processing an input event whose internal
+ // timestamp is in the past.
+ if (mLastSimulatedTime && aTime < mLastSimulatedTime) {
+ mStartTime = mLastSimulatedTime;
+ } else {
+ mStartTime = aTime;
+ }
+
+ if (!mIsFirstIteration) {
+ mStartPos = PositionAt(mStartTime);
+ }
+
+ mLastSimulatedTime = mStartTime;
+ mDestination = aDestination;
+ mModelX = NonOscillatingAxisPhysicsMSDModel(
+ mStartPos.x, aDestination.x, aCurrentVelocity.width, springConstant, 1);
+ mModelY = NonOscillatingAxisPhysicsMSDModel(
+ mStartPos.y, aDestination.y, aCurrentVelocity.height, springConstant, 1);
+ mIsFirstIteration = false;
+}
+
+void ScrollAnimationMSDPhysics::ApplyContentShift(const CSSPoint& aShiftDelta) {
+ nsPoint shiftDelta = CSSPoint::ToAppUnits(aShiftDelta);
+ mStartPos += shiftDelta;
+ mDestination += shiftDelta;
+ TimeStamp currentTime = mLastSimulatedTime;
+ nsPoint currentPosition = PositionAt(currentTime) + shiftDelta;
+ nsSize currentVelocity = VelocityAt(currentTime);
+ double springConstant = ComputeSpringConstant(currentTime);
+ mModelX = NonOscillatingAxisPhysicsMSDModel(currentPosition.x, mDestination.x,
+ currentVelocity.width,
+ springConstant, 1);
+ mModelY = NonOscillatingAxisPhysicsMSDModel(currentPosition.y, mDestination.y,
+ currentVelocity.height,
+ springConstant, 1);
+}
+
+double ScrollAnimationMSDPhysics::ComputeSpringConstant(
+ const TimeStamp& aTime) {
+ if (!mPreviousEventTime) {
+ mPreviousEventTime = aTime;
+ mPreviousDelta = TimeDuration();
+ return StaticPrefs::
+ general_smoothScroll_msdPhysics_motionBeginSpringConstant();
+ }
+
+ TimeDuration delta = aTime - mPreviousEventTime;
+ TimeDuration previousDelta = mPreviousDelta;
+
+ mPreviousEventTime = aTime;
+ mPreviousDelta = delta;
+
+ double deltaMS = delta.ToMilliseconds();
+ if (deltaMS >=
+ StaticPrefs::
+ general_smoothScroll_msdPhysics_continuousMotionMaxDeltaMS()) {
+ return StaticPrefs::
+ general_smoothScroll_msdPhysics_motionBeginSpringConstant();
+ }
+
+ if (previousDelta &&
+ deltaMS >=
+ StaticPrefs::general_smoothScroll_msdPhysics_slowdownMinDeltaMS() &&
+ deltaMS >=
+ previousDelta.ToMilliseconds() *
+ StaticPrefs::
+ general_smoothScroll_msdPhysics_slowdownMinDeltaRatio()) {
+ // The rate of events has slowed (the time delta between events has
+ // increased) enough that we think that the current scroll motion is coming
+ // to a stop. Use a stiffer spring in order to reach the destination more
+ // quickly.
+ return StaticPrefs::
+ general_smoothScroll_msdPhysics_slowdownSpringConstant();
+ }
+
+ return StaticPrefs::general_smoothScroll_msdPhysics_regularSpringConstant();
+}
+
+void ScrollAnimationMSDPhysics::SimulateUntil(const TimeStamp& aTime) {
+ if (!mLastSimulatedTime || aTime < mLastSimulatedTime) {
+ return;
+ }
+ TimeDuration delta = aTime - mLastSimulatedTime;
+ mModelX.Simulate(delta);
+ mModelY.Simulate(delta);
+ mLastSimulatedTime = aTime;
+}
+
+nsPoint ScrollAnimationMSDPhysics::PositionAt(const TimeStamp& aTime) {
+ SimulateUntil(aTime);
+ return nsPoint(NSToCoordRound(mModelX.GetPosition()),
+ NSToCoordRound(mModelY.GetPosition()));
+}
+
+nsSize ScrollAnimationMSDPhysics::VelocityAt(const TimeStamp& aTime) {
+ SimulateUntil(aTime);
+ return nsSize(NSToCoordRound(mModelX.GetVelocity()),
+ NSToCoordRound(mModelY.GetVelocity()));
+}
+
+static double ClampVelocityToMaximum(double aVelocity, double aInitialPosition,
+ double aDestination,
+ double aSpringConstant) {
+ // Clamp velocity to the maximum value it could obtain if we started at this
+ // position with zero velocity (see bug 1866904 comment 3). With a damping
+ // ratio >= 1.0, this should be low enough to avoid overshooting the
+ // destination.
+ double velocityLimit =
+ sqrt(aSpringConstant) * abs(aDestination - aInitialPosition);
+ return clamped(aVelocity, -velocityLimit, velocityLimit);
+}
+
+ScrollAnimationMSDPhysics::NonOscillatingAxisPhysicsMSDModel::
+ NonOscillatingAxisPhysicsMSDModel(double aInitialPosition,
+ double aInitialDestination,
+ double aInitialVelocity,
+ double aSpringConstant,
+ double aDampingRatio)
+ : AxisPhysicsMSDModel(
+ aInitialPosition, aInitialDestination,
+ ClampVelocityToMaximum(aInitialVelocity, aInitialPosition,
+ aInitialDestination, aSpringConstant),
+ aSpringConstant, aDampingRatio) {
+ MOZ_ASSERT(aDampingRatio >= 1.0,
+ "Damping ratio must be >= 1.0 to avoid oscillation");
+}
diff --git a/layout/generic/ScrollAnimationMSDPhysics.h b/layout/generic/ScrollAnimationMSDPhysics.h
new file mode 100644
index 0000000000..016f1dc46e
--- /dev/null
+++ b/layout/generic/ScrollAnimationMSDPhysics.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollAnimationMSDPhysics_h_
+#define mozilla_layout_ScrollAnimationMSDPhysics_h_
+
+#include "ScrollAnimationPhysics.h"
+#include "mozilla/layers/AxisPhysicsMSDModel.h"
+
+namespace mozilla {
+
+// This class implements a cubic MSD timing function and automatically
+// adapts the animation duration based on the scrolling rate.
+class ScrollAnimationMSDPhysics final : public ScrollAnimationPhysics {
+ public:
+ typedef mozilla::layers::AxisPhysicsMSDModel AxisPhysicsMSDModel;
+
+ explicit ScrollAnimationMSDPhysics(const nsPoint& aStartPos);
+
+ void Update(const TimeStamp& aTime, const nsPoint& aDestination,
+ const nsSize& aCurrentVelocity) override;
+
+ void ApplyContentShift(const CSSPoint& aShiftDelta) override;
+
+ // Get the velocity at a point in time in nscoords/sec.
+ nsSize VelocityAt(const TimeStamp& aTime) override;
+
+ // Returns the expected scroll position at a given point in time, in app
+ // units, relative to the scroll frame.
+ nsPoint PositionAt(const TimeStamp& aTime) override;
+
+ bool IsFinished(const TimeStamp& aTime) override {
+ SimulateUntil(aTime);
+ return mModelX.IsFinished(1) && mModelY.IsFinished(1);
+ }
+
+ protected:
+ // A wrapper around AxisPhysicsMSDModel which takes additional steps to avoid
+ // oscillating motion.
+ class NonOscillatingAxisPhysicsMSDModel : public AxisPhysicsMSDModel {
+ public:
+ NonOscillatingAxisPhysicsMSDModel(double aInitialPosition,
+ double aInitialDestination,
+ double aInitialVelocity,
+ double aSpringConstant,
+ double aDampingRatio);
+ };
+
+ double ComputeSpringConstant(const TimeStamp& aTime);
+ void SimulateUntil(const TimeStamp& aTime);
+
+ TimeStamp mPreviousEventTime;
+ TimeDuration mPreviousDelta;
+
+ TimeStamp mStartTime;
+
+ nsPoint mStartPos;
+ nsPoint mDestination;
+ TimeStamp mLastSimulatedTime;
+ NonOscillatingAxisPhysicsMSDModel mModelX;
+ NonOscillatingAxisPhysicsMSDModel mModelY;
+ bool mIsFirstIteration;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_layout_ScrollAnimationMSDPhysics_h_
diff --git a/layout/generic/ScrollAnimationPhysics.h b/layout/generic/ScrollAnimationPhysics.h
new file mode 100644
index 0000000000..0a1bae0532
--- /dev/null
+++ b/layout/generic/ScrollAnimationPhysics.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollAnimationPhysics_h_
+#define mozilla_layout_ScrollAnimationPhysics_h_
+
+#include "mozilla/TimeStamp.h"
+#include "nsPoint.h"
+#include "Units.h"
+
+namespace mozilla {
+
+class ScrollAnimationPhysics {
+ public:
+ // Update the animation to have |aDestination| as its new destination.
+ // The animation's current position remains unchanged, and the shape
+ // of the animation curve is recomputed between the current position
+ // and the new destination.
+ // This is used in cases where an input event that would cause another
+ // animation of this kind is received while this animation is running.
+ virtual void Update(const TimeStamp& aTime, const nsPoint& aDestination,
+ const nsSize& aCurrentVelocity) = 0;
+
+ // Shift both the current position and the destination of the animation
+ // by |aShiftDelta|. The progress of the animation along its animation
+ // curve is unchanged.
+ // This is used in cases where the main thread changes the scroll offset
+ // (e.g. via scrollBy()) but we want the "momentum" represented by the
+ // animation to be preserved.
+ virtual void ApplyContentShift(const CSSPoint& aShiftDelta) = 0;
+
+ // Get the velocity at a point in time in nscoords/sec.
+ virtual nsSize VelocityAt(const TimeStamp& aTime) = 0;
+
+ // Returns the expected scroll position at a given point in time, in app
+ // units, relative to the scroll frame.
+ virtual nsPoint PositionAt(const TimeStamp& aTime) = 0;
+
+ virtual bool IsFinished(const TimeStamp& aTime) = 0;
+
+ virtual ~ScrollAnimationPhysics() = default;
+};
+
+// Helper for accelerated wheel deltas. This can be called from the main thread
+// or the APZ Controller thread.
+static inline double ComputeAcceleratedWheelDelta(double aDelta,
+ int32_t aCounter,
+ int32_t aFactor) {
+ if (!aDelta) {
+ return aDelta;
+ }
+ return (aDelta * aCounter * double(aFactor) / 10);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_layout_ScrollAnimationPhysics_h_
diff --git a/layout/generic/ScrollGeneration.cpp b/layout/generic/ScrollGeneration.cpp
new file mode 100644
index 0000000000..5f576187b3
--- /dev/null
+++ b/layout/generic/ScrollGeneration.cpp
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollGeneration.h"
+
+#include <ostream>
+
+namespace mozilla {
+
+template <typename Tag>
+ScrollGeneration<Tag>::ScrollGeneration() : mValue(0) {}
+
+template <typename Tag>
+ScrollGeneration<Tag>::ScrollGeneration(uint64_t aValue) : mValue(aValue) {}
+
+template <typename Tag>
+bool ScrollGeneration<Tag>::operator<(
+ const ScrollGeneration<Tag>& aOther) const {
+ return mValue < aOther.mValue;
+}
+
+template <typename Tag>
+bool ScrollGeneration<Tag>::operator==(
+ const ScrollGeneration<Tag>& aOther) const {
+ return mValue == aOther.mValue;
+}
+
+template <typename Tag>
+bool ScrollGeneration<Tag>::operator!=(
+ const ScrollGeneration<Tag>& aOther) const {
+ return !(*this == aOther);
+}
+
+template <typename Tag>
+std::ostream& operator<<(std::ostream& aStream,
+ const ScrollGeneration<Tag>& aGen) {
+ return aStream << aGen.mValue;
+}
+
+template struct ScrollGeneration<APZTag>;
+template struct ScrollGeneration<MainThreadTag>;
+
+template std::ostream& operator<<(std::ostream&,
+ const ScrollGeneration<APZTag>&);
+template std::ostream& operator<<(std::ostream&,
+ const ScrollGeneration<MainThreadTag>&);
+
+} // namespace mozilla
diff --git a/layout/generic/ScrollGeneration.h b/layout/generic/ScrollGeneration.h
new file mode 100644
index 0000000000..90eb243e9e
--- /dev/null
+++ b/layout/generic/ScrollGeneration.h
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ScrollGeneration_h_
+#define mozilla_ScrollGeneration_h_
+
+#include <cstdint>
+#include <iosfwd>
+
+namespace mozilla {
+
+struct ScrollGenerationCounter;
+
+class APZTag {};
+class MainThreadTag {};
+
+template <typename Tag>
+struct ScrollGeneration;
+
+template <typename Tag>
+std::ostream& operator<<(std::ostream& aStream,
+ const ScrollGeneration<Tag>& aGen);
+
+template <typename Tag>
+struct ScrollGeneration {
+ friend struct ScrollGenerationCounter;
+
+ private:
+ // Private constructor; use ScrollGenerationCounter to get a new instance.
+ explicit ScrollGeneration(uint64_t aValue);
+
+ public:
+ // Dummy constructor, needed for IPDL purposes. Not intended for manual use.
+ ScrollGeneration();
+
+ uint64_t Raw() const { return mValue; }
+
+ bool operator<(const ScrollGeneration<Tag>& aOther) const;
+ bool operator==(const ScrollGeneration<Tag>& aOther) const;
+ bool operator!=(const ScrollGeneration<Tag>& aOther) const;
+
+ friend std::ostream& operator<< <>(std::ostream& aStream,
+ const ScrollGeneration<Tag>& aGen);
+
+ private:
+ uint64_t mValue;
+};
+
+using APZScrollGeneration = ScrollGeneration<APZTag>;
+using MainThreadScrollGeneration = ScrollGeneration<MainThreadTag>;
+
+struct ScrollGenerationCounter {
+ MainThreadScrollGeneration NewMainThreadGeneration() {
+ uint64_t value = ++mCounter;
+ return MainThreadScrollGeneration(value);
+ }
+
+ APZScrollGeneration NewAPZGeneration() {
+ uint64_t value = ++mCounter;
+ return APZScrollGeneration(value);
+ }
+
+ private:
+ uint64_t mCounter = 0;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollGeneration_h_
diff --git a/layout/generic/ScrollOrigin.h b/layout/generic/ScrollOrigin.h
new file mode 100644
index 0000000000..f1ad0e3367
--- /dev/null
+++ b/layout/generic/ScrollOrigin.h
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ScrollOrigin_h_
+#define mozilla_ScrollOrigin_h_
+
+namespace mozilla {
+
+// A scroll origin is a bit of a combination of "what part of the code caused
+// this scroll" and "what special properties does this scroll have, in the
+// context of what caused it". See specific comments below.
+enum class ScrollOrigin : uint8_t {
+ // This is used as an initial value for the "LastScrollOrigin" property on
+ // scrollable frames. It is not intended to be an actual scroll origin, but
+ // a sentinel value that indicates that there was no "last scroll". It is
+ // used similarly for the "LastSmoothScrollOrigin" property, to indicate
+ // no smooth scroll is in progress.
+ None,
+
+ // This is a default value that we use when we don't know of a more specific
+ // value that we can use.
+ NotSpecified,
+ // The scroll came from APZ code.
+ Apz,
+ // The scroll came from an attempt at restoring a scroll position saved in
+ // the bfcache or history.
+ Restore,
+ // The scroll came from a "relative" scroll method like ScrollBy, where the
+ // scroll destination is indicated by a delta from the current position
+ // instead of an absolute value.
+ Relative,
+ // The scroll came from an attempt by the main thread to re-clamp the scroll
+ // position after a reflow.
+ Clamp,
+ // The scroll came from a scroll adjustment triggered by scroll anchoring.
+ AnchorAdjustment,
+
+ // The following scroll origins also are associated with prefs of the form
+ // general.smoothScroll.<origin>(.*)
+ // e.g. general.smoothScroll.lines indicates whether or not a scroll with
+ // origin Lines will be animated smoothly, and e.g. subprefs like
+ // general.smoothScroll.lines.durationMinMS control some of the animation
+ // timing behavior.
+
+ // The scroll came from some sort of input that's not one of the above or
+ // below values. Generally this means it came from a content-exposed API,
+ // like window.scrollTo, but may also be from other sources that don't need
+ // any particular special handling.
+ Other,
+ // The scroll was originated by pixel-scrolling input device (e.g. precision
+ // mouse wheel).
+ Pixels,
+ // The scroll was originated by a line-scrolling input device (e.g. up/down
+ // keyboard buttons).
+ Lines,
+ // The scroll was originated by a page-scrolling input device (e.g. pgup/
+ // pgdn keyboard buttons).
+ Pages,
+ // The scroll was originated by a mousewheel that scrolls by lines.
+ MouseWheel,
+ // The scroll was originated by moving the scrollbars.
+ Scrollbars,
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollOrigin_h_
diff --git a/layout/generic/ScrollPositionUpdate.cpp b/layout/generic/ScrollPositionUpdate.cpp
new file mode 100644
index 0000000000..9607d2d18a
--- /dev/null
+++ b/layout/generic/ScrollPositionUpdate.cpp
@@ -0,0 +1,156 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollPositionUpdate.h"
+
+#include <ostream>
+
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+static ScrollGenerationCounter sGenerationCounter;
+
+ScrollPositionUpdate::ScrollPositionUpdate()
+ : mType(ScrollUpdateType::Absolute),
+ mScrollMode(ScrollMode::Normal),
+ mScrollOrigin(ScrollOrigin::None),
+ mTriggeredByScript(ScrollTriggeredByScript::No) {}
+
+/*static*/
+ScrollPositionUpdate ScrollPositionUpdate::NewScrollframe(
+ nsPoint aInitialPosition) {
+ ScrollPositionUpdate ret;
+ ret.mScrollGeneration = sGenerationCounter.NewMainThreadGeneration();
+ ret.mScrollMode = ScrollMode::Instant;
+ ret.mDestination = CSSPoint::FromAppUnits(aInitialPosition);
+ return ret;
+}
+
+/*static*/
+ScrollPositionUpdate ScrollPositionUpdate::NewScroll(ScrollOrigin aOrigin,
+ nsPoint aDestination) {
+ MOZ_ASSERT(aOrigin != ScrollOrigin::NotSpecified);
+ MOZ_ASSERT(aOrigin != ScrollOrigin::None);
+
+ ScrollPositionUpdate ret;
+ ret.mScrollGeneration = sGenerationCounter.NewMainThreadGeneration();
+ ret.mType = ScrollUpdateType::Absolute;
+ ret.mScrollMode = ScrollMode::Instant;
+ ret.mScrollOrigin = aOrigin;
+ ret.mDestination = CSSPoint::FromAppUnits(aDestination);
+ return ret;
+}
+
+/*static*/
+ScrollPositionUpdate ScrollPositionUpdate::NewRelativeScroll(
+ nsPoint aSource, nsPoint aDestination) {
+ ScrollPositionUpdate ret;
+ ret.mScrollGeneration = sGenerationCounter.NewMainThreadGeneration();
+ ret.mType = ScrollUpdateType::Relative;
+ ret.mScrollMode = ScrollMode::Instant;
+ ret.mScrollOrigin = ScrollOrigin::Relative;
+ ret.mDestination = CSSPoint::FromAppUnits(aDestination);
+ ret.mSource = CSSPoint::FromAppUnits(aSource);
+ return ret;
+}
+
+/*static*/
+ScrollPositionUpdate ScrollPositionUpdate::NewSmoothScroll(
+ ScrollMode aMode, ScrollOrigin aOrigin, nsPoint aDestination,
+ ScrollTriggeredByScript aTriggeredByScript,
+ UniquePtr<ScrollSnapTargetIds> aSnapTargetIds) {
+ MOZ_ASSERT(aOrigin != ScrollOrigin::NotSpecified);
+ MOZ_ASSERT(aOrigin != ScrollOrigin::None);
+ MOZ_ASSERT(aMode == ScrollMode::Smooth || aMode == ScrollMode::SmoothMsd);
+
+ ScrollPositionUpdate ret;
+ ret.mScrollGeneration = sGenerationCounter.NewMainThreadGeneration();
+ ret.mType = ScrollUpdateType::Absolute;
+ ret.mScrollMode = aMode;
+ ret.mScrollOrigin = aOrigin;
+ ret.mDestination = CSSPoint::FromAppUnits(aDestination);
+ ret.mTriggeredByScript = aTriggeredByScript;
+ if (aSnapTargetIds) {
+ ret.mSnapTargetIds = *aSnapTargetIds;
+ }
+ return ret;
+}
+
+/*static*/
+ScrollPositionUpdate ScrollPositionUpdate::NewPureRelativeScroll(
+ ScrollOrigin aOrigin, ScrollMode aMode, const nsPoint& aDelta) {
+ MOZ_ASSERT(aOrigin != ScrollOrigin::NotSpecified);
+ MOZ_ASSERT(aOrigin != ScrollOrigin::None);
+
+ ScrollPositionUpdate ret;
+ ret.mScrollGeneration = sGenerationCounter.NewMainThreadGeneration();
+ ret.mType = ScrollUpdateType::PureRelative;
+ ret.mScrollMode = aMode;
+ ret.mScrollOrigin = aOrigin;
+ ret.mDelta = CSSPoint::FromAppUnits(aDelta);
+ return ret;
+}
+
+/*static*/
+ScrollPositionUpdate ScrollPositionUpdate::NewMergeableScroll(
+ ScrollOrigin aOrigin, nsPoint aDestination) {
+ MOZ_ASSERT(aOrigin == ScrollOrigin::AnchorAdjustment);
+
+ ScrollPositionUpdate ret;
+ ret.mScrollGeneration = sGenerationCounter.NewMainThreadGeneration();
+ ret.mType = ScrollUpdateType::MergeableAbsolute;
+ ret.mScrollMode = ScrollMode::Instant;
+ ret.mScrollOrigin = aOrigin;
+ ret.mDestination = CSSPoint::FromAppUnits(aDestination);
+ return ret;
+}
+
+bool ScrollPositionUpdate::operator==(
+ const ScrollPositionUpdate& aOther) const {
+ // instances are immutable, and all the fields are set when the generation
+ // is set. So if the generation matches, these instances are identical.
+ return mScrollGeneration == aOther.mScrollGeneration;
+}
+
+MainThreadScrollGeneration ScrollPositionUpdate::GetGeneration() const {
+ return mScrollGeneration;
+}
+
+ScrollUpdateType ScrollPositionUpdate::GetType() const { return mType; }
+
+ScrollMode ScrollPositionUpdate::GetMode() const { return mScrollMode; }
+
+ScrollOrigin ScrollPositionUpdate::GetOrigin() const { return mScrollOrigin; }
+
+CSSPoint ScrollPositionUpdate::GetDestination() const {
+ MOZ_ASSERT(mType == ScrollUpdateType::Absolute ||
+ mType == ScrollUpdateType::MergeableAbsolute ||
+ mType == ScrollUpdateType::Relative);
+ return mDestination;
+}
+
+CSSPoint ScrollPositionUpdate::GetSource() const {
+ MOZ_ASSERT(mType == ScrollUpdateType::Relative);
+ return mSource;
+}
+
+CSSPoint ScrollPositionUpdate::GetDelta() const {
+ MOZ_ASSERT(mType == ScrollUpdateType::PureRelative);
+ return mDelta;
+}
+
+std::ostream& operator<<(std::ostream& aStream,
+ const ScrollPositionUpdate& aUpdate) {
+ aStream << "{ gen=" << aUpdate.mScrollGeneration
+ << ", type=" << (int)aUpdate.mType
+ << ", mode=" << (int)aUpdate.mScrollMode
+ << ", origin=" << (int)aUpdate.mScrollOrigin
+ << ", dst=" << aUpdate.mDestination << ", src=" << aUpdate.mSource
+ << ", delta=" << aUpdate.mDelta
+ << ", triggered by script=" << aUpdate.WasTriggeredByScript() << " }";
+ return aStream;
+}
+
+} // namespace mozilla
diff --git a/layout/generic/ScrollPositionUpdate.h b/layout/generic/ScrollPositionUpdate.h
new file mode 100644
index 0000000000..0e8dc020c1
--- /dev/null
+++ b/layout/generic/ScrollPositionUpdate.h
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ScrollPositionUpdate_h_
+#define mozilla_ScrollPositionUpdate_h_
+
+#include <cstdint>
+#include <iosfwd>
+
+#include "nsPoint.h"
+#include "mozilla/ScrollGeneration.h"
+#include "mozilla/ScrollOrigin.h"
+#include "mozilla/ScrollSnapTargetId.h"
+#include "mozilla/ScrollTypes.h"
+#include "Units.h"
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+
+enum class ScrollUpdateType {
+ // A scroll update to a specific destination, regardless of the current
+ // scroll position.
+ Absolute,
+ // A scroll update by a specific amount, based off a given starting scroll
+ // position. XXX Fold this into PureRelative, it should be relatively
+ // straightforward after bug 1655733.
+ Relative,
+ // A scroll update by a specific amount, where only the delta is provided.
+ // The delta should be applied to whatever the current scroll position is
+ // on the receiver side.
+ PureRelative,
+ // Similar to |Absolute|, but even if there's an active async scroll animation
+ // the position update will NOT cancel the async scroll animation.
+ MergeableAbsolute,
+};
+
+enum class ScrollTriggeredByScript : bool { No, Yes };
+
+/**
+ * This class represents an update to the scroll position that is initiated by
+ * something on the main thread. A list of these classes is accumulated by
+ * scrollframes on the main thread, and the list is sent over as part of a
+ * paint transaction to the compositor. The compositor can then iterate through
+ * the scroll updates and apply/merge them with scrolling that has already
+ * occurred independently on the compositor side.
+ */
+class ScrollPositionUpdate {
+ friend struct IPC::ParamTraits<mozilla::ScrollPositionUpdate>;
+
+ public:
+ // Constructor for IPC use only.
+ explicit ScrollPositionUpdate();
+
+ // Create a ScrollPositionUpdate for a newly created (or reconstructed)
+ // scrollframe.
+ static ScrollPositionUpdate NewScrollframe(nsPoint aInitialPosition);
+ // Create a ScrollPositionUpdate for a new absolute/instant scroll, to
+ // the given destination.
+ static ScrollPositionUpdate NewScroll(ScrollOrigin aOrigin,
+ nsPoint aDestination);
+ // Create a ScrollPositionUpdate for a new relative/instant scroll, with
+ // the given source/destination.
+ static ScrollPositionUpdate NewRelativeScroll(nsPoint aSource,
+ nsPoint aDestination);
+ // Create a ScrollPositionUpdate for a new absolute/smooth scroll, which
+ // animates smoothly to the given destination from whatever the current
+ // scroll position is in the receiver.
+ // If the smooth operation involves snapping to |aDestination|,
+ // |aSnapTargetIds| has snap-target-ids for snapping. Once after this smooth
+ // scroll finished on the target APZC, the ids will be reported back to the
+ // main-thread as the last snap target ids which will be used for re-snapping
+ // to the same snapped element(s).
+ static ScrollPositionUpdate NewSmoothScroll(
+ ScrollMode aMode, ScrollOrigin aOrigin, nsPoint aDestination,
+ ScrollTriggeredByScript aTriggeredByScript,
+ UniquePtr<ScrollSnapTargetIds> aSnapTargetIds);
+ // Create a ScrollPositionUpdate for a new pure-relative scroll. The
+ // aMode parameter controls whether or not this is a smooth animation or
+ // instantaneous scroll.
+ static ScrollPositionUpdate NewPureRelativeScroll(ScrollOrigin aOrigin,
+ ScrollMode aMode,
+ const nsPoint& aDelta);
+
+ static ScrollPositionUpdate NewMergeableScroll(ScrollOrigin aOrigin,
+ nsPoint aDestination);
+
+ bool operator==(const ScrollPositionUpdate& aOther) const;
+
+ MainThreadScrollGeneration GetGeneration() const;
+ ScrollUpdateType GetType() const;
+ ScrollMode GetMode() const;
+ ScrollOrigin GetOrigin() const;
+ // GetDestination is only valid for Absolute and Relative types; it asserts
+ // otherwise.
+ CSSPoint GetDestination() const;
+ // GetSource is only valid for the Relative type; it asserts otherwise.
+ CSSPoint GetSource() const;
+ // GetDelta is only valid for the PureRelative type; it asserts otherwise.
+ CSSPoint GetDelta() const;
+
+ ScrollTriggeredByScript GetScrollTriggeredByScript() const {
+ return mTriggeredByScript;
+ }
+ bool WasTriggeredByScript() const {
+ return mTriggeredByScript == ScrollTriggeredByScript::Yes;
+ }
+ const ScrollSnapTargetIds& GetSnapTargetIds() const { return mSnapTargetIds; }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const ScrollPositionUpdate& aUpdate);
+
+ private:
+ MainThreadScrollGeneration mScrollGeneration;
+ // Refer to the ScrollUpdateType documentation for what the types mean.
+ // All fields are populated for all types, except as noted below.
+ ScrollUpdateType mType;
+ ScrollMode mScrollMode;
+ ScrollOrigin mScrollOrigin;
+ // mDestination is not populated when mType == PureRelative.
+ CSSPoint mDestination;
+ // mSource is not populated when mType == Absolute || mType == PureRelative.
+ CSSPoint mSource;
+ // mDelta is not populated when mType == Absolute || mType == Relative.
+ CSSPoint mDelta;
+ ScrollTriggeredByScript mTriggeredByScript;
+ ScrollSnapTargetIds mSnapTargetIds;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollPositionUpdate_h_
diff --git a/layout/generic/ScrollSnap.cpp b/layout/generic/ScrollSnap.cpp
new file mode 100644
index 0000000000..cfe9fb1cbb
--- /dev/null
+++ b/layout/generic/ScrollSnap.cpp
@@ -0,0 +1,788 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollSnap.h"
+
+#include "FrameMetrics.h"
+
+#include "mozilla/ScrollSnapInfo.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsTArray.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+namespace mozilla {
+
+/**
+ * Keeps track of the current best edge to snap to. The criteria for
+ * adding an edge depends on the scrolling unit.
+ */
+class CalcSnapPoints final {
+ using SnapTarget = ScrollSnapInfo::SnapTarget;
+
+ public:
+ CalcSnapPoints(ScrollUnit aUnit, ScrollSnapFlags aSnapFlags,
+ const nsPoint& aDestination, const nsPoint& aStartPos);
+ struct SnapPosition : public SnapTarget {
+ SnapPosition(const SnapTarget& aSnapTarget, nscoord aPosition,
+ nscoord aDistanceOnOtherAxis)
+ : SnapTarget(aSnapTarget),
+ mPosition(aPosition),
+ mDistanceOnOtherAxis(aDistanceOnOtherAxis) {}
+
+ nscoord mPosition;
+ // The distance from the scroll destination to this snap position on the
+ // other axis. This value is used if there are multiple SnapPositions on
+ // this axis, but the positions on the other axis are different.
+ nscoord mDistanceOnOtherAxis;
+ };
+
+ void AddHorizontalEdge(const SnapTarget& aTarget);
+ void AddVerticalEdge(const SnapTarget& aTarget);
+
+ struct CandidateTracker {
+ // keeps track of the position of the current second best edge on the
+ // opposite side of the best edge on this axis.
+ // We use NSCoordSaturatingSubtract to calculate the distance between a
+ // given position and this second best edge position so that it can be an
+ // uninitialized value as the maximum possible value, because the first
+ // distance calculation would always be nscoord_MAX.
+ nscoord mSecondBestEdge = nscoord_MAX;
+
+ // Assuming in most cases there's no multiple coincide snap points.
+ AutoTArray<ScrollSnapTargetId, 1> mTargetIds;
+ // keeps track of the positions of the current best edge on this axis.
+ // NOTE: Each SnapPosition.mPosition points the same snap position on this
+ // axis but other member variables of SnapPosition may have different
+ // values.
+ AutoTArray<SnapPosition, 1> mBestEdges;
+ bool EdgeFound() const { return !mBestEdges.IsEmpty(); }
+ };
+ void AddEdge(const SnapPosition& aEdge, nscoord aDestination,
+ nscoord aStartPos, nscoord aScrollingDirection,
+ CandidateTracker* aCandidateTracker);
+ SnapDestination GetBestEdge(const nsSize& aSnapportSize) const;
+ nscoord XDistanceBetweenBestAndSecondEdge() const {
+ return std::abs(NSCoordSaturatingSubtract(
+ mTrackerOnX.mSecondBestEdge,
+ mTrackerOnX.EdgeFound() ? mTrackerOnX.mBestEdges[0].mPosition
+ : mDestination.x,
+ nscoord_MAX));
+ }
+ nscoord YDistanceBetweenBestAndSecondEdge() const {
+ return std::abs(NSCoordSaturatingSubtract(
+ mTrackerOnY.mSecondBestEdge,
+ mTrackerOnY.EdgeFound() ? mTrackerOnY.mBestEdges[0].mPosition
+ : mDestination.y,
+ nscoord_MAX));
+ }
+ const nsPoint& Destination() const { return mDestination; }
+
+ protected:
+ ScrollUnit mUnit;
+ ScrollSnapFlags mSnapFlags;
+ nsPoint mDestination; // gives the position after scrolling but before
+ // snapping
+ nsPoint mStartPos; // gives the position before scrolling
+ nsIntPoint mScrollingDirection; // always -1, 0, or 1
+ CandidateTracker mTrackerOnX;
+ CandidateTracker mTrackerOnY;
+};
+
+CalcSnapPoints::CalcSnapPoints(ScrollUnit aUnit, ScrollSnapFlags aSnapFlags,
+ const nsPoint& aDestination,
+ const nsPoint& aStartPos)
+ : mUnit(aUnit),
+ mSnapFlags(aSnapFlags),
+ mDestination(aDestination),
+ mStartPos(aStartPos) {
+ MOZ_ASSERT(aSnapFlags != ScrollSnapFlags::Disabled);
+
+ nsPoint direction = aDestination - aStartPos;
+ mScrollingDirection = nsIntPoint(0, 0);
+ if (direction.x < 0) {
+ mScrollingDirection.x = -1;
+ }
+ if (direction.x > 0) {
+ mScrollingDirection.x = 1;
+ }
+ if (direction.y < 0) {
+ mScrollingDirection.y = -1;
+ }
+ if (direction.y > 0) {
+ mScrollingDirection.y = 1;
+ }
+}
+
+SnapDestination CalcSnapPoints::GetBestEdge(const nsSize& aSnapportSize) const {
+ if (mTrackerOnX.EdgeFound() && mTrackerOnY.EdgeFound()) {
+ nsPoint bestCandidate(mTrackerOnX.mBestEdges[0].mPosition,
+ mTrackerOnY.mBestEdges[0].mPosition);
+ nsRect snappedPort = nsRect(bestCandidate, aSnapportSize);
+
+ // If we've found the candidates on both axes, it's possible some of
+ // candidates will be outside of the snapport if we snap to the point
+ // (mTrackerOnX.mBestEdges[0].mPosition,
+ // mTrackerOnY.mBestEdges[0].mPosition). So we need to get the intersection
+ // of the snap area of each snap target element on each axis and the
+ // snapport to tell whether it's outside of the snapport or not.
+ //
+ // Also if at least either one of the elements will be outside of the
+ // snapport if we snap to (mTrackerOnX.mBestEdges[0].mPosition,
+ // mTrackerOnY.mBestEdges[0].mPosition). We need to choose one of
+ // combinations of the candidates which is closest to the destination.
+ //
+ // So here we iterate over mTrackerOnX and mTrackerOnY just once
+ // respectively for both purposes to avoid iterating over them again and
+ // again.
+ //
+ // NOTE: Ideally we have to iterate over every possible combinations of
+ // (mTrackerOnX.mBestEdges[i].mSnapPoint.mY,
+ // mTrackerOnY.mBestEdges[j].mSnapPoint.mX) and tell whether the given
+ // combination will be visible in the snapport or not (maybe we should
+ // choose the one that the visible area, i.e., the intersection area of
+ // the snap target elements and the snapport, is the largest one rather than
+ // the closest one?). But it will be inefficient, so here we will not
+ // iterate all the combinations, we just iterate all the snap target
+ // elements in each axis respectively.
+
+ AutoTArray<ScrollSnapTargetId, 1> visibleTargetIdsOnX;
+ nscoord minimumDistanceOnY = nscoord_MAX;
+ size_t minimumXIndex = 0;
+ AutoTArray<ScrollSnapTargetId, 1> minimumDistanceTargetIdsOnX;
+ for (size_t i = 0; i < mTrackerOnX.mBestEdges.Length(); i++) {
+ const auto& targetX = mTrackerOnX.mBestEdges[i];
+ if (targetX.mSnapArea.Intersects(snappedPort)) {
+ visibleTargetIdsOnX.AppendElement(targetX.mTargetId);
+ }
+
+ if (targetX.mDistanceOnOtherAxis < minimumDistanceOnY) {
+ minimumDistanceOnY = targetX.mDistanceOnOtherAxis;
+ minimumXIndex = i;
+ minimumDistanceTargetIdsOnX =
+ AutoTArray<ScrollSnapTargetId, 1>{targetX.mTargetId};
+ } else if (minimumDistanceOnY != nscoord_MAX &&
+ targetX.mDistanceOnOtherAxis == minimumDistanceOnY) {
+ minimumDistanceTargetIdsOnX.AppendElement(targetX.mTargetId);
+ }
+ }
+
+ AutoTArray<ScrollSnapTargetId, 1> visibleTargetIdsOnY;
+ nscoord minimumDistanceOnX = nscoord_MAX;
+ size_t minimumYIndex = 0;
+ AutoTArray<ScrollSnapTargetId, 1> minimumDistanceTargetIdsOnY;
+ for (size_t i = 0; i < mTrackerOnY.mBestEdges.Length(); i++) {
+ const auto& targetY = mTrackerOnY.mBestEdges[i];
+ if (targetY.mSnapArea.Intersects(snappedPort)) {
+ visibleTargetIdsOnY.AppendElement(targetY.mTargetId);
+ }
+
+ if (targetY.mDistanceOnOtherAxis < minimumDistanceOnX) {
+ minimumDistanceOnX = targetY.mDistanceOnOtherAxis;
+ minimumYIndex = i;
+ minimumDistanceTargetIdsOnY =
+ AutoTArray<ScrollSnapTargetId, 1>{targetY.mTargetId};
+ } else if (minimumDistanceOnX != nscoord_MAX &&
+ targetY.mDistanceOnOtherAxis == minimumDistanceOnX) {
+ minimumDistanceTargetIdsOnY.AppendElement(targetY.mTargetId);
+ }
+ }
+
+ // If we have the target ids on both axes, it means the target elements
+ // (ids) specifying the best edge on X axis and the target elements
+ // specifying the best edge on Y axis are visible if we snap to the best
+ // edge. Thus they are valid snap positions.
+ if (!visibleTargetIdsOnX.IsEmpty() && !visibleTargetIdsOnY.IsEmpty()) {
+ return SnapDestination{
+ bestCandidate,
+ ScrollSnapTargetIds{visibleTargetIdsOnX, visibleTargetIdsOnY}};
+ }
+
+ // Now we've already known that snapping to
+ // (mTrackerOnX.mBestEdges[0].mPosition,
+ // mTrackerOnY.mBestEdges[0].mPosition) will make all candidates of
+ // mTrackerX or mTrackerY (or both) outside of the snapport. We need to
+ // choose another combination where candidates of both mTrackerX/Y are
+ // inside the snapport.
+
+ // There are three possibilities;
+ // 1) There's no candidate on X axis in mTrackerOnY (that means
+ // each candidate's scroll-snap-align is `none` on X axis), but there's
+ // any candidate in mTrackerOnX, the closest candidates of mTrackerOnX
+ // should be used.
+ // 2) There's no candidate on Y axis in mTrackerOnX (that means
+ // each candidate's scroll-snap-align is `none` on Y axis), but there's
+ // any candidate in mTrackerOnY, the closest candidates of mTrackerOnY
+ // should be used.
+ // 3) There are candidates on both axes. Choosing a combination such as
+ // (mTrackerOnX.mBestEdges[i].mSnapPoint.mX,
+ // mTrackerOnY.mBestEdges[i].mSnapPoint.mY)
+ // would require us to iterate over the candidates again if the
+ // combination position is outside the snapport, which we don't want to
+ // do. Instead, we choose either one of the axis' candidates.
+ if ((minimumDistanceOnX == nscoord_MAX) &&
+ minimumDistanceOnY != nscoord_MAX) {
+ bestCandidate.y = *mTrackerOnX.mBestEdges[minimumXIndex].mSnapPoint.mY;
+ return SnapDestination{bestCandidate,
+ ScrollSnapTargetIds{minimumDistanceTargetIdsOnX,
+ minimumDistanceTargetIdsOnX}};
+ }
+
+ if (minimumDistanceOnX != nscoord_MAX &&
+ minimumDistanceOnY == nscoord_MAX) {
+ bestCandidate.x = *mTrackerOnY.mBestEdges[minimumYIndex].mSnapPoint.mX;
+ return SnapDestination{bestCandidate,
+ ScrollSnapTargetIds{minimumDistanceTargetIdsOnY,
+ minimumDistanceTargetIdsOnY}};
+ }
+
+ if (minimumDistanceOnX != nscoord_MAX &&
+ minimumDistanceOnY != nscoord_MAX) {
+ // If we've found candidates on both axes, choose the closest point either
+ // on X axis or Y axis from the scroll destination. I.e. choose
+ // `minimumXIndex` one or `minimumYIndex` one to make at least one of
+ // snap target elements visible inside the snapport.
+ //
+ // For example,
+ // [bestCandidate.x, mTrackerOnX.mBestEdges[minimumXIndex].mSnapPoint.mY]
+ // is a candidate generated from a single element, thus snapping to the
+ // point would definitely make the element visible inside the snapport.
+ if (hypotf(NSCoordToFloat(mDestination.x -
+ mTrackerOnX.mBestEdges[0].mPosition),
+ NSCoordToFloat(minimumDistanceOnY)) <
+ hypotf(NSCoordToFloat(minimumDistanceOnX),
+ NSCoordToFloat(mDestination.y -
+ mTrackerOnY.mBestEdges[0].mPosition))) {
+ bestCandidate.y = *mTrackerOnX.mBestEdges[minimumXIndex].mSnapPoint.mY;
+ } else {
+ bestCandidate.x = *mTrackerOnY.mBestEdges[minimumYIndex].mSnapPoint.mX;
+ }
+ return SnapDestination{bestCandidate,
+ ScrollSnapTargetIds{minimumDistanceTargetIdsOnX,
+ minimumDistanceTargetIdsOnY}};
+ }
+ MOZ_ASSERT_UNREACHABLE("There's at least one candidate on either axis");
+ // `minimumDistanceOnX == nscoord_MAX && minimumDistanceOnY == nscoord_MAX`
+ // should not happen but we fall back for safety.
+ }
+
+ return SnapDestination{
+ nsPoint(
+ mTrackerOnX.EdgeFound() ? mTrackerOnX.mBestEdges[0].mPosition
+ // In the case of IntendedEndPosition (i.e. the destination point is
+ // explicitely specied, e.g. scrollTo) use the destination point if we
+ // didn't find any candidates.
+ : !(mSnapFlags & ScrollSnapFlags::IntendedDirection) ? mDestination.x
+ : mStartPos.x,
+ mTrackerOnY.EdgeFound() ? mTrackerOnY.mBestEdges[0].mPosition
+ // Same as above X axis case, use the destination point if we didn't
+ // find any candidates.
+ : !(mSnapFlags & ScrollSnapFlags::IntendedDirection) ? mDestination.y
+ : mStartPos.y),
+ ScrollSnapTargetIds{mTrackerOnX.mTargetIds, mTrackerOnY.mTargetIds}};
+}
+
+void CalcSnapPoints::AddHorizontalEdge(const SnapTarget& aTarget) {
+ MOZ_ASSERT(aTarget.mSnapPoint.mY);
+ AddEdge(SnapPosition{aTarget, *aTarget.mSnapPoint.mY,
+ aTarget.mSnapPoint.mX
+ ? std::abs(mDestination.x - *aTarget.mSnapPoint.mX)
+ : nscoord_MAX},
+ mDestination.y, mStartPos.y, mScrollingDirection.y, &mTrackerOnY);
+}
+
+void CalcSnapPoints::AddVerticalEdge(const SnapTarget& aTarget) {
+ MOZ_ASSERT(aTarget.mSnapPoint.mX);
+ AddEdge(SnapPosition{aTarget, *aTarget.mSnapPoint.mX,
+ aTarget.mSnapPoint.mY
+ ? std::abs(mDestination.y - *aTarget.mSnapPoint.mY)
+ : nscoord_MAX},
+ mDestination.x, mStartPos.x, mScrollingDirection.x, &mTrackerOnX);
+}
+
+void CalcSnapPoints::AddEdge(const SnapPosition& aEdge, nscoord aDestination,
+ nscoord aStartPos, nscoord aScrollingDirection,
+ CandidateTracker* aCandidateTracker) {
+ if (mSnapFlags & ScrollSnapFlags::IntendedDirection) {
+ // In the case of intended direction, we only want to snap to points ahead
+ // of the direction we are scrolling.
+ if (aScrollingDirection == 0 ||
+ (aEdge.mPosition - aStartPos) * aScrollingDirection <= 0) {
+ // The scroll direction is neutral - will not hit a snap point, or the
+ // edge is not in the direction we are scrolling, skip it.
+ return;
+ }
+ }
+
+ if (!aCandidateTracker->EdgeFound()) {
+ aCandidateTracker->mBestEdges = AutoTArray<SnapPosition, 1>{aEdge};
+ aCandidateTracker->mTargetIds =
+ AutoTArray<ScrollSnapTargetId, 1>{aEdge.mTargetId};
+ return;
+ }
+
+ auto isPreferredStopAlways = [&](const SnapPosition& aSnapPosition) -> bool {
+ MOZ_ASSERT(mSnapFlags & ScrollSnapFlags::IntendedDirection);
+ // In the case of intended direction scroll operations, `scroll-snap-stop:
+ // always` snap points in between the start point and the scroll destination
+ // are preferable preferable. In other words any `scroll-snap-stop: always`
+ // snap points can be handled as if it's `scroll-snap-stop: normal`.
+ return aSnapPosition.mScrollSnapStop == StyleScrollSnapStop::Always &&
+ std::abs(aSnapPosition.mPosition - aStartPos) <
+ std::abs(aDestination - aStartPos);
+ };
+
+ const bool isOnOppositeSide =
+ ((aEdge.mPosition - aDestination) > 0) !=
+ ((aCandidateTracker->mBestEdges[0].mPosition - aDestination) > 0);
+ const nscoord distanceFromStart = aEdge.mPosition - aStartPos;
+ // A utility function to update the best and the second best edges in the
+ // given conditions.
+ // |aIsCloserThanBest| True if the current candidate is closer than the best
+ // edge.
+ // |aIsCloserThanSecond| True if the current candidate is closer than
+ // the second best edge.
+ const nscoord distanceFromDestination = aEdge.mPosition - aDestination;
+ auto updateBestEdges = [&](bool aIsCloserThanBest, bool aIsCloserThanSecond) {
+ if (aIsCloserThanBest) {
+ if (mSnapFlags & ScrollSnapFlags::IntendedDirection &&
+ isPreferredStopAlways(aEdge)) {
+ // In the case of intended direction scroll operations and the new best
+ // candidate is `scroll-snap-stop: always` and if it's closer to the
+ // start position than the destination, thus we won't use the second
+ // best edge since even if the snap port of the best edge covers entire
+ // snapport, the `scroll-snap-stop: always` snap point is preferred than
+ // any points.
+ // NOTE: We've already ignored snap points behind start points so that
+ // we can use std::abs here in the comparison.
+ //
+ // For example, if there's a `scroll-snap-stop: always` in between the
+ // start point and destination, no `snap-overflow` mechanism should
+ // happen, if there's `scroll-snap-stop: always` further than the
+ // destination, `snap-overflow` might happen something like below
+ // diagram.
+ // start always dest other always
+ // |------------|---------|------|
+ aCandidateTracker->mSecondBestEdge = aEdge.mPosition;
+ } else if (isOnOppositeSide) {
+ // Replace the second best edge with the current best edge only if the
+ // new best edge (aEdge) is on the opposite side of the current best
+ // edge.
+ aCandidateTracker->mSecondBestEdge =
+ aCandidateTracker->mBestEdges[0].mPosition;
+ }
+ aCandidateTracker->mBestEdges = AutoTArray<SnapPosition, 1>{aEdge};
+ aCandidateTracker->mTargetIds =
+ AutoTArray<ScrollSnapTargetId, 1>{aEdge.mTargetId};
+ } else {
+ if (aEdge.mPosition == aCandidateTracker->mBestEdges[0].mPosition) {
+ aCandidateTracker->mTargetIds.AppendElement(aEdge.mTargetId);
+ aCandidateTracker->mBestEdges.AppendElement(aEdge);
+ }
+ if (aIsCloserThanSecond && isOnOppositeSide) {
+ aCandidateTracker->mSecondBestEdge = aEdge.mPosition;
+ }
+ }
+ };
+
+ bool isCandidateOfBest = false;
+ bool isCandidateOfSecondBest = false;
+ switch (mUnit) {
+ case ScrollUnit::DEVICE_PIXELS:
+ case ScrollUnit::LINES:
+ case ScrollUnit::WHOLE: {
+ isCandidateOfBest =
+ std::abs(distanceFromDestination) <
+ std::abs(aCandidateTracker->mBestEdges[0].mPosition - aDestination);
+ isCandidateOfSecondBest =
+ std::abs(distanceFromDestination) <
+ std::abs(NSCoordSaturatingSubtract(aCandidateTracker->mSecondBestEdge,
+ aDestination, nscoord_MAX));
+ break;
+ }
+ case ScrollUnit::PAGES: {
+ // distance to the edge from the scrolling destination in the direction of
+ // scrolling
+ nscoord overshoot = distanceFromDestination * aScrollingDirection;
+ // distance to the current best edge from the scrolling destination in the
+ // direction of scrolling
+ nscoord curOvershoot =
+ (aCandidateTracker->mBestEdges[0].mPosition - aDestination) *
+ aScrollingDirection;
+
+ nscoord secondOvershoot =
+ NSCoordSaturatingSubtract(aCandidateTracker->mSecondBestEdge,
+ aDestination, nscoord_MAX) *
+ aScrollingDirection;
+
+ // edges between the current position and the scrolling destination are
+ // favoured to preserve context
+ if (overshoot < 0) {
+ isCandidateOfBest = overshoot > curOvershoot || curOvershoot >= 0;
+ isCandidateOfSecondBest =
+ overshoot > secondOvershoot || secondOvershoot >= 0;
+ }
+ // if there are no edges between the current position and the scrolling
+ // destination the closest edge beyond the destination is used
+ if (overshoot > 0) {
+ isCandidateOfBest = overshoot < curOvershoot;
+ isCandidateOfSecondBest = overshoot < secondOvershoot;
+ }
+ }
+ }
+
+ if (mSnapFlags & ScrollSnapFlags::IntendedDirection) {
+ if (isPreferredStopAlways(aEdge)) {
+ // If the given position is `scroll-snap-stop: always` and if the position
+ // is in between the start and the destination positions, update the best
+ // position based on the distance from the __start__ point.
+ isCandidateOfBest =
+ std::abs(distanceFromStart) <
+ std::abs(aCandidateTracker->mBestEdges[0].mPosition - aStartPos);
+ } else if (isPreferredStopAlways(aCandidateTracker->mBestEdges[0])) {
+ // If we've found a preferable `scroll-snap-stop:always` position as the
+ // best, do not update it unless the given position is also
+ // `scroll-snap-stop: always`.
+ isCandidateOfBest = false;
+ }
+ }
+
+ updateBestEdges(isCandidateOfBest, isCandidateOfSecondBest);
+}
+
+static void ProcessSnapPositions(CalcSnapPoints& aCalcSnapPoints,
+ const ScrollSnapInfo& aSnapInfo) {
+ aSnapInfo.ForEachValidTargetFor(
+ aCalcSnapPoints.Destination(), [&](const auto& aTarget) -> bool {
+ if (aTarget.mSnapPoint.mX && aSnapInfo.mScrollSnapStrictnessX !=
+ StyleScrollSnapStrictness::None) {
+ aCalcSnapPoints.AddVerticalEdge(aTarget);
+ }
+ if (aTarget.mSnapPoint.mY && aSnapInfo.mScrollSnapStrictnessY !=
+ StyleScrollSnapStrictness::None) {
+ aCalcSnapPoints.AddHorizontalEdge(aTarget);
+ }
+ return true;
+ });
+}
+
+Maybe<SnapDestination> ScrollSnapUtils::GetSnapPointForDestination(
+ const ScrollSnapInfo& aSnapInfo, ScrollUnit aUnit,
+ ScrollSnapFlags aSnapFlags, const nsRect& aScrollRange,
+ const nsPoint& aStartPos, const nsPoint& aDestination) {
+ if (aSnapInfo.mScrollSnapStrictnessY == StyleScrollSnapStrictness::None &&
+ aSnapInfo.mScrollSnapStrictnessX == StyleScrollSnapStrictness::None) {
+ return Nothing();
+ }
+
+ if (!aSnapInfo.HasSnapPositions()) {
+ return Nothing();
+ }
+
+ CalcSnapPoints calcSnapPoints(aUnit, aSnapFlags, aDestination, aStartPos);
+
+ ProcessSnapPositions(calcSnapPoints, aSnapInfo);
+
+ // If the distance between the first and the second candidate snap points
+ // is larger than the snapport size and the snapport is covered by larger
+ // elements, any points inside the covering area should be valid snap
+ // points.
+ // https://drafts.csswg.org/css-scroll-snap-1/#snap-overflow
+ // NOTE: |aDestination| sometimes points outside of the scroll range, e.g.
+ // by the APZC fling, so for the overflow checks we need to clamp it.
+ nsPoint clampedDestination = aScrollRange.ClampPoint(aDestination);
+ for (auto range : aSnapInfo.mXRangeWiderThanSnapport) {
+ if (range.IsValid(clampedDestination.x, aSnapInfo.mSnapportSize.width) &&
+ calcSnapPoints.XDistanceBetweenBestAndSecondEdge() >
+ aSnapInfo.mSnapportSize.width) {
+ calcSnapPoints.AddVerticalEdge(ScrollSnapInfo::SnapTarget{
+ Some(clampedDestination.x), Nothing(), range.mSnapArea,
+ StyleScrollSnapStop::Normal, range.mTargetId});
+ break;
+ }
+ }
+ for (auto range : aSnapInfo.mYRangeWiderThanSnapport) {
+ if (range.IsValid(clampedDestination.y, aSnapInfo.mSnapportSize.height) &&
+ calcSnapPoints.YDistanceBetweenBestAndSecondEdge() >
+ aSnapInfo.mSnapportSize.height) {
+ calcSnapPoints.AddHorizontalEdge(ScrollSnapInfo::SnapTarget{
+ Nothing(), Some(clampedDestination.y), range.mSnapArea,
+ StyleScrollSnapStop::Normal, range.mTargetId});
+ break;
+ }
+ }
+
+ bool snapped = false;
+ auto finalPos = calcSnapPoints.GetBestEdge(aSnapInfo.mSnapportSize);
+ constexpr float proximityRatio = 0.3;
+ if (aSnapInfo.mScrollSnapStrictnessY ==
+ StyleScrollSnapStrictness::Proximity &&
+ std::abs(aDestination.y - finalPos.mPosition.y) >
+ aSnapInfo.mSnapportSize.height * proximityRatio) {
+ finalPos.mPosition.y = aDestination.y;
+ } else if (aSnapInfo.mScrollSnapStrictnessY !=
+ StyleScrollSnapStrictness::None &&
+ aDestination.y != finalPos.mPosition.y) {
+ snapped = true;
+ }
+ if (aSnapInfo.mScrollSnapStrictnessX ==
+ StyleScrollSnapStrictness::Proximity &&
+ std::abs(aDestination.x - finalPos.mPosition.x) >
+ aSnapInfo.mSnapportSize.width * proximityRatio) {
+ finalPos.mPosition.x = aDestination.x;
+ } else if (aSnapInfo.mScrollSnapStrictnessX !=
+ StyleScrollSnapStrictness::None &&
+ aDestination.x != finalPos.mPosition.x) {
+ snapped = true;
+ }
+ return snapped ? Some(finalPos) : Nothing();
+}
+
+ScrollSnapTargetId ScrollSnapUtils::GetTargetIdFor(const nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame && aFrame->GetContent());
+ return ScrollSnapTargetId{reinterpret_cast<uintptr_t>(aFrame->GetContent())};
+}
+
+static std::pair<Maybe<nscoord>, Maybe<nscoord>> GetCandidateInLastTargets(
+ const ScrollSnapInfo& aSnapInfo, const nsPoint& aCurrentPosition,
+ const UniquePtr<ScrollSnapTargetIds>& aLastSnapTargetIds,
+ const nsIContent* aFocusedContent) {
+ ScrollSnapTargetId targetIdForFocusedContent = ScrollSnapTargetId::None;
+ if (aFocusedContent && aFocusedContent->GetPrimaryFrame()) {
+ targetIdForFocusedContent =
+ ScrollSnapUtils::GetTargetIdFor(aFocusedContent->GetPrimaryFrame());
+ }
+
+ // Note: Below algorithm doesn't care about cases where the last snap point
+ // was on an element larger than the snapport since it's not clear to us
+ // what we should do for now.
+ // https://github.com/w3c/csswg-drafts/issues/7438
+ const ScrollSnapInfo::SnapTarget* focusedTarget = nullptr;
+ Maybe<nscoord> x, y;
+ aSnapInfo.ForEachValidTargetFor(
+ aCurrentPosition, [&](const auto& aTarget) -> bool {
+ if (aTarget.mSnapPoint.mX && aSnapInfo.mScrollSnapStrictnessX !=
+ StyleScrollSnapStrictness::None) {
+ if (aLastSnapTargetIds->mIdsOnX.Contains(aTarget.mTargetId)) {
+ if (targetIdForFocusedContent == aTarget.mTargetId) {
+ // If we've already found the candidate on Y axis, but if snapping
+ // to the point results this target is scrolled out, we can't use
+ // it.
+ if ((y && !aTarget.mSnapArea.Intersects(
+ nsRect(nsPoint(*aTarget.mSnapPoint.mX, *y),
+ aSnapInfo.mSnapportSize)))) {
+ y.reset();
+ }
+
+ focusedTarget = &aTarget;
+ // If the focused one is valid, then it's the candidate.
+ x = aTarget.mSnapPoint.mX;
+ }
+
+ if (!x) {
+ // Update the candidate on X axis only if
+ // 1) we haven't yet found the candidate on Y axis
+ // 2) or if we've found the candiate on Y axis and if snapping to
+ // the
+ // candidate position result the target element is visible
+ // inside the snapport.
+ if (!y || (y && aTarget.mSnapArea.Intersects(
+ nsRect(nsPoint(*aTarget.mSnapPoint.mX, *y),
+ aSnapInfo.mSnapportSize)))) {
+ x = aTarget.mSnapPoint.mX;
+ }
+ }
+ }
+ }
+ if (aTarget.mSnapPoint.mY && aSnapInfo.mScrollSnapStrictnessY !=
+ StyleScrollSnapStrictness::None) {
+ if (aLastSnapTargetIds->mIdsOnY.Contains(aTarget.mTargetId)) {
+ if (targetIdForFocusedContent == aTarget.mTargetId) {
+ NS_ASSERTION(
+ !focusedTarget || focusedTarget == &aTarget,
+ "If the focused target has been found on X axis, the "
+ "target should be same");
+ // If we've already found the candidate on X axis other than the
+ // focused one, but if snapping to the point results this target
+ // is scrolled out, we can't use it.
+ if (!focusedTarget &&
+ (x && !aTarget.mSnapArea.Intersects(
+ nsRect(nsPoint(*x, *aTarget.mSnapPoint.mY),
+ aSnapInfo.mSnapportSize)))) {
+ x.reset();
+ }
+
+ focusedTarget = &aTarget;
+ y = aTarget.mSnapPoint.mY;
+ }
+
+ if (!y) {
+ if (!x || (x && aTarget.mSnapArea.Intersects(
+ nsRect(nsPoint(*x, *aTarget.mSnapPoint.mY),
+ aSnapInfo.mSnapportSize)))) {
+ y = aTarget.mSnapPoint.mY;
+ }
+ }
+ }
+ }
+
+ // If we found candidates on both axes, it's the one we need.
+ if (x && y &&
+ // If we haven't found the focused target, it's possible that we
+ // haven't iterated it, don't break in such case.
+ (targetIdForFocusedContent == ScrollSnapTargetId::None ||
+ focusedTarget)) {
+ return false;
+ }
+ return true;
+ });
+
+ return {x, y};
+}
+
+Maybe<SnapDestination> ScrollSnapUtils::GetSnapPointForResnap(
+ const ScrollSnapInfo& aSnapInfo, const nsRect& aScrollRange,
+ const nsPoint& aCurrentPosition,
+ const UniquePtr<ScrollSnapTargetIds>& aLastSnapTargetIds,
+ const nsIContent* aFocusedContent) {
+ if (!aLastSnapTargetIds) {
+ return GetSnapPointForDestination(aSnapInfo, ScrollUnit::DEVICE_PIXELS,
+ ScrollSnapFlags::IntendedEndPosition,
+ aScrollRange, aCurrentPosition,
+ aCurrentPosition);
+ }
+
+ auto [x, y] = GetCandidateInLastTargets(aSnapInfo, aCurrentPosition,
+ aLastSnapTargetIds, aFocusedContent);
+ if (!x && !y) {
+ // In the worst case there's no longer valid snap points previously snapped,
+ // try to find new valid snap points.
+ return GetSnapPointForDestination(aSnapInfo, ScrollUnit::DEVICE_PIXELS,
+ ScrollSnapFlags::IntendedEndPosition,
+ aScrollRange, aCurrentPosition,
+ aCurrentPosition);
+ }
+
+ // If there's no candidate on one of the axes in the last snap points, try
+ // to find a new candidate.
+ if (!x || !y) {
+ nsPoint newPosition =
+ nsPoint(x ? *x : aCurrentPosition.x, y ? *y : aCurrentPosition.y);
+ CalcSnapPoints calcSnapPoints(ScrollUnit::DEVICE_PIXELS,
+ ScrollSnapFlags::IntendedEndPosition,
+ newPosition, newPosition);
+
+ aSnapInfo.ForEachValidTargetFor(
+ newPosition, [&, &x = x, &y = y](const auto& aTarget) -> bool {
+ if (!x && aTarget.mSnapPoint.mX &&
+ aSnapInfo.mScrollSnapStrictnessX !=
+ StyleScrollSnapStrictness::None) {
+ calcSnapPoints.AddVerticalEdge(aTarget);
+ }
+ if (!y && aTarget.mSnapPoint.mY &&
+ aSnapInfo.mScrollSnapStrictnessY !=
+ StyleScrollSnapStrictness::None) {
+ calcSnapPoints.AddHorizontalEdge(aTarget);
+ }
+ return true;
+ });
+
+ auto finalPos = calcSnapPoints.GetBestEdge(aSnapInfo.mSnapportSize);
+ if (!x) {
+ x = Some(finalPos.mPosition.x);
+ }
+ if (!y) {
+ y = Some(finalPos.mPosition.y);
+ }
+ }
+
+ SnapDestination snapTarget{nsPoint(*x, *y)};
+ // Collect snap points where the position is still same as the new snap
+ // position.
+ aSnapInfo.ForEachValidTargetFor(
+ snapTarget.mPosition, [&, &x = x, &y = y](const auto& aTarget) -> bool {
+ if (aTarget.mSnapPoint.mX &&
+ aSnapInfo.mScrollSnapStrictnessX !=
+ StyleScrollSnapStrictness::None &&
+ aTarget.mSnapPoint.mX == x) {
+ snapTarget.mTargetIds.mIdsOnX.AppendElement(aTarget.mTargetId);
+ }
+
+ if (aTarget.mSnapPoint.mY &&
+ aSnapInfo.mScrollSnapStrictnessY !=
+ StyleScrollSnapStrictness::None &&
+ aTarget.mSnapPoint.mY == y) {
+ snapTarget.mTargetIds.mIdsOnY.AppendElement(aTarget.mTargetId);
+ }
+ return true;
+ });
+ return Some(snapTarget);
+}
+
+void ScrollSnapUtils::PostPendingResnapIfNeededFor(nsIFrame* aFrame) {
+ ScrollSnapTargetId id = GetTargetIdFor(aFrame);
+ if (id == ScrollSnapTargetId::None) {
+ return;
+ }
+
+ if (nsIScrollableFrame* sf = nsLayoutUtils::GetNearestScrollableFrame(
+ aFrame, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN)) {
+ sf->PostPendingResnapIfNeeded(aFrame);
+ }
+}
+
+void ScrollSnapUtils::PostPendingResnapFor(nsIFrame* aFrame) {
+ if (nsIScrollableFrame* sf = nsLayoutUtils::GetNearestScrollableFrame(
+ aFrame, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN)) {
+ sf->PostPendingResnap();
+ }
+}
+
+bool ScrollSnapUtils::NeedsToRespectTargetWritingMode(
+ const nsSize& aSnapAreaSize, const nsSize& aSnapportSize) {
+ // Use the writing-mode on the target element if the snap area is larger than
+ // the snapport.
+ // https://drafts.csswg.org/css-scroll-snap/#snap-scope
+ //
+ // It's unclear `larger` means that the size is larger than only on the target
+ // axis. If it doesn't, it will pick the same axis in the case where only one
+ // axis is larger. For example, if an element size is (200 x 10) and the
+ // snapport size is (100 x 100) and if the element's writing mode is different
+ // from the scroller's writing mode, then `scroll-snap-align: start start`
+ // will be conflict.
+ return aSnapAreaSize.width > aSnapportSize.width ||
+ aSnapAreaSize.height > aSnapportSize.height;
+}
+
+static nsRect InflateByScrollMargin(const nsRect& aTargetRect,
+ const nsMargin& aScrollMargin,
+ const nsRect& aScrolledRect) {
+ // Inflate the rect by scroll-margin.
+ nsRect result = aTargetRect;
+ result.Inflate(aScrollMargin);
+
+ // But don't be beyond the limit boundary.
+ return result.Intersect(aScrolledRect);
+}
+
+nsRect ScrollSnapUtils::GetSnapAreaFor(const nsIFrame* aFrame,
+ const nsIFrame* aScrolledFrame,
+ const nsRect& aScrolledRect) {
+ nsRect targetRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ aFrame, aFrame->GetRectRelativeToSelf(), aScrolledFrame);
+
+ // The snap area contains scroll-margin values.
+ // https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-area
+ nsMargin scrollMargin = aFrame->StyleMargin()->GetScrollMargin();
+ return InflateByScrollMargin(targetRect, scrollMargin, aScrolledRect);
+}
+
+} // namespace mozilla
diff --git a/layout/generic/ScrollSnap.h b/layout/generic/ScrollSnap.h
new file mode 100644
index 0000000000..a599c0507b
--- /dev/null
+++ b/layout/generic/ScrollSnap.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollSnap_h_
+#define mozilla_layout_ScrollSnap_h_
+
+#include <memory>
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/ScrollSnapTargetId.h"
+#include "mozilla/Maybe.h"
+
+class nsIContent;
+class nsIFrame;
+struct nsPoint;
+struct nsRect;
+struct nsSize;
+
+namespace mozilla {
+
+struct ScrollSnapInfo;
+
+struct ScrollSnapUtils {
+ /**
+ * GetSnapPointForDestination determines which point to snap to after
+ * scrolling. |aStartPos| gives the position before scrolling and
+ * |aDestination| gives the position after scrolling, with no snapping.
+ * Behaviour is dependent on the value of |aUnit|.
+ * |aSnapInfo| and |aScrollRange| are characteristics of the scroll frame for
+ * which snapping is being performed.
+ * If a suitable snap point could be found, it is returned. Otherwise, an
+ * empty Maybe is returned.
+ * IMPORTANT NOTE: This function is designed to be called both on and off
+ * the main thread. If modifying its implementation, be sure
+ * not to touch main-thread-only data structures without
+ * appropriate locking.
+ */
+ static Maybe<SnapDestination> GetSnapPointForDestination(
+ const ScrollSnapInfo& aSnapInfo, ScrollUnit aUnit,
+ ScrollSnapFlags aSnapFlags, const nsRect& aScrollRange,
+ const nsPoint& aStartPos, const nsPoint& aDestination);
+
+ /**
+ * Similar to above GetSnapPointForDestination but for re-snapping.
+ *
+ * |aCurrentPosition| are the snap point(s) last time when we scrolled.
+ * |aLastSnapTargetIds| are the snap point(s) last time when we scrolled if
+ * exists.
+ * |aFocusedContent| is the focused content in the document if exists.
+ * Other parameters are same as GetSnapPointForDestination.
+ */
+
+ static mozilla::Maybe<SnapDestination> GetSnapPointForResnap(
+ const ScrollSnapInfo& aSnapInfo, const nsRect& aScrollRange,
+ const nsPoint& aCurrentPosition,
+ const UniquePtr<ScrollSnapTargetIds>& aLastSnapTargetIds,
+ const nsIContent* aFocusedContent);
+
+ static ScrollSnapTargetId GetTargetIdFor(const nsIFrame* aFrame);
+
+ // Post a pending re-snap request if the given |aFrame| is one of the snap
+ // points on the last scroll operation.
+ static void PostPendingResnapIfNeededFor(nsIFrame* aFrame);
+
+ // Similar to above PostPendingResnapIfNeededFor but post a pending re-snap
+ // request even if the given |aFrame| is not one of the last snap point.
+ // This is basically used for cases there was no valid snap point on the last
+ // scroll operation but the given |aFrame| might be a valid snap point now,
+ // e.g changing the scroll-snap-align property from `none` to something.
+ static void PostPendingResnapFor(nsIFrame* aFrame);
+
+ // Returns true if the writing-mode of the snap target element needs to be
+ // respected to resolve scroll-snap-align property.
+ // Note that usually the scroll container's writing-mode is used for resolving
+ // the property but there's a special case defined in the CSS scroll snap
+ // spec.
+ static bool NeedsToRespectTargetWritingMode(const nsSize& aSnapAreaSize,
+ const nsSize& aSnapportSize);
+
+ // Returns the scroll snap area for the snap target frame |aFrame| inside the
+ // nearest scroll container |aScrolledFrame| and its scrolled rect
+ // |aScrolledRect|.
+ static nsRect GetSnapAreaFor(const nsIFrame* aFrame,
+ const nsIFrame* aScrolledFrame,
+ const nsRect& aScrolledRect);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_layout_ScrollSnap_h_
diff --git a/layout/generic/ScrollSnapInfo.cpp b/layout/generic/ScrollSnapInfo.cpp
new file mode 100644
index 0000000000..7f9f410c33
--- /dev/null
+++ b/layout/generic/ScrollSnapInfo.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollSnapInfo.h"
+
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsStyleStruct.h"
+#include "mozilla/WritingModes.h"
+
+namespace mozilla {
+
+ScrollSnapInfo::ScrollSnapInfo()
+ : mScrollSnapStrictnessX(StyleScrollSnapStrictness::None),
+ mScrollSnapStrictnessY(StyleScrollSnapStrictness::None) {}
+
+bool ScrollSnapInfo::HasScrollSnapping() const {
+ return mScrollSnapStrictnessY != StyleScrollSnapStrictness::None ||
+ mScrollSnapStrictnessX != StyleScrollSnapStrictness::None;
+}
+
+bool ScrollSnapInfo::HasSnapPositions() const {
+ if (!HasScrollSnapping()) {
+ return false;
+ }
+
+ for (const auto& target : mSnapTargets) {
+ if ((target.mSnapPoint.mX &&
+ mScrollSnapStrictnessX != StyleScrollSnapStrictness::None) ||
+ (target.mSnapPoint.mY &&
+ mScrollSnapStrictnessY != StyleScrollSnapStrictness::None)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ScrollSnapInfo::InitializeScrollSnapStrictness(
+ WritingMode aWritingMode, const nsStyleDisplay* aDisplay) {
+ if (aDisplay->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) {
+ return;
+ }
+
+ mScrollSnapStrictnessX = StyleScrollSnapStrictness::None;
+ mScrollSnapStrictnessY = StyleScrollSnapStrictness::None;
+
+ switch (aDisplay->mScrollSnapType.axis) {
+ case StyleScrollSnapAxis::X:
+ mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
+ break;
+ case StyleScrollSnapAxis::Y:
+ mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
+ break;
+ case StyleScrollSnapAxis::Block:
+ if (aWritingMode.IsVertical()) {
+ mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
+ } else {
+ mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
+ }
+ break;
+ case StyleScrollSnapAxis::Inline:
+ if (aWritingMode.IsVertical()) {
+ mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
+ } else {
+ mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
+ }
+ break;
+ case StyleScrollSnapAxis::Both:
+ mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
+ mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
+ break;
+ }
+}
+
+void ScrollSnapInfo::ForEachValidTargetFor(
+ const nsPoint& aDestination,
+ const std::function<bool(const SnapTarget&)>& aFunc) const {
+ for (const auto& target : mSnapTargets) {
+ nsPoint snapPoint(
+ mScrollSnapStrictnessX != StyleScrollSnapStrictness::None &&
+ target.mSnapPoint.mX
+ ? *target.mSnapPoint.mX
+ : aDestination.x,
+ mScrollSnapStrictnessY != StyleScrollSnapStrictness::None &&
+ target.mSnapPoint.mY
+ ? *target.mSnapPoint.mY
+ : aDestination.y);
+ nsRect snappedPort = nsRect(snapPoint, mSnapportSize);
+ // Ignore snap points if snapping to the point would leave the snap area
+ // outside of the snapport.
+ // https://drafts.csswg.org/css-scroll-snap-1/#snap-scope
+ if (!snappedPort.Intersects(target.mSnapArea)) {
+ continue;
+ }
+
+ if (!aFunc(target)) {
+ break;
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/generic/ScrollSnapInfo.h b/layout/generic/ScrollSnapInfo.h
new file mode 100644
index 0000000000..bca148427b
--- /dev/null
+++ b/layout/generic/ScrollSnapInfo.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollSnapInfo_h_
+#define mozilla_layout_ScrollSnapInfo_h_
+
+#include <memory>
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/ScrollSnapTargetId.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsPoint.h"
+
+class nsIContent;
+class nsIFrame;
+struct nsRect;
+struct nsSize;
+struct nsStyleDisplay;
+
+namespace mozilla {
+
+enum class StyleScrollSnapStrictness : uint8_t;
+class WritingMode;
+
+struct SnapPoint {
+ SnapPoint() = default;
+ explicit SnapPoint(const nsPoint& aPoint)
+ : mX(Some(aPoint.x)), mY(Some(aPoint.y)) {}
+ SnapPoint(Maybe<nscoord>&& aX, Maybe<nscoord>&& aY)
+ : mX(std::move(aX)), mY(std::move(aY)) {}
+
+ bool operator==(const SnapPoint& aOther) const {
+ return mX == aOther.mX && mY == aOther.mY;
+ }
+
+ Maybe<nscoord> mX;
+ Maybe<nscoord> mY;
+};
+
+struct ScrollSnapInfo {
+ using ScrollDirection = layers::ScrollDirection;
+ ScrollSnapInfo();
+
+ bool operator==(const ScrollSnapInfo& aOther) const {
+ return mScrollSnapStrictnessX == aOther.mScrollSnapStrictnessX &&
+ mScrollSnapStrictnessY == aOther.mScrollSnapStrictnessY &&
+ mSnapTargets == aOther.mSnapTargets &&
+ mXRangeWiderThanSnapport == aOther.mXRangeWiderThanSnapport &&
+ mYRangeWiderThanSnapport == aOther.mYRangeWiderThanSnapport &&
+ mSnapportSize == aOther.mSnapportSize;
+ }
+
+ bool HasScrollSnapping() const;
+ bool HasSnapPositions() const;
+
+ void InitializeScrollSnapStrictness(WritingMode aWritingMode,
+ const nsStyleDisplay* aDisplay);
+
+ // The scroll frame's scroll-snap-type.
+ StyleScrollSnapStrictness mScrollSnapStrictnessX;
+ StyleScrollSnapStrictness mScrollSnapStrictnessY;
+
+ struct SnapTarget {
+ // The scroll positions corresponding to scroll-snap-align values.
+ SnapPoint mSnapPoint;
+
+ // https://drafts.csswg.org/css-scroll-snap/#scroll-snap-area
+ nsRect mSnapArea;
+
+ // https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-snap-stop
+ StyleScrollSnapStop mScrollSnapStop = StyleScrollSnapStop::Normal;
+
+ // Use for tracking the last snapped target.
+ ScrollSnapTargetId mTargetId = ScrollSnapTargetId::None;
+
+ SnapTarget() = default;
+
+ SnapTarget(Maybe<nscoord>&& aSnapPositionX, Maybe<nscoord>&& aSnapPositionY,
+ const nsRect& aSnapArea, StyleScrollSnapStop aScrollSnapStop,
+ ScrollSnapTargetId aTargetId)
+ : mSnapPoint(std::move(aSnapPositionX), std::move(aSnapPositionY)),
+ mSnapArea(aSnapArea),
+ mScrollSnapStop(aScrollSnapStop),
+ mTargetId(aTargetId) {}
+
+ bool operator==(const SnapTarget& aOther) const {
+ return mSnapPoint == aOther.mSnapPoint && mSnapArea == aOther.mSnapArea &&
+ mScrollSnapStop == aOther.mScrollSnapStop &&
+ mTargetId == aOther.mTargetId;
+ }
+ };
+
+ CopyableTArray<SnapTarget> mSnapTargets;
+
+ // A utility function to iterate over the valid snap targets for the given
+ // |aDestination| until |aFunc| returns false.
+ void ForEachValidTargetFor(
+ const nsPoint& aDestination,
+ const std::function<bool(const SnapTarget&)>& aFunc) const;
+
+ struct ScrollSnapRange {
+ ScrollSnapRange() = default;
+
+ ScrollSnapRange(const nsRect& aSnapArea, ScrollDirection aDirection,
+ ScrollSnapTargetId aTargetId)
+ : mSnapArea(aSnapArea), mDirection(aDirection), mTargetId(aTargetId) {}
+
+ nsRect mSnapArea;
+ ScrollDirection mDirection;
+ ScrollSnapTargetId mTargetId;
+
+ bool operator==(const ScrollSnapRange& aOther) const {
+ return mDirection == aOther.mDirection && mSnapArea == aOther.mSnapArea &&
+ mTargetId == aOther.mTargetId;
+ }
+
+ nscoord Start() const {
+ return mDirection == ScrollDirection::eHorizontal ? mSnapArea.X()
+ : mSnapArea.Y();
+ }
+
+ nscoord End() const {
+ return mDirection == ScrollDirection::eHorizontal ? mSnapArea.XMost()
+ : mSnapArea.YMost();
+ }
+
+ // Returns true if |aPoint| is a valid snap position in this range.
+ bool IsValid(nscoord aPoint, nscoord aSnapportSize) const {
+ MOZ_ASSERT(End() - Start() > aSnapportSize);
+ return Start() <= aPoint && aPoint <= End() - aSnapportSize;
+ }
+ };
+ // An array of the range that the target element is larger than the snapport
+ // on the axis.
+ // Snap positions in this range will be valid snap positions in the case where
+ // the distance between the closest snap position and the second closest snap
+ // position is still larger than the snapport size.
+ // See https://drafts.csswg.org/css-scroll-snap-1/#snap-overflow
+ //
+ // Note: This range contains scroll-margin values.
+ CopyableTArray<ScrollSnapRange> mXRangeWiderThanSnapport;
+ CopyableTArray<ScrollSnapRange> mYRangeWiderThanSnapport;
+
+ // Note: This snapport size has been already deflated by scroll-padding.
+ nsSize mSnapportSize;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_layout_ScrollSnapInfo_h_
diff --git a/layout/generic/ScrollSnapTargetId.h b/layout/generic/ScrollSnapTargetId.h
new file mode 100644
index 0000000000..f53e736fa1
--- /dev/null
+++ b/layout/generic/ScrollSnapTargetId.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ScrollSnapTargetId_h_
+#define mozilla_ScrollSnapTargetId_h_
+
+#include <cstdint>
+#include "nsPoint.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+namespace mozilla {
+
+// The id for each scroll snap target element to track the last snapped element.
+// 0 means it wasn't snapped on the last scroll operation.
+enum class ScrollSnapTargetId : uintptr_t {
+ None = 0,
+};
+
+struct ScrollSnapTargetIds {
+ CopyableTArray<ScrollSnapTargetId> mIdsOnX;
+ CopyableTArray<ScrollSnapTargetId> mIdsOnY;
+ bool operator==(const ScrollSnapTargetIds& aOther) const {
+ return mIdsOnX == aOther.mIdsOnX && mIdsOnY == aOther.mIdsOnY;
+ }
+};
+
+struct SnapDestination {
+ nsPoint mPosition;
+ ScrollSnapTargetIds mTargetIds;
+};
+
+struct CSSSnapDestination {
+ CSSPoint mPosition;
+ ScrollSnapTargetIds mTargetIds;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollSnapTargetId_h_
diff --git a/layout/generic/ScrollVelocityQueue.cpp b/layout/generic/ScrollVelocityQueue.cpp
new file mode 100644
index 0000000000..44a8ae0033
--- /dev/null
+++ b/layout/generic/ScrollVelocityQueue.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollVelocityQueue.h"
+
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsPresContext.h"
+#include "nsRefreshDriver.h"
+
+namespace mozilla {
+namespace layout {
+
+void ScrollVelocityQueue::Sample(const nsPoint& aScrollPosition) {
+ float flingSensitivity =
+ StaticPrefs::layout_css_scroll_snap_prediction_sensitivity();
+ int maxVelocity =
+ StaticPrefs::layout_css_scroll_snap_prediction_max_velocity();
+ maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
+ int maxOffset = maxVelocity * flingSensitivity;
+ TimeStamp currentRefreshTime =
+ mPresContext->RefreshDriver()->MostRecentRefresh();
+ if (mSampleTime.IsNull()) {
+ mAccumulator = nsPoint();
+ } else {
+ uint32_t durationMs = (currentRefreshTime - mSampleTime).ToMilliseconds();
+ if (durationMs > StaticPrefs::apz_velocity_relevance_time_ms()) {
+ mAccumulator = nsPoint();
+ mQueue.Clear();
+ } else if (durationMs == 0) {
+ mAccumulator += aScrollPosition - mLastPosition;
+ } else {
+ nsPoint velocity = mAccumulator * 1000 / durationMs;
+ velocity.Clamp(maxVelocity);
+ mQueue.AppendElement(std::make_pair(durationMs, velocity));
+ mAccumulator = aScrollPosition - mLastPosition;
+ }
+ }
+ mAccumulator.Clamp(maxOffset);
+ mSampleTime = currentRefreshTime;
+ mLastPosition = aScrollPosition;
+ TrimQueue();
+}
+
+void ScrollVelocityQueue::TrimQueue() {
+ if (mSampleTime.IsNull()) {
+ // There are no samples, nothing to do here.
+ return;
+ }
+
+ TimeStamp currentRefreshTime =
+ mPresContext->RefreshDriver()->MostRecentRefresh();
+ uint32_t timeDelta = (currentRefreshTime - mSampleTime).ToMilliseconds();
+ for (int i = mQueue.Length() - 1; i >= 0; i--) {
+ timeDelta += mQueue[i].first;
+ if (timeDelta >= StaticPrefs::apz_velocity_relevance_time_ms()) {
+ // The rest of the samples have expired and should be dropped
+ for (; i >= 0; i--) {
+ mQueue.RemoveElementAt(0);
+ }
+ }
+ }
+}
+
+void ScrollVelocityQueue::Reset() {
+ mAccumulator = nsPoint();
+ mSampleTime = TimeStamp();
+ mQueue.Clear();
+}
+
+/**
+ Calculate the velocity of the scroll frame, in appunits / second.
+*/
+nsPoint ScrollVelocityQueue::GetVelocity() {
+ TrimQueue();
+ if (mQueue.Length() == 0) {
+ // If getting the scroll velocity before any scrolling has occurred,
+ // the velocity must be (0, 0)
+ return nsPoint();
+ }
+ nsPoint velocity;
+ for (int i = mQueue.Length() - 1; i >= 0; i--) {
+ velocity += mQueue[i].second;
+ }
+ return velocity / mQueue.Length();
+ ;
+}
+
+} // namespace layout
+} // namespace mozilla
diff --git a/layout/generic/ScrollVelocityQueue.h b/layout/generic/ScrollVelocityQueue.h
new file mode 100644
index 0000000000..86791e866d
--- /dev/null
+++ b/layout/generic/ScrollVelocityQueue.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ScrollVelocityQueue_h_
+#define ScrollVelocityQueue_h_
+
+#include "nsTArray.h"
+#include "nsPoint.h"
+#include "mozilla/TimeStamp.h"
+
+class nsPresContext;
+
+namespace mozilla {
+namespace layout {
+
+/**
+ * ScrollVelocityQueue is used to determine the current velocity of a
+ * scroll frame, derived from scroll position samples.
+ *
+ * Using the last iteration's scroll position, stored in mLastPosition, a
+ * delta of the scroll position is calculated and accumulated in mAccumulator
+ * until the refresh driver returns a new timestamp for MostRecentRefresh().
+ *
+ * When there is a new timestamp from the refresh driver, the accumulated
+ * change in scroll position is divided by the delta of the timestamp to
+ * get an average velocity over that period. This velocity is pushed into
+ * mQueue as a std::pair associating each velocity with the
+ * duration over which it was sampled.
+ *
+ * Samples are removed from mQueue, leaving only those necessary to determine
+ * the average velocity over the recent relevant period, which has a duration
+ * set by the apz.velocity_relevance_time_ms preference.
+ *
+ * The velocity of each sample is clamped to a value set by the
+ * layout.css.scroll-snap.prediction-max-velocity.
+ *
+ * As the average velocity will later be integrated over a duration set by
+ * the layout.css.scroll-snap.prediction-sensitivity preference and the
+ * velocity samples are clamped to a set value, the maximum expected scroll
+ * offset can be calculated. This maximum offset is used to clamp
+ * mAccumulator, eliminating samples that would otherwise result in scroll
+ * snap position selection that is not consistent with the user's perception
+ * of scroll velocity.
+ */
+
+class ScrollVelocityQueue final {
+ public:
+ explicit ScrollVelocityQueue(nsPresContext* aPresContext)
+ : mPresContext(aPresContext) {}
+
+ // Sample() is to be called periodically when scroll movement occurs, to
+ // record samples of scroll position used later by GetVelocity().
+ void Sample(const nsPoint& aScrollPosition);
+
+ // Discards velocity samples, resulting in velocity of 0 returned by
+ // GetVelocity until move scroll position updates.
+ void Reset();
+
+ // Get scroll velocity averaged from recent movement, in appunits / second
+ nsPoint GetVelocity();
+
+ private:
+ // A queue of (duration, velocity) pairs; these are the historical average
+ // velocities over the given durations. Durations are in milliseconds,
+ // velocities are in app units per second.
+ nsTArray<std::pair<uint32_t, nsPoint> > mQueue;
+
+ // Accumulates the distance and direction travelled by the scroll frame since
+ // mSampleTime.
+ nsPoint mAccumulator;
+
+ // Time that mAccumulator was last reset and began accumulating.
+ TimeStamp mSampleTime;
+
+ // Scroll offset at the mAccumulator was last reset and began
+ // accumulating.
+ nsPoint mLastPosition;
+
+ // PresContext of the containing frame, used to get timebase
+ nsPresContext* mPresContext;
+
+ // Remove samples from mQueue that no longer contribute to GetVelocity()
+ // due to their age
+ void TrimQueue();
+};
+
+} // namespace layout
+} // namespace mozilla
+
+#endif /* !defined(ScrollVelocityQueue_h_) */
diff --git a/layout/generic/ScrollbarActivity.cpp b/layout/generic/ScrollbarActivity.cpp
new file mode 100644
index 0000000000..e9865c62f9
--- /dev/null
+++ b/layout/generic/ScrollbarActivity.cpp
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollbarActivity.h"
+#include "nsIScrollbarMediator.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsContentUtils.h"
+#include "nsITimer.h"
+#include "nsQueryFrame.h"
+#include "nsIScrollableFrame.h"
+#include "PresShell.h"
+#include "nsLayoutUtils.h"
+#include "nsScrollbarFrame.h"
+#include "nsRefreshDriver.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/LookAndFeel.h"
+
+namespace mozilla::layout {
+
+using mozilla::dom::Element;
+
+NS_IMPL_ISUPPORTS(ScrollbarActivity, nsIDOMEventListener)
+
+static bool DisplayOnMouseMove() {
+ return LookAndFeel::GetInt(LookAndFeel::IntID::ScrollbarDisplayOnMouseMove);
+}
+
+void ScrollbarActivity::Destroy() {
+ StopListeningForScrollbarEvents();
+ StopListeningForScrollAreaEvents();
+ CancelFadeTimer();
+}
+
+void ScrollbarActivity::ActivityOccurred() {
+ ActivityStarted();
+ ActivityStopped();
+}
+
+static void SetBooleanAttribute(Element* aElement, nsAtom* aAttribute,
+ bool aValue) {
+ if (aElement) {
+ if (aValue) {
+ aElement->SetAttr(kNameSpaceID_None, aAttribute, u"true"_ns, true);
+ } else {
+ aElement->UnsetAttr(kNameSpaceID_None, aAttribute, true);
+ }
+ }
+}
+
+void ScrollbarActivity::ActivityStarted() {
+ const bool wasActive = IsActive();
+ mNestedActivityCounter++;
+ if (wasActive) {
+ return;
+ }
+ CancelFadeTimer();
+ StartListeningForScrollbarEvents();
+ StartListeningForScrollAreaEvents();
+ SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, true);
+ SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, true);
+ mScrollbarEffectivelyVisible = true;
+}
+
+void ScrollbarActivity::ActivityStopped() {
+ if (!IsActive()) {
+ // This can happen if there was a frame reconstruction while the activity
+ // was ongoing. In this case we just do nothing. We should probably handle
+ // this case better.
+ return;
+ }
+ mNestedActivityCounter--;
+ if (IsActive()) {
+ return;
+ }
+ // Clear sticky scrollbar hover status.
+ HoveredScrollbar(nullptr);
+ StartFadeTimer();
+}
+
+NS_IMETHODIMP
+ScrollbarActivity::HandleEvent(dom::Event* aEvent) {
+ if (!mScrollbarEffectivelyVisible && !DisplayOnMouseMove()) {
+ return NS_OK;
+ }
+
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ if (type.EqualsLiteral("mousemove")) {
+ // Mouse motions anywhere in the scrollable frame should keep the
+ // scrollbars visible, but we have to be careful as content descendants of
+ // our scrollable content aren't necessarily scrolled by our scroll frame
+ // (if they are out of flow and their containing block is not a descendant
+ // of our scroll frame) and we don't want those to activate us.
+ nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame);
+ MOZ_ASSERT(scrollFrame);
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(mScrollableFrame);
+ nsCOMPtr<nsIContent> targetContent =
+ do_QueryInterface(aEvent->GetOriginalTarget());
+ nsIFrame* targetFrame =
+ targetContent ? targetContent->GetPrimaryFrame() : nullptr;
+ if ((scrollableFrame && scrollableFrame->IsRootScrollFrameOfDocument()) ||
+ !targetFrame ||
+ nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
+ scrollFrame, targetFrame,
+ scrollFrame->PresShell()->GetRootFrame())) {
+ ActivityOccurred();
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> targetContent =
+ do_QueryInterface(aEvent->GetOriginalTarget());
+
+ HandleEventForScrollbar(type, targetContent, GetHorizontalScrollbar(),
+ &mHScrollbarHovered);
+ HandleEventForScrollbar(type, targetContent, GetVerticalScrollbar(),
+ &mVScrollbarHovered);
+
+ return NS_OK;
+}
+
+void ScrollbarActivity::HandleEventForScrollbar(const nsAString& aType,
+ nsIContent* aTarget,
+ Element* aScrollbar,
+ bool* aStoredHoverState) {
+ if (!aTarget || !aScrollbar ||
+ !aTarget->IsInclusiveDescendantOf(aScrollbar)) {
+ return;
+ }
+
+ if (aType.EqualsLiteral("mousedown")) {
+ ActivityStarted();
+ } else if (aType.EqualsLiteral("mouseup")) {
+ ActivityStopped();
+ } else if (aType.EqualsLiteral("mouseover") ||
+ aType.EqualsLiteral("mouseout")) {
+ bool newHoveredState = aType.EqualsLiteral("mouseover");
+ if (newHoveredState && !*aStoredHoverState) {
+ ActivityStarted();
+ HoveredScrollbar(aScrollbar);
+ } else if (*aStoredHoverState && !newHoveredState) {
+ ActivityStopped();
+ // Don't call HoveredScrollbar(nullptr) here because we want the hover
+ // attribute to stick until the scrollbars are hidden.
+ }
+ *aStoredHoverState = newHoveredState;
+ }
+}
+
+void ScrollbarActivity::StartListeningForScrollbarEvents() {
+ if (mListeningForScrollbarEvents) {
+ return;
+ }
+
+ mHorizontalScrollbar = GetHorizontalScrollbar();
+ mVerticalScrollbar = GetVerticalScrollbar();
+
+ AddScrollbarEventListeners(mHorizontalScrollbar);
+ AddScrollbarEventListeners(mVerticalScrollbar);
+
+ mListeningForScrollbarEvents = true;
+}
+
+void ScrollbarActivity::StopListeningForScrollbarEvents() {
+ if (!mListeningForScrollbarEvents) return;
+
+ RemoveScrollbarEventListeners(mHorizontalScrollbar);
+ RemoveScrollbarEventListeners(mVerticalScrollbar);
+
+ mHorizontalScrollbar = nullptr;
+ mVerticalScrollbar = nullptr;
+ mListeningForScrollbarEvents = false;
+}
+
+void ScrollbarActivity::StartListeningForScrollAreaEvents() {
+ if (mListeningForScrollAreaEvents) {
+ return;
+ }
+ nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
+ scrollArea->GetContent()->AddEventListener(u"mousemove"_ns, this, true);
+ mListeningForScrollAreaEvents = true;
+}
+
+void ScrollbarActivity::StopListeningForScrollAreaEvents() {
+ if (!mListeningForScrollAreaEvents) {
+ return;
+ }
+ nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
+ scrollArea->GetContent()->RemoveEventListener(u"mousemove"_ns, this, true);
+ mListeningForScrollAreaEvents = false;
+}
+
+void ScrollbarActivity::AddScrollbarEventListeners(
+ dom::EventTarget* aScrollbar) {
+ if (aScrollbar) {
+ aScrollbar->AddEventListener(u"mousedown"_ns, this, true);
+ aScrollbar->AddEventListener(u"mouseup"_ns, this, true);
+ aScrollbar->AddEventListener(u"mouseover"_ns, this, true);
+ aScrollbar->AddEventListener(u"mouseout"_ns, this, true);
+ }
+}
+
+void ScrollbarActivity::RemoveScrollbarEventListeners(
+ dom::EventTarget* aScrollbar) {
+ if (aScrollbar) {
+ aScrollbar->RemoveEventListener(u"mousedown"_ns, this, true);
+ aScrollbar->RemoveEventListener(u"mouseup"_ns, this, true);
+ aScrollbar->RemoveEventListener(u"mouseover"_ns, this, true);
+ aScrollbar->RemoveEventListener(u"mouseout"_ns, this, true);
+ }
+}
+
+void ScrollbarActivity::CancelFadeTimer() {
+ if (mFadeTimer) {
+ mFadeTimer->Cancel();
+ }
+}
+
+void ScrollbarActivity::StartFadeTimer() {
+ CancelFadeTimer();
+ if (StaticPrefs::layout_testing_overlay_scrollbars_always_visible()) {
+ return;
+ }
+ if (!mFadeTimer) {
+ mFadeTimer = NS_NewTimer();
+ }
+ mFadeTimer->InitWithNamedFuncCallback(
+ [](nsITimer*, void* aClosure) {
+ RefPtr<ScrollbarActivity> activity =
+ static_cast<ScrollbarActivity*>(aClosure);
+ activity->BeginFade();
+ },
+ this, LookAndFeel::GetInt(LookAndFeel::IntID::ScrollbarFadeBeginDelay),
+ nsITimer::TYPE_ONE_SHOT, "ScrollbarActivity::FadeBeginTimerFired");
+}
+
+void ScrollbarActivity::BeginFade() {
+ MOZ_ASSERT(!IsActive());
+ mScrollbarEffectivelyVisible = false;
+ SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, false);
+ SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, false);
+}
+
+static void MaybeInvalidateScrollbarForHover(
+ Element* aScrollbarToInvalidate, Element* aScrollbarAboutToGetHover) {
+ if (aScrollbarToInvalidate) {
+ bool hasHover = aScrollbarToInvalidate->HasAttr(nsGkAtoms::hover);
+ bool willHaveHover = aScrollbarAboutToGetHover == aScrollbarToInvalidate;
+ if (hasHover != willHaveHover) {
+ if (nsIFrame* f = aScrollbarToInvalidate->GetPrimaryFrame()) {
+ f->SchedulePaint();
+ }
+ }
+ }
+}
+
+void ScrollbarActivity::HoveredScrollbar(Element* aScrollbar) {
+ Element* vertScrollbar = GetVerticalScrollbar();
+ Element* horzScrollbar = GetHorizontalScrollbar();
+ MaybeInvalidateScrollbarForHover(vertScrollbar, aScrollbar);
+ MaybeInvalidateScrollbarForHover(horzScrollbar, aScrollbar);
+
+ SetBooleanAttribute(horzScrollbar, nsGkAtoms::hover, false);
+ SetBooleanAttribute(vertScrollbar, nsGkAtoms::hover, false);
+ SetBooleanAttribute(aScrollbar, nsGkAtoms::hover, true);
+}
+
+Element* ScrollbarActivity::GetScrollbarContent(bool aVertical) {
+ nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical);
+ return box ? box->GetContent()->AsElement() : nullptr;
+}
+
+} // namespace mozilla::layout
diff --git a/layout/generic/ScrollbarActivity.h b/layout/generic/ScrollbarActivity.h
new file mode 100644
index 0000000000..c92665b4a2
--- /dev/null
+++ b/layout/generic/ScrollbarActivity.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ScrollbarActivity_h___
+#define ScrollbarActivity_h___
+
+#include "mozilla/Assertions.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
+
+class nsIContent;
+class nsIScrollbarMediator;
+class nsITimer;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+class EventTarget;
+} // namespace dom
+
+namespace layout {
+
+/**
+ * ScrollbarActivity
+ *
+ * This class manages scrollbar behavior that imitates the native Mac OS X
+ * Lion overlay scrollbar behavior: Scrollbars are only shown while "scrollbar
+ * activity" occurs, and they're hidden with a fade animation after a short
+ * delay.
+ *
+ * Scrollbar activity has these states:
+ * - inactive:
+ * Scrollbars are hidden.
+ * - ongoing activity:
+ * Scrollbars are visible and being operated on in some way, for example
+ * because they're hovered or pressed.
+ * - active, but waiting for fade out
+ * Scrollbars are still completely visible but are about to fade away.
+ * - fading out
+ * Scrollbars are subject to a fade-out animation.
+ *
+ * Initial scrollbar activity needs to be reported by the scrollbar holder that
+ * owns the ScrollbarActivity instance. This needs to happen via a call to
+ * ActivityOccurred(), for example when the current scroll position or the size
+ * of the scroll area changes.
+ *
+ * As soon as scrollbars are visible, the ScrollbarActivity class manages the
+ * rest of the activity behavior: It ensures that mouse motions inside the
+ * scroll area keep the scrollbars visible, and that scrollbars don't fade away
+ * while they're being hovered / dragged. It also sets a sticky hover attribute
+ * on the most recently hovered scrollbar.
+ *
+ * ScrollbarActivity falls into hibernation after the scrollbars have faded
+ * out. It only starts acting after the next call to ActivityOccurred() /
+ * ActivityStarted().
+ */
+
+class ScrollbarActivity final : public nsIDOMEventListener {
+ public:
+ explicit ScrollbarActivity(nsIScrollbarMediator* aScrollableFrame)
+ : mScrollableFrame(aScrollableFrame) {
+ MOZ_ASSERT(mScrollableFrame);
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ void Destroy();
+
+ void ActivityOccurred();
+ void ActivityStarted();
+ void ActivityStopped();
+
+ bool IsActive() const { return mNestedActivityCounter; }
+
+ protected:
+ virtual ~ScrollbarActivity() = default;
+
+ void StartFadeTimer();
+ void CancelFadeTimer();
+ void BeginFade();
+ void HandleEventForScrollbar(const nsAString& aType, nsIContent* aTarget,
+ dom::Element* aScrollbar,
+ bool* aStoredHoverState);
+
+ void StartListeningForScrollbarEvents();
+ void StartListeningForScrollAreaEvents();
+ void StopListeningForScrollbarEvents();
+ void StopListeningForScrollAreaEvents();
+ void AddScrollbarEventListeners(dom::EventTarget* aScrollbar);
+ void RemoveScrollbarEventListeners(dom::EventTarget* aScrollbar);
+
+ void HoveredScrollbar(dom::Element* aScrollbar);
+
+ dom::Element* GetScrollbarContent(bool aVertical);
+ dom::Element* GetHorizontalScrollbar() { return GetScrollbarContent(false); }
+ dom::Element* GetVerticalScrollbar() { return GetScrollbarContent(true); }
+
+ nsIScrollbarMediator* const mScrollableFrame;
+ nsCOMPtr<dom::EventTarget> mHorizontalScrollbar; // null while inactive
+ nsCOMPtr<dom::EventTarget> mVerticalScrollbar; // null while inactive
+ nsCOMPtr<nsITimer> mFadeTimer;
+ uint32_t mNestedActivityCounter = 0;
+ // This boolean is true from the point activity starts to the point BeginFade
+ // runs, and effectively reflects the "active" attribute of the scrollbar.
+ bool mScrollbarEffectivelyVisible = false;
+ bool mListeningForScrollbarEvents = false;
+ bool mListeningForScrollAreaEvents = false;
+ bool mHScrollbarHovered = false;
+ bool mVScrollbarHovered = false;
+};
+
+} // namespace layout
+} // namespace mozilla
+
+#endif /* ScrollbarActivity_h___ */
diff --git a/layout/generic/ScrollbarPreferences.h b/layout/generic/ScrollbarPreferences.h
new file mode 100644
index 0000000000..8fd05443e9
--- /dev/null
+++ b/layout/generic/ScrollbarPreferences.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ScrollbarPreferences_h
+#define mozilla_ScrollbarPreferences_h
+
+#include <cstdint>
+
+namespace mozilla {
+
+enum class ScrollbarPreference : uint8_t {
+ Auto,
+ Never,
+ LAST = Never,
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/generic/SelectionMovementUtils.cpp b/layout/generic/SelectionMovementUtils.cpp
new file mode 100644
index 0000000000..a71da2c205
--- /dev/null
+++ b/layout/generic/SelectionMovementUtils.cpp
@@ -0,0 +1,682 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ErrorList.h"
+#include "SelectionMovementUtils.h"
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "nsBidiPresUtils.h"
+#include "nsBlockFrame.h"
+#include "nsCaret.h"
+#include "nsCOMPtr.h"
+#include "nsFrameSelection.h"
+#include "nsFrameTraversal.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsTextFrame.h"
+
+namespace mozilla {
+using namespace dom;
+// FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode.
+// Therefore, this may not be intended by the original author.
+
+// static
+Result<PeekOffsetStruct, nsresult>
+SelectionMovementUtils::PeekOffsetForCaretMove(
+ nsIContent* aContent, uint32_t aOffset, nsDirection aDirection,
+ CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel,
+ const nsSelectionAmount aAmount, const nsPoint& aDesiredCaretPos,
+ PeekOffsetOptions aOptions) {
+ const PrimaryFrameData frameForFocus =
+ SelectionMovementUtils::GetPrimaryFrameForCaret(
+ aContent, aOffset, aOptions.contains(PeekOffsetOption::Visual), aHint,
+ aCaretBidiLevel);
+ if (!frameForFocus.mFrame) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ aOptions += {PeekOffsetOption::JumpLines, PeekOffsetOption::IsKeyboardSelect};
+ PeekOffsetStruct pos(
+ aAmount, aDirection,
+ static_cast<int32_t>(frameForFocus.mOffsetInFrameContent),
+ aDesiredCaretPos, aOptions);
+ nsresult rv = frameForFocus.mFrame->PeekOffset(&pos);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+ return pos;
+}
+
+// static
+nsPrevNextBidiLevels SelectionMovementUtils::GetPrevNextBidiLevels(
+ nsIContent* aNode, uint32_t aContentOffset, CaretAssociationHint aHint,
+ bool aJumpLines) {
+ // Get the level of the frames on each side
+ nsIFrame* currentFrame;
+ uint32_t currentOffset;
+ nsDirection direction;
+
+ nsPrevNextBidiLevels levels{};
+ levels.SetData(nullptr, nullptr, intl::BidiEmbeddingLevel::LTR(),
+ intl::BidiEmbeddingLevel::LTR());
+
+ currentFrame = SelectionMovementUtils::GetFrameForNodeOffset(
+ aNode, aContentOffset, aHint, &currentOffset);
+ if (!currentFrame) {
+ return levels;
+ }
+
+ auto [frameStart, frameEnd] = currentFrame->GetOffsets();
+
+ if (0 == frameStart && 0 == frameEnd) {
+ direction = eDirPrevious;
+ } else if (static_cast<uint32_t>(frameStart) == currentOffset) {
+ direction = eDirPrevious;
+ } else if (static_cast<uint32_t>(frameEnd) == currentOffset) {
+ direction = eDirNext;
+ } else {
+ // we are neither at the beginning nor at the end of the frame, so we have
+ // no worries
+ intl::BidiEmbeddingLevel currentLevel = currentFrame->GetEmbeddingLevel();
+ levels.SetData(currentFrame, currentFrame, currentLevel, currentLevel);
+ return levels;
+ }
+
+ PeekOffsetOptions peekOffsetOptions{PeekOffsetOption::StopAtScroller};
+ if (aJumpLines) {
+ peekOffsetOptions += PeekOffsetOption::JumpLines;
+ }
+ nsIFrame* newFrame =
+ currentFrame->GetFrameFromDirection(direction, peekOffsetOptions).mFrame;
+
+ FrameBidiData currentBidi = currentFrame->GetBidiData();
+ intl::BidiEmbeddingLevel currentLevel = currentBidi.embeddingLevel;
+ intl::BidiEmbeddingLevel newLevel =
+ newFrame ? newFrame->GetEmbeddingLevel() : currentBidi.baseLevel;
+
+ // If not jumping lines, disregard br frames, since they might be positioned
+ // incorrectly.
+ // XXX This could be removed once bug 339786 is fixed.
+ if (!aJumpLines) {
+ if (currentFrame->IsBrFrame()) {
+ currentFrame = nullptr;
+ currentLevel = currentBidi.baseLevel;
+ }
+ if (newFrame && newFrame->IsBrFrame()) {
+ newFrame = nullptr;
+ newLevel = currentBidi.baseLevel;
+ }
+ }
+
+ if (direction == eDirNext) {
+ levels.SetData(currentFrame, newFrame, currentLevel, newLevel);
+ } else {
+ levels.SetData(newFrame, currentFrame, newLevel, currentLevel);
+ }
+
+ return levels;
+}
+
+// static
+Result<nsIFrame*, nsresult> SelectionMovementUtils::GetFrameFromLevel(
+ nsIFrame* aFrameIn, nsDirection aDirection,
+ intl::BidiEmbeddingLevel aBidiLevel) {
+ if (!aFrameIn) {
+ return Err(NS_ERROR_NULL_POINTER);
+ }
+
+ intl::BidiEmbeddingLevel foundLevel = intl::BidiEmbeddingLevel::LTR();
+
+ nsFrameIterator frameIterator(aFrameIn->PresContext(), aFrameIn,
+ nsFrameIterator::Type::Leaf,
+ false, // aVisual
+ false, // aLockInScrollView
+ false, // aFollowOOFs
+ false // aSkipPopupChecks
+ );
+
+ nsIFrame* foundFrame = aFrameIn;
+ nsIFrame* theFrame = nullptr;
+ do {
+ theFrame = foundFrame;
+ foundFrame = frameIterator.Traverse(aDirection == eDirNext);
+ if (!foundFrame) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ foundLevel = foundFrame->GetEmbeddingLevel();
+
+ } while (foundLevel > aBidiLevel);
+
+ MOZ_ASSERT(theFrame);
+ return theFrame;
+}
+
+bool SelectionMovementUtils::AdjustFrameForLineStart(nsIFrame*& aFrame,
+ uint32_t& aFrameOffset) {
+ if (!aFrame->HasSignificantTerminalNewline()) {
+ return false;
+ }
+
+ auto [start, end] = aFrame->GetOffsets();
+ if (aFrameOffset != static_cast<uint32_t>(end)) {
+ return false;
+ }
+
+ nsIFrame* nextSibling = aFrame->GetNextSibling();
+ if (!nextSibling) {
+ return false;
+ }
+
+ aFrame = nextSibling;
+ std::tie(start, end) = aFrame->GetOffsets();
+ aFrameOffset = start;
+ return true;
+}
+
+static bool IsDisplayContents(const nsIContent* aContent) {
+ return aContent->IsElement() && aContent->AsElement()->IsDisplayContents();
+}
+
+// static
+nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset(
+ nsIContent* aNode, uint32_t aOffset, CaretAssociationHint aHint,
+ uint32_t* aReturnOffset /* = nullptr */) {
+ if (!aNode) {
+ return nullptr;
+ }
+
+ if (static_cast<int32_t>(aOffset) < 0) {
+ return nullptr;
+ }
+
+ if (!aNode->GetPrimaryFrame() && !IsDisplayContents(aNode)) {
+ return nullptr;
+ }
+
+ nsIFrame* returnFrame = nullptr;
+ nsCOMPtr<nsIContent> theNode;
+ uint32_t offsetInFrameContent;
+
+ while (true) {
+ offsetInFrameContent = aOffset;
+
+ theNode = aNode;
+
+ if (aNode->IsElement()) {
+ uint32_t childIndex = 0;
+ uint32_t numChildren = theNode->GetChildCount();
+
+ if (aHint == CaretAssociationHint::Before) {
+ if (aOffset > 0) {
+ childIndex = aOffset - 1;
+ } else {
+ childIndex = aOffset;
+ }
+ } else {
+ MOZ_ASSERT(aHint == CaretAssociationHint::After);
+ if (aOffset >= numChildren) {
+ if (numChildren > 0) {
+ childIndex = numChildren - 1;
+ } else {
+ childIndex = 0;
+ }
+ } else {
+ childIndex = aOffset;
+ }
+ }
+
+ if (childIndex > 0 || numChildren > 0) {
+ nsCOMPtr<nsIContent> childNode =
+ theNode->GetChildAt_Deprecated(childIndex);
+
+ if (!childNode) {
+ break;
+ }
+
+ theNode = childNode;
+ }
+
+ // Now that we have the child node, check if it too
+ // can contain children. If so, descend into child.
+ if (theNode->IsElement() && theNode->GetChildCount() &&
+ !theNode->HasIndependentSelection()) {
+ aNode = theNode;
+ aOffset = aOffset > childIndex ? theNode->GetChildCount() : 0;
+ continue;
+ }
+ // Check to see if theNode is a text node. If it is, translate
+ // aOffset into an offset into the text node.
+
+ RefPtr<Text> textNode = theNode->GetAsText();
+ if (textNode) {
+ if (theNode->GetPrimaryFrame()) {
+ if (aOffset > childIndex) {
+ uint32_t textLength = textNode->Length();
+
+ offsetInFrameContent = textLength;
+ } else {
+ offsetInFrameContent = 0;
+ }
+ } else {
+ uint32_t numChildren = aNode->GetChildCount();
+ uint32_t newChildIndex = aHint == CaretAssociationHint::Before
+ ? childIndex - 1
+ : childIndex + 1;
+
+ if (newChildIndex < numChildren) {
+ nsCOMPtr<nsIContent> newChildNode =
+ aNode->GetChildAt_Deprecated(newChildIndex);
+ if (!newChildNode) {
+ return nullptr;
+ }
+
+ aNode = newChildNode;
+ aOffset = aHint == CaretAssociationHint::Before
+ ? aNode->GetChildCount()
+ : 0;
+ continue;
+ } // newChildIndex is illegal which means we're at first or last
+ // child. Just use original node to get the frame.
+ theNode = aNode;
+ }
+ }
+ }
+
+ // If the node is a ShadowRoot, the frame needs to be adjusted,
+ // because a ShadowRoot does not get a frame. Its children are rendered
+ // as children of the host.
+ if (ShadowRoot* shadow = ShadowRoot::FromNode(theNode)) {
+ theNode = shadow->GetHost();
+ }
+
+ returnFrame = theNode->GetPrimaryFrame();
+ if (!returnFrame) {
+ if (aHint == CaretAssociationHint::Before) {
+ if (aOffset > 0) {
+ --aOffset;
+ continue;
+ }
+ break;
+ }
+ if (aOffset < theNode->GetChildCount()) {
+ ++aOffset;
+ continue;
+ }
+ break;
+ }
+
+ break;
+ } // end while
+
+ if (!returnFrame) {
+ return nullptr;
+ }
+
+ // If we ended up here and were asked to position the caret after a visible
+ // break, let's return the frame on the next line instead if it exists.
+ if (aOffset > 0 && (uint32_t)aOffset >= aNode->Length() &&
+ theNode == aNode->GetLastChild()) {
+ nsIFrame* newFrame;
+ nsLayoutUtils::IsInvisibleBreak(theNode, &newFrame);
+ if (newFrame) {
+ returnFrame = newFrame;
+ offsetInFrameContent = 0;
+ }
+ }
+
+ // find the child frame containing the offset we want
+ int32_t unused = 0;
+ returnFrame->GetChildFrameContainingOffset(
+ static_cast<int32_t>(offsetInFrameContent),
+ aHint == CaretAssociationHint::After, &unused, &returnFrame);
+ if (aReturnOffset) {
+ *aReturnOffset = offsetInFrameContent;
+ }
+ return returnFrame;
+}
+
+/**
+ * Find the first frame in an in-order traversal of the frame subtree rooted
+ * at aFrame which is either a text frame logically at the end of a line,
+ * or which is aStopAtFrame. Return null if no such frame is found. We don't
+ * descend into the children of non-eLineParticipant frames.
+ */
+static nsIFrame* CheckForTrailingTextFrameRecursive(nsIFrame* aFrame,
+ nsIFrame* aStopAtFrame) {
+ if (aFrame == aStopAtFrame ||
+ ((aFrame->IsTextFrame() &&
+ (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine()))) {
+ return aFrame;
+ }
+ if (!aFrame->IsLineParticipant()) {
+ return nullptr;
+ }
+
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ if (nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame)) {
+ return r;
+ }
+ }
+ return nullptr;
+}
+
+static nsLineBox* FindContainingLine(nsIFrame* aFrame) {
+ while (aFrame && aFrame->IsLineParticipant()) {
+ nsIFrame* parent = aFrame->GetParent();
+ nsBlockFrame* blockParent = do_QueryFrame(parent);
+ if (blockParent) {
+ bool isValid;
+ nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
+ return isValid ? iter.GetLine().get() : nullptr;
+ }
+ aFrame = parent;
+ }
+ return nullptr;
+}
+
+static void AdjustCaretFrameForLineEnd(nsIFrame** aFrame, uint32_t* aOffset,
+ bool aEditableOnly) {
+ nsLineBox* line = FindContainingLine(*aFrame);
+ if (!line) {
+ return;
+ }
+ uint32_t count = line->GetChildCount();
+ for (nsIFrame* f = line->mFirstChild; count > 0;
+ --count, f = f->GetNextSibling()) {
+ nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
+ if (r == *aFrame) {
+ return;
+ }
+ if (!r) {
+ continue;
+ }
+ // If found text frame is non-editable but the start frame content is
+ // editable, we don't want to put caret into the non-editable text node.
+ // We should return the given frame as-is in this case.
+ if (aEditableOnly && !r->GetContent()->IsEditable()) {
+ return;
+ }
+ // We found our frame, but we may not be able to properly paint the caret
+ // if -moz-user-modify differs from our actual frame.
+ MOZ_ASSERT(r->IsTextFrame(), "Expected text frame");
+ *aFrame = r;
+ *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
+ return;
+ // FYI: Setting the caret association hint was done during a call of
+ // GetPrimaryFrameForCaretAtFocusNode. Therefore, this may not be intended
+ // by the original author.
+ }
+}
+
+CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset(
+ const nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
+ uint32_t aOffset, CaretAssociationHint aFrameHint,
+ intl::BidiEmbeddingLevel aBidiLevel,
+ ForceEditableRegion aForceEditableRegion) {
+ if (!aContentNode || !aContentNode->IsInComposedDoc()) {
+ return {};
+ }
+
+ CaretFrameData result;
+ result.mHint = aFrameHint;
+ if (aFrameSelection) {
+ PresShell* presShell = aFrameSelection->GetPresShell();
+ if (!presShell) {
+ return {};
+ }
+
+ if (!aContentNode || !aContentNode->IsInComposedDoc() ||
+ presShell->GetDocument() != aContentNode->GetComposedDoc()) {
+ return {};
+ }
+
+ result.mHint = aFrameSelection->GetHint();
+ }
+
+ MOZ_ASSERT_IF(aForceEditableRegion == ForceEditableRegion::Yes,
+ aContentNode->IsEditable());
+
+ result.mFrame = result.mUnadjustedFrame =
+ SelectionMovementUtils::GetFrameForNodeOffset(
+ aContentNode, aOffset, aFrameHint, &result.mOffsetInFrameContent);
+ if (!result.mFrame) {
+ return {};
+ }
+
+ if (SelectionMovementUtils::AdjustFrameForLineStart(
+ result.mFrame, result.mOffsetInFrameContent)) {
+ result.mHint = CaretAssociationHint::After;
+ } else {
+ // if the frame is after a text frame that's logically at the end of the
+ // line (e.g. if the frame is a <br> frame), then put the caret at the end
+ // of that text frame instead. This way, the caret will be positioned as if
+ // trailing whitespace was not trimmed.
+ AdjustCaretFrameForLineEnd(
+ &result.mFrame, &result.mOffsetInFrameContent,
+ aForceEditableRegion == ForceEditableRegion::Yes);
+ }
+
+ // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
+ //
+ // Direction Style from visibility->mDirection
+ // ------------------
+ if (!result.mFrame->PresContext()->BidiEnabled()) {
+ return result;
+ }
+
+ // If there has been a reflow, take the caret Bidi level to be the level of
+ // the current frame
+ if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
+ aBidiLevel = result.mFrame->GetEmbeddingLevel();
+ }
+
+ nsIFrame* frameBefore;
+ nsIFrame* frameAfter;
+ intl::BidiEmbeddingLevel
+ levelBefore; // Bidi level of the character before the caret
+ intl::BidiEmbeddingLevel
+ levelAfter; // Bidi level of the character after the caret
+
+ auto [start, end] = result.mFrame->GetOffsets();
+ if (start == 0 || end == 0 ||
+ static_cast<uint32_t>(start) == result.mOffsetInFrameContent ||
+ static_cast<uint32_t>(end) == result.mOffsetInFrameContent) {
+ nsPrevNextBidiLevels levels = SelectionMovementUtils::GetPrevNextBidiLevels(
+ aContentNode, aOffset, result.mHint, false);
+
+ /* Boundary condition, we need to know the Bidi levels of the characters
+ * before and after the caret */
+ if (levels.mFrameBefore || levels.mFrameAfter) {
+ frameBefore = levels.mFrameBefore;
+ frameAfter = levels.mFrameAfter;
+ levelBefore = levels.mLevelBefore;
+ levelAfter = levels.mLevelAfter;
+
+ if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore)) {
+ aBidiLevel =
+ std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3
+ aBidiLevel =
+ std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4
+ if (aBidiLevel == levelBefore || // rule c1
+ (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
+ aBidiLevel.IsSameDirection(levelBefore)) || // rule c5
+ (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
+ aBidiLevel.IsSameDirection(levelBefore))) // rule c9
+ {
+ if (result.mFrame != frameBefore) {
+ if (frameBefore) { // if there is a frameBefore, move into it
+ result.mFrame = frameBefore;
+ std::tie(start, end) = result.mFrame->GetOffsets();
+ result.mOffsetInFrameContent = end;
+ } else {
+ // if there is no frameBefore, we must be at the beginning of
+ // the line so we stay with the current frame. Exception: when
+ // the first frame on the line has a different Bidi level from
+ // the paragraph level, there is no real frame for the caret to
+ // be in. We have to find the visually first frame on the line.
+ intl::BidiEmbeddingLevel baseLevel = frameAfter->GetBaseLevel();
+ if (baseLevel != levelAfter) {
+ PeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0,
+ nsPoint(0, 0),
+ {PeekOffsetOption::StopAtScroller,
+ PeekOffsetOption::Visual});
+ if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
+ result.mFrame = pos.mResultFrame;
+ result.mOffsetInFrameContent = pos.mContentOffset;
+ }
+ }
+ }
+ }
+ } else if (aBidiLevel == levelAfter || // rule c2
+ (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
+ aBidiLevel.IsSameDirection(levelAfter)) || // rule c6
+ (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
+ aBidiLevel.IsSameDirection(levelAfter))) // rule c10
+ {
+ if (result.mFrame != frameAfter) {
+ if (frameAfter) {
+ // if there is a frameAfter, move into it
+ result.mFrame = frameAfter;
+ std::tie(start, end) = result.mFrame->GetOffsets();
+ result.mOffsetInFrameContent = start;
+ } else {
+ // if there is no frameAfter, we must be at the end of the line
+ // so we stay with the current frame.
+ // Exception: when the last frame on the line has a different
+ // Bidi level from the paragraph level, there is no real frame
+ // for the caret to be in. We have to find the visually last
+ // frame on the line.
+ intl::BidiEmbeddingLevel baseLevel = frameBefore->GetBaseLevel();
+ if (baseLevel != levelBefore) {
+ PeekOffsetStruct pos(eSelectEndLine, eDirNext, 0, nsPoint(0, 0),
+ {PeekOffsetOption::StopAtScroller,
+ PeekOffsetOption::Visual});
+ if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
+ result.mFrame = pos.mResultFrame;
+ result.mOffsetInFrameContent = pos.mContentOffset;
+ }
+ }
+ }
+ }
+ } else if (aBidiLevel > levelBefore &&
+ aBidiLevel < levelAfter && // rule c7/8
+ // before and after have the same parity
+ levelBefore.IsSameDirection(levelAfter) &&
+ // caret has different parity
+ !aBidiLevel.IsSameDirection(levelAfter)) {
+ MOZ_ASSERT_IF(aFrameSelection && aFrameSelection->GetPresShell(),
+ aFrameSelection->GetPresShell()->GetPresContext() ==
+ frameAfter->PresContext());
+ Result<nsIFrame*, nsresult> frameOrError =
+ SelectionMovementUtils::GetFrameFromLevel(frameAfter, eDirNext,
+ aBidiLevel);
+ if (MOZ_LIKELY(frameOrError.isOk())) {
+ result.mFrame = frameOrError.unwrap();
+ std::tie(start, end) = result.mFrame->GetOffsets();
+ levelAfter = result.mFrame->GetEmbeddingLevel();
+ if (aBidiLevel.IsRTL()) {
+ // c8: caret to the right of the rightmost character
+ result.mOffsetInFrameContent = levelAfter.IsRTL() ? start : end;
+ } else {
+ // c7: caret to the left of the leftmost character
+ result.mOffsetInFrameContent = levelAfter.IsRTL() ? end : start;
+ }
+ }
+ } else if (aBidiLevel < levelBefore &&
+ aBidiLevel > levelAfter && // rule c11/12
+ // before and after have the same parity
+ levelBefore.IsSameDirection(levelAfter) &&
+ // caret has different parity
+ !aBidiLevel.IsSameDirection(levelAfter)) {
+ MOZ_ASSERT_IF(aFrameSelection && aFrameSelection->GetPresShell(),
+ aFrameSelection->GetPresShell()->GetPresContext() ==
+ frameBefore->PresContext());
+ Result<nsIFrame*, nsresult> frameOrError =
+ SelectionMovementUtils::GetFrameFromLevel(
+ frameBefore, eDirPrevious, aBidiLevel);
+ if (MOZ_LIKELY(frameOrError.isOk())) {
+ result.mFrame = frameOrError.unwrap();
+ std::tie(start, end) = result.mFrame->GetOffsets();
+ levelBefore = result.mFrame->GetEmbeddingLevel();
+ if (aBidiLevel.IsRTL()) {
+ // c12: caret to the left of the leftmost character
+ result.mOffsetInFrameContent = levelBefore.IsRTL() ? end : start;
+ } else {
+ // c11: caret to the right of the rightmost character
+ result.mOffsetInFrameContent = levelBefore.IsRTL() ? start : end;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+// static
+PrimaryFrameData SelectionMovementUtils::GetPrimaryFrameForCaret(
+ nsIContent* aContent, uint32_t aOffset, bool aVisual,
+ CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel) {
+ MOZ_ASSERT(aContent);
+
+ {
+ const PrimaryFrameData result =
+ SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset(
+ aContent, aOffset, aVisual, aHint, aCaretBidiLevel);
+ if (result.mFrame) {
+ return result;
+ }
+ }
+
+ // If aContent is whitespace only, we promote focus node to parent because
+ // whitespace only node might have no frame.
+
+ if (!aContent->TextIsOnlyWhitespace()) {
+ return {};
+ }
+
+ nsIContent* parent = aContent->GetParent();
+ if (NS_WARN_IF(!parent)) {
+ return {};
+ }
+ const Maybe<uint32_t> offset = parent->ComputeIndexOf(aContent);
+ if (NS_WARN_IF(offset.isNothing())) {
+ return {};
+ }
+ return SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset(
+ parent, *offset, aVisual, aHint, aCaretBidiLevel);
+}
+
+// static
+PrimaryFrameData SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset(
+ nsIContent* aContent, uint32_t aOffset, bool aVisual,
+ CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel) {
+ if (aVisual) {
+ const CaretFrameData result =
+ SelectionMovementUtils::GetCaretFrameForNodeOffset(
+ nullptr, aContent, aOffset, aHint, aCaretBidiLevel,
+ aContent && aContent->IsEditable() ? ForceEditableRegion::Yes
+ : ForceEditableRegion::No);
+ return {result.mFrame, result.mOffsetInFrameContent, result.mHint};
+ }
+
+ uint32_t offset = 0;
+ nsIFrame* theFrame = SelectionMovementUtils::GetFrameForNodeOffset(
+ aContent, aOffset, aHint, &offset);
+ return {theFrame, offset, aHint};
+}
+
+} // namespace mozilla
diff --git a/layout/generic/SelectionMovementUtils.h b/layout/generic/SelectionMovementUtils.h
new file mode 100644
index 0000000000..5d81949488
--- /dev/null
+++ b/layout/generic/SelectionMovementUtils.h
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SelectionMovementUtils_h
+#define mozilla_SelectionMovementUtils_h
+
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/Result.h"
+#include "nsIFrame.h"
+
+struct nsPrevNextBidiLevels;
+
+namespace mozilla {
+
+class PresShell;
+enum class PeekOffsetOption : uint16_t;
+
+namespace intl {
+class BidiEmbeddingLevel;
+}
+
+struct MOZ_STACK_CLASS PrimaryFrameData {
+ // The frame which should be used to layout the caret.
+ nsIFrame* mFrame = nullptr;
+ // The offset in content of mFrame. This is valid only when mFrame is not
+ // nullptr.
+ uint32_t mOffsetInFrameContent = 0;
+ // Whether the caret should be put before or after the point. This is valid
+ // only when mFrame is not nullptr.
+ CaretAssociationHint mHint{0}; // Before
+};
+
+struct MOZ_STACK_CLASS CaretFrameData : public PrimaryFrameData {
+ // The frame which is found only from a DOM point. This frame becomes
+ // different from mFrame when the point is around end of a line or
+ // at a bidi text boundary.
+ nsIFrame* mUnadjustedFrame = nullptr;
+};
+
+enum class ForceEditableRegion : bool { No, Yes };
+
+class SelectionMovementUtils final {
+ public:
+ using PeekOffsetOptions = EnumSet<PeekOffsetOption>;
+
+ /**
+ * Given a node and its child offset, return the nsIFrame and the offset into
+ * that frame.
+ *
+ * @param aNode input parameter for the node to look at
+ * TODO: Make this `const nsIContent*` for `ContentEventHandler`.
+ * @param aOffset offset into above node.
+ * @param aReturnOffset will contain offset into frame.
+ */
+ static nsIFrame* GetFrameForNodeOffset(nsIContent* aNode, uint32_t aOffset,
+ CaretAssociationHint aHint,
+ uint32_t* aReturnOffset = nullptr);
+
+ /**
+ * GetPrevNextBidiLevels will return the frames and associated Bidi levels of
+ * the characters logically before and after a (collapsed) selection.
+ *
+ * @param aNode is the node containing the selection
+ * @param aContentOffset is the offset of the selection in the node
+ * @param aJumpLines
+ * If true, look across line boundaries.
+ * If false, behave as if there were base-level frames at line edges.
+ *
+ * @return A struct holding the before/after frame and the before/after
+ * level.
+ *
+ * At the beginning and end of each line there is assumed to be a frame with
+ * Bidi level equal to the paragraph embedding level.
+ *
+ * In these cases the before frame and after frame respectively will be
+ * nullptr.
+ */
+ static nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
+ uint32_t aContentOffset,
+ CaretAssociationHint aHint,
+ bool aJumpLines);
+
+ /**
+ * PeekOffsetForCaretMove() only peek offset for caret move from the specified
+ * point of the normal selection. I.e., won't change selection ranges nor
+ * bidi information.
+ */
+ static Result<PeekOffsetStruct, nsresult> PeekOffsetForCaretMove(
+ nsIContent* aContent, uint32_t aOffset, nsDirection aDirection,
+ CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel,
+ const nsSelectionAmount aAmount, const nsPoint& aDesiredCaretPos,
+ PeekOffsetOptions aOptions);
+
+ /**
+ * IsIntraLineCaretMove() is a helper method for PeekOffsetForCaretMove()
+ * and CreateRangeExtendedToSomwhereFromNormalSelection(). This returns
+ * whether aAmount is intra line move or is crossing hard line break.
+ * This returns error if aMount is not supported by the methods.
+ */
+ static Result<bool, nsresult> IsIntraLineCaretMove(
+ nsSelectionAmount aAmount) {
+ switch (aAmount) {
+ case eSelectCharacter:
+ case eSelectCluster:
+ case eSelectWord:
+ case eSelectWordNoSpace:
+ case eSelectBeginLine:
+ case eSelectEndLine:
+ return true;
+ case eSelectLine:
+ return false;
+ default:
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+
+ /**
+ * Return a frame for considering caret geometry.
+ *
+ * @param aFrameSelection [optional] If this is specified and selection in
+ * aContent is not managed by the specified
+ * instance, return nullptr.
+ * @param aContentNode The content node where selection is collapsed.
+ * @param aOffset Collapsed position in aContentNode
+ * @param aFrameHint Caret association hint.
+ * @param aBidiLevel
+ * @param aForceEditableRegion Whether selection should be limited in
+ * editable region or not.
+ */
+ static CaretFrameData GetCaretFrameForNodeOffset(
+ const nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
+ uint32_t aOffset, CaretAssociationHint aFrameHint,
+ intl::BidiEmbeddingLevel aBidiLevel,
+ ForceEditableRegion aForceEditableRegion);
+
+ static bool AdjustFrameForLineStart(nsIFrame*& aFrame,
+ uint32_t& aFrameOffset);
+
+ /**
+ * Get primary frame and some other data for putting caret or extending
+ * selection at the point.
+ */
+ static PrimaryFrameData GetPrimaryFrameForCaret(
+ nsIContent* aContent, uint32_t aOffset, bool aVisual,
+ CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
+
+ private:
+ /**
+ * GetFrameFromLevel will scan in a given direction
+ * until it finds a frame with a Bidi level less than or equal to a given
+ * level. It will return the last frame before this.
+ *
+ * @param aPresContext is the context to use
+ * @param aFrameIn is the frame to start from
+ * @param aDirection is the direction to scan
+ * @param aBidiLevel is the level to search for
+ */
+ static Result<nsIFrame*, nsresult> GetFrameFromLevel(
+ nsIFrame* aFrameIn, nsDirection aDirection,
+ intl::BidiEmbeddingLevel aBidiLevel);
+
+ // This is helper method for GetPrimaryFrameForCaret.
+ // If aVisual is true, this returns caret frame.
+ // If false, this returns primary frame.
+ static PrimaryFrameData GetPrimaryOrCaretFrameForNodeOffset(
+ nsIContent* aContent, uint32_t aOffset, bool aVisual,
+ CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_SelectionMovementUtils_h
diff --git a/layout/generic/StickyScrollContainer.cpp b/layout/generic/StickyScrollContainer.cpp
new file mode 100644
index 0000000000..2e9d32ab5f
--- /dev/null
+++ b/layout/generic/StickyScrollContainer.cpp
@@ -0,0 +1,415 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * compute sticky positioning, both during reflow and when the scrolling
+ * container scrolls
+ */
+
+#include "StickyScrollContainer.h"
+
+#include "mozilla/OverflowChangedTracker.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla {
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(StickyScrollContainerProperty,
+ StickyScrollContainer)
+
+StickyScrollContainer::StickyScrollContainer(nsIScrollableFrame* aScrollFrame)
+ : mScrollFrame(aScrollFrame) {
+ mScrollFrame->AddScrollPositionListener(this);
+}
+
+StickyScrollContainer::~StickyScrollContainer() {
+ mScrollFrame->RemoveScrollPositionListener(this);
+}
+
+// static
+StickyScrollContainer* StickyScrollContainer::GetStickyScrollContainerForFrame(
+ nsIFrame* aFrame) {
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
+ aFrame->GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_STOP_AT_PAGE |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+ if (!scrollFrame) {
+ // We might not find any, for instance in the case of
+ // <html style="position: fixed">
+ return nullptr;
+ }
+ nsIFrame* frame = do_QueryFrame(scrollFrame);
+ StickyScrollContainer* s =
+ frame->GetProperty(StickyScrollContainerProperty());
+ if (!s) {
+ s = new StickyScrollContainer(scrollFrame);
+ frame->SetProperty(StickyScrollContainerProperty(), s);
+ }
+ return s;
+}
+
+// static
+void StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary(
+ nsIFrame* aFrame, nsIFrame* aOldParent) {
+ nsIScrollableFrame* oldScrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
+ aOldParent, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+ if (!oldScrollFrame) {
+ // XXX maybe aFrame has sticky descendants that can be sticky now, but
+ // we aren't going to handle that.
+ return;
+ }
+
+ StickyScrollContainer* oldSSC =
+ static_cast<nsIFrame*>(do_QueryFrame(oldScrollFrame))
+ ->GetProperty(StickyScrollContainerProperty());
+ if (!oldSSC) {
+ // aOldParent had no sticky descendants, so aFrame doesn't have any sticky
+ // descendants, and we're done here.
+ return;
+ }
+
+ auto i = oldSSC->mFrames.Length();
+ while (i-- > 0) {
+ nsIFrame* f = oldSSC->mFrames[i];
+ StickyScrollContainer* newSSC = GetStickyScrollContainerForFrame(f);
+ if (newSSC != oldSSC) {
+ oldSSC->RemoveFrame(f);
+ if (newSSC) {
+ newSSC->AddFrame(f);
+ }
+ }
+ }
+}
+
+// static
+StickyScrollContainer*
+StickyScrollContainer::GetStickyScrollContainerForScrollFrame(
+ nsIFrame* aFrame) {
+ return aFrame->GetProperty(StickyScrollContainerProperty());
+}
+
+static nscoord ComputeStickySideOffset(
+ Side aSide, const StyleRect<LengthPercentageOrAuto>& aOffset,
+ nscoord aPercentBasis) {
+ auto& side = aOffset.Get(aSide);
+ if (side.IsAuto()) {
+ return NS_AUTOOFFSET;
+ }
+ return nsLayoutUtils::ComputeCBDependentValue(aPercentBasis, side);
+}
+
+// static
+void StickyScrollContainer::ComputeStickyOffsets(nsIFrame* aFrame) {
+ nsIScrollableFrame* scrollableFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(
+ aFrame->GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+
+ if (!scrollableFrame) {
+ // Bail.
+ return;
+ }
+
+ nsSize scrollContainerSize = scrollableFrame->GetScrolledFrame()
+ ->GetContentRectRelativeToSelf()
+ .Size();
+
+ nsMargin computedOffsets;
+ const nsStylePosition* position = aFrame->StylePosition();
+
+ computedOffsets.left = ComputeStickySideOffset(eSideLeft, position->mOffset,
+ scrollContainerSize.width);
+ computedOffsets.right = ComputeStickySideOffset(eSideRight, position->mOffset,
+ scrollContainerSize.width);
+ computedOffsets.top = ComputeStickySideOffset(eSideTop, position->mOffset,
+ scrollContainerSize.height);
+ computedOffsets.bottom = ComputeStickySideOffset(
+ eSideBottom, position->mOffset, scrollContainerSize.height);
+
+ // Store the offset
+ nsMargin* offsets = aFrame->GetProperty(nsIFrame::ComputedOffsetProperty());
+ if (offsets) {
+ *offsets = computedOffsets;
+ } else {
+ aFrame->SetProperty(nsIFrame::ComputedOffsetProperty(),
+ new nsMargin(computedOffsets));
+ }
+}
+
+static constexpr nscoord gUnboundedNegative = nscoord_MIN / 2;
+static constexpr nscoord gUnboundedExtent = nscoord_MAX;
+static constexpr nscoord gUnboundedPositive =
+ gUnboundedNegative + gUnboundedExtent;
+
+void StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame,
+ nsRect* aStick,
+ nsRect* aContain) const {
+ NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame),
+ "Can't sticky position individual continuations");
+
+ aStick->SetRect(gUnboundedNegative, gUnboundedNegative, gUnboundedExtent,
+ gUnboundedExtent);
+ aContain->SetRect(gUnboundedNegative, gUnboundedNegative, gUnboundedExtent,
+ gUnboundedExtent);
+
+ const nsMargin* computedOffsets =
+ aFrame->GetProperty(nsIFrame::ComputedOffsetProperty());
+ if (!computedOffsets) {
+ // We haven't reflowed the scroll frame yet, so offsets haven't been
+ // computed. Bail.
+ return;
+ }
+
+ nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame();
+ nsIFrame* cbFrame = aFrame->GetContainingBlock();
+ NS_ASSERTION(cbFrame == scrolledFrame ||
+ nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame),
+ "Scroll frame should be an ancestor of the containing block");
+
+ nsRect rect =
+ nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame->GetParent());
+
+ // FIXME(bug 1421660): Table row groups aren't supposed to be containing
+ // blocks, but we treat them as such (maybe it's the right thing to do!).
+ // Anyway, not having this basically disables position: sticky on table cells,
+ // which would be really unfortunate, and doesn't match what other browsers
+ // do.
+ if (cbFrame != scrolledFrame && cbFrame->IsTableRowGroupFrame()) {
+ cbFrame = cbFrame->GetContainingBlock();
+ }
+
+ // Containing block limits for the position of aFrame relative to its parent.
+ // The margin box of the sticky element stays within the content box of the
+ // contaning-block element.
+ if (cbFrame == scrolledFrame) {
+ // cbFrame is the scrolledFrame, and it won't have continuations. Unlike the
+ // else clause, we consider scrollable overflow rect because and the union
+ // of its in-flow rects doesn't include the scrollable overflow area.
+ *aContain = cbFrame->ScrollableOverflowRectRelativeToSelf();
+ nsLayoutUtils::TransformRect(cbFrame, aFrame->GetParent(), *aContain);
+ } else {
+ *aContain = nsLayoutUtils::GetAllInFlowRectsUnion(
+ cbFrame, aFrame->GetParent(), nsLayoutUtils::RECTS_USE_CONTENT_BOX);
+ }
+
+ nsRect marginRect = nsLayoutUtils::GetAllInFlowRectsUnion(
+ aFrame, aFrame->GetParent(), nsLayoutUtils::RECTS_USE_MARGIN_BOX);
+
+ // Deflate aContain by the difference between the union of aFrame's
+ // continuations' margin boxes and the union of their border boxes, so that
+ // by keeping aFrame within aContain, we keep the union of the margin boxes
+ // within the containing block's content box.
+ aContain->Deflate(marginRect - rect);
+
+ // Deflate aContain by the border-box size, to form a constraint on the
+ // upper-left corner of aFrame and continuations.
+ aContain->Deflate(nsMargin(0, rect.width, rect.height, 0));
+
+ nsMargin sfPadding = scrolledFrame->GetUsedPadding();
+ nsPoint sfOffset = aFrame->GetParent()->GetOffsetTo(scrolledFrame);
+
+ // Top
+ if (computedOffsets->top != NS_AUTOOFFSET) {
+ aStick->SetTopEdge(mScrollPosition.y + sfPadding.top +
+ computedOffsets->top - sfOffset.y);
+ }
+
+ nsSize sfSize = scrolledFrame->GetContentRectRelativeToSelf().Size();
+
+ // Bottom
+ if (computedOffsets->bottom != NS_AUTOOFFSET &&
+ (computedOffsets->top == NS_AUTOOFFSET ||
+ rect.height <= sfSize.height - computedOffsets->TopBottom())) {
+ aStick->SetBottomEdge(mScrollPosition.y + sfPadding.top + sfSize.height -
+ computedOffsets->bottom - rect.height - sfOffset.y);
+ }
+
+ StyleDirection direction = cbFrame->StyleVisibility()->mDirection;
+
+ // Left
+ if (computedOffsets->left != NS_AUTOOFFSET &&
+ (computedOffsets->right == NS_AUTOOFFSET ||
+ direction == StyleDirection::Ltr ||
+ rect.width <= sfSize.width - computedOffsets->LeftRight())) {
+ aStick->SetLeftEdge(mScrollPosition.x + sfPadding.left +
+ computedOffsets->left - sfOffset.x);
+ }
+
+ // Right
+ if (computedOffsets->right != NS_AUTOOFFSET &&
+ (computedOffsets->left == NS_AUTOOFFSET ||
+ direction == StyleDirection::Rtl ||
+ rect.width <= sfSize.width - computedOffsets->LeftRight())) {
+ aStick->SetRightEdge(mScrollPosition.x + sfPadding.left + sfSize.width -
+ computedOffsets->right - rect.width - sfOffset.x);
+ }
+
+ // These limits are for the bounding box of aFrame's continuations. Convert
+ // to limits for aFrame itself.
+ nsPoint frameOffset = aFrame->GetPosition() - rect.TopLeft();
+ aStick->MoveBy(frameOffset);
+ aContain->MoveBy(frameOffset);
+}
+
+nsPoint StickyScrollContainer::ComputePosition(nsIFrame* aFrame) const {
+ nsRect stick;
+ nsRect contain;
+ ComputeStickyLimits(aFrame, &stick, &contain);
+
+ nsPoint position = aFrame->GetNormalPosition();
+
+ // For each sticky direction (top, bottom, left, right), move the frame along
+ // the appropriate axis, based on the scroll position, but limit this to keep
+ // the element's margin box within the containing block.
+ position.y = std::max(position.y, std::min(stick.y, contain.YMost()));
+ position.y = std::min(position.y, std::max(stick.YMost(), contain.y));
+ position.x = std::max(position.x, std::min(stick.x, contain.XMost()));
+ position.x = std::min(position.x, std::max(stick.XMost(), contain.x));
+
+ return position;
+}
+
+bool StickyScrollContainer::IsStuckInYDirection(nsIFrame* aFrame) const {
+ nsPoint position = ComputePosition(aFrame);
+ return position.y != aFrame->GetNormalPosition().y;
+}
+
+void StickyScrollContainer::GetScrollRanges(nsIFrame* aFrame,
+ nsRectAbsolute* aOuter,
+ nsRectAbsolute* aInner) const {
+ // We need to use the first in flow; continuation frames should not move
+ // relative to each other and should get identical scroll ranges.
+ // Also, ComputeStickyLimits requires this.
+ nsIFrame* firstCont =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+
+ nsRect stickRect;
+ nsRect containRect;
+ ComputeStickyLimits(firstCont, &stickRect, &containRect);
+
+ nsRectAbsolute stick = nsRectAbsolute::FromRect(stickRect);
+ nsRectAbsolute contain = nsRectAbsolute::FromRect(containRect);
+
+ aOuter->SetBox(gUnboundedNegative, gUnboundedNegative, gUnboundedPositive,
+ gUnboundedPositive);
+ aInner->SetBox(gUnboundedNegative, gUnboundedNegative, gUnboundedPositive,
+ gUnboundedPositive);
+
+ const nsPoint normalPosition = firstCont->GetNormalPosition();
+
+ // Bottom and top
+ if (stick.YMost() != gUnboundedPositive) {
+ aOuter->SetTopEdge(contain.Y() - stick.YMost());
+ aInner->SetTopEdge(normalPosition.y - stick.YMost());
+ }
+
+ if (stick.Y() != gUnboundedNegative) {
+ aInner->SetBottomEdge(normalPosition.y - stick.Y());
+ aOuter->SetBottomEdge(contain.YMost() - stick.Y());
+ }
+
+ // Right and left
+ if (stick.XMost() != gUnboundedPositive) {
+ aOuter->SetLeftEdge(contain.X() - stick.XMost());
+ aInner->SetLeftEdge(normalPosition.x - stick.XMost());
+ }
+
+ if (stick.X() != gUnboundedNegative) {
+ aInner->SetRightEdge(normalPosition.x - stick.X());
+ aOuter->SetRightEdge(contain.XMost() - stick.X());
+ }
+
+ // Make sure |inner| does not extend outside of |outer|. (The consumers of
+ // the Layers API, to which this information is propagated, expect this
+ // invariant to hold.) The calculated value of |inner| can sometimes extend
+ // outside of |outer|, for example due to margin collapsing, since
+ // GetNormalPosition() returns the actual position after margin collapsing,
+ // while |contain| is calculated based on the frame's GetUsedMargin() which
+ // is pre-collapsing.
+ // Note that this doesn't necessarily solve all problems stemming from
+ // comparing pre- and post-collapsing margins (TODO: find a proper solution).
+ *aInner = aInner->Intersect(*aOuter);
+ if (aInner->IsEmpty()) {
+ // This might happen if aInner didn't intersect aOuter at all initially,
+ // in which case aInner is empty and outside aOuter. Make sure it doesn't
+ // extend outside aOuter.
+ *aInner = aInner->MoveInsideAndClamp(*aOuter);
+ }
+}
+
+void StickyScrollContainer::PositionContinuations(nsIFrame* aFrame) {
+ NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame),
+ "Should be starting from the first continuation");
+ bool hadProperty;
+ nsPoint translation =
+ ComputePosition(aFrame) - aFrame->GetNormalPosition(&hadProperty);
+ if (NS_WARN_IF(!hadProperty)) {
+ // If the frame was never relatively positioned, don't move its position
+ // dynamically. There are a variety of frames for which `position` doesn't
+ // really apply like frames inside svg which would get here and be sticky
+ // only in one direction.
+ return;
+ }
+
+ // Move all continuation frames by the same amount.
+ for (nsIFrame* cont = aFrame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ cont->SetPosition(cont->GetNormalPosition() + translation);
+ }
+}
+
+void StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition,
+ nsIFrame* aSubtreeRoot) {
+#ifdef DEBUG
+ {
+ nsIFrame* scrollFrameAsFrame = do_QueryFrame(mScrollFrame);
+ NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == scrollFrameAsFrame,
+ "If reflowing, should be reflowing the scroll frame");
+ }
+#endif
+ mScrollPosition = aScrollPosition;
+
+ OverflowChangedTracker oct;
+ oct.SetSubtreeRoot(aSubtreeRoot);
+ for (nsTArray<nsIFrame*>::size_type i = 0; i < mFrames.Length(); i++) {
+ nsIFrame* f = mFrames[i];
+ if (!nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(f)) {
+ // This frame was added in nsIFrame::Init before we knew it wasn't
+ // the first ib-split-sibling.
+ mFrames.RemoveElementAt(i);
+ --i;
+ continue;
+ }
+
+ if (aSubtreeRoot) {
+ // Reflowing the scroll frame, so recompute offsets.
+ ComputeStickyOffsets(f);
+ }
+ // mFrames will only contain first continuations, because we filter in
+ // nsIFrame::Init.
+ PositionContinuations(f);
+
+ f = f->GetParent();
+ if (f != aSubtreeRoot) {
+ for (nsIFrame* cont = f; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ oct.AddFrame(cont, OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ }
+ }
+ oct.Flush();
+}
+
+void StickyScrollContainer::ScrollPositionWillChange(nscoord aX, nscoord aY) {}
+
+void StickyScrollContainer::ScrollPositionDidChange(nscoord aX, nscoord aY) {
+ UpdatePositions(nsPoint(aX, aY), nullptr);
+}
+
+} // namespace mozilla
diff --git a/layout/generic/StickyScrollContainer.h b/layout/generic/StickyScrollContainer.h
new file mode 100644
index 0000000000..64f2aea508
--- /dev/null
+++ b/layout/generic/StickyScrollContainer.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * compute sticky positioning, both during reflow and when the scrolling
+ * container scrolls
+ */
+
+#ifndef StickyScrollContainer_h
+#define StickyScrollContainer_h
+
+#include "nsPoint.h"
+#include "nsRectAbsolute.h"
+#include "nsTArray.h"
+#include "nsIScrollPositionListener.h"
+
+struct nsRect;
+class nsIFrame;
+class nsIScrollableFrame;
+
+namespace mozilla {
+
+class StickyScrollContainer final : public nsIScrollPositionListener {
+ public:
+ /**
+ * Find (and create if necessary) the StickyScrollContainer associated with
+ * the scroll container of the given frame, if a scroll container exists.
+ */
+ static StickyScrollContainer* GetStickyScrollContainerForFrame(
+ nsIFrame* aFrame);
+
+ /**
+ * Find the StickyScrollContainer associated with the given scroll frame,
+ * if it exists.
+ */
+ static StickyScrollContainer* GetStickyScrollContainerForScrollFrame(
+ nsIFrame* aScrollFrame);
+
+ /**
+ * aFrame may have moved into or out of a scroll frame's frame subtree.
+ */
+ static void NotifyReparentedFrameAcrossScrollFrameBoundary(
+ nsIFrame* aFrame, nsIFrame* aOldParent);
+
+ void AddFrame(nsIFrame* aFrame) { mFrames.AppendElement(aFrame); }
+ void RemoveFrame(nsIFrame* aFrame) { mFrames.RemoveElement(aFrame); }
+
+ nsIScrollableFrame* ScrollFrame() const { return mScrollFrame; }
+
+ // Compute the offsets for a sticky position element
+ static void ComputeStickyOffsets(nsIFrame* aFrame);
+
+ /**
+ * Compute the position of a sticky positioned frame, based on information
+ * stored in its properties along with our scroll frame and scroll position.
+ */
+ nsPoint ComputePosition(nsIFrame* aFrame) const;
+
+ /**
+ * Compute where a frame should not scroll with the page, represented by the
+ * difference of two rectangles.
+ */
+ void GetScrollRanges(nsIFrame* aFrame, nsRectAbsolute* aOuter,
+ nsRectAbsolute* aInner) const;
+
+ /**
+ * Compute and set the position of a frame and its following continuations.
+ */
+ void PositionContinuations(nsIFrame* aFrame);
+
+ /**
+ * Compute and set the position of all sticky frames, given the current
+ * scroll position of the scroll frame. If not in reflow, aSubtreeRoot should
+ * be null; otherwise, overflow-area updates will be limited to not affect
+ * aSubtreeRoot or its ancestors.
+ */
+ void UpdatePositions(nsPoint aScrollPosition, nsIFrame* aSubtreeRoot);
+
+ // nsIScrollPositionListener
+ virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) override;
+ virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) override;
+
+ ~StickyScrollContainer();
+
+ const nsTArray<nsIFrame*>& GetFrames() const { return mFrames; }
+
+ /**
+ * Returns true if the frame is "stuck" in the y direction, ie it's acting
+ * like fixed position. aFrame should be in GetFrames().
+ */
+ bool IsStuckInYDirection(nsIFrame* aFrame) const;
+
+ private:
+ explicit StickyScrollContainer(nsIScrollableFrame* aScrollFrame);
+
+ /**
+ * Compute two rectangles that determine sticky positioning: |aStick|, based
+ * on the scroll container, and |aContain|, based on the containing block.
+ * Sticky positioning keeps the frame position (its upper-left corner) always
+ * within |aContain| and secondarily within |aStick|.
+ */
+ void ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
+ nsRect* aContain) const;
+
+ nsIScrollableFrame* const mScrollFrame;
+ nsTArray<nsIFrame*> mFrames;
+ nsPoint mScrollPosition;
+};
+
+} // namespace mozilla
+
+#endif /* StickyScrollContainer_h */
diff --git a/layout/generic/TextDrawTarget.h b/layout/generic/TextDrawTarget.h
new file mode 100644
index 0000000000..551dde2ff0
--- /dev/null
+++ b/layout/generic/TextDrawTarget.h
@@ -0,0 +1,616 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TextDrawTarget_h
+#define TextDrawTarget_h
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+
+namespace mozilla {
+namespace layout {
+
+using namespace gfx;
+
+// This class is a fake DrawTarget, used to intercept text draw calls, while
+// also collecting up the other aspects of text natively.
+//
+// When using advanced-layers in nsDisplayText's constructor, we construct this
+// and run the full painting algorithm with this as the DrawTarget. This is
+// done to avoid having to massively refactor gecko's text painting code (which
+// has lots of components shared between other rendering algorithms).
+//
+// In some phases of the painting algorithm, we can grab the relevant values
+// and feed them directly into TextDrawTarget. For instance, selections,
+// decorations, and shadows are handled in this manner. In those cases we can
+// also short-circuit the painting algorithm to save work.
+//
+// In other phases, the computed values are sufficiently buried in complex
+// code that it's best for us to just intercept the final draw calls. This
+// is how we handle computing the glyphs of the main text and text-emphasis
+// (see our overloaded FillGlyphs implementation).
+//
+// To be clear: this is a big hack. With time we hope to refactor the codebase
+// so that all the elements of text are handled directly by TextDrawTarget,
+// which is to say everything is done like we do selections and shadows now.
+// This design is a good step for doing this work incrementally.
+//
+// This is also likely to be a bit buggy (missing or misinterpreted info)
+// while we further develop the design.
+//
+// TextDrawTarget doesn't yet support all features. See mHasUnsupportedFeatures
+// for details.
+class TextDrawTarget : public DrawTarget {
+ public:
+ explicit TextDrawTarget(wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayItem* aItem, nsRect& aBounds,
+ bool aCallerDoesSaveRestore = false)
+ : mCallerDoesSaveRestore(aCallerDoesSaveRestore), mBuilder(aBuilder) {
+ Reinitialize(aResources, aSc, aManager, aItem, aBounds);
+ }
+
+ // Prevent this from being copied
+ TextDrawTarget(const TextDrawTarget& src) = delete;
+ TextDrawTarget& operator=(const TextDrawTarget&) = delete;
+
+ ~TextDrawTarget() { MOZ_ASSERT(mFinished); }
+
+ void Reinitialize(wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayItem* aItem, nsRect& aBounds) {
+ mResources = &aResources;
+ mSc = &aSc;
+ mManager = aManager;
+ mHasUnsupportedFeatures = false;
+ mHasShadows = false;
+
+ SetPermitSubpixelAA(true);
+
+ // Compute clip/bounds
+ auto appUnitsPerDevPixel =
+ aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect layoutBoundsRect =
+ LayoutDeviceRect::FromAppUnits(aBounds, appUnitsPerDevPixel);
+ LayoutDeviceRect layoutClipRect = layoutBoundsRect;
+ mBoundsRect = wr::ToLayoutRect(layoutBoundsRect);
+
+ // Add 1 pixel of dirty area around clip rect to allow us to paint
+ // antialiased pixels beyond the measured text extents.
+ layoutClipRect.Inflate(1);
+ mSize = IntSize::Ceil(layoutClipRect.Width(), layoutClipRect.Height());
+ mClipStack.ClearAndRetainStorage();
+ mClipStack.AppendElement(layoutClipRect);
+
+ mBackfaceVisible = !aItem->BackfaceIsHidden();
+
+ if (!mCallerDoesSaveRestore) {
+ mBuilder.Save();
+ }
+ }
+
+ void FoundUnsupportedFeature() { mHasUnsupportedFeatures = true; }
+ bool CheckHasUnsupportedFeatures() {
+ MOZ_ASSERT(mCallerDoesSaveRestore);
+#ifdef DEBUG
+ MOZ_ASSERT(!mFinished);
+ mFinished = true;
+#endif
+ return mHasUnsupportedFeatures;
+ }
+
+ bool Finish() {
+ MOZ_ASSERT(!mCallerDoesSaveRestore);
+#ifdef DEBUG
+ mFinished = true;
+#endif
+ if (mHasUnsupportedFeatures) {
+ mBuilder.Restore();
+ return false;
+ }
+ mBuilder.ClearSave();
+ return true;
+ }
+
+ wr::FontInstanceFlags GetWRGlyphFlags() const { return mWRGlyphFlags; }
+ void SetWRGlyphFlags(wr::FontInstanceFlags aFlags) { mWRGlyphFlags = aFlags; }
+
+ class AutoRestoreWRGlyphFlags {
+ public:
+ ~AutoRestoreWRGlyphFlags() {
+ if (mTarget) {
+ mTarget->SetWRGlyphFlags(mFlags);
+ }
+ }
+
+ void Save(TextDrawTarget* aTarget) {
+ // This allows for recursive saves, in case the flags need to be modified
+ // under multiple conditions (i.e. transforms and synthetic italics),
+ // since the flags will be restored to the first saved value in the
+ // destructor on scope exit.
+ if (!mTarget) {
+ // Only record the first save with the original flags that will be
+ // restored.
+ mTarget = aTarget;
+ mFlags = aTarget->GetWRGlyphFlags();
+ } else {
+ // Ensure that this is actually a recursive save to the same target
+ MOZ_ASSERT(
+ mTarget == aTarget,
+ "Recursive save of WR glyph flags to different TextDrawTargets");
+ }
+ }
+
+ private:
+ TextDrawTarget* mTarget = nullptr;
+ wr::FontInstanceFlags mFlags = {0};
+ };
+
+ // This overload just stores the glyphs/font/color.
+ void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) override {
+ // Make sure we're only given boring color patterns
+ MOZ_RELEASE_ASSERT(aOptions.mCompositionOp == CompositionOp::OP_OVER);
+ MOZ_RELEASE_ASSERT(aOptions.mAlpha == 1.0f);
+ MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR);
+
+ // Make sure the font exists, and can be serialized
+ MOZ_RELEASE_ASSERT(aFont);
+ if (!aFont->CanSerialize()) {
+ FoundUnsupportedFeature();
+ return;
+ }
+
+ auto* colorPat = static_cast<const ColorPattern*>(&aPattern);
+ auto color = wr::ToColorF(colorPat->mColor);
+ MOZ_ASSERT(aBuffer.mNumGlyphs);
+ auto glyphs = Range<const wr::GlyphInstance>(
+ reinterpret_cast<const wr::GlyphInstance*>(aBuffer.mGlyphs),
+ aBuffer.mNumGlyphs);
+ // MSVC won't let us use offsetof on the following directly so we give it a
+ // name with typedef
+ typedef std::remove_reference<decltype(aBuffer.mGlyphs[0])>::type GlyphType;
+ // Compare gfx::Glyph and wr::GlyphInstance to make sure that they are
+ // structurally equivalent to ensure that our cast above was ok
+ static_assert(
+ std::is_same<decltype(aBuffer.mGlyphs[0].mIndex),
+ decltype(glyphs[0].index)>() &&
+ std::is_same<decltype(aBuffer.mGlyphs[0].mPosition.x.value),
+ decltype(glyphs[0].point.x)>() &&
+ std::is_same<decltype(aBuffer.mGlyphs[0].mPosition.y.value),
+ decltype(glyphs[0].point.y)>() &&
+ offsetof(GlyphType, mIndex) == offsetof(wr::GlyphInstance, index) &&
+ offsetof(GlyphType, mPosition) ==
+ offsetof(wr::GlyphInstance, point) &&
+ offsetof(decltype(aBuffer.mGlyphs[0].mPosition), x) ==
+ offsetof(decltype(glyphs[0].point), x) &&
+ offsetof(decltype(aBuffer.mGlyphs[0].mPosition), y) ==
+ offsetof(decltype(glyphs[0].point), y) &&
+ std::is_standard_layout<
+ std::remove_reference<decltype(aBuffer.mGlyphs[0])>>::value &&
+ std::is_standard_layout<
+ std::remove_reference<decltype(glyphs[0])>>::value &&
+ sizeof(aBuffer.mGlyphs[0]) == sizeof(glyphs[0]) &&
+ sizeof(aBuffer.mGlyphs[0].mPosition) == sizeof(glyphs[0].point),
+ "glyph buf types don't match");
+
+ wr::GlyphOptions glyphOptions;
+ glyphOptions.render_mode =
+ wr::ToFontRenderMode(aOptions.mAntialiasMode, GetPermitSubpixelAA());
+ glyphOptions.flags = mWRGlyphFlags;
+
+ mManager->WrBridge()->PushGlyphs(mBuilder, *mResources, glyphs, aFont,
+ color, *mSc, mBoundsRect, ClipRect(),
+ mBackfaceVisible, &glyphOptions);
+ }
+
+ void PushClipRect(const Rect& aRect) override {
+ LayoutDeviceRect rect = LayoutDeviceRect::FromUnknownRect(aRect);
+ rect = rect.Intersect(mClipStack.LastElement());
+ mClipStack.AppendElement(rect);
+ }
+
+ void PopClip() override { mClipStack.RemoveLastElement(); }
+
+ IntSize GetSize() const override { return mSize; }
+
+ void AppendShadow(const wr::Shadow& aShadow, bool aInflate) {
+ mBuilder.PushShadow(mBoundsRect, ClipRect(), mBackfaceVisible, aShadow,
+ aInflate);
+ mHasShadows = true;
+ }
+
+ void TerminateShadows() {
+ if (mHasShadows) {
+ mBuilder.PopAllShadows();
+ mHasShadows = false;
+ }
+ }
+
+ void AppendSelectionRect(const LayoutDeviceRect& aRect,
+ const DeviceColor& aColor) {
+ auto rect = wr::ToLayoutRect(aRect);
+ auto color = wr::ToColorF(aColor);
+ mBuilder.PushRect(rect, ClipRect(), mBackfaceVisible, false, false, color);
+ }
+
+ // This function is basically designed to slide into the decoration drawing
+ // code of nsCSSRendering with minimum disruption, to minimize the
+ // chances of implementation drift. As such, it mostly looks like a call
+ // to a skia-style StrokeLine method: two end-points, with a thickness
+ // and style. Notably the end-points are *centered* in the block direction,
+ // even though webrender wants a rect-like representation, where the points
+ // are on corners.
+ //
+ // So we mangle the format here in a single centralized place, where neither
+ // webrender nor nsCSSRendering has to care about this mismatch.
+ //
+ // NOTE: we assume the points are axis-aligned, and aStart should be used
+ // as the top-left corner of the rect.
+ void AppendDecoration(const Point& aStart, const Point& aEnd,
+ const float aThickness, const bool aVertical,
+ const DeviceColor& aColor,
+ const StyleTextDecorationStyle aStyle) {
+ auto pos = LayoutDevicePoint::FromUnknownPoint(aStart);
+ LayoutDeviceSize size;
+
+ if (aVertical) {
+ pos.x -= aThickness / 2; // adjust from center to corner
+ size = LayoutDeviceSize(aThickness,
+ ViewAs<LayoutDevicePixel>(aEnd.y - aStart.y));
+ } else {
+ pos.y -= aThickness / 2; // adjust from center to corner
+ size = LayoutDeviceSize(ViewAs<LayoutDevicePixel>(aEnd.x - aStart.x),
+ aThickness);
+ }
+
+ wr::Line decoration;
+ decoration.bounds = wr::ToLayoutRect(LayoutDeviceRect(pos, size));
+ decoration.wavyLineThickness = 0; // dummy value, unused
+ decoration.color = wr::ToColorF(aColor);
+ decoration.orientation = aVertical ? wr::LineOrientation::Vertical
+ : wr::LineOrientation::Horizontal;
+
+ switch (aStyle) {
+ case StyleTextDecorationStyle::Solid:
+ decoration.style = wr::LineStyle::Solid;
+ break;
+ case StyleTextDecorationStyle::Dotted:
+ decoration.style = wr::LineStyle::Dotted;
+ break;
+ case StyleTextDecorationStyle::Dashed:
+ decoration.style = wr::LineStyle::Dashed;
+ break;
+ // Wavy lines should go through AppendWavyDecoration
+ case StyleTextDecorationStyle::Wavy:
+ // Double lines should be lowered to two solid lines
+ case StyleTextDecorationStyle::Double:
+ default:
+ MOZ_CRASH("TextDrawTarget received unsupported line style");
+ }
+
+ mBuilder.PushLine(ClipRect(), mBackfaceVisible, decoration);
+ }
+
+ // Seperated out from AppendDecoration because Wavy Lines are completely
+ // different, and trying to merge the concept is more of a mess than it's
+ // worth.
+ void AppendWavyDecoration(const Rect& aBounds, const float aThickness,
+ const bool aVertical, const DeviceColor& aColor) {
+ wr::Line decoration;
+
+ decoration.bounds =
+ wr::ToLayoutRect(LayoutDeviceRect::FromUnknownRect(aBounds));
+ decoration.wavyLineThickness = aThickness;
+ decoration.color = wr::ToColorF(aColor);
+ decoration.orientation = aVertical ? wr::LineOrientation::Vertical
+ : wr::LineOrientation::Horizontal;
+ decoration.style = wr::LineStyle::Wavy;
+
+ mBuilder.PushLine(ClipRect(), mBackfaceVisible, decoration);
+ }
+
+ layers::WebRenderBridgeChild* WrBridge() { return mManager->WrBridge(); }
+ layers::WebRenderLayerManager* WrLayerManager() {
+ return mManager->LayerManager();
+ }
+
+ Maybe<wr::ImageKey> DefineImage(const IntSize& aSize, uint32_t aStride,
+ SurfaceFormat aFormat, const uint8_t* aData) {
+ wr::ImageKey key = mManager->WrBridge()->GetNextImageKey();
+ wr::ImageDescriptor desc(aSize, aStride, aFormat);
+ Range<uint8_t> bytes(const_cast<uint8_t*>(aData), aStride * aSize.height);
+ if (mResources->AddImage(key, desc, bytes)) {
+ return Some(key);
+ }
+ return Nothing();
+ }
+
+ void PushImage(wr::ImageKey aKey, const Rect& aBounds, const Rect& aClip,
+ wr::ImageRendering aFilter, const wr::ColorF& aColor) {
+ if (!aClip.Intersects(GeckoClipRect().ToUnknownRect())) {
+ return;
+ }
+ mBuilder.PushImage(wr::ToLayoutRect(aBounds), wr::ToLayoutRect(aClip), true,
+ false, aFilter, aKey, true, aColor);
+ }
+
+ LayoutDeviceRect GeckoClipRect() { return mClipStack.LastElement(); }
+
+ private:
+ wr::LayoutRect ClipRect() {
+ return wr::ToLayoutRect(mClipStack.LastElement());
+ }
+ // Whether anything unsupported was encountered. This will result in this
+ // text being emitted as a blob, which means subpixel-AA can't be used and
+ // that performance will probably be a bit worse. At this point, we've
+ // properly implemented everything that shows up a lot, so you can assume
+ // that the remaining things we don't implement are fairly rare. The complete
+ // set of things that we don't implement are as follows:
+ //
+ // * Unserializable Fonts: WR lives across an IPC boundary
+ // * Text-Combine-Upright Squishing: no one's really bothered to impl it yet
+ // * Text-Stroke: not a real standard (exists for webcompat)
+ // * SVG Glyphs: not a real standard (we got overzealous with svg)
+ // * Color Glyphs (Emoji) With Transparency: requires us to apply transparency
+ // with a composited layer (a single emoji can be many single-color glyphs)
+ //
+ // The transparent colored-glyphs issue is probably the most valuable to fix,
+ // since ideally it would also result in us fixing transparency for all
+ // intersecting glyphs (which currently look bad with or without webrender,
+ // so there's no fallback like with emoji). Specifically, transparency
+ // looks bad for "cursive" fonts where glyphs overlap at the seams. Since
+ // this is more common for non-latin scripts (e.g. मनीष), this amounts to us
+ // treating non-latin scripts poorly... unless they're emoji. Yikes!
+ bool mHasUnsupportedFeatures = false;
+
+ // The caller promises to call Save/Restore on the builder as needed.
+ bool mCallerDoesSaveRestore = false;
+#ifdef DEBUG
+ bool mFinished = false;
+#endif
+
+ // Whether PopAllShadows needs to be called
+ bool mHasShadows = false;
+
+ // Things used to push to webrender
+ wr::DisplayListBuilder& mBuilder;
+ wr::IpcResourceUpdateQueue* mResources;
+ const layers::StackingContextHelper* mSc;
+ layers::RenderRootStateManager* mManager;
+
+ // Computed facts
+ IntSize mSize;
+ wr::LayoutRect mBoundsRect;
+ AutoTArray<LayoutDeviceRect, 3> mClipStack;
+ bool mBackfaceVisible;
+
+ wr::FontInstanceFlags mWRGlyphFlags = {0};
+
+ // The rest of this is dummy implementations of DrawTarget's API
+ public:
+ DrawTargetType GetType() const override {
+ return DrawTargetType::SOFTWARE_RASTER;
+ }
+
+ BackendType GetBackendType() const override {
+ return BackendType::WEBRENDER_TEXT;
+ }
+
+ bool IsRecording() const override { return true; }
+
+ already_AddRefed<SourceSurface> Snapshot() override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ already_AddRefed<SourceSurface> IntoLuminanceSource(
+ LuminanceType aLuminanceType, float aOpacity) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ void Flush() override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void DrawSurface(SourceSurface* aSurface, const Rect& aDest,
+ const Rect& aSource, const DrawSurfaceOptions& aSurfOptions,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void DrawSurfaceWithShadow(SourceSurface* aSurface, const Point& aDest,
+ const ShadowOptions& aShadow,
+ CompositionOp aOperator) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void ClearRect(const Rect& aRect) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect,
+ const IntPoint& aDestination) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions = DrawOptions()) override {
+ MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR);
+
+ if (!aRect.Intersects(GeckoClipRect().ToUnknownRect())) {
+ return;
+ }
+ auto rect = wr::ToLayoutRect(LayoutDeviceRect::FromUnknownRect(aRect));
+ auto color =
+ wr::ToColorF(static_cast<const ColorPattern&>(aPattern).mColor);
+ mBuilder.PushRect(rect, ClipRect(), mBackfaceVisible, false, false, color);
+ }
+
+ void StrokeRect(const Rect& aRect, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) override {
+ MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR &&
+ aStrokeOptions.mDashLength == 0);
+
+ wr::LayoutSideOffsets widths = {
+ aStrokeOptions.mLineWidth, aStrokeOptions.mLineWidth,
+ aStrokeOptions.mLineWidth, aStrokeOptions.mLineWidth};
+ wr::ColorF color =
+ wr::ToColorF(static_cast<const ColorPattern&>(aPattern).mColor);
+ wr::BorderSide sides[4] = {{color, wr::BorderStyle::Solid},
+ {color, wr::BorderStyle::Solid},
+ {color, wr::BorderStyle::Solid},
+ {color, wr::BorderStyle::Solid}};
+ wr::BorderRadius radius = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
+ LayoutDeviceRect rect = LayoutDeviceRect::FromUnknownRect(aRect);
+ rect.Inflate(aStrokeOptions.mLineWidth / 2);
+ if (!rect.Intersects(GeckoClipRect())) {
+ return;
+ }
+ wr::LayoutRect bounds = wr::ToLayoutRect(rect);
+ mBuilder.PushBorder(bounds, ClipRect(), true, widths,
+ Range<const wr::BorderSide>(sides, 4), radius);
+ }
+
+ void StrokeLine(const Point& aStart, const Point& aEnd,
+ const Pattern& aPattern, const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void Stroke(const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void Mask(const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void MaskSurface(const Pattern& aSource, SourceSurface* aMask, Point aOffset,
+ const DrawOptions& aOptions) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ bool Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void PushClip(const Path* aPath) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void PushDeviceSpaceClipRects(const IntRect* aRects,
+ uint32_t aCount) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+
+ void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
+ const Matrix& aMaskTransform, const IntRect& aBounds,
+ bool aCopyBackground) override {
+ // Fine to pretend we do this
+ }
+
+ void PopLayer() override {
+ // Fine to pretend we do this
+ }
+
+ already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(
+ unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat) const override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ already_AddRefed<SourceSurface> OptimizeSourceSurface(
+ SourceSurface* aSurface) const override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface(
+ const NativeSurface& aSurface) const override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ already_AddRefed<DrawTarget> CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ bool CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return false;
+ }
+
+ virtual RefPtr<DrawTarget> CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ already_AddRefed<PathBuilder> CreatePathBuilder(
+ FillRule aFillRule) const override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ already_AddRefed<FilterNode> CreateFilter(FilterType aType) override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ already_AddRefed<GradientStops> CreateGradientStops(
+ GradientStop* aStops, uint32_t aNumStops,
+ ExtendMode aExtendMode) const override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ return nullptr;
+ }
+
+ void DetachAllSnapshots() override {
+ MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
+ }
+};
+
+} // namespace layout
+} // namespace mozilla
+
+#endif
diff --git a/layout/generic/TextOverflow.cpp b/layout/generic/TextOverflow.cpp
new file mode 100644
index 0000000000..f529a2268d
--- /dev/null
+++ b/layout/generic/TextOverflow.cpp
@@ -0,0 +1,936 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TextOverflow.h"
+#include <algorithm>
+
+// Please maintain alphabetical order below
+#include "gfxContext.h"
+#include "nsBlockFrame.h"
+#include "nsCaret.h"
+#include "nsContentUtils.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsFontMetrics.h"
+#include "nsGfxScrollFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsTextFrame.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Selection.h"
+#include "TextDrawTarget.h"
+
+using mozilla::layout::TextDrawTarget;
+
+namespace mozilla {
+namespace css {
+
+class LazyReferenceRenderingDrawTargetGetterFromFrame final
+ : public gfxFontGroup::LazyReferenceDrawTargetGetter {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ explicit LazyReferenceRenderingDrawTargetGetterFromFrame(nsIFrame* aFrame)
+ : mFrame(aFrame) {}
+ virtual already_AddRefed<DrawTarget> GetRefDrawTarget() override {
+ UniquePtr<gfxContext> ctx =
+ mFrame->PresShell()->CreateReferenceRenderingContext();
+ RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
+ return dt.forget();
+ }
+
+ private:
+ nsIFrame* mFrame;
+};
+
+static gfxTextRun* GetEllipsisTextRun(nsIFrame* aFrame) {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+ LazyReferenceRenderingDrawTargetGetterFromFrame lazyRefDrawTargetGetter(
+ aFrame);
+ return fm->GetThebesFontGroup()->GetEllipsisTextRun(
+ aFrame->PresContext()->AppUnitsPerDevPixel(),
+ nsLayoutUtils::GetTextRunOrientFlagsForStyle(aFrame->Style()),
+ lazyRefDrawTargetGetter);
+}
+
+static nsIFrame* GetSelfOrNearestBlock(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ return aFrame->IsBlockFrameOrSubclass()
+ ? aFrame
+ : nsLayoutUtils::FindNearestBlockAncestor(aFrame);
+}
+
+// Return true if the frame is an atomic inline-level element.
+// It's not supposed to be called for block frames since we never
+// process block descendants for text-overflow.
+static bool IsAtomicElement(nsIFrame* aFrame, LayoutFrameType aFrameType) {
+ MOZ_ASSERT(!aFrame->IsBlockFrameOrSubclass() || !aFrame->IsBlockOutside(),
+ "unexpected block frame");
+ MOZ_ASSERT(aFrameType != LayoutFrameType::Placeholder,
+ "unexpected placeholder frame");
+ return !aFrame->IsLineParticipant();
+}
+
+static bool IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight,
+ nscoord* aSnappedLeft, nscoord* aSnappedRight) {
+ *aSnappedLeft = aLeft;
+ *aSnappedRight = aRight;
+ if (aLeft <= 0 && aRight <= 0) {
+ return false;
+ }
+ return !aFrame->MeasureCharClippedText(aLeft, aRight, aSnappedLeft,
+ aSnappedRight);
+}
+
+static bool IsInlineAxisOverflowVisible(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame && aFrame->IsBlockFrameOrSubclass(),
+ "expected a block frame");
+
+ nsIFrame* f = aFrame;
+ while (f && f->Style()->IsAnonBox() && !f->IsScrollFrame()) {
+ f = f->GetParent();
+ }
+ if (!f) {
+ return true;
+ }
+ auto overflow = aFrame->GetWritingMode().IsVertical()
+ ? f->StyleDisplay()->mOverflowY
+ : f->StyleDisplay()->mOverflowX;
+ return overflow == StyleOverflow::Visible;
+}
+
+static void ClipMarker(const nsRect& aContentArea, const nsRect& aMarkerRect,
+ DisplayListClipState::AutoSaveRestore& aClipState) {
+ nscoord rightOverflow = aMarkerRect.XMost() - aContentArea.XMost();
+ nsRect markerRect = aMarkerRect;
+ if (rightOverflow > 0) {
+ // Marker overflows on the right side (content width < marker width).
+ markerRect.width -= rightOverflow;
+ aClipState.ClipContentDescendants(markerRect);
+ } else {
+ nscoord leftOverflow = aContentArea.x - aMarkerRect.x;
+ if (leftOverflow > 0) {
+ // Marker overflows on the left side
+ markerRect.width -= leftOverflow;
+ markerRect.x += leftOverflow;
+ aClipState.ClipContentDescendants(markerRect);
+ }
+ }
+}
+
+static void InflateIStart(WritingMode aWM, LogicalRect* aRect, nscoord aDelta) {
+ nscoord iend = aRect->IEnd(aWM);
+ aRect->IStart(aWM) -= aDelta;
+ aRect->ISize(aWM) = std::max(iend - aRect->IStart(aWM), 0);
+}
+
+static void InflateIEnd(WritingMode aWM, LogicalRect* aRect, nscoord aDelta) {
+ aRect->ISize(aWM) = std::max(aRect->ISize(aWM) + aDelta, 0);
+}
+
+static bool IsFrameDescendantOfAny(
+ nsIFrame* aChild, const TextOverflow::FrameHashtable& aSetOfFrames,
+ nsIFrame* aCommonAncestor) {
+ for (nsIFrame* f = aChild; f && f != aCommonAncestor;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ if (aSetOfFrames.Contains(f)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+class nsDisplayTextOverflowMarker final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ const nsRect& aRect, nscoord aAscent,
+ const StyleTextOverflowSide& aStyle)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mRect(aRect),
+ mStyle(aStyle),
+ mAscent(aAscent) {
+ MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTextOverflowMarker)
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
+ return mRect.Union(shadowRect);
+ }
+
+ virtual nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
+ // On OS X, web authors can turn off subpixel text rendering using the
+ // CSS property -moz-osx-font-smoothing. If they do that, we don't need
+ // to use component alpha layers for the affected text.
+ if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
+ return nsRect();
+ }
+ }
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ void PaintTextToContext(gfxContext* aCtx, nsPoint aOffsetFromRect);
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
+ private:
+ nsRect mRect; // in reference frame coordinates
+ const StyleTextOverflowSide mStyle;
+ nscoord mAscent; // baseline for the marker text in mRect
+};
+
+static void PaintTextShadowCallback(gfxContext* aCtx, nsPoint aShadowOffset,
+ const nscolor& aShadowColor, void* aData) {
+ reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->PaintTextToContext(
+ aCtx, aShadowOffset);
+}
+
+void nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nscolor foregroundColor =
+ nsLayoutUtils::GetColor(mFrame, &nsStyleText::mWebkitTextFillColor);
+
+ // Paint the text-shadows for the overflow marker
+ nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect,
+ GetPaintRect(aBuilder, aCtx), foregroundColor,
+ PaintTextShadowCallback, (void*)this);
+ aCtx->SetColor(gfx::sRGBColor::FromABGR(foregroundColor));
+ PaintTextToContext(aCtx, nsPoint(0, 0));
+}
+
+void nsDisplayTextOverflowMarker::PaintTextToContext(gfxContext* aCtx,
+ nsPoint aOffsetFromRect) {
+ WritingMode wm = mFrame->GetWritingMode();
+ nsPoint pt(mRect.x, mRect.y);
+ if (wm.IsVertical()) {
+ if (wm.IsVerticalLR()) {
+ pt.x = NSToCoordFloor(
+ nsLayoutUtils::GetSnappedBaselineX(mFrame, aCtx, pt.x, mAscent));
+ } else {
+ pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
+ mFrame, aCtx, pt.x + mRect.width, -mAscent));
+ }
+ } else {
+ pt.y = NSToCoordFloor(
+ nsLayoutUtils::GetSnappedBaselineY(mFrame, aCtx, pt.y, mAscent));
+ }
+ pt += aOffsetFromRect;
+
+ if (mStyle.IsEllipsis()) {
+ gfxTextRun* textRun = GetEllipsisTextRun(mFrame);
+ if (textRun) {
+ NS_ASSERTION(!textRun->IsRightToLeft(),
+ "Ellipsis textruns should always be LTR!");
+ gfx::Point gfxPt(pt.x, pt.y);
+ auto& paletteCache = mFrame->PresContext()->FontPaletteCache();
+ textRun->Draw(gfxTextRun::Range(textRun), gfxPt,
+ gfxTextRun::DrawParams(aCtx, paletteCache));
+ }
+ } else {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(mFrame);
+ NS_ConvertUTF8toUTF16 str16{mStyle.AsString().AsString()};
+ nsLayoutUtils::DrawString(mFrame, *fm, aCtx, str16.get(), str16.Length(),
+ pt);
+ }
+}
+
+bool nsDisplayTextOverflowMarker::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ bool snap;
+ nsRect bounds = GetBounds(aDisplayListBuilder, &snap);
+ if (bounds.IsEmpty()) {
+ return true;
+ }
+
+ // Run the rendering algorithm to capture the glyphs and shadows
+ RefPtr<TextDrawTarget> textDrawer =
+ new TextDrawTarget(aBuilder, aResources, aSc, aManager, this, bounds);
+ MOZ_ASSERT(textDrawer->IsValid());
+ if (!textDrawer->IsValid()) {
+ return false;
+ }
+ gfxContext captureCtx(textDrawer);
+ Paint(aDisplayListBuilder, &captureCtx);
+ textDrawer->TerminateShadows();
+
+ return textDrawer->Finish();
+}
+
+TextOverflow::TextOverflow(nsDisplayListBuilder* aBuilder,
+ nsBlockFrame* aBlockFrame)
+ : mContentArea(aBlockFrame->GetWritingMode(),
+ aBlockFrame->GetContentRectRelativeToSelf(),
+ aBlockFrame->GetSize()),
+ mBuilder(aBuilder),
+ mBlock(aBlockFrame),
+ mScrollableFrame(nsLayoutUtils::GetScrollableFrameFor(aBlockFrame)),
+ mMarkerList(aBuilder),
+ mBlockSize(aBlockFrame->GetSize()),
+ mBlockWM(aBlockFrame->GetWritingMode()),
+ mCanHaveInlineAxisScrollbar(false),
+ mInLineClampContext(aBlockFrame->IsInLineClampContext()),
+ mAdjustForPixelSnapping(false) {
+ if (mScrollableFrame) {
+ auto scrollbarStyle = mBlockWM.IsVertical()
+ ? mScrollableFrame->GetScrollStyles().mVertical
+ : mScrollableFrame->GetScrollStyles().mHorizontal;
+ mCanHaveInlineAxisScrollbar = scrollbarStyle != StyleOverflow::Hidden;
+ if (!mAdjustForPixelSnapping) {
+ // Scrolling to the end position can leave some text still overflowing due
+ // to pixel snapping behaviour in our scrolling code.
+ mAdjustForPixelSnapping = mCanHaveInlineAxisScrollbar;
+ }
+ // Use a null containerSize to convert a vector from logical to physical.
+ const nsSize nullContainerSize;
+ mContentArea.MoveBy(
+ mBlockWM, LogicalPoint(mBlockWM, mScrollableFrame->GetScrollPosition(),
+ nullContainerSize));
+ }
+ StyleDirection direction = aBlockFrame->StyleVisibility()->mDirection;
+ const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
+
+ const auto& textOverflow = style->mTextOverflow;
+ bool shouldToggleDirection =
+ textOverflow.sides_are_logical && (direction == StyleDirection::Rtl);
+ const auto& leftSide =
+ shouldToggleDirection ? textOverflow.second : textOverflow.first;
+ const auto& rightSide =
+ shouldToggleDirection ? textOverflow.first : textOverflow.second;
+
+ if (mBlockWM.IsBidiLTR()) {
+ mIStart.Init(leftSide);
+ mIEnd.Init(rightSide);
+ } else {
+ mIStart.Init(rightSide);
+ mIEnd.Init(leftSide);
+ }
+ // The left/right marker string is setup in ExamineLineFrames when a line
+ // has overflow on that side.
+}
+
+/* static */
+Maybe<TextOverflow> TextOverflow::WillProcessLines(
+ nsDisplayListBuilder* aBuilder, nsBlockFrame* aBlockFrame) {
+ // Ignore text-overflow and -webkit-line-clamp for event and frame visibility
+ // processing.
+ if (aBuilder->IsForEventDelivery() || aBuilder->IsForFrameVisibility() ||
+ !CanHaveOverflowMarkers(aBlockFrame)) {
+ return Nothing();
+ }
+ nsIScrollableFrame* scrollableFrame =
+ nsLayoutUtils::GetScrollableFrameFor(aBlockFrame);
+ if (scrollableFrame && scrollableFrame->IsTransformingByAPZ()) {
+ // If the APZ is actively scrolling this, don't bother with markers.
+ return Nothing();
+ }
+ return Some(TextOverflow(aBuilder, aBlockFrame));
+}
+
+void TextOverflow::ExamineFrameSubtree(nsIFrame* aFrame,
+ const LogicalRect& aContentArea,
+ const LogicalRect& aInsideMarkersArea,
+ FrameHashtable* aFramesToHide,
+ AlignmentEdges* aAlignmentEdges,
+ bool* aFoundVisibleTextOrAtomic,
+ InnerClipEdges* aClippedMarkerEdges) {
+ const LayoutFrameType frameType = aFrame->Type();
+ if (frameType == LayoutFrameType::Br ||
+ frameType == LayoutFrameType::Placeholder) {
+ return;
+ }
+ const bool isAtomic = IsAtomicElement(aFrame, frameType);
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ LogicalRect childRect =
+ GetLogicalScrollableOverflowRectRelativeToBlock(aFrame);
+ bool overflowIStart =
+ childRect.IStart(mBlockWM) < aContentArea.IStart(mBlockWM);
+ bool overflowIEnd = childRect.IEnd(mBlockWM) > aContentArea.IEnd(mBlockWM);
+ if (overflowIStart) {
+ mIStart.mHasOverflow = true;
+ }
+ if (overflowIEnd) {
+ mIEnd.mHasOverflow = true;
+ }
+ if (isAtomic && ((mIStart.mActive && overflowIStart) ||
+ (mIEnd.mActive && overflowIEnd))) {
+ aFramesToHide->Insert(aFrame);
+ } else if (isAtomic || frameType == LayoutFrameType::Text) {
+ AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea, aFramesToHide,
+ aAlignmentEdges, aFoundVisibleTextOrAtomic,
+ aClippedMarkerEdges);
+ }
+ }
+ if (isAtomic) {
+ return;
+ }
+
+ for (nsIFrame* child : aFrame->PrincipalChildList()) {
+ ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea, aFramesToHide,
+ aAlignmentEdges, aFoundVisibleTextOrAtomic,
+ aClippedMarkerEdges);
+ }
+}
+
+void TextOverflow::AnalyzeMarkerEdges(nsIFrame* aFrame,
+ LayoutFrameType aFrameType,
+ const LogicalRect& aInsideMarkersArea,
+ FrameHashtable* aFramesToHide,
+ AlignmentEdges* aAlignmentEdges,
+ bool* aFoundVisibleTextOrAtomic,
+ InnerClipEdges* aClippedMarkerEdges) {
+ MOZ_ASSERT(aFrameType == LayoutFrameType::Text ||
+ IsAtomicElement(aFrame, aFrameType));
+ LogicalRect borderRect(mBlockWM,
+ nsRect(aFrame->GetOffsetTo(mBlock), aFrame->GetSize()),
+ mBlockSize);
+ nscoord istartOverlap = std::max(
+ aInsideMarkersArea.IStart(mBlockWM) - borderRect.IStart(mBlockWM), 0);
+ nscoord iendOverlap = std::max(
+ borderRect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM), 0);
+ bool insideIStartEdge =
+ aInsideMarkersArea.IStart(mBlockWM) <= borderRect.IEnd(mBlockWM);
+ bool insideIEndEdge =
+ borderRect.IStart(mBlockWM) <= aInsideMarkersArea.IEnd(mBlockWM);
+
+ if (istartOverlap > 0) {
+ aClippedMarkerEdges->AccumulateIStart(mBlockWM, borderRect);
+ if (!mIStart.mActive) {
+ istartOverlap = 0;
+ }
+ }
+ if (iendOverlap > 0) {
+ aClippedMarkerEdges->AccumulateIEnd(mBlockWM, borderRect);
+ if (!mIEnd.mActive) {
+ iendOverlap = 0;
+ }
+ }
+
+ if ((istartOverlap > 0 && insideIStartEdge) ||
+ (iendOverlap > 0 && insideIEndEdge)) {
+ if (aFrameType == LayoutFrameType::Text) {
+ auto textFrame = static_cast<nsTextFrame*>(aFrame);
+ if ((aInsideMarkersArea.IStart(mBlockWM) <
+ aInsideMarkersArea.IEnd(mBlockWM)) &&
+ textFrame->HasNonSuppressedText()) {
+ // a clipped text frame and there is some room between the markers
+ nscoord snappedIStart, snappedIEnd;
+ bool isFullyClipped =
+ mBlockWM.IsBidiLTR()
+ ? IsFullyClipped(textFrame, istartOverlap, iendOverlap,
+ &snappedIStart, &snappedIEnd)
+ : IsFullyClipped(textFrame, iendOverlap, istartOverlap,
+ &snappedIEnd, &snappedIStart);
+ if (!isFullyClipped) {
+ LogicalRect snappedRect = borderRect;
+ if (istartOverlap > 0) {
+ snappedRect.IStart(mBlockWM) += snappedIStart;
+ snappedRect.ISize(mBlockWM) -= snappedIStart;
+ }
+ if (iendOverlap > 0) {
+ snappedRect.ISize(mBlockWM) -= snappedIEnd;
+ }
+ aAlignmentEdges->AccumulateInner(mBlockWM, snappedRect);
+ *aFoundVisibleTextOrAtomic = true;
+ }
+ }
+ } else {
+ aFramesToHide->Insert(aFrame);
+ }
+ } else if (!insideIStartEdge || !insideIEndEdge) {
+ // frame is outside
+ if (!insideIStartEdge) {
+ aAlignmentEdges->AccumulateOuter(mBlockWM, borderRect);
+ }
+ if (IsAtomicElement(aFrame, aFrameType)) {
+ aFramesToHide->Insert(aFrame);
+ }
+ } else {
+ // frame is inside
+ aAlignmentEdges->AccumulateInner(mBlockWM, borderRect);
+ if (aFrameType == LayoutFrameType::Text) {
+ auto textFrame = static_cast<nsTextFrame*>(aFrame);
+ if (textFrame->HasNonSuppressedText()) {
+ *aFoundVisibleTextOrAtomic = true;
+ }
+ } else {
+ *aFoundVisibleTextOrAtomic = true;
+ }
+ }
+}
+
+LogicalRect TextOverflow::ExamineLineFrames(nsLineBox* aLine,
+ FrameHashtable* aFramesToHide,
+ AlignmentEdges* aAlignmentEdges) {
+ // No ellipsing for 'clip' style.
+ bool suppressIStart = mIStart.IsSuppressed(mInLineClampContext);
+ bool suppressIEnd = mIEnd.IsSuppressed(mInLineClampContext);
+ if (mCanHaveInlineAxisScrollbar) {
+ LogicalPoint pos(mBlockWM, mScrollableFrame->GetScrollPosition(),
+ mBlockSize);
+ LogicalRect scrollRange(mBlockWM, mScrollableFrame->GetScrollRange(),
+ mBlockSize);
+ // No ellipsing when nothing to scroll to on that side (this includes
+ // overflow:auto that doesn't trigger a horizontal scrollbar).
+ if (pos.I(mBlockWM) <= scrollRange.IStart(mBlockWM)) {
+ suppressIStart = true;
+ }
+ if (pos.I(mBlockWM) >= scrollRange.IEnd(mBlockWM)) {
+ // Except that we always want to display a -webkit-line-clamp ellipsis.
+ if (!mIEnd.mHasBlockEllipsis) {
+ suppressIEnd = true;
+ }
+ }
+ }
+
+ LogicalRect contentArea = mContentArea;
+ bool snapStart = true, snapEnd = true;
+ nscoord startEdge, endEdge;
+ if (aLine->GetFloatEdges(&startEdge, &endEdge)) {
+ // Narrow the |contentArea| to account for any floats on this line, and
+ // don't bother with the snapping quirk on whichever side(s) we narrow.
+ nscoord delta = endEdge - contentArea.IEnd(mBlockWM);
+ if (delta < 0) {
+ nscoord newSize = contentArea.ISize(mBlockWM) + delta;
+ contentArea.ISize(mBlockWM) = std::max(nscoord(0), newSize);
+ snapEnd = false;
+ }
+ delta = startEdge - contentArea.IStart(mBlockWM);
+ if (delta > 0) {
+ contentArea.IStart(mBlockWM) = startEdge;
+ nscoord newSize = contentArea.ISize(mBlockWM) - delta;
+ contentArea.ISize(mBlockWM) = std::max(nscoord(0), newSize);
+ snapStart = false;
+ }
+ }
+ // Save the non-snapped area since that's what we want to use when placing
+ // the markers (our return value). The snapped area is only for analysis.
+ LogicalRect nonSnappedContentArea = contentArea;
+ if (mAdjustForPixelSnapping) {
+ const nscoord scrollAdjust = mBlock->PresContext()->AppUnitsPerDevPixel();
+ if (snapStart) {
+ InflateIStart(mBlockWM, &contentArea, scrollAdjust);
+ }
+ if (snapEnd) {
+ InflateIEnd(mBlockWM, &contentArea, scrollAdjust);
+ }
+ }
+
+ LogicalRect lineRect(mBlockWM, aLine->ScrollableOverflowRect(), mBlockSize);
+ const bool istartWantsMarker =
+ !suppressIStart &&
+ lineRect.IStart(mBlockWM) < contentArea.IStart(mBlockWM);
+ const bool iendWantsTextOverflowMarker =
+ !suppressIEnd && lineRect.IEnd(mBlockWM) > contentArea.IEnd(mBlockWM);
+ const bool iendWantsBlockEllipsisMarker =
+ !suppressIEnd && mIEnd.mHasBlockEllipsis;
+ const bool iendWantsMarker =
+ iendWantsTextOverflowMarker || iendWantsBlockEllipsisMarker;
+ if (!istartWantsMarker && !iendWantsMarker) {
+ // We don't need any markers on this line.
+ return nonSnappedContentArea;
+ }
+
+ int pass = 0;
+ bool retryEmptyLine = true;
+ bool guessIStart = istartWantsMarker;
+ bool guessIEnd = iendWantsMarker;
+ mIStart.mActive = istartWantsMarker;
+ mIEnd.mActive = iendWantsMarker;
+ mIStart.mEdgeAligned = mCanHaveInlineAxisScrollbar && istartWantsMarker;
+ mIEnd.mEdgeAligned =
+ mCanHaveInlineAxisScrollbar && iendWantsTextOverflowMarker;
+ bool clippedIStartMarker = false;
+ bool clippedIEndMarker = false;
+ do {
+ // Setup marker strings as needed.
+ if (guessIStart) {
+ mIStart.SetupString(mBlock);
+ }
+ if (guessIEnd) {
+ mIEnd.SetupString(mBlock);
+ }
+
+ // If there is insufficient space for both markers then keep the one on the
+ // end side per the block's 'direction'.
+ nscoord istartMarkerISize = mIStart.mActive ? mIStart.mISize : 0;
+ nscoord iendMarkerISize = mIEnd.mActive ? mIEnd.mISize : 0;
+ if (istartMarkerISize && iendMarkerISize &&
+ istartMarkerISize + iendMarkerISize > contentArea.ISize(mBlockWM)) {
+ istartMarkerISize = 0;
+ }
+
+ // Calculate the area between the potential markers aligned at the
+ // block's edge.
+ LogicalRect insideMarkersArea = nonSnappedContentArea;
+ if (guessIStart) {
+ InflateIStart(mBlockWM, &insideMarkersArea, -istartMarkerISize);
+ }
+ if (guessIEnd) {
+ InflateIEnd(mBlockWM, &insideMarkersArea, -iendMarkerISize);
+ }
+
+ // Analyze the frames on aLine for the overflow situation at the content
+ // edges and at the edges of the area between the markers.
+ bool foundVisibleTextOrAtomic = false;
+ int32_t n = aLine->GetChildCount();
+ nsIFrame* child = aLine->mFirstChild;
+ InnerClipEdges clippedMarkerEdges;
+ for (; n-- > 0; child = child->GetNextSibling()) {
+ ExamineFrameSubtree(child, contentArea, insideMarkersArea, aFramesToHide,
+ aAlignmentEdges, &foundVisibleTextOrAtomic,
+ &clippedMarkerEdges);
+ }
+ if (!foundVisibleTextOrAtomic && retryEmptyLine) {
+ aAlignmentEdges->mAssignedInner = false;
+ aAlignmentEdges->mIEndOuter = 0;
+ aFramesToHide->Clear();
+ pass = -1;
+ if (mIStart.IsNeeded() && mIStart.mActive && !clippedIStartMarker) {
+ if (clippedMarkerEdges.mAssignedIStart &&
+ clippedMarkerEdges.mIStart >
+ nonSnappedContentArea.IStart(mBlockWM)) {
+ mIStart.mISize = clippedMarkerEdges.mIStart -
+ nonSnappedContentArea.IStart(mBlockWM);
+ NS_ASSERTION(mIStart.mISize < mIStart.mIntrinsicISize,
+ "clipping a marker should make it strictly smaller");
+ clippedIStartMarker = true;
+ } else {
+ mIStart.mActive = guessIStart = false;
+ }
+ continue;
+ }
+ if (mIEnd.IsNeeded() && mIEnd.mActive && !clippedIEndMarker) {
+ if (clippedMarkerEdges.mAssignedIEnd &&
+ nonSnappedContentArea.IEnd(mBlockWM) > clippedMarkerEdges.mIEnd) {
+ mIEnd.mISize =
+ nonSnappedContentArea.IEnd(mBlockWM) - clippedMarkerEdges.mIEnd;
+ NS_ASSERTION(mIEnd.mISize < mIEnd.mIntrinsicISize,
+ "clipping a marker should make it strictly smaller");
+ clippedIEndMarker = true;
+ } else {
+ mIEnd.mActive = guessIEnd = false;
+ }
+ continue;
+ }
+ // The line simply has no visible content even without markers,
+ // so examine the line again without suppressing markers.
+ retryEmptyLine = false;
+ mIStart.mISize = mIStart.mIntrinsicISize;
+ mIStart.mActive = guessIStart = istartWantsMarker;
+ mIEnd.mISize = mIEnd.mIntrinsicISize;
+ mIEnd.mActive = guessIEnd = iendWantsMarker;
+ // If we wanted to place a block ellipsis but didn't, due to not having
+ // any visible content to align to or the line's content being scrolled
+ // out of view, then clip the ellipsis so that it looks like it is aligned
+ // with the out of view content.
+ if (mIEnd.IsNeeded() && mIEnd.mActive && mIEnd.mHasBlockEllipsis) {
+ NS_ASSERTION(nonSnappedContentArea.IStart(mBlockWM) >
+ aAlignmentEdges->mIEndOuter,
+ "Expected the alignment edge for the out of view content "
+ "to be before the start of the content area");
+ mIEnd.mISize = std::max(
+ mIEnd.mIntrinsicISize - (nonSnappedContentArea.IStart(mBlockWM) -
+ aAlignmentEdges->mIEndOuter),
+ 0);
+ }
+ continue;
+ }
+ if (guessIStart == (mIStart.mActive && mIStart.IsNeeded()) &&
+ guessIEnd == (mIEnd.mActive && mIEnd.IsNeeded())) {
+ break;
+ } else {
+ guessIStart = mIStart.mActive && mIStart.IsNeeded();
+ guessIEnd = mIEnd.mActive && mIEnd.IsNeeded();
+ mIStart.Reset();
+ mIEnd.Reset();
+ aFramesToHide->Clear();
+ }
+ NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
+ } while (++pass != 2);
+ if (!istartWantsMarker || !mIStart.mActive) {
+ mIStart.Reset();
+ }
+ if (!iendWantsMarker || !mIEnd.mActive) {
+ mIEnd.Reset();
+ }
+ return nonSnappedContentArea;
+}
+
+void TextOverflow::ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine,
+ uint32_t aLineNumber) {
+ if (mIStart.mStyle->IsClip() && mIEnd.mStyle->IsClip() &&
+ !aLine->HasLineClampEllipsis()) {
+ return;
+ }
+
+ mIStart.Reset();
+ mIStart.mActive = !mIStart.mStyle->IsClip();
+ mIEnd.Reset();
+ mIEnd.mHasBlockEllipsis = aLine->HasLineClampEllipsis();
+ mIEnd.mActive = !mIEnd.IsSuppressed(mInLineClampContext);
+
+ FrameHashtable framesToHide(64);
+ AlignmentEdges alignmentEdges;
+ const LogicalRect contentArea =
+ ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
+ bool needIStart = mIStart.IsNeeded();
+ bool needIEnd = mIEnd.IsNeeded();
+ if (!needIStart && !needIEnd) {
+ return;
+ }
+ NS_ASSERTION(!mIStart.IsSuppressed(mInLineClampContext) || !needIStart,
+ "left marker when not needed");
+ NS_ASSERTION(!mIEnd.IsSuppressed(mInLineClampContext) || !needIEnd,
+ "right marker when not needed");
+
+ // If there is insufficient space for both markers then keep the one on the
+ // end side per the block's 'direction'.
+ if (needIStart && needIEnd &&
+ mIStart.mISize + mIEnd.mISize > contentArea.ISize(mBlockWM)) {
+ needIStart = false;
+ }
+ LogicalRect insideMarkersArea = contentArea;
+ if (needIStart) {
+ InflateIStart(mBlockWM, &insideMarkersArea, -mIStart.mISize);
+ }
+ if (needIEnd) {
+ InflateIEnd(mBlockWM, &insideMarkersArea, -mIEnd.mISize);
+ }
+
+ if (alignmentEdges.mAssignedInner) {
+ if (mIStart.mEdgeAligned) {
+ alignmentEdges.mIStart = insideMarkersArea.IStart(mBlockWM);
+ }
+ if (mIEnd.mEdgeAligned) {
+ alignmentEdges.mIEnd = insideMarkersArea.IEnd(mBlockWM);
+ }
+ LogicalRect alignmentRect(mBlockWM, alignmentEdges.mIStart,
+ insideMarkersArea.BStart(mBlockWM),
+ alignmentEdges.ISize(), 1);
+ insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect);
+ } else {
+ // There was no content on the line that was visible at the current scolled
+ // position. If we wanted to place a block ellipsis but failed due to
+ // having no visible content to align it to, we still need to ensure it
+ // is displayed. It goes at the start of the line, even though it's an
+ // IEnd marker, since that is the side of the line that the content has
+ // been scrolled past. We set the insideMarkersArea to a zero-sized
+ // rectangle placed next to the scrolled-out-of-view content.
+ if (mIEnd.mHasBlockEllipsis) {
+ insideMarkersArea = LogicalRect(mBlockWM, alignmentEdges.mIEndOuter,
+ insideMarkersArea.BStart(mBlockWM), 0, 1);
+ }
+ }
+
+ // Clip and remove display items as needed at the final marker edges.
+ nsDisplayList* lists[] = {aLists.Content(), aLists.PositionedDescendants()};
+ for (uint32_t i = 0; i < ArrayLength(lists); ++i) {
+ PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea);
+ }
+ CreateMarkers(aLine, needIStart, needIEnd, insideMarkersArea, contentArea,
+ aLineNumber);
+}
+
+void TextOverflow::PruneDisplayListContents(
+ nsDisplayList* aList, const FrameHashtable& aFramesToHide,
+ const LogicalRect& aInsideMarkersArea) {
+ for (nsDisplayItem* item : aList->TakeItems()) {
+ nsIFrame* itemFrame = item->Frame();
+ if (IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) {
+ item->Destroy(mBuilder);
+ continue;
+ }
+
+ nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren();
+ if (wrapper) {
+ if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) {
+ PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea);
+ }
+ }
+
+ nsDisplayText* textItem =
+ itemFrame ? nsDisplayText::CheckCast(item) : nullptr;
+ if (textItem && GetSelfOrNearestBlock(itemFrame) == mBlock) {
+ LogicalRect rect =
+ GetLogicalScrollableOverflowRectRelativeToBlock(itemFrame);
+ if (mIStart.IsNeeded()) {
+ nscoord istart =
+ aInsideMarkersArea.IStart(mBlockWM) - rect.IStart(mBlockWM);
+ if (istart > 0) {
+ (mBlockWM.IsBidiLTR() ? textItem->VisIStartEdge()
+ : textItem->VisIEndEdge()) = istart;
+ }
+ }
+ if (mIEnd.IsNeeded()) {
+ nscoord iend = rect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM);
+ if (iend > 0) {
+ (mBlockWM.IsBidiLTR() ? textItem->VisIEndEdge()
+ : textItem->VisIStartEdge()) = iend;
+ }
+ }
+ }
+
+ aList->AppendToTop(item);
+ }
+}
+
+/* static */
+bool TextOverflow::HasClippedTextOverflow(nsIFrame* aBlockFrame) {
+ const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
+ return style->mTextOverflow.first.IsClip() &&
+ style->mTextOverflow.second.IsClip();
+}
+
+/* static */
+bool TextOverflow::HasBlockEllipsis(nsIFrame* aBlockFrame) {
+ nsBlockFrame* f = do_QueryFrame(aBlockFrame);
+ return f && f->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
+}
+
+static bool BlockCanHaveLineClampEllipsis(nsBlockFrame* aBlockFrame,
+ bool aBeforeReflow) {
+ if (aBeforeReflow) {
+ return aBlockFrame->IsInLineClampContext();
+ }
+ return aBlockFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
+}
+
+/* static */
+bool TextOverflow::CanHaveOverflowMarkers(nsBlockFrame* aBlockFrame,
+ BeforeReflow aBeforeReflow) {
+ if (BlockCanHaveLineClampEllipsis(aBlockFrame,
+ aBeforeReflow == BeforeReflow::Yes)) {
+ return true;
+ }
+
+ // Nothing to do for text-overflow:clip or if 'overflow-x/y:visible'.
+ if (HasClippedTextOverflow(aBlockFrame) ||
+ IsInlineAxisOverflowVisible(aBlockFrame)) {
+ return false;
+ }
+
+ // Skip ComboboxControlFrame because it would clip the drop-down arrow.
+ // Its anon block inherits 'text-overflow' and does what is expected.
+ if (aBlockFrame->IsComboboxControlFrame()) {
+ return false;
+ }
+
+ // Inhibit the markers if a descendant content owns the caret.
+ RefPtr<nsCaret> caret = aBlockFrame->PresShell()->GetCaret();
+ if (caret && caret->IsVisible()) {
+ RefPtr<dom::Selection> domSelection = caret->GetSelection();
+ if (domSelection) {
+ nsCOMPtr<nsIContent> content =
+ nsIContent::FromNodeOrNull(domSelection->GetFocusNode());
+ if (content &&
+ content->IsInclusiveDescendantOf(aBlockFrame->GetContent())) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void TextOverflow::CreateMarkers(const nsLineBox* aLine, bool aCreateIStart,
+ bool aCreateIEnd,
+ const LogicalRect& aInsideMarkersArea,
+ const LogicalRect& aContentArea,
+ uint32_t aLineNumber) {
+ if (!mBlock->IsVisibleForPainting()) {
+ return;
+ }
+
+ if (aCreateIStart) {
+ DisplayListClipState::AutoSaveRestore clipState(mBuilder);
+
+ LogicalRect markerLogicalRect(
+ mBlockWM, aInsideMarkersArea.IStart(mBlockWM) - mIStart.mIntrinsicISize,
+ aLine->BStart(), mIStart.mIntrinsicISize, aLine->BSize());
+ nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
+ nsRect markerRect =
+ markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
+ ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
+ markerRect, clipState);
+
+ mMarkerList.AppendNewToTopWithIndex<nsDisplayTextOverflowMarker>(
+ mBuilder, mBlock, /* aIndex = */ (aLineNumber << 1) + 0, markerRect,
+ aLine->GetLogicalAscent(), *mIStart.mStyle);
+ }
+
+ if (aCreateIEnd) {
+ DisplayListClipState::AutoSaveRestore clipState(mBuilder);
+
+ LogicalRect markerLogicalRect(mBlockWM, aInsideMarkersArea.IEnd(mBlockWM),
+ aLine->BStart(), mIEnd.mIntrinsicISize,
+ aLine->BSize());
+ nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
+ nsRect markerRect =
+ markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
+ ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
+ markerRect, clipState);
+
+ mMarkerList.AppendNewToTopWithIndex<nsDisplayTextOverflowMarker>(
+ mBuilder, mBlock, /* aIndex = */ (aLineNumber << 1) + 1, markerRect,
+ aLine->GetLogicalAscent(),
+ mIEnd.mHasBlockEllipsis ? StyleTextOverflowSide::Ellipsis()
+ : *mIEnd.mStyle);
+ }
+}
+
+void TextOverflow::Marker::SetupString(nsIFrame* aFrame) {
+ if (mInitialized) {
+ return;
+ }
+
+ // A limitation here is that at the IEnd of a line, we only ever render one of
+ // a text-overflow marker and a -webkit-line-clamp block ellipsis. Since we
+ // don't track the block ellipsis string and the text-overflow marker string
+ // separately, if both apply to the element, we will always use "…" as the
+ // string for text-overflow.
+ if (HasBlockEllipsis(aFrame) || mStyle->IsEllipsis()) {
+ gfxTextRun* textRun = GetEllipsisTextRun(aFrame);
+ if (textRun) {
+ mISize = textRun->GetAdvanceWidth();
+ } else {
+ mISize = 0;
+ }
+ } else {
+ UniquePtr<gfxContext> rc =
+ aFrame->PresShell()->CreateReferenceRenderingContext();
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+ mISize = nsLayoutUtils::AppUnitWidthOfStringBidi(
+ NS_ConvertUTF8toUTF16(mStyle->AsString().AsString()), aFrame, *fm, *rc);
+ }
+ mIntrinsicISize = mISize;
+ mInitialized = true;
+}
+
+} // namespace css
+} // namespace mozilla
diff --git a/layout/generic/TextOverflow.h b/layout/generic/TextOverflow.h
new file mode 100644
index 0000000000..7f0911b93d
--- /dev/null
+++ b/layout/generic/TextOverflow.h
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TextOverflow_h_
+#define TextOverflow_h_
+
+#include "nsDisplayList.h"
+#include "nsTHashSet.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WritingModes.h"
+#include <algorithm>
+
+class nsIScrollableFrame;
+class nsBlockFrame;
+class nsLineBox;
+
+namespace mozilla {
+namespace css {
+
+/**
+ * A class for rendering CSS3 text-overflow.
+ * Usage:
+ * 1. allocate an object using WillProcessLines
+ * 2. then call ProcessLine for each line you are building display lists for
+ *
+ * Note that this class is non-reassignable; we don't want to be making
+ * arbitrary copies. (But we do have a move constructor, since that's required
+ * in order to be stored in Maybe<>).
+ */
+class TextOverflow final {
+ private:
+ /**
+ * Private constructor, for internal use only. Client code should call
+ * WillProcessLines(), which is basically the factory function for
+ * TextOverflow instances.
+ */
+ TextOverflow(nsDisplayListBuilder* aBuilder, nsBlockFrame*);
+
+ public:
+ ~TextOverflow() = default;
+
+ /**
+ * Allocate an object for text-overflow processing. (Factory function.)
+ * @return nullptr if no processing is necessary. The caller owns the object.
+ */
+ static Maybe<TextOverflow> WillProcessLines(nsDisplayListBuilder* aBuilder,
+ nsBlockFrame*);
+
+ /**
+ * This is a factory-constructed non-reassignable class, so we delete nearly
+ * all constructors and reassignment operators. We only provide a
+ * move-constructor, because that's required for Maybe<TextOverflow> to work
+ * (and that's what our factory method returns).
+ */
+ TextOverflow(TextOverflow&&) = default;
+
+ TextOverflow() = delete;
+ TextOverflow(const TextOverflow&) = delete;
+ TextOverflow& operator=(const TextOverflow&) = delete;
+ TextOverflow& operator=(TextOverflow&&) = delete;
+
+ /**
+ * Analyze the display lists for text overflow and what kind of item is at
+ * the content edges. Add display items for text-overflow markers as needed
+ * and remove or clip items that would overlap a marker.
+ */
+ void ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine,
+ uint32_t aLineNumber);
+
+ /**
+ * Get the resulting text-overflow markers (the list may be empty).
+ * @return a DisplayList containing any text-overflow markers.
+ */
+ nsDisplayList& GetMarkers() { return mMarkerList; }
+
+ // Returns whether aBlockFrame has text-overflow:clip on both sides.
+ static bool HasClippedTextOverflow(nsIFrame* aBlockFrame);
+
+ // Returns whether aBlockFrame has a block ellipsis on one of its lines.
+ static bool HasBlockEllipsis(nsIFrame* aBlockFrame);
+
+ // Returns whether the given block frame needs analysis for text overflow.
+ // The BeforeReflow flag indicates whether we can be faster and more precise
+ // for line-clamp ellipsis (only returning true iff the block actually uses
+ // it).
+ enum class BeforeReflow : bool { No, Yes };
+ static bool CanHaveOverflowMarkers(nsBlockFrame*,
+ BeforeReflow = BeforeReflow::No);
+
+ typedef nsTHashSet<nsIFrame*> FrameHashtable;
+
+ private:
+ typedef mozilla::WritingMode WritingMode;
+ typedef mozilla::LogicalRect LogicalRect;
+
+ // Edges to align the IStart and IEnd markers to.
+ struct AlignmentEdges {
+ AlignmentEdges()
+ : mIStart(0), mIEnd(0), mIEndOuter(0), mAssignedInner(false) {}
+ void AccumulateInner(WritingMode aWM, const LogicalRect& aRect) {
+ if (MOZ_LIKELY(mAssignedInner)) {
+ mIStart = std::min(mIStart, aRect.IStart(aWM));
+ mIEnd = std::max(mIEnd, aRect.IEnd(aWM));
+ } else {
+ mIStart = aRect.IStart(aWM);
+ mIEnd = aRect.IEnd(aWM);
+ mAssignedInner = true;
+ }
+ }
+ void AccumulateOuter(WritingMode aWM, const LogicalRect& aRect) {
+ mIEndOuter = std::max(mIEndOuter, aRect.IEnd(aWM));
+ }
+ nscoord ISize() { return mIEnd - mIStart; }
+
+ // The outermost edges of all text and atomic inline-level frames that are
+ // inside the area between the markers.
+ nscoord mIStart;
+ nscoord mIEnd;
+
+ // The closest IEnd edge of all text and atomic inline-level frames that
+ // fall completely before the IStart edge of the content area. (Used to
+ // align a block ellipsis when there are no visible frames to align to.)
+ nscoord mIEndOuter;
+
+ bool mAssignedInner;
+ };
+
+ struct InnerClipEdges {
+ InnerClipEdges()
+ : mIStart(0), mIEnd(0), mAssignedIStart(false), mAssignedIEnd(false) {}
+ void AccumulateIStart(WritingMode aWM, const LogicalRect& aRect) {
+ if (MOZ_LIKELY(mAssignedIStart)) {
+ mIStart = std::max(mIStart, aRect.IStart(aWM));
+ } else {
+ mIStart = aRect.IStart(aWM);
+ mAssignedIStart = true;
+ }
+ }
+ void AccumulateIEnd(WritingMode aWM, const LogicalRect& aRect) {
+ if (MOZ_LIKELY(mAssignedIEnd)) {
+ mIEnd = std::min(mIEnd, aRect.IEnd(aWM));
+ } else {
+ mIEnd = aRect.IEnd(aWM);
+ mAssignedIEnd = true;
+ }
+ }
+ nscoord mIStart;
+ nscoord mIEnd;
+ bool mAssignedIStart;
+ bool mAssignedIEnd;
+ };
+
+ LogicalRect GetLogicalScrollableOverflowRectRelativeToBlock(
+ nsIFrame* aFrame) const {
+ return LogicalRect(
+ mBlockWM,
+ aFrame->ScrollableOverflowRect() + aFrame->GetOffsetTo(mBlock),
+ mBlockSize);
+ }
+
+ /**
+ * Examines frames on the line to determine whether we should draw a left
+ * and/or right marker, and if so, which frames should be completely hidden
+ * and the bounds of what will be displayed between the markers.
+ * @param aLine the line we're processing
+ * @param aFramesToHide frames that should have their display items removed
+ * @param aAlignmentEdges edges the markers will be aligned to, including
+ * the outermost edges of all text and atomic inline-level frames that
+ * are inside the content area, and the closest IEnd edge of such a frame
+ * outside the content area
+ * @return the area inside which we should add any markers;
+ * this is the block's content area narrowed by any floats on this line.
+ */
+ LogicalRect ExamineLineFrames(nsLineBox* aLine, FrameHashtable* aFramesToHide,
+ AlignmentEdges* aAlignmentEdges);
+
+ /**
+ * LineHasOverflowingText calls this to analyze edges, both the block's
+ * content edges and the hypothetical marker edges aligned at the block edges.
+ * @param aFrame the descendant frame of mBlock that we're analyzing
+ * @param aContentArea the block's content area
+ * @param aInsideMarkersArea the rectangle between the markers
+ * @param aFramesToHide frames that should have their display items removed
+ * @param aAlignmentEdges edges the markers will be aligned to, including
+ * the outermost edges of all text and atomic inline-level frames that
+ * are inside the content area, and the closest IEnd edge of such a frame
+ * outside the content area
+ * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
+ * inline-level frame is visible between the marker edges
+ * @param aClippedMarkerEdges the innermost edges of all text and atomic
+ * inline-level frames that are clipped by the current marker width
+ */
+ void ExamineFrameSubtree(nsIFrame* aFrame, const LogicalRect& aContentArea,
+ const LogicalRect& aInsideMarkersArea,
+ FrameHashtable* aFramesToHide,
+ AlignmentEdges* aAlignmentEdges,
+ bool* aFoundVisibleTextOrAtomic,
+ InnerClipEdges* aClippedMarkerEdges);
+
+ /**
+ * ExamineFrameSubtree calls this to analyze a frame against the hypothetical
+ * marker edges (aInsideMarkersArea) for text frames and atomic inline-level
+ * elements. A text frame adds its extent inside aInsideMarkersArea where
+ * grapheme clusters are fully visible. An atomic adds its border box if
+ * it's fully inside aInsideMarkersArea, otherwise the frame is added to
+ * aFramesToHide.
+ * @param aFrame the descendant frame of mBlock that we're analyzing
+ * @param aFrameType aFrame's frame type
+ * @param aInsideMarkersArea the rectangle between the markers
+ * @param aFramesToHide frames that should have their display items removed
+ * @param aAlignmentEdges the outermost edges of all text and atomic
+ * inline-level frames that are inside the area between the markers
+ * inside aInsideMarkersArea
+ * @param aAlignmentEdges edges the markers will be aligned to, including
+ * the outermost edges of all text and atomic inline-level frames that
+ * are inside aInsideMarkersArea, and the closest IEnd edge of such a frame
+ * outside the content area
+ * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
+ * inline-level frame is visible between the marker edges
+ * @param aClippedMarkerEdges the innermost edges of all text and atomic
+ * inline-level frames that are clipped by the current marker width
+ */
+ void AnalyzeMarkerEdges(nsIFrame* aFrame, mozilla::LayoutFrameType aFrameType,
+ const LogicalRect& aInsideMarkersArea,
+ FrameHashtable* aFramesToHide,
+ AlignmentEdges* aAlignmentEdges,
+ bool* aFoundVisibleTextOrAtomic,
+ InnerClipEdges* aClippedMarkerEdges);
+
+ /**
+ * Clip or remove items given the final marker edges. ("clip" here just means
+ * assigning mVisIStartEdge/mVisIEndEdge for any nsCharClipDisplayItem that
+ * needs it; see nsDisplayList.h for a description of that item).
+ * @param aFramesToHide remove display items for these frames
+ * @param aInsideMarkersArea is the area inside the markers
+ */
+ void PruneDisplayListContents(nsDisplayList* aList,
+ const FrameHashtable& aFramesToHide,
+ const LogicalRect& aInsideMarkersArea);
+
+ /**
+ * ProcessLine calls this to create display items for the markers and insert
+ * them into mMarkerList.
+ * @param aLine the line we're processing
+ * @param aCreateIStart if true, create a marker on the inline start side
+ * @param aCreateIEnd if true, create a marker on the inline end side
+ * @param aInsideMarkersArea is the area inside the markers
+ * @param aContentArea is the area inside which we should add the markers;
+ * this is the block's content area narrowed by any floats on this line.
+ */
+ void CreateMarkers(const nsLineBox* aLine, bool aCreateIStart,
+ bool aCreateIEnd, const LogicalRect& aInsideMarkersArea,
+ const LogicalRect& aContentArea, uint32_t aLineNumber);
+
+ LogicalRect mContentArea;
+ nsDisplayListBuilder* mBuilder;
+ nsIFrame* mBlock;
+ nsIScrollableFrame* mScrollableFrame;
+ nsDisplayList mMarkerList;
+ nsSize mBlockSize;
+ WritingMode mBlockWM;
+ bool mCanHaveInlineAxisScrollbar;
+ // When we're in a -webkit-line-clamp context, we should ignore inline-end
+ // text-overflow markers. See nsBlockFrame::IsInLineClampContext.
+ const bool mInLineClampContext;
+ bool mAdjustForPixelSnapping;
+
+ class Marker {
+ public:
+ void Init(const StyleTextOverflowSide& aStyle) {
+ mInitialized = false;
+ mISize = 0;
+ mStyle = &aStyle;
+ mIntrinsicISize = 0;
+ mHasOverflow = false;
+ mHasBlockEllipsis = false;
+ mActive = false;
+ mEdgeAligned = false;
+ }
+
+ /**
+ * Setup the marker string and calculate its size, if not done already.
+ */
+ void SetupString(nsIFrame* aFrame);
+
+ bool IsSuppressed(bool aInLineClampContext) const {
+ if (aInLineClampContext) {
+ return !mHasBlockEllipsis;
+ }
+ return mStyle->IsClip();
+ }
+ bool IsNeeded() const { return mHasOverflow || mHasBlockEllipsis; }
+ void Reset() {
+ mHasOverflow = false;
+ mHasBlockEllipsis = false;
+ mEdgeAligned = false;
+ }
+
+ // The current width of the marker, the range is [0 .. mIntrinsicISize].
+ nscoord mISize;
+ // The intrinsic width of the marker.
+ nscoord mIntrinsicISize;
+ // The text-overflow style for this side. Ignored if we're rendering a
+ // block ellipsis.
+ const StyleTextOverflowSide* mStyle;
+ // True if there is visible overflowing inline content on this side.
+ bool mHasOverflow;
+ // True if this side has a block ellipsis (from -webkit-line-clamp).
+ bool mHasBlockEllipsis;
+ // True if mISize and mIntrinsicISize have been setup from style.
+ bool mInitialized;
+ // True if the style is not text-overflow:clip on this side and the marker
+ // won't cause the line to become empty.
+ bool mActive;
+ // True if this marker is aligned to the edge of the content box, so that
+ // when scrolling the marker doesn't jump around.
+ bool mEdgeAligned;
+ };
+
+ Marker mIStart; // the inline start marker
+ Marker mIEnd; // the inline end marker
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* !defined(TextOverflow_h_) */
diff --git a/layout/generic/ViewportFrame.cpp b/layout/generic/ViewportFrame.cpp
new file mode 100644
index 0000000000..1837441e86
--- /dev/null
+++ b/layout/generic/ViewportFrame.cpp
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * rendering object that is the root of the frame tree, which contains
+ * the document's scrollbars and contains fixed-positioned elements
+ */
+
+#include "mozilla/ViewportFrame.h"
+
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/RestyleManager.h"
+#include "nsGkAtoms.h"
+#include "nsIScrollableFrame.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "nsCanvasFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsSubDocumentFrame.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "MobileViewportManager.h"
+
+using namespace mozilla;
+typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
+
+ViewportFrame* NS_NewViewportFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) ViewportFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(ViewportFrame)
+NS_QUERYFRAME_HEAD(ViewportFrame)
+ NS_QUERYFRAME_ENTRY(ViewportFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+void ViewportFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ // No need to call CreateView() here - the frame ctor will call SetView()
+ // with the ViewManager's root view, so we'll assign it in SetViewInternal().
+
+ nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(this);
+ if (parent) {
+ nsFrameState state = parent->GetStateBits();
+
+ AddStateBits(state & (NS_FRAME_IN_POPUP));
+ }
+}
+
+void ViewportFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ AUTO_PROFILER_LABEL("ViewportFrame::BuildDisplayList",
+ GRAPHICS_DisplayListBuilding);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ nsDisplayListCollection set(aBuilder);
+ BuildDisplayListForChild(aBuilder, kid, set);
+
+ // If we have a scrollframe then it takes care of creating the display list
+ // for the top layer, but otherwise we need to do it here.
+ if (!kid->IsScrollFrame()) {
+ bool isOpaque = false;
+ if (auto* list = BuildDisplayListForTopLayer(aBuilder, &isOpaque)) {
+ if (isOpaque) {
+ set.DeleteAll(aBuilder);
+ }
+ set.PositionedDescendants()->AppendToTop(list);
+ }
+ }
+
+ set.MoveTo(aLists);
+}
+
+#ifdef DEBUG
+/**
+ * Returns whether we are going to put an element in the top layer for
+ * fullscreen. This function should matches the CSS rule in ua.css.
+ */
+static bool ShouldInTopLayerForFullscreen(dom::Element* aElement) {
+ return !!aElement->GetParent();
+}
+#endif // DEBUG
+
+static void BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList) {
+ nsRect visible;
+ nsRect dirty;
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
+ nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData =
+ nsDisplayListBuilder::GetOutOfFlowData(aFrame);
+ if (savedOutOfFlowData) {
+ visible =
+ savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, aFrame, &dirty);
+ // This function is called after we've finished building display items for
+ // the root scroll frame. That means that the content clip from the root
+ // scroll frame is no longer on aBuilder. However, we need to make sure
+ // that the display items we build in this function have finite clipped
+ // bounds with respect to the root ASR, so we restore the *combined clip*
+ // that we saved earlier. The combined clip will include the clip from the
+ // root scroll frame.
+ clipState.SetClipChainForContainingBlockDescendants(
+ savedOutOfFlowData->mCombinedClipChain);
+ asrSetter.SetCurrentActiveScrolledRoot(
+ savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
+ asrSetter.SetCurrentScrollParentId(savedOutOfFlowData->mScrollParentId);
+ }
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, aFrame, visible, dirty);
+
+ nsDisplayList list(aBuilder);
+ aFrame->BuildDisplayListForStackingContext(aBuilder, &list);
+ aList->AppendToTop(&list);
+}
+
+static bool BackdropListIsOpaque(ViewportFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList) {
+ // The common case for ::backdrop elements on the top layer is a single
+ // fixed position container, holding an opaque background color covering
+ // the whole viewport.
+ if (aList->Length() != 1 ||
+ aList->GetTop()->GetType() != DisplayItemType::TYPE_FIXED_POSITION) {
+ return false;
+ }
+
+ // Make sure the fixed position container isn't clipped or scrollable.
+ nsDisplayFixedPosition* fixed =
+ static_cast<nsDisplayFixedPosition*>(aList->GetTop());
+ if (fixed->GetActiveScrolledRoot() || fixed->GetClipChain()) {
+ return false;
+ }
+
+ nsDisplayList* children = fixed->GetChildren();
+ if (!children->GetTop() ||
+ children->GetTop()->GetType() != DisplayItemType::TYPE_BACKGROUND_COLOR) {
+ return false;
+ }
+
+ nsDisplayBackgroundColor* child =
+ static_cast<nsDisplayBackgroundColor*>(children->GetTop());
+ if (child->GetActiveScrolledRoot() || child->GetClipChain()) {
+ return false;
+ }
+
+ // Check that the background color is both opaque, and covering the
+ // whole viewport.
+ bool dummy;
+ nsRegion opaque = child->GetOpaqueRegion(aBuilder, &dummy);
+ return opaque.Contains(aFrame->GetRect());
+}
+
+nsDisplayWrapList* ViewportFrame::BuildDisplayListForTopLayer(
+ nsDisplayListBuilder* aBuilder, bool* aIsOpaque) {
+ nsDisplayList topLayerList(aBuilder);
+
+ nsTArray<dom::Element*> topLayer = PresContext()->Document()->GetTopLayer();
+ for (dom::Element* elem : topLayer) {
+ nsIFrame* frame = elem->GetPrimaryFrame();
+ if (!frame) {
+ continue;
+ }
+
+ if (frame->IsHiddenByContentVisibilityOnAnyAncestor(
+ nsIFrame::IncludeContentVisibility::Hidden)) {
+ continue;
+ }
+
+ // There are two cases where an element in fullscreen is not in
+ // the top layer:
+ // 1. When building display list for purpose other than painting,
+ // it is possible that there is inconsistency between the style
+ // info and the content tree.
+ // 2. This is an element which we are not going to put in the top
+ // layer for fullscreen. See ShouldInTopLayerForFullscreen().
+ // In both cases, we want to skip the frame here and paint it in
+ // the normal path.
+ if (frame->StyleDisplay()->mTopLayer == StyleTopLayer::None) {
+ MOZ_ASSERT(!aBuilder->IsForPainting() ||
+ !ShouldInTopLayerForFullscreen(elem));
+ continue;
+ }
+ MOZ_ASSERT(ShouldInTopLayerForFullscreen(elem));
+ // Inner SVG, MathML elements, as well as children of some XUL
+ // elements are not allowed to be out-of-flow. They should not
+ // be handled as top layer element here.
+ if (!frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ MOZ_ASSERT(!elem->GetParent()->IsHTMLElement(),
+ "HTML element should always be out-of-flow if in the top "
+ "layer");
+ continue;
+ }
+ if (nsIFrame* backdropPh =
+ frame->GetChildList(FrameChildListID::Backdrop).FirstChild()) {
+ MOZ_ASSERT(!backdropPh->GetNextSibling(), "more than one ::backdrop?");
+ MOZ_ASSERT(backdropPh->HasAnyStateBits(NS_FRAME_FIRST_REFLOW),
+ "did you intend to reflow ::backdrop placeholders?");
+ nsIFrame* backdropFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPh);
+ BuildDisplayListForTopLayerFrame(aBuilder, backdropFrame, &topLayerList);
+
+ if (aIsOpaque) {
+ *aIsOpaque = BackdropListIsOpaque(this, aBuilder, &topLayerList);
+ }
+ }
+ BuildDisplayListForTopLayerFrame(aBuilder, frame, &topLayerList);
+ }
+
+ if (nsCanvasFrame* canvasFrame = PresShell()->GetCanvasFrame()) {
+ if (dom::Element* container = canvasFrame->GetCustomContentContainer()) {
+ if (nsIFrame* frame = container->GetPrimaryFrame()) {
+ MOZ_ASSERT(frame->StyleDisplay()->mTopLayer != StyleTopLayer::None,
+ "ua.css should ensure this");
+ MOZ_ASSERT(frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+ BuildDisplayListForTopLayerFrame(aBuilder, frame, &topLayerList);
+ }
+ }
+ }
+ if (topLayerList.IsEmpty()) {
+ return nullptr;
+ }
+ nsPoint offset = aBuilder->GetCurrentFrame()->GetOffsetTo(this);
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
+ aBuilder, this, aBuilder->GetVisibleRect() + offset,
+ aBuilder->GetDirtyRect() + offset);
+ // Wrap the whole top layer in a single item with maximum z-index,
+ // and append it at the very end, so that it stays at the topmost.
+ nsDisplayWrapList* wrapList = MakeDisplayItemWithIndex<nsDisplayWrapper>(
+ aBuilder, this, 2, &topLayerList, aBuilder->CurrentActiveScrolledRoot(),
+ false);
+ if (!wrapList) {
+ return nullptr;
+ }
+ wrapList->SetOverrideZIndex(
+ std::numeric_limits<decltype(wrapList->ZIndex())>::max());
+ return wrapList;
+}
+
+#ifdef DEBUG
+void ViewportFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
+ NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
+ nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
+}
+
+void ViewportFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
+ NS_ASSERTION(GetChildList(aListID).IsEmpty(), "Shouldn't have any kids!");
+ nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+}
+
+void ViewportFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
+ nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
+}
+#endif
+
+/* virtual */
+nscoord ViewportFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ if (mFrames.IsEmpty())
+ result = 0;
+ else
+ result = mFrames.FirstChild()->GetMinISize(aRenderingContext);
+
+ return result;
+}
+
+/* virtual */
+nscoord ViewportFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ if (mFrames.IsEmpty())
+ result = 0;
+ else
+ result = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
+
+ return result;
+}
+
+nsPoint ViewportFrame::AdjustReflowInputForScrollbars(
+ ReflowInput* aReflowInput) const {
+ // Get our prinicpal child frame and see if we're scrollable
+ nsIFrame* kidFrame = mFrames.FirstChild();
+ nsIScrollableFrame* scrollingFrame = do_QueryFrame(kidFrame);
+
+ if (scrollingFrame) {
+ WritingMode wm = aReflowInput->GetWritingMode();
+ LogicalMargin scrollbars(wm, scrollingFrame->GetActualScrollbarSizes());
+ aReflowInput->SetComputedISize(
+ aReflowInput->ComputedISize() - scrollbars.IStartEnd(wm),
+ ReflowInput::ResetResizeFlags::No);
+ aReflowInput->SetAvailableISize(aReflowInput->AvailableISize() -
+ scrollbars.IStartEnd(wm));
+ aReflowInput->SetComputedBSize(
+ aReflowInput->ComputedBSize() - scrollbars.BStartEnd(wm),
+ ReflowInput::ResetResizeFlags::No);
+ return nsPoint(scrollbars.Left(wm), scrollbars.Top(wm));
+ }
+ return nsPoint(0, 0);
+}
+
+nsRect ViewportFrame::AdjustReflowInputAsContainingBlock(
+ ReflowInput* aReflowInput) const {
+ const nsPoint origin = AdjustReflowInputForScrollbars(aReflowInput);
+ nsRect rect(origin, aReflowInput->ComputedPhysicalSize());
+ rect.SizeTo(AdjustViewportSizeForFixedPosition(rect));
+
+ return rect;
+}
+
+void ViewportFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("ViewportFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE_REFLOW_IN("ViewportFrame::Reflow");
+
+ // Because |Reflow| sets ComputedBSize() on the child to our
+ // ComputedBSize().
+ AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+
+ // Set our size up front, since some parts of reflow depend on it
+ // being already set. Note that the computed height may be
+ // unconstrained; that's ok. Consumers should watch out for that.
+ SetSize(aReflowInput.ComputedPhysicalSize());
+
+ // Reflow the main content first so that the placeholders of the
+ // fixed-position frames will be in the right places on an initial
+ // reflow.
+ nscoord kidBSize = 0;
+ WritingMode wm = aReflowInput.GetWritingMode();
+
+ if (mFrames.NotEmpty()) {
+ // Deal with a non-incremental reflow or an incremental reflow
+ // targeted at our one-and-only principal child frame.
+ if (aReflowInput.ShouldReflowAllKids() ||
+ mFrames.FirstChild()->IsSubtreeDirty()) {
+ // Reflow our one-and-only principal child frame
+ nsIFrame* kidFrame = mFrames.FirstChild();
+ ReflowOutput kidDesiredSize(aReflowInput);
+ const WritingMode kidWM = kidFrame->GetWritingMode();
+ LogicalSize availableSpace = aReflowInput.AvailableSize(kidWM);
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame,
+ availableSpace);
+
+ // Reflow the frame
+ kidReflowInput.SetComputedBSize(aReflowInput.ComputedBSize());
+ ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, 0, 0,
+ ReflowChildFlags::Default, aStatus);
+ kidBSize = kidDesiredSize.BSize(wm);
+
+ FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, &kidReflowInput,
+ 0, 0, ReflowChildFlags::Default);
+ } else {
+ kidBSize = LogicalSize(wm, mFrames.FirstChild()->GetSize()).BSize(wm);
+ }
+ }
+
+ NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
+ "shouldn't happen anymore");
+
+ // Return the max size as our desired size
+ LogicalSize maxSize(wm, aReflowInput.AvailableISize(),
+ // Being flowed initially at an unconstrained block size
+ // means we should return our child's intrinsic size.
+ aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE
+ ? aReflowInput.ComputedBSize()
+ : kidBSize);
+ aDesiredSize.SetSize(wm, maxSize);
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ if (HasAbsolutelyPositionedChildren()) {
+ // Make a copy of the reflow input and change the computed width and height
+ // to reflect the available space for the fixed items
+ ReflowInput reflowInput(aReflowInput);
+
+ if (reflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
+ // We have an intrinsic-block-size document with abs-pos/fixed-pos
+ // children. Set the available block-size and computed block-size to our
+ // chosen block-size.
+ reflowInput.SetAvailableBSize(maxSize.BSize(wm));
+ // Not having border/padding simplifies things
+ NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(),
+ "Viewports can't have border/padding");
+ reflowInput.SetComputedBSize(maxSize.BSize(wm));
+ }
+
+ nsRect rect = AdjustReflowInputAsContainingBlock(&reflowInput);
+ AbsPosReflowFlags flags =
+ AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized
+ GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowInput,
+ aStatus, rect, flags,
+ /* aOverflowAreas = */ nullptr);
+ }
+
+ if (mFrames.NotEmpty()) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild());
+ }
+
+ // If we were dirty then do a repaint
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ InvalidateFrame();
+ }
+
+ // Clipping is handled by the document container (e.g., nsSubDocumentFrame),
+ // so we don't need to change our overflow areas.
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus);
+}
+
+void ViewportFrame::UpdateStyle(ServoRestyleState& aRestyleState) {
+ RefPtr<ComputedStyle> newStyle =
+ aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
+ Style()->GetPseudoType(), nullptr);
+
+ MOZ_ASSERT(!GetNextContinuation(), "Viewport has continuations?");
+ SetComputedStyle(newStyle);
+
+ UpdateStyleOfOwnedAnonBoxes(aRestyleState);
+}
+
+void ViewportFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ if (mFrames.NotEmpty()) {
+ aResult.AppendElement(mFrames.FirstChild());
+ }
+}
+
+nsSize ViewportFrame::AdjustViewportSizeForFixedPosition(
+ const nsRect& aViewportRect) const {
+ nsSize result = aViewportRect.Size();
+
+ mozilla::PresShell* presShell = PresShell();
+ // Layout fixed position elements to the visual viewport size if and only if
+ // it has been set and it is larger than the computed size, otherwise use the
+ // computed size.
+ if (presShell->IsVisualViewportSizeSet()) {
+ if (presShell->GetDynamicToolbarState() == DynamicToolbarState::Collapsed &&
+ result < presShell->GetVisualViewportSizeUpdatedByDynamicToolbar()) {
+ // We need to use the viewport size updated by the dynamic toolbar in the
+ // case where the dynamic toolbar is completely hidden.
+ result = presShell->GetVisualViewportSizeUpdatedByDynamicToolbar();
+ } else if (result < presShell->GetVisualViewportSize()) {
+ result = presShell->GetVisualViewportSize();
+ }
+ }
+ // Expand the size to the layout viewport size if necessary.
+ const nsSize layoutViewportSize = presShell->GetLayoutViewportSize();
+ if (result < layoutViewportSize) {
+ result = layoutViewportSize;
+ }
+
+ return result;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult ViewportFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Viewport"_ns, aResult);
+}
+#endif
diff --git a/layout/generic/ViewportFrame.h b/layout/generic/ViewportFrame.h
new file mode 100644
index 0000000000..f369e4de7a
--- /dev/null
+++ b/layout/generic/ViewportFrame.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * rendering object that is the root of the frame tree, which contains
+ * the document's scrollbars and contains fixed-positioned elements
+ */
+
+#ifndef mozilla_ViewportFrame_h
+#define mozilla_ViewportFrame_h
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+
+class nsPresContext;
+
+namespace mozilla {
+
+class nsDisplayWrapList;
+class ServoRestyleState;
+
+/**
+ * ViewportFrame is the parent of a single child - the doc root frame or a
+ * scroll frame containing the doc root frame. ViewportFrame stores this child
+ * in its primary child list.
+ */
+class ViewportFrame : public nsContainerFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(ViewportFrame)
+
+ explicit ViewportFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : ViewportFrame(aStyle, aPresContext, kClassID) {}
+
+ virtual ~ViewportFrame() = default; // useful for debugging
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+#ifdef DEBUG
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override;
+#endif
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ nsDisplayWrapList* BuildDisplayListForTopLayer(nsDisplayListBuilder* aBuilder,
+ bool* aIsOpaque = nullptr);
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ bool ComputeCustomOverflow(mozilla::OverflowAreas&) override { return false; }
+
+ /**
+ * Adjust aReflowInput to account for scrollbars and pres shell
+ * GetVisualViewportSizeSet and
+ * GetContentDocumentFixedPositionMargins adjustments.
+ * @return the rect to use as containing block rect
+ */
+ nsRect AdjustReflowInputAsContainingBlock(ReflowInput* aReflowInput) const;
+
+ /**
+ * Update our style (and recursively the styles of any anonymous boxes we
+ * might own)
+ */
+ void UpdateStyle(ServoRestyleState& aStyleSet);
+
+ /**
+ * Return our single anonymous box child.
+ */
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+ // Returns adjusted viewport size to reflect the positions that position:fixed
+ // elements are attached.
+ nsSize AdjustViewportSizeForFixedPosition(const nsRect& aViewportRect) const;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ protected:
+ ViewportFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, ClassID aID)
+ : nsContainerFrame(aStyle, aPresContext, aID), mView(nullptr) {}
+
+ /**
+ * Calculate how much room is available for fixed frames. That means
+ * determining if the viewport is scrollable and whether the vertical and/or
+ * horizontal scrollbars are visible. Adjust the computed isize/bsize and
+ * available isize for aReflowInput accordingly.
+ * @return the current scroll position, or (0,0) if not scrollable.
+ */
+ nsPoint AdjustReflowInputForScrollbars(ReflowInput* aReflowInput) const;
+
+ nsView* GetViewInternal() const override { return mView; }
+ void SetViewInternal(nsView* aView) override { mView = aView; }
+
+ private:
+ mozilla::FrameChildListID GetAbsoluteListID() const override {
+ return FrameChildListID::Fixed;
+ }
+
+ nsView* mView;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ViewportFrame_h
diff --git a/layout/generic/Visibility.h b/layout/generic/Visibility.h
new file mode 100644
index 0000000000..a5fbbdbde7
--- /dev/null
+++ b/layout/generic/Visibility.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Declares visibility-related types. @Visibility is an enumeration of the
+ * possible visibility states of a frame. @OnNonvisible is an enumeration that
+ * allows callers to request a specific action when a frame transitions from
+ * visible to nonvisible.
+ */
+
+#ifndef mozilla_layout_generic_Visibility_h
+#define mozilla_layout_generic_Visibility_h
+
+namespace mozilla {
+
+// Visibility states for frames.
+enum class Visibility : uint8_t {
+ // Indicates that we're not tracking visibility for this frame.
+ Untracked,
+
+ // Indicates that the frame is probably nonvisible. Visible frames *may* be
+ // ApproximatelyNonVisible because approximate visibility is not updated
+ // synchronously. Some truly nonvisible frames may be marked
+ // ApproximatelyVisible instead if our heuristics lead us to think they may
+ // be visible soon.
+ ApproximatelyNonVisible,
+
+ // Indicates that the frame is either visible now or is likely to be visible
+ // soon according to our heuristics. As with ApproximatelyNonVisible , it's
+ // important to note that approximately visibility is not updated
+ // synchronously, so this information may be out of date.
+ ApproximatelyVisible,
+};
+
+// Requested actions when frames transition to the nonvisible state.
+enum class OnNonvisible : uint8_t {
+ DiscardImages // Discard images associated with the frame.
+};
+
+} // namespace mozilla
+
+#endif // mozilla_layout_generic_Visibility_h
diff --git a/layout/generic/WBRFrame.cpp b/layout/generic/WBRFrame.cpp
new file mode 100644
index 0000000000..3ad733a2a4
--- /dev/null
+++ b/layout/generic/WBRFrame.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for HTML <wbr> elements */
+
+#include "mozilla/PresShell.h"
+#include "nsHTMLParts.h"
+#include "nsIFrame.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+
+class WBRFrame final : public nsIFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(WBRFrame)
+
+ friend nsIFrame* ::NS_NewWBRFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ virtual FrameSearchResult PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) override;
+ virtual FrameSearchResult PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset,
+ PeekOffsetCharacterOptions aOptions =
+ PeekOffsetCharacterOptions()) override;
+ virtual FrameSearchResult PeekOffsetWord(
+ bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
+ int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) override;
+
+ protected:
+ explicit WBRFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID) {}
+};
+
+} // namespace mozilla
+
+nsIFrame* NS_NewWBRFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) WBRFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(WBRFrame)
+
+nsIFrame::FrameSearchResult WBRFrame::PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // WBR frames can not contain text or non-rendered whitespace
+ return CONTINUE;
+}
+
+nsIFrame::FrameSearchResult WBRFrame::PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // WBR frames can not contain characters
+ return CONTINUE;
+}
+
+nsIFrame::FrameSearchResult WBRFrame::PeekOffsetWord(
+ bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
+ int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // WBR frames can never contain text so we'll never find a word inside them
+ return CONTINUE;
+}
diff --git a/layout/generic/WritingModes.h b/layout/generic/WritingModes.h
new file mode 100644
index 0000000000..2f28b73775
--- /dev/null
+++ b/layout/generic/WritingModes.h
@@ -0,0 +1,2244 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WritingModes_h_
+#define WritingModes_h_
+
+#include <ostream>
+
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/EnumeratedRange.h"
+
+#include "nsRect.h"
+#include "nsBidiUtils.h"
+#include "nsStyleStruct.h"
+
+// It is the caller's responsibility to operate on logical-coordinate objects
+// with matched writing modes. Failure to do so will be a runtime bug; the
+// compiler can't catch it, but in debug mode, we'll throw an assertion.
+// NOTE that in non-debug builds, a writing mode mismatch error will NOT be
+// detected, yet the results will be nonsense (and may lead to further layout
+// failures). Therefore, it is important to test (and fuzz-test) writing-mode
+// support using debug builds.
+
+// Methods in logical-coordinate classes that take another logical-coordinate
+// object as a parameter should call CHECK_WRITING_MODE on it to verify that
+// the writing modes match.
+// (In some cases, there are internal (private) methods that don't do this;
+// such methods should only be used by other methods that have already checked
+// the writing modes.)
+// The check ignores the StyleWritingMode::VERTICAL_SIDEWAYS and
+// StyleWritingMode::TEXT_SIDEWAYS bit of writing mode, because
+// this does not affect the interpretation of logical coordinates.
+
+#define CHECK_WRITING_MODE(param) \
+ NS_ASSERTION(param.IgnoreSideways() == GetWritingMode().IgnoreSideways(), \
+ "writing-mode mismatch")
+
+namespace mozilla {
+
+namespace widget {
+struct IMENotification;
+} // namespace widget
+
+// Logical axis, edge, side and corner constants for use in various places.
+enum LogicalAxis : uint8_t {
+ eLogicalAxisBlock = 0x0,
+ eLogicalAxisInline = 0x1
+};
+enum LogicalEdge { eLogicalEdgeStart = 0x0, eLogicalEdgeEnd = 0x1 };
+enum LogicalSide : uint8_t {
+ eLogicalSideBStart = (eLogicalAxisBlock << 1) | eLogicalEdgeStart, // 0x0
+ eLogicalSideBEnd = (eLogicalAxisBlock << 1) | eLogicalEdgeEnd, // 0x1
+ eLogicalSideIStart = (eLogicalAxisInline << 1) | eLogicalEdgeStart, // 0x2
+ eLogicalSideIEnd = (eLogicalAxisInline << 1) | eLogicalEdgeEnd // 0x3
+};
+constexpr auto AllLogicalSides() {
+ return mozilla::MakeInclusiveEnumeratedRange(eLogicalSideBStart,
+ eLogicalSideIEnd);
+}
+
+enum LogicalCorner {
+ eLogicalCornerBStartIStart = 0,
+ eLogicalCornerBStartIEnd = 1,
+ eLogicalCornerBEndIEnd = 2,
+ eLogicalCornerBEndIStart = 3
+};
+
+// Physical axis constants.
+enum PhysicalAxis { eAxisVertical = 0x0, eAxisHorizontal = 0x1 };
+
+// Represents zero or more physical axes.
+enum class PhysicalAxes : uint8_t {
+ None = 0x0,
+ Horizontal = 0x1,
+ Vertical = 0x2,
+ Both = Horizontal | Vertical,
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PhysicalAxes)
+
+inline LogicalAxis GetOrthogonalAxis(LogicalAxis aAxis) {
+ return aAxis == eLogicalAxisBlock ? eLogicalAxisInline : eLogicalAxisBlock;
+}
+
+inline bool IsInline(LogicalSide aSide) { return aSide & 0x2; }
+inline bool IsBlock(LogicalSide aSide) { return !IsInline(aSide); }
+inline bool IsEnd(LogicalSide aSide) { return aSide & 0x1; }
+inline bool IsStart(LogicalSide aSide) { return !IsEnd(aSide); }
+
+inline LogicalAxis GetAxis(LogicalSide aSide) {
+ return IsInline(aSide) ? eLogicalAxisInline : eLogicalAxisBlock;
+}
+
+inline LogicalEdge GetEdge(LogicalSide aSide) {
+ return IsEnd(aSide) ? eLogicalEdgeEnd : eLogicalEdgeStart;
+}
+
+inline LogicalEdge GetOppositeEdge(LogicalEdge aEdge) {
+ // This relies on the only two LogicalEdge enum values being 0 and 1.
+ return LogicalEdge(1 - aEdge);
+}
+
+inline LogicalSide MakeLogicalSide(LogicalAxis aAxis, LogicalEdge aEdge) {
+ return LogicalSide((aAxis << 1) | aEdge);
+}
+
+inline LogicalSide GetOppositeSide(LogicalSide aSide) {
+ return MakeLogicalSide(GetAxis(aSide), GetOppositeEdge(GetEdge(aSide)));
+}
+
+enum LogicalSideBits {
+ eLogicalSideBitsNone = 0,
+ eLogicalSideBitsBStart = 1 << eLogicalSideBStart,
+ eLogicalSideBitsBEnd = 1 << eLogicalSideBEnd,
+ eLogicalSideBitsIEnd = 1 << eLogicalSideIEnd,
+ eLogicalSideBitsIStart = 1 << eLogicalSideIStart,
+ eLogicalSideBitsBBoth = eLogicalSideBitsBStart | eLogicalSideBitsBEnd,
+ eLogicalSideBitsIBoth = eLogicalSideBitsIStart | eLogicalSideBitsIEnd,
+ eLogicalSideBitsAll = eLogicalSideBitsBBoth | eLogicalSideBitsIBoth
+};
+
+enum LineRelativeDir {
+ eLineRelativeDirOver = eLogicalSideBStart,
+ eLineRelativeDirUnder = eLogicalSideBEnd,
+ eLineRelativeDirLeft = eLogicalSideIStart,
+ eLineRelativeDirRight = eLogicalSideIEnd
+};
+
+/**
+ * mozilla::WritingMode is an immutable class representing a
+ * writing mode.
+ *
+ * It efficiently stores the writing mode and can rapidly compute
+ * interesting things about it for use in layout.
+ *
+ * Writing modes are computed from the CSS 'direction',
+ * 'writing-mode', and 'text-orientation' properties.
+ * See CSS3 Writing Modes for more information
+ * http://www.w3.org/TR/css3-writing-modes/
+ */
+class WritingMode {
+ public:
+ /**
+ * Absolute inline flow direction
+ */
+ enum InlineDir {
+ eInlineLTR = 0x00, // text flows horizontally left to right
+ eInlineRTL = 0x02, // text flows horizontally right to left
+ eInlineTTB = 0x01, // text flows vertically top to bottom
+ eInlineBTT = 0x03, // text flows vertically bottom to top
+ };
+
+ /**
+ * Absolute block flow direction
+ */
+ enum BlockDir {
+ eBlockTB = 0x00, // horizontal lines stack top to bottom
+ eBlockRL = 0x01, // vertical lines stack right to left
+ eBlockLR = 0x05, // vertical lines stack left to right
+ };
+
+ /**
+ * Line-relative (bidi-relative) inline flow direction
+ */
+ enum BidiDir {
+ eBidiLTR = 0x00, // inline flow matches bidi LTR text
+ eBidiRTL = 0x10, // inline flow matches bidi RTL text
+ };
+
+ /**
+ * Unknown writing mode (should never actually be stored or used anywhere).
+ */
+ enum { eUnknownWritingMode = 0xff };
+
+ /**
+ * Return the absolute inline flow direction as an InlineDir
+ */
+ InlineDir GetInlineDir() const {
+ return InlineDir(mWritingMode._0 & eInlineMask);
+ }
+
+ /**
+ * Return the absolute block flow direction as a BlockDir
+ */
+ BlockDir GetBlockDir() const {
+ return BlockDir(mWritingMode._0 & eBlockMask);
+ }
+
+ /**
+ * Return the line-relative inline flow direction as a BidiDir
+ */
+ BidiDir GetBidiDir() const {
+ return BidiDir((mWritingMode & StyleWritingMode::RTL)._0);
+ }
+
+ /**
+ * Return true if the inline flow direction is against physical direction
+ * (i.e. right-to-left or bottom-to-top).
+ * This occurs when writing-mode is sideways-lr OR direction is rtl (but not
+ * if both of those are true).
+ */
+ bool IsInlineReversed() const {
+ return !!(mWritingMode & StyleWritingMode::INLINE_REVERSED);
+ }
+
+ /**
+ * Return true if bidi direction is LTR. (Convenience method)
+ */
+ bool IsBidiLTR() const { return eBidiLTR == GetBidiDir(); }
+
+ /**
+ * Return true if bidi direction is RTL. (Convenience method)
+ */
+ bool IsBidiRTL() const { return eBidiRTL == GetBidiDir(); }
+
+ /**
+ * True if it is vertical and vertical-lr, or is horizontal and bidi LTR.
+ */
+ bool IsPhysicalLTR() const {
+ return IsVertical() ? IsVerticalLR() : IsBidiLTR();
+ }
+
+ /**
+ * True if it is vertical and vertical-rl, or is horizontal and bidi RTL.
+ */
+ bool IsPhysicalRTL() const {
+ return IsVertical() ? IsVerticalRL() : IsBidiRTL();
+ }
+
+ /**
+ * True if vertical-mode block direction is LR (convenience method).
+ */
+ bool IsVerticalLR() const { return eBlockLR == GetBlockDir(); }
+
+ /**
+ * True if vertical-mode block direction is RL (convenience method).
+ */
+ bool IsVerticalRL() const { return eBlockRL == GetBlockDir(); }
+
+ /**
+ * True if vertical writing mode, i.e. when
+ * writing-mode: vertical-lr | vertical-rl.
+ */
+ bool IsVertical() const {
+ return !!(mWritingMode & StyleWritingMode::VERTICAL);
+ }
+
+ /**
+ * True if line-over/line-under are inverted from block-start/block-end.
+ * This is true only when writing-mode is vertical-lr.
+ */
+ bool IsLineInverted() const {
+ return !!(mWritingMode & StyleWritingMode::LINE_INVERTED);
+ }
+
+ /**
+ * Block-axis flow-relative to line-relative factor.
+ * May be used as a multiplication factor for block-axis coordinates
+ * to convert between flow- and line-relative coordinate systems (e.g.
+ * positioning an over- or under-line decoration).
+ */
+ int FlowRelativeToLineRelativeFactor() const {
+ return IsLineInverted() ? -1 : 1;
+ }
+
+ /**
+ * True if vertical sideways writing mode, i.e. when
+ * writing-mode: sideways-lr | sideways-rl.
+ */
+ bool IsVerticalSideways() const {
+ return !!(mWritingMode & StyleWritingMode::VERTICAL_SIDEWAYS);
+ }
+
+ /**
+ * True if this is writing-mode: sideways-rl (convenience method).
+ */
+ bool IsSidewaysRL() const { return IsVerticalRL() && IsVerticalSideways(); }
+
+ /**
+ * True if this is writing-mode: sideways-lr (convenience method).
+ */
+ bool IsSidewaysLR() const { return IsVerticalLR() && IsVerticalSideways(); }
+
+ /**
+ * True if either text-orientation or writing-mode will force all text to be
+ * rendered sideways in vertical lines, in which case we should prefer an
+ * alphabetic baseline; otherwise, the default is centered.
+ *
+ * Note that some glyph runs may be rendered sideways even if this is false,
+ * due to text-orientation:mixed resolution, but in that case the dominant
+ * baseline remains centered.
+ */
+ bool IsSideways() const {
+ return !!(mWritingMode & (StyleWritingMode::VERTICAL_SIDEWAYS |
+ StyleWritingMode::TEXT_SIDEWAYS));
+ }
+
+#ifdef DEBUG
+ // Used by CHECK_WRITING_MODE to compare modes without regard for the
+ // StyleWritingMode::VERTICAL_SIDEWAYS or StyleWritingMode::TEXT_SIDEWAYS
+ // flags.
+ WritingMode IgnoreSideways() const {
+ return WritingMode(mWritingMode._0 & ~(StyleWritingMode::VERTICAL_SIDEWAYS |
+ StyleWritingMode::TEXT_SIDEWAYS)
+ ._0);
+ }
+#endif
+
+ /**
+ * Return true if boxes with this writing mode should use central baselines.
+ */
+ bool IsCentralBaseline() const { return IsVertical() && !IsSideways(); }
+
+ /**
+ * Return true if boxes with this writing mode should use alphabetical
+ * baselines.
+ */
+ bool IsAlphabeticalBaseline() const { return !IsCentralBaseline(); }
+
+ static mozilla::PhysicalAxis PhysicalAxisForLogicalAxis(
+ uint8_t aWritingModeValue, LogicalAxis aAxis) {
+ // This relies on bit 0 of a writing-value mode indicating vertical
+ // orientation and bit 0 of a LogicalAxis value indicating the inline axis,
+ // so that it can correctly form mozilla::PhysicalAxis values using bit
+ // manipulation.
+ static_assert(uint8_t(StyleWritingModeProperty::HorizontalTb) == 0 &&
+ uint8_t(StyleWritingModeProperty::VerticalRl) == 1 &&
+ uint8_t(StyleWritingModeProperty::VerticalLr) == 3 &&
+ eLogicalAxisBlock == 0 && eLogicalAxisInline == 1 &&
+ eAxisVertical == 0 && eAxisHorizontal == 1,
+ "unexpected writing-mode, logical axis or physical axis "
+ "constant values");
+ return mozilla::PhysicalAxis((aWritingModeValue ^ aAxis) & 0x1);
+ }
+
+ mozilla::PhysicalAxis PhysicalAxis(LogicalAxis aAxis) const {
+ // This will set wm to either StyleWritingModel::HorizontalTB or
+ // StyleWritingModeProperty::VerticalRL, and not the other two (real
+ // and hypothetical) values. But this is fine; we only need to
+ // distinguish between vertical and horizontal in
+ // PhysicalAxisForLogicalAxis.
+ const auto wm = (mWritingMode & StyleWritingMode::VERTICAL)._0;
+ return PhysicalAxisForLogicalAxis(wm, aAxis);
+ }
+
+ static mozilla::Side PhysicalSideForBlockAxis(uint8_t aWritingModeValue,
+ LogicalEdge aEdge) {
+ // indexes are StyleWritingModeProperty values, which are the same as these
+ // two-bit values:
+ // bit 0 = the StyleWritingMode::VERTICAL value
+ // bit 1 = the StyleWritingMode::VERTICAL_LR value
+ static const mozilla::Side kLogicalBlockSides[][2] = {
+ {eSideTop, eSideBottom}, // horizontal-tb
+ {eSideRight, eSideLeft}, // vertical-rl
+ {eSideBottom, eSideTop}, // (horizontal-bt)
+ {eSideLeft, eSideRight}, // vertical-lr
+ };
+
+ // Ignore the SidewaysMask bit of the writing-mode value, as this has no
+ // effect on the side mappings.
+ aWritingModeValue &= ~kWritingModeSidewaysMask;
+
+ // What's left of the writing-mode should be in the range 0-3:
+ NS_ASSERTION(aWritingModeValue < 4, "invalid aWritingModeValue value");
+
+ return kLogicalBlockSides[aWritingModeValue][aEdge];
+ }
+
+ mozilla::Side PhysicalSideForInlineAxis(LogicalEdge aEdge) const {
+ // indexes are four-bit values:
+ // bit 0 = the StyleWritingMode::VERTICAL value
+ // bit 1 = the StyleWritingMode::INLINE_REVERSED value
+ // bit 2 = the StyleWritingMode::VERTICAL_LR value
+ // bit 3 = the StyleWritingMode::LINE_INVERTED value
+ // Not all of these combinations can actually be specified via CSS: there
+ // is no horizontal-bt writing-mode, and no text-orientation value that
+ // produces "inverted" text. (The former 'sideways-left' value, no longer
+ // in the spec, would have produced this in vertical-rl mode.)
+ static const mozilla::Side kLogicalInlineSides[][2] = {
+ {eSideLeft, eSideRight}, // horizontal-tb ltr
+ {eSideTop, eSideBottom}, // vertical-rl ltr
+ {eSideRight, eSideLeft}, // horizontal-tb rtl
+ {eSideBottom, eSideTop}, // vertical-rl rtl
+ {eSideRight, eSideLeft}, // (horizontal-bt) (inverted) ltr
+ {eSideTop, eSideBottom}, // sideways-lr rtl
+ {eSideLeft, eSideRight}, // (horizontal-bt) (inverted) rtl
+ {eSideBottom, eSideTop}, // sideways-lr ltr
+ {eSideLeft, eSideRight}, // horizontal-tb (inverted) rtl
+ {eSideTop, eSideBottom}, // vertical-rl (inverted) rtl
+ {eSideRight, eSideLeft}, // horizontal-tb (inverted) ltr
+ {eSideBottom, eSideTop}, // vertical-rl (inverted) ltr
+ {eSideLeft, eSideRight}, // (horizontal-bt) ltr
+ {eSideTop, eSideBottom}, // vertical-lr ltr
+ {eSideRight, eSideLeft}, // (horizontal-bt) rtl
+ {eSideBottom, eSideTop}, // vertical-lr rtl
+ };
+
+ // Inline axis sides depend on all three of writing-mode, text-orientation
+ // and direction, which are encoded in the StyleWritingMode::VERTICAL,
+ // StyleWritingMode::INLINE_REVERSED, StyleWritingMode::VERTICAL_LR and
+ // StyleWritingMode::LINE_INVERTED bits. Use these four bits to index into
+ // kLogicalInlineSides.
+ MOZ_ASSERT(StyleWritingMode::VERTICAL._0 == 0x01 &&
+ StyleWritingMode::INLINE_REVERSED._0 == 0x02 &&
+ StyleWritingMode::VERTICAL_LR._0 == 0x04 &&
+ StyleWritingMode::LINE_INVERTED._0 == 0x08,
+ "unexpected mask values");
+ int index = mWritingMode._0 & 0x0F;
+ return kLogicalInlineSides[index][aEdge];
+ }
+
+ /**
+ * Returns the physical side corresponding to the specified logical side,
+ * given the current writing mode.
+ */
+ mozilla::Side PhysicalSide(LogicalSide aSide) const {
+ if (IsBlock(aSide)) {
+ MOZ_ASSERT(StyleWritingMode::VERTICAL._0 == 0x01 &&
+ StyleWritingMode::VERTICAL_LR._0 == 0x04,
+ "unexpected mask values");
+ const uint8_t wm =
+ ((mWritingMode & StyleWritingMode::VERTICAL_LR)._0 >> 1) |
+ (mWritingMode & StyleWritingMode::VERTICAL)._0;
+ return PhysicalSideForBlockAxis(wm, GetEdge(aSide));
+ }
+
+ return PhysicalSideForInlineAxis(GetEdge(aSide));
+ }
+
+ /**
+ * Returns the logical side corresponding to the specified physical side,
+ * given the current writing mode.
+ * (This is the inverse of the PhysicalSide() method above.)
+ */
+ LogicalSide LogicalSideForPhysicalSide(mozilla::Side aSide) const {
+ // clang-format off
+ // indexes are four-bit values:
+ // bit 0 = the StyleWritingMode::VERTICAL value
+ // bit 1 = the StyleWritingMode::INLINE_REVERSED value
+ // bit 2 = the StyleWritingMode::VERTICAL_LR value
+ // bit 3 = the StyleWritingMode::LINE_INVERTED value
+ static const LogicalSide kPhysicalToLogicalSides[][4] = {
+ // top right
+ // bottom left
+ { eLogicalSideBStart, eLogicalSideIEnd,
+ eLogicalSideBEnd, eLogicalSideIStart }, // horizontal-tb ltr
+ { eLogicalSideIStart, eLogicalSideBStart,
+ eLogicalSideIEnd, eLogicalSideBEnd }, // vertical-rl ltr
+ { eLogicalSideBStart, eLogicalSideIStart,
+ eLogicalSideBEnd, eLogicalSideIEnd }, // horizontal-tb rtl
+ { eLogicalSideIEnd, eLogicalSideBStart,
+ eLogicalSideIStart, eLogicalSideBEnd }, // vertical-rl rtl
+ { eLogicalSideBEnd, eLogicalSideIStart,
+ eLogicalSideBStart, eLogicalSideIEnd }, // (horizontal-bt) (inv) ltr
+ { eLogicalSideIStart, eLogicalSideBEnd,
+ eLogicalSideIEnd, eLogicalSideBStart }, // vertical-lr sw-left rtl
+ { eLogicalSideBEnd, eLogicalSideIEnd,
+ eLogicalSideBStart, eLogicalSideIStart }, // (horizontal-bt) (inv) rtl
+ { eLogicalSideIEnd, eLogicalSideBEnd,
+ eLogicalSideIStart, eLogicalSideBStart }, // vertical-lr sw-left ltr
+ { eLogicalSideBStart, eLogicalSideIEnd,
+ eLogicalSideBEnd, eLogicalSideIStart }, // horizontal-tb (inv) rtl
+ { eLogicalSideIStart, eLogicalSideBStart,
+ eLogicalSideIEnd, eLogicalSideBEnd }, // vertical-rl sw-left rtl
+ { eLogicalSideBStart, eLogicalSideIStart,
+ eLogicalSideBEnd, eLogicalSideIEnd }, // horizontal-tb (inv) ltr
+ { eLogicalSideIEnd, eLogicalSideBStart,
+ eLogicalSideIStart, eLogicalSideBEnd }, // vertical-rl sw-left ltr
+ { eLogicalSideBEnd, eLogicalSideIEnd,
+ eLogicalSideBStart, eLogicalSideIStart }, // (horizontal-bt) ltr
+ { eLogicalSideIStart, eLogicalSideBEnd,
+ eLogicalSideIEnd, eLogicalSideBStart }, // vertical-lr ltr
+ { eLogicalSideBEnd, eLogicalSideIStart,
+ eLogicalSideBStart, eLogicalSideIEnd }, // (horizontal-bt) rtl
+ { eLogicalSideIEnd, eLogicalSideBEnd,
+ eLogicalSideIStart, eLogicalSideBStart }, // vertical-lr rtl
+ };
+ // clang-format on
+
+ MOZ_ASSERT(StyleWritingMode::VERTICAL._0 == 0x01 &&
+ StyleWritingMode::INLINE_REVERSED._0 == 0x02 &&
+ StyleWritingMode::VERTICAL_LR._0 == 0x04 &&
+ StyleWritingMode::LINE_INVERTED._0 == 0x08,
+ "unexpected mask values");
+ int index = mWritingMode._0 & 0x0F;
+ return kPhysicalToLogicalSides[index][aSide];
+ }
+
+ /**
+ * Returns the logical side corresponding to the specified
+ * line-relative direction, given the current writing mode.
+ */
+ LogicalSide LogicalSideForLineRelativeDir(LineRelativeDir aDir) const {
+ auto side = static_cast<LogicalSide>(aDir);
+ if (IsInline(side)) {
+ return IsBidiLTR() ? side : GetOppositeSide(side);
+ }
+ return !IsLineInverted() ? side : GetOppositeSide(side);
+ }
+
+ /**
+ * Default constructor gives us a horizontal, LTR writing mode.
+ * XXX We will probably eliminate this and require explicit initialization
+ * in all cases once transition is complete.
+ */
+ WritingMode() : mWritingMode{0} {}
+
+ /**
+ * Construct writing mode based on a ComputedStyle.
+ */
+ explicit WritingMode(const ComputedStyle* aComputedStyle) {
+ NS_ASSERTION(aComputedStyle, "we need an ComputedStyle here");
+ mWritingMode = aComputedStyle->WritingMode();
+ }
+
+ /**
+ * This function performs fixup for elements with 'unicode-bidi: plaintext',
+ * where inline directionality is derived from the Unicode bidi categories
+ * of the element's content, and not the CSS 'direction' property.
+ *
+ * The WritingMode constructor will have already incorporated the 'direction'
+ * property into our flag bits, so such elements need to use this method
+ * (after resolving the bidi level of their content) to update the direction
+ * bits as needed.
+ *
+ * If it turns out that our bidi direction already matches what plaintext
+ * resolution determined, there's nothing to do here. If it didn't (i.e. if
+ * the rtl-ness doesn't match), then we correct the direction by flipping the
+ * same bits that get flipped in the constructor's CSS 'direction'-based
+ * chunk.
+ *
+ * XXX change uint8_t to UBiDiLevel after bug 924851
+ */
+ void SetDirectionFromBidiLevel(mozilla::intl::BidiEmbeddingLevel level) {
+ if (level.IsRTL() == IsBidiLTR()) {
+ mWritingMode ^= StyleWritingMode::RTL | StyleWritingMode::INLINE_REVERSED;
+ }
+ }
+
+ /**
+ * Compare two WritingModes for equality.
+ */
+ bool operator==(const WritingMode& aOther) const {
+ return mWritingMode == aOther.mWritingMode;
+ }
+
+ bool operator!=(const WritingMode& aOther) const {
+ return mWritingMode != aOther.mWritingMode;
+ }
+
+ /**
+ * Check whether two modes are orthogonal to each other.
+ */
+ bool IsOrthogonalTo(const WritingMode& aOther) const {
+ return IsVertical() != aOther.IsVertical();
+ }
+
+ /**
+ * Returns true if this WritingMode's aLogicalAxis has the same physical
+ * start side as the parallel axis of WritingMode |aOther|.
+ *
+ * @param aLogicalAxis The axis to compare from this WritingMode.
+ * @param aOther The other WritingMode (from which we'll choose the axis
+ * that's parallel to this WritingMode's aLogicalAxis, for
+ * comparison).
+ */
+ bool ParallelAxisStartsOnSameSide(LogicalAxis aLogicalAxis,
+ const WritingMode& aOther) const {
+ mozilla::Side myStartSide =
+ this->PhysicalSide(MakeLogicalSide(aLogicalAxis, eLogicalEdgeStart));
+
+ // Figure out which of aOther's axes is parallel to |this| WritingMode's
+ // aLogicalAxis, and get its physical start side as well.
+ LogicalAxis otherWMAxis = aOther.IsOrthogonalTo(*this)
+ ? GetOrthogonalAxis(aLogicalAxis)
+ : aLogicalAxis;
+ mozilla::Side otherWMStartSide =
+ aOther.PhysicalSide(MakeLogicalSide(otherWMAxis, eLogicalEdgeStart));
+
+ NS_ASSERTION(myStartSide % 2 == otherWMStartSide % 2,
+ "Should end up with sides in the same physical axis");
+ return myStartSide == otherWMStartSide;
+ }
+
+ uint8_t GetBits() const { return mWritingMode._0; }
+
+ private:
+ friend class LogicalPoint;
+ friend class LogicalSize;
+ friend struct LogicalSides;
+ friend class LogicalMargin;
+ friend class LogicalRect;
+
+ friend struct IPC::ParamTraits<WritingMode>;
+ // IMENotification cannot store this class directly since this has some
+ // constructors. Therefore, it stores mWritingMode and recreate the
+ // instance from it.
+ friend struct widget::IMENotification;
+
+ /**
+ * Return a WritingMode representing an unknown value.
+ */
+ static inline WritingMode Unknown() {
+ return WritingMode(eUnknownWritingMode);
+ }
+
+ /**
+ * Constructing a WritingMode with an arbitrary value is a private operation
+ * currently only used by the Unknown() and IgnoreSideways() methods.
+ */
+ explicit WritingMode(uint8_t aValue) : mWritingMode{aValue} {}
+
+ StyleWritingMode mWritingMode;
+
+ enum Masks {
+ // Masks for output enums
+ eInlineMask = 0x03, // VERTICAL | INLINE_REVERSED
+ eBlockMask = 0x05, // VERTICAL | VERTICAL_LR
+ };
+};
+
+inline std::ostream& operator<<(std::ostream& aStream, const WritingMode& aWM) {
+ return aStream << (aWM.IsVertical()
+ ? aWM.IsVerticalLR() ? aWM.IsBidiLTR()
+ ? aWM.IsSideways()
+ ? "sw-lr-ltr"
+ : "v-lr-ltr"
+ : aWM.IsSideways() ? "sw-lr-rtl"
+ : "v-lr-rtl"
+ : aWM.IsBidiLTR()
+ ? aWM.IsSideways() ? "sw-rl-ltr" : "v-rl-ltr"
+ : aWM.IsSideways() ? "sw-rl-rtl"
+ : "v-rl-rtl"
+ : aWM.IsBidiLTR() ? "h-ltr"
+ : "h-rtl");
+}
+
+/**
+ * Logical-coordinate classes:
+ *
+ * There are three sets of coordinate space:
+ * - physical (top, left, bottom, right)
+ * relative to graphics coord system
+ * - flow-relative (block-start, inline-start, block-end, inline-end)
+ * relative to block/inline flow directions
+ * - line-relative (line-over, line-left, line-under, line-right)
+ * relative to glyph orientation / inline bidi directions
+ * See CSS3 Writing Modes for more information
+ * http://www.w3.org/TR/css3-writing-modes/#abstract-box
+ *
+ * For shorthand, B represents the block-axis
+ * I represents the inline-axis
+ *
+ * The flow-relative geometric classes store coords in flow-relative space.
+ * They use a private ns{Point,Size,Rect,Margin} member to store the actual
+ * coordinate values, but reinterpret them as logical instead of physical.
+ * This allows us to easily perform calculations in logical space (provided
+ * writing modes of the operands match), by simply mapping to nsPoint (etc)
+ * methods.
+ *
+ * Physical-coordinate accessors/setters are responsible to translate these
+ * internal logical values as necessary.
+ *
+ * In DEBUG builds, the logical types store their WritingMode and check
+ * that the same WritingMode is passed whenever callers ask them to do a
+ * writing-mode-dependent operation. Non-DEBUG builds do NOT check this,
+ * to avoid the overhead of storing WritingMode fields.
+ *
+ * Open question: do we need a different set optimized for line-relative
+ * math, for use in nsLineLayout and the like? Or is multiplying values
+ * by FlowRelativeToLineRelativeFactor() enough?
+ */
+
+/**
+ * Flow-relative point
+ */
+class LogicalPoint {
+ public:
+ explicit LogicalPoint(WritingMode aWritingMode)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mPoint(0, 0) {
+ }
+
+ // Construct from a writing mode and individual coordinates (which MUST be
+ // values in that writing mode, NOT physical coordinates!)
+ LogicalPoint(WritingMode aWritingMode, nscoord aI, nscoord aB)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mPoint(aI, aB) {
+ }
+
+ // Construct from a writing mode and a physical point, within a given
+ // containing rectangle's size (defining the conversion between LTR
+ // and RTL coordinates, and between TTB and BTT coordinates).
+ LogicalPoint(WritingMode aWritingMode, const nsPoint& aPoint,
+ const nsSize& aContainerSize)
+#ifdef DEBUG
+ : mWritingMode(aWritingMode)
+#endif
+ {
+ if (aWritingMode.IsVertical()) {
+ I() = aWritingMode.IsInlineReversed() ? aContainerSize.height - aPoint.y
+ : aPoint.y;
+ B() = aWritingMode.IsVerticalLR() ? aPoint.x
+ : aContainerSize.width - aPoint.x;
+ } else {
+ I() = aWritingMode.IsInlineReversed() ? aContainerSize.width - aPoint.x
+ : aPoint.x;
+ B() = aPoint.y;
+ }
+ }
+
+ /**
+ * Read-only (const) access to the logical coordinates.
+ */
+ nscoord I(WritingMode aWritingMode) const // inline-axis
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mPoint.x;
+ }
+ nscoord B(WritingMode aWritingMode) const // block-axis
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mPoint.y;
+ }
+ nscoord Pos(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? I(aWM) : B(aWM);
+ }
+ nscoord LineRelative(WritingMode aWritingMode,
+ const nsSize& aContainerSize) const // line-axis
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsBidiLTR()) {
+ return I();
+ }
+ return (aWritingMode.IsVertical() ? aContainerSize.height
+ : aContainerSize.width) -
+ I();
+ }
+
+ /**
+ * These non-const accessors return a reference (lvalue) that can be
+ * assigned to by callers.
+ */
+ nscoord& I(WritingMode aWritingMode) // inline-axis
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mPoint.x;
+ }
+ nscoord& B(WritingMode aWritingMode) // block-axis
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mPoint.y;
+ }
+ nscoord& Pos(LogicalAxis aAxis, WritingMode aWM) {
+ return aAxis == eLogicalAxisInline ? I(aWM) : B(aWM);
+ }
+
+ /**
+ * Return a physical point corresponding to our logical coordinates,
+ * converted according to our writing mode.
+ */
+ nsPoint GetPhysicalPoint(WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsVertical()) {
+ return nsPoint(
+ aWritingMode.IsVerticalLR() ? B() : aContainerSize.width - B(),
+ aWritingMode.IsInlineReversed() ? aContainerSize.height - I() : I());
+ } else {
+ return nsPoint(
+ aWritingMode.IsInlineReversed() ? aContainerSize.width - I() : I(),
+ B());
+ }
+ }
+
+ /**
+ * Return the equivalent point in a different writing mode.
+ */
+ LogicalPoint ConvertTo(WritingMode aToMode, WritingMode aFromMode,
+ const nsSize& aContainerSize) const {
+ CHECK_WRITING_MODE(aFromMode);
+ return aToMode == aFromMode
+ ? *this
+ : LogicalPoint(aToMode,
+ GetPhysicalPoint(aFromMode, aContainerSize),
+ aContainerSize);
+ }
+
+ bool operator==(const LogicalPoint& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return mPoint == aOther.mPoint;
+ }
+
+ bool operator!=(const LogicalPoint& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return mPoint != aOther.mPoint;
+ }
+
+ LogicalPoint operator+(const LogicalPoint& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ // In non-debug builds, LogicalPoint does not store the WritingMode,
+ // so the first parameter here (which will always be eUnknownWritingMode)
+ // is ignored.
+ return LogicalPoint(GetWritingMode(), mPoint.x + aOther.mPoint.x,
+ mPoint.y + aOther.mPoint.y);
+ }
+
+ LogicalPoint& operator+=(const LogicalPoint& aOther) {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ I() += aOther.I();
+ B() += aOther.B();
+ return *this;
+ }
+
+ LogicalPoint operator-(const LogicalPoint& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ // In non-debug builds, LogicalPoint does not store the WritingMode,
+ // so the first parameter here (which will always be eUnknownWritingMode)
+ // is ignored.
+ return LogicalPoint(GetWritingMode(), mPoint.x - aOther.mPoint.x,
+ mPoint.y - aOther.mPoint.y);
+ }
+
+ LogicalPoint& operator-=(const LogicalPoint& aOther) {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ I() -= aOther.I();
+ B() -= aOther.B();
+ return *this;
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const LogicalPoint& aPoint) {
+ return aStream << aPoint.mPoint;
+ }
+
+ private:
+ friend class LogicalRect;
+
+ /**
+ * NOTE that in non-DEBUG builds, GetWritingMode() always returns
+ * eUnknownWritingMode, as the current mode is not stored in the logical-
+ * geometry classes. Therefore, this method is private; it is used ONLY
+ * by the DEBUG-mode checking macros in this class and its friends;
+ * other code is not allowed to ask a logical point for its writing mode,
+ * as this info will simply not be available in non-DEBUG builds.
+ *
+ * Also, in non-DEBUG builds, CHECK_WRITING_MODE does nothing, and the
+ * WritingMode parameter to logical methods will generally be optimized
+ * away altogether.
+ */
+#ifdef DEBUG
+ WritingMode GetWritingMode() const { return mWritingMode; }
+#else
+ WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
+#endif
+
+ // We don't allow construction of a LogicalPoint with no writing mode.
+ LogicalPoint() = delete;
+
+ // Accessors that don't take or check a WritingMode value.
+ // These are for internal use only; they are called by methods that have
+ // themselves already checked the WritingMode passed by the caller.
+ nscoord I() const // inline-axis
+ {
+ return mPoint.x;
+ }
+ nscoord B() const // block-axis
+ {
+ return mPoint.y;
+ }
+
+ nscoord& I() // inline-axis
+ {
+ return mPoint.x;
+ }
+ nscoord& B() // block-axis
+ {
+ return mPoint.y;
+ }
+
+#ifdef DEBUG
+ WritingMode mWritingMode;
+#endif
+
+ // We use an nsPoint to hold the coordinates, but reinterpret its .x and .y
+ // fields as the inline and block directions. Hence, this is not exposed
+ // directly, but only through accessors that will map them according to the
+ // writing mode.
+ nsPoint mPoint;
+};
+
+/**
+ * Flow-relative size
+ */
+class LogicalSize {
+ public:
+ explicit LogicalSize(WritingMode aWritingMode)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mSize(0, 0) {
+ }
+
+ LogicalSize(WritingMode aWritingMode, nscoord aISize, nscoord aBSize)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mSize(aISize, aBSize) {
+ }
+
+ LogicalSize(WritingMode aWritingMode, const nsSize& aPhysicalSize)
+#ifdef DEBUG
+ : mWritingMode(aWritingMode)
+#endif
+ {
+ if (aWritingMode.IsVertical()) {
+ ISize() = aPhysicalSize.height;
+ BSize() = aPhysicalSize.width;
+ } else {
+ ISize() = aPhysicalSize.width;
+ BSize() = aPhysicalSize.height;
+ }
+ }
+
+ void SizeTo(WritingMode aWritingMode, nscoord aISize, nscoord aBSize) {
+ CHECK_WRITING_MODE(aWritingMode);
+ mSize.SizeTo(aISize, aBSize);
+ }
+
+ /**
+ * Dimensions in logical and physical terms
+ */
+ nscoord ISize(WritingMode aWritingMode) const // inline-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mSize.width;
+ }
+ nscoord BSize(WritingMode aWritingMode) const // block-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mSize.height;
+ }
+ nscoord Size(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
+ }
+
+ nscoord Width(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical() ? BSize() : ISize();
+ }
+ nscoord Height(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical() ? ISize() : BSize();
+ }
+
+ /**
+ * Writable references to the logical dimensions
+ */
+ nscoord& ISize(WritingMode aWritingMode) // inline-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mSize.width;
+ }
+ nscoord& BSize(WritingMode aWritingMode) // block-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mSize.height;
+ }
+ nscoord& Size(LogicalAxis aAxis, WritingMode aWM) {
+ return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
+ }
+
+ /**
+ * Return an nsSize containing our physical dimensions
+ */
+ nsSize GetPhysicalSize(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical() ? nsSize(BSize(), ISize())
+ : nsSize(ISize(), BSize());
+ }
+
+ /**
+ * Return a LogicalSize representing this size in a different writing mode
+ */
+ LogicalSize ConvertTo(WritingMode aToMode, WritingMode aFromMode) const {
+#ifdef DEBUG
+ // In DEBUG builds make sure to return a LogicalSize with the
+ // expected writing mode
+ CHECK_WRITING_MODE(aFromMode);
+ return aToMode == aFromMode
+ ? *this
+ : LogicalSize(aToMode, GetPhysicalSize(aFromMode));
+#else
+ // optimization for non-DEBUG builds where LogicalSize doesn't store
+ // the writing mode
+ return (aToMode == aFromMode || !aToMode.IsOrthogonalTo(aFromMode))
+ ? *this
+ : LogicalSize(aToMode, BSize(), ISize());
+#endif
+ }
+
+ /**
+ * Test if a size is (0, 0).
+ */
+ bool IsAllZero() const { return ISize() == 0 && BSize() == 0; }
+
+ /**
+ * Various binary operators on LogicalSize. These are valid ONLY for operands
+ * that share the same writing mode.
+ */
+ bool operator==(const LogicalSize& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return mSize == aOther.mSize;
+ }
+
+ bool operator!=(const LogicalSize& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return mSize != aOther.mSize;
+ }
+
+ LogicalSize operator+(const LogicalSize& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return LogicalSize(GetWritingMode(), ISize() + aOther.ISize(),
+ BSize() + aOther.BSize());
+ }
+ LogicalSize& operator+=(const LogicalSize& aOther) {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ ISize() += aOther.ISize();
+ BSize() += aOther.BSize();
+ return *this;
+ }
+
+ LogicalSize operator-(const LogicalSize& aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return LogicalSize(GetWritingMode(), ISize() - aOther.ISize(),
+ BSize() - aOther.BSize());
+ }
+ LogicalSize& operator-=(const LogicalSize& aOther) {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ ISize() -= aOther.ISize();
+ BSize() -= aOther.BSize();
+ return *this;
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const LogicalSize& aSize) {
+ return aStream << aSize.mSize;
+ }
+
+ private:
+ friend class LogicalRect;
+
+ LogicalSize() = delete;
+
+#ifdef DEBUG
+ WritingMode GetWritingMode() const { return mWritingMode; }
+#else
+ WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
+#endif
+
+ nscoord ISize() const // inline-size
+ {
+ return mSize.width;
+ }
+ nscoord BSize() const // block-size
+ {
+ return mSize.height;
+ }
+
+ nscoord& ISize() // inline-size
+ {
+ return mSize.width;
+ }
+ nscoord& BSize() // block-size
+ {
+ return mSize.height;
+ }
+
+#ifdef DEBUG
+ WritingMode mWritingMode;
+#endif
+ nsSize mSize;
+};
+
+/**
+ * LogicalSides represents a set of logical sides.
+ */
+struct LogicalSides final {
+ explicit LogicalSides(WritingMode aWritingMode)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mBits(0) {
+ }
+ LogicalSides(WritingMode aWritingMode, LogicalSideBits aSideBits)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mBits(aSideBits) {
+ MOZ_ASSERT((aSideBits & ~eLogicalSideBitsAll) == 0, "illegal side bits");
+ }
+ bool IsEmpty() const { return mBits == 0; }
+ bool BStart() const { return mBits & eLogicalSideBitsBStart; }
+ bool BEnd() const { return mBits & eLogicalSideBitsBEnd; }
+ bool IStart() const { return mBits & eLogicalSideBitsIStart; }
+ bool IEnd() const { return mBits & eLogicalSideBitsIEnd; }
+ bool Contains(LogicalSideBits aSideBits) const {
+ MOZ_ASSERT((aSideBits & ~eLogicalSideBitsAll) == 0, "illegal side bits");
+ return (mBits & aSideBits) == aSideBits;
+ }
+ LogicalSides operator|(LogicalSides aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return *this | LogicalSideBits(aOther.mBits);
+ }
+ LogicalSides operator|(LogicalSideBits aSideBits) const {
+ return LogicalSides(GetWritingMode(), LogicalSideBits(mBits | aSideBits));
+ }
+ LogicalSides& operator|=(LogicalSides aOther) {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return *this |= LogicalSideBits(aOther.mBits);
+ }
+ LogicalSides& operator|=(LogicalSideBits aSideBits) {
+ mBits |= aSideBits;
+ return *this;
+ }
+ bool operator==(LogicalSides aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return mBits == aOther.mBits;
+ }
+ bool operator!=(LogicalSides aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ return !(*this == aOther);
+ }
+
+#ifdef DEBUG
+ WritingMode GetWritingMode() const { return mWritingMode; }
+#else
+ WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
+#endif
+
+ private:
+#ifdef DEBUG
+ WritingMode mWritingMode;
+#endif
+ uint8_t mBits;
+};
+
+/**
+ * Flow-relative margin
+ */
+class LogicalMargin {
+ public:
+ explicit LogicalMargin(WritingMode aWritingMode)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mMargin(0, 0, 0, 0) {
+ }
+
+ LogicalMargin(WritingMode aWritingMode, nscoord aBStart, nscoord aIEnd,
+ nscoord aBEnd, nscoord aIStart)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mMargin(aBStart, aIEnd, aBEnd, aIStart) {
+ }
+
+ LogicalMargin(WritingMode aWritingMode, const nsMargin& aPhysicalMargin)
+#ifdef DEBUG
+ : mWritingMode(aWritingMode)
+#endif
+ {
+ if (aWritingMode.IsVertical()) {
+ if (aWritingMode.IsVerticalLR()) {
+ mMargin.top = aPhysicalMargin.left;
+ mMargin.bottom = aPhysicalMargin.right;
+ } else {
+ mMargin.top = aPhysicalMargin.right;
+ mMargin.bottom = aPhysicalMargin.left;
+ }
+ if (aWritingMode.IsInlineReversed()) {
+ mMargin.left = aPhysicalMargin.bottom;
+ mMargin.right = aPhysicalMargin.top;
+ } else {
+ mMargin.left = aPhysicalMargin.top;
+ mMargin.right = aPhysicalMargin.bottom;
+ }
+ } else {
+ mMargin.top = aPhysicalMargin.top;
+ mMargin.bottom = aPhysicalMargin.bottom;
+ if (aWritingMode.IsInlineReversed()) {
+ mMargin.left = aPhysicalMargin.right;
+ mMargin.right = aPhysicalMargin.left;
+ } else {
+ mMargin.left = aPhysicalMargin.left;
+ mMargin.right = aPhysicalMargin.right;
+ }
+ }
+ }
+
+ nscoord IStart(WritingMode aWritingMode) const // inline-start margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.left;
+ }
+ nscoord IEnd(WritingMode aWritingMode) const // inline-end margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.right;
+ }
+ nscoord BStart(WritingMode aWritingMode) const // block-start margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.top;
+ }
+ nscoord BEnd(WritingMode aWritingMode) const // block-end margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.bottom;
+ }
+ nscoord Start(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
+ }
+ nscoord End(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM);
+ }
+
+ nscoord& IStart(WritingMode aWritingMode) // inline-start margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.left;
+ }
+ nscoord& IEnd(WritingMode aWritingMode) // inline-end margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.right;
+ }
+ nscoord& BStart(WritingMode aWritingMode) // block-start margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.top;
+ }
+ nscoord& BEnd(WritingMode aWritingMode) // block-end margin
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.bottom;
+ }
+ nscoord& Start(LogicalAxis aAxis, WritingMode aWM) {
+ return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
+ }
+ nscoord& End(LogicalAxis aAxis, WritingMode aWM) {
+ return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM);
+ }
+
+ nscoord IStartEnd(WritingMode aWritingMode) const // inline margins
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.LeftRight();
+ }
+ nscoord BStartEnd(WritingMode aWritingMode) const // block margins
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mMargin.TopBottom();
+ }
+ nscoord StartEnd(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? IStartEnd(aWM) : BStartEnd(aWM);
+ }
+
+ nscoord Side(LogicalSide aSide, WritingMode aWM) const {
+ switch (aSide) {
+ case eLogicalSideBStart:
+ return BStart(aWM);
+ case eLogicalSideBEnd:
+ return BEnd(aWM);
+ case eLogicalSideIStart:
+ return IStart(aWM);
+ case eLogicalSideIEnd:
+ return IEnd(aWM);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
+ return BStart(aWM);
+ }
+ nscoord& Side(LogicalSide aSide, WritingMode aWM) {
+ switch (aSide) {
+ case eLogicalSideBStart:
+ return BStart(aWM);
+ case eLogicalSideBEnd:
+ return BEnd(aWM);
+ case eLogicalSideIStart:
+ return IStart(aWM);
+ case eLogicalSideIEnd:
+ return IEnd(aWM);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
+ return BStart(aWM);
+ }
+
+ /*
+ * Return margin values for line-relative sides, as defined in
+ * http://www.w3.org/TR/css-writing-modes-3/#line-directions:
+ *
+ * line-left
+ * Nominally the side from which LTR text would start.
+ * line-right
+ * Nominally the side from which RTL text would start. (Opposite of
+ * line-left.)
+ */
+ nscoord LineLeft(WritingMode aWritingMode) const {
+ // We don't need to CHECK_WRITING_MODE here because the IStart or IEnd
+ // accessor that we call will do it.
+ return aWritingMode.IsBidiLTR() ? IStart(aWritingMode) : IEnd(aWritingMode);
+ }
+ nscoord LineRight(WritingMode aWritingMode) const {
+ return aWritingMode.IsBidiLTR() ? IEnd(aWritingMode) : IStart(aWritingMode);
+ }
+
+ /**
+ * Return a LogicalSize representing the total size of the inline-
+ * and block-dimension margins.
+ */
+ LogicalSize Size(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return LogicalSize(aWritingMode, IStartEnd(), BStartEnd());
+ }
+
+ /**
+ * Return a LogicalPoint representing an offset to the start-sides, i.e.
+ * inline-start and block-start.
+ */
+ LogicalPoint StartOffset(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return LogicalPoint(aWritingMode, IStart(), BStart());
+ }
+
+ /**
+ * Accessors for physical margins, using our writing mode to convert from
+ * logical values.
+ */
+ nscoord Top(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical()
+ ? (aWritingMode.IsInlineReversed() ? IEnd() : IStart())
+ : BStart();
+ }
+
+ nscoord Bottom(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical()
+ ? (aWritingMode.IsInlineReversed() ? IStart() : IEnd())
+ : BEnd();
+ }
+
+ nscoord Left(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical()
+ ? (aWritingMode.IsVerticalLR() ? BStart() : BEnd())
+ : (aWritingMode.IsInlineReversed() ? IEnd() : IStart());
+ }
+
+ nscoord Right(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical()
+ ? (aWritingMode.IsVerticalLR() ? BEnd() : BStart())
+ : (aWritingMode.IsInlineReversed() ? IStart() : IEnd());
+ }
+
+ nscoord LeftRight(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical() ? BStartEnd() : IStartEnd();
+ }
+
+ nscoord TopBottom(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical() ? IStartEnd() : BStartEnd();
+ }
+
+ void SizeTo(WritingMode aWritingMode, nscoord aBStart, nscoord aIEnd,
+ nscoord aBEnd, nscoord aIStart) {
+ CHECK_WRITING_MODE(aWritingMode);
+ mMargin.SizeTo(aBStart, aIEnd, aBEnd, aIStart);
+ }
+
+ /**
+ * Return an nsMargin containing our physical coordinates
+ */
+ nsMargin GetPhysicalMargin(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical()
+ ? (aWritingMode.IsVerticalLR()
+ ? (aWritingMode.IsInlineReversed()
+ ? nsMargin(IEnd(), BEnd(), IStart(), BStart())
+ : nsMargin(IStart(), BEnd(), IEnd(), BStart()))
+ : (aWritingMode.IsInlineReversed()
+ ? nsMargin(IEnd(), BStart(), IStart(), BEnd())
+ : nsMargin(IStart(), BStart(), IEnd(), BEnd())))
+ : (aWritingMode.IsInlineReversed()
+ ? nsMargin(BStart(), IStart(), BEnd(), IEnd())
+ : nsMargin(BStart(), IEnd(), BEnd(), IStart()));
+ }
+
+ /**
+ * Return a LogicalMargin representing this margin in a different
+ * writing mode
+ */
+ LogicalMargin ConvertTo(WritingMode aToMode, WritingMode aFromMode) const {
+ CHECK_WRITING_MODE(aFromMode);
+ return aToMode == aFromMode
+ ? *this
+ : LogicalMargin(aToMode, GetPhysicalMargin(aFromMode));
+ }
+
+ LogicalMargin& ApplySkipSides(LogicalSides aSkipSides) {
+ CHECK_WRITING_MODE(aSkipSides.GetWritingMode());
+ if (aSkipSides.BStart()) {
+ BStart() = 0;
+ }
+ if (aSkipSides.BEnd()) {
+ BEnd() = 0;
+ }
+ if (aSkipSides.IStart()) {
+ IStart() = 0;
+ }
+ if (aSkipSides.IEnd()) {
+ IEnd() = 0;
+ }
+ return *this;
+ }
+
+ bool IsAllZero() const {
+ return (mMargin.left == 0 && mMargin.top == 0 && mMargin.right == 0 &&
+ mMargin.bottom == 0);
+ }
+
+ bool operator==(const LogicalMargin& aMargin) const {
+ CHECK_WRITING_MODE(aMargin.GetWritingMode());
+ return mMargin == aMargin.mMargin;
+ }
+
+ bool operator!=(const LogicalMargin& aMargin) const {
+ CHECK_WRITING_MODE(aMargin.GetWritingMode());
+ return mMargin != aMargin.mMargin;
+ }
+
+ LogicalMargin operator+(const LogicalMargin& aMargin) const {
+ CHECK_WRITING_MODE(aMargin.GetWritingMode());
+ return LogicalMargin(GetWritingMode(), BStart() + aMargin.BStart(),
+ IEnd() + aMargin.IEnd(), BEnd() + aMargin.BEnd(),
+ IStart() + aMargin.IStart());
+ }
+
+ LogicalMargin operator+=(const LogicalMargin& aMargin) {
+ CHECK_WRITING_MODE(aMargin.GetWritingMode());
+ mMargin += aMargin.mMargin;
+ return *this;
+ }
+
+ LogicalMargin operator-(const LogicalMargin& aMargin) const {
+ CHECK_WRITING_MODE(aMargin.GetWritingMode());
+ return LogicalMargin(GetWritingMode(), BStart() - aMargin.BStart(),
+ IEnd() - aMargin.IEnd(), BEnd() - aMargin.BEnd(),
+ IStart() - aMargin.IStart());
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const LogicalMargin& aMargin) {
+ return aStream << aMargin.mMargin;
+ }
+
+ private:
+ friend class LogicalRect;
+
+ LogicalMargin() = delete;
+
+#ifdef DEBUG
+ WritingMode GetWritingMode() const { return mWritingMode; }
+#else
+ WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
+#endif
+
+ nscoord IStart() const // inline-start margin
+ {
+ return mMargin.left;
+ }
+ nscoord IEnd() const // inline-end margin
+ {
+ return mMargin.right;
+ }
+ nscoord BStart() const // block-start margin
+ {
+ return mMargin.top;
+ }
+ nscoord BEnd() const // block-end margin
+ {
+ return mMargin.bottom;
+ }
+
+ nscoord& IStart() // inline-start margin
+ {
+ return mMargin.left;
+ }
+ nscoord& IEnd() // inline-end margin
+ {
+ return mMargin.right;
+ }
+ nscoord& BStart() // block-start margin
+ {
+ return mMargin.top;
+ }
+ nscoord& BEnd() // block-end margin
+ {
+ return mMargin.bottom;
+ }
+
+ nscoord IStartEnd() const // inline margins
+ {
+ return mMargin.LeftRight();
+ }
+ nscoord BStartEnd() const // block margins
+ {
+ return mMargin.TopBottom();
+ }
+
+#ifdef DEBUG
+ WritingMode mWritingMode;
+#endif
+ nsMargin mMargin;
+};
+
+/**
+ * Flow-relative rectangle
+ */
+class LogicalRect {
+ public:
+ explicit LogicalRect(WritingMode aWritingMode)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mIStart(0),
+ mBStart(0),
+ mISize(0),
+ mBSize(0) {
+ }
+
+ LogicalRect(WritingMode aWritingMode, nscoord aIStart, nscoord aBStart,
+ nscoord aISize, nscoord aBSize)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mIStart(aIStart),
+ mBStart(aBStart),
+ mISize(aISize),
+ mBSize(aBSize) {
+ }
+
+ LogicalRect(WritingMode aWritingMode, const LogicalPoint& aOrigin,
+ const LogicalSize& aSize)
+ :
+#ifdef DEBUG
+ mWritingMode(aWritingMode),
+#endif
+ mIStart(aOrigin.mPoint.x),
+ mBStart(aOrigin.mPoint.y),
+ mISize(aSize.mSize.width),
+ mBSize(aSize.mSize.height) {
+ CHECK_WRITING_MODE(aOrigin.GetWritingMode());
+ CHECK_WRITING_MODE(aSize.GetWritingMode());
+ }
+
+ LogicalRect(WritingMode aWritingMode, const nsRect& aRect,
+ const nsSize& aContainerSize)
+#ifdef DEBUG
+ : mWritingMode(aWritingMode)
+#endif
+ {
+ if (aWritingMode.IsVertical()) {
+ mBStart = aWritingMode.IsVerticalLR()
+ ? aRect.X()
+ : aContainerSize.width - aRect.XMost();
+ mIStart = aWritingMode.IsInlineReversed()
+ ? aContainerSize.height - aRect.YMost()
+ : aRect.Y();
+ mBSize = aRect.Width();
+ mISize = aRect.Height();
+ } else {
+ mIStart = aWritingMode.IsInlineReversed()
+ ? aContainerSize.width - aRect.XMost()
+ : aRect.X();
+ mBStart = aRect.Y();
+ mISize = aRect.Width();
+ mBSize = aRect.Height();
+ }
+ }
+
+ /**
+ * Inline- and block-dimension geometry.
+ */
+ nscoord IStart(WritingMode aWritingMode) const // inline-start edge
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mIStart;
+ }
+ nscoord IEnd(WritingMode aWritingMode) const // inline-end edge
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mIStart + mISize;
+ }
+ nscoord ISize(WritingMode aWritingMode) const // inline-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mISize;
+ }
+
+ nscoord BStart(WritingMode aWritingMode) const // block-start edge
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mBStart;
+ }
+ nscoord BEnd(WritingMode aWritingMode) const // block-end edge
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mBStart + mBSize;
+ }
+ nscoord BSize(WritingMode aWritingMode) const // block-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mBSize;
+ }
+
+ nscoord Start(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
+ }
+ nscoord End(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM);
+ }
+ nscoord Size(LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
+ }
+
+ /**
+ * Writable (reference) accessors are only available for the basic logical
+ * fields (Start and Size), not derivatives like End.
+ */
+ nscoord& IStart(WritingMode aWritingMode) // inline-start edge
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mIStart;
+ }
+ nscoord& ISize(WritingMode aWritingMode) // inline-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mISize;
+ }
+ nscoord& BStart(WritingMode aWritingMode) // block-start edge
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mBStart;
+ }
+ nscoord& BSize(WritingMode aWritingMode) // block-size
+ {
+ CHECK_WRITING_MODE(aWritingMode);
+ return mBSize;
+ }
+ nscoord& Start(LogicalAxis aAxis, WritingMode aWM) {
+ return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
+ }
+ nscoord& Size(LogicalAxis aAxis, WritingMode aWM) {
+ return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
+ }
+
+ /**
+ * Accessors for line-relative coordinates
+ */
+ nscoord LineLeft(WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsBidiLTR()) {
+ return IStart();
+ }
+ nscoord containerISize = aWritingMode.IsVertical() ? aContainerSize.height
+ : aContainerSize.width;
+ return containerISize - IEnd();
+ }
+ nscoord LineRight(WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsBidiLTR()) {
+ return IEnd();
+ }
+ nscoord containerISize = aWritingMode.IsVertical() ? aContainerSize.height
+ : aContainerSize.width;
+ return containerISize - IStart();
+ }
+
+ /**
+ * Physical coordinates of the rect.
+ */
+ nscoord X(WritingMode aWritingMode, nscoord aContainerWidth) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsVertical()) {
+ return aWritingMode.IsVerticalLR() ? mBStart : aContainerWidth - BEnd();
+ }
+ return aWritingMode.IsInlineReversed() ? aContainerWidth - IEnd() : mIStart;
+ }
+
+ nscoord Y(WritingMode aWritingMode, nscoord aContainerHeight) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsVertical()) {
+ return aWritingMode.IsInlineReversed() ? aContainerHeight - IEnd()
+ : mIStart;
+ }
+ return mBStart;
+ }
+
+ nscoord Width(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical() ? mBSize : mISize;
+ }
+
+ nscoord Height(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return aWritingMode.IsVertical() ? mISize : mBSize;
+ }
+
+ nscoord XMost(WritingMode aWritingMode, nscoord aContainerWidth) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsVertical()) {
+ return aWritingMode.IsVerticalLR() ? BEnd() : aContainerWidth - mBStart;
+ }
+ return aWritingMode.IsInlineReversed() ? aContainerWidth - mIStart : IEnd();
+ }
+
+ nscoord YMost(WritingMode aWritingMode, nscoord aContainerHeight) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsVertical()) {
+ return aWritingMode.IsInlineReversed() ? aContainerHeight - mIStart
+ : IEnd();
+ }
+ return BEnd();
+ }
+
+ bool IsEmpty() const { return mISize <= 0 || mBSize <= 0; }
+
+ bool IsAllZero() const {
+ return (mIStart == 0 && mBStart == 0 && mISize == 0 && mBSize == 0);
+ }
+
+ bool IsZeroSize() const { return (mISize == 0 && mBSize == 0); }
+
+ void SetEmpty() { mISize = mBSize = 0; }
+
+ bool IsEqualEdges(const LogicalRect aOther) const {
+ CHECK_WRITING_MODE(aOther.GetWritingMode());
+ bool result = mIStart == aOther.mIStart && mBStart == aOther.mBStart &&
+ mISize == aOther.mISize && mBSize == aOther.mBSize;
+
+ // We want the same result as nsRect, so assert we get it.
+ MOZ_ASSERT(result ==
+ nsRect(mIStart, mBStart, mISize, mBSize)
+ .IsEqualEdges(nsRect(aOther.mIStart, aOther.mBStart,
+ aOther.mISize, aOther.mBSize)));
+ return result;
+ }
+
+ LogicalPoint Origin(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return LogicalPoint(aWritingMode, IStart(), BStart());
+ }
+ void SetOrigin(WritingMode aWritingMode, const LogicalPoint& aPoint) {
+ IStart(aWritingMode) = aPoint.I(aWritingMode);
+ BStart(aWritingMode) = aPoint.B(aWritingMode);
+ }
+
+ LogicalSize Size(WritingMode aWritingMode) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ return LogicalSize(aWritingMode, ISize(), BSize());
+ }
+
+ LogicalRect operator+(const LogicalPoint& aPoint) const {
+ CHECK_WRITING_MODE(aPoint.GetWritingMode());
+ return LogicalRect(GetWritingMode(), IStart() + aPoint.I(),
+ BStart() + aPoint.B(), ISize(), BSize());
+ }
+
+ LogicalRect& operator+=(const LogicalPoint& aPoint) {
+ CHECK_WRITING_MODE(aPoint.GetWritingMode());
+ mIStart += aPoint.mPoint.x;
+ mBStart += aPoint.mPoint.y;
+ return *this;
+ }
+
+ LogicalRect operator-(const LogicalPoint& aPoint) const {
+ CHECK_WRITING_MODE(aPoint.GetWritingMode());
+ return LogicalRect(GetWritingMode(), IStart() - aPoint.I(),
+ BStart() - aPoint.B(), ISize(), BSize());
+ }
+
+ LogicalRect& operator-=(const LogicalPoint& aPoint) {
+ CHECK_WRITING_MODE(aPoint.GetWritingMode());
+ mIStart -= aPoint.mPoint.x;
+ mBStart -= aPoint.mPoint.y;
+ return *this;
+ }
+
+ void MoveBy(WritingMode aWritingMode, const LogicalPoint& aDelta) {
+ CHECK_WRITING_MODE(aWritingMode);
+ CHECK_WRITING_MODE(aDelta.GetWritingMode());
+ IStart() += aDelta.I();
+ BStart() += aDelta.B();
+ }
+
+ void Inflate(nscoord aD) {
+#ifdef DEBUG
+ // Compute using nsRect and assert the results match
+ nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
+ rectDebug.Inflate(aD);
+#endif
+ mIStart -= aD;
+ mBStart -= aD;
+ mISize += 2 * aD;
+ mBSize += 2 * aD;
+ MOZ_ASSERT(
+ rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
+ }
+ void Inflate(nscoord aDI, nscoord aDB) {
+#ifdef DEBUG
+ // Compute using nsRect and assert the results match
+ nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
+ rectDebug.Inflate(aDI, aDB);
+#endif
+ mIStart -= aDI;
+ mBStart -= aDB;
+ mISize += 2 * aDI;
+ mBSize += 2 * aDB;
+ MOZ_ASSERT(
+ rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
+ }
+ void Inflate(WritingMode aWritingMode, const LogicalMargin& aMargin) {
+ CHECK_WRITING_MODE(aWritingMode);
+ CHECK_WRITING_MODE(aMargin.GetWritingMode());
+#ifdef DEBUG
+ // Compute using nsRect and assert the results match
+ nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
+ rectDebug.Inflate(aMargin.mMargin);
+#endif
+ mIStart -= aMargin.mMargin.left;
+ mBStart -= aMargin.mMargin.top;
+ mISize += aMargin.mMargin.LeftRight();
+ mBSize += aMargin.mMargin.TopBottom();
+ MOZ_ASSERT(
+ rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
+ }
+
+ void Deflate(nscoord aD) {
+#ifdef DEBUG
+ // Compute using nsRect and assert the results match
+ nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
+ rectDebug.Deflate(aD);
+#endif
+ mIStart += aD;
+ mBStart += aD;
+ mISize = std::max(0, mISize - 2 * aD);
+ mBSize = std::max(0, mBSize - 2 * aD);
+ MOZ_ASSERT(
+ rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
+ }
+ void Deflate(nscoord aDI, nscoord aDB) {
+#ifdef DEBUG
+ // Compute using nsRect and assert the results match
+ nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
+ rectDebug.Deflate(aDI, aDB);
+#endif
+ mIStart += aDI;
+ mBStart += aDB;
+ mISize = std::max(0, mISize - 2 * aDI);
+ mBSize = std::max(0, mBSize - 2 * aDB);
+ MOZ_ASSERT(
+ rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
+ }
+ void Deflate(WritingMode aWritingMode, const LogicalMargin& aMargin) {
+ CHECK_WRITING_MODE(aWritingMode);
+ CHECK_WRITING_MODE(aMargin.GetWritingMode());
+#ifdef DEBUG
+ // Compute using nsRect and assert the results match
+ nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
+ rectDebug.Deflate(aMargin.mMargin);
+#endif
+ mIStart += aMargin.mMargin.left;
+ mBStart += aMargin.mMargin.top;
+ mISize = std::max(0, mISize - aMargin.mMargin.LeftRight());
+ mBSize = std::max(0, mBSize - aMargin.mMargin.TopBottom());
+ MOZ_ASSERT(
+ rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
+ }
+
+ /**
+ * Return an nsRect containing our physical coordinates within the given
+ * container size.
+ */
+ nsRect GetPhysicalRect(WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ CHECK_WRITING_MODE(aWritingMode);
+ if (aWritingMode.IsVertical()) {
+ return nsRect(aWritingMode.IsVerticalLR() ? BStart()
+ : aContainerSize.width - BEnd(),
+ aWritingMode.IsInlineReversed()
+ ? aContainerSize.height - IEnd()
+ : IStart(),
+ BSize(), ISize());
+ } else {
+ return nsRect(aWritingMode.IsInlineReversed()
+ ? aContainerSize.width - IEnd()
+ : IStart(),
+ BStart(), ISize(), BSize());
+ }
+ }
+
+ /**
+ * Return a LogicalRect representing this rect in a different writing mode
+ */
+ LogicalRect ConvertTo(WritingMode aToMode, WritingMode aFromMode,
+ const nsSize& aContainerSize) const {
+ CHECK_WRITING_MODE(aFromMode);
+ return aToMode == aFromMode
+ ? *this
+ : LogicalRect(aToMode,
+ GetPhysicalRect(aFromMode, aContainerSize),
+ aContainerSize);
+ }
+
+ /**
+ * Set *this to be the rectangle containing the intersection of aRect1
+ * and aRect2, return whether the intersection is non-empty.
+ */
+ bool IntersectRect(const LogicalRect& aRect1, const LogicalRect& aRect2) {
+ CHECK_WRITING_MODE(aRect1.mWritingMode);
+ CHECK_WRITING_MODE(aRect2.mWritingMode);
+#ifdef DEBUG
+ // Compute using nsRect and assert the results match
+ nsRect rectDebug;
+ rectDebug.IntersectRect(
+ nsRect(aRect1.mIStart, aRect1.mBStart, aRect1.mISize, aRect1.mBSize),
+ nsRect(aRect2.mIStart, aRect2.mBStart, aRect2.mISize, aRect2.mBSize));
+#endif
+
+ nscoord iEnd = std::min(aRect1.IEnd(), aRect2.IEnd());
+ mIStart = std::max(aRect1.mIStart, aRect2.mIStart);
+ mISize = iEnd - mIStart;
+
+ nscoord bEnd = std::min(aRect1.BEnd(), aRect2.BEnd());
+ mBStart = std::max(aRect1.mBStart, aRect2.mBStart);
+ mBSize = bEnd - mBStart;
+
+ if (mISize < 0 || mBSize < 0) {
+ mISize = 0;
+ mBSize = 0;
+ }
+
+ MOZ_ASSERT(
+ (rectDebug.IsEmpty() && (mISize == 0 || mBSize == 0)) ||
+ rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
+ return mISize > 0 && mBSize > 0;
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const LogicalRect& aRect) {
+ return aStream << '(' << aRect.IStart() << ',' << aRect.BStart() << ','
+ << aRect.ISize() << ',' << aRect.BSize() << ')';
+ }
+
+ private:
+ LogicalRect() = delete;
+
+#ifdef DEBUG
+ WritingMode GetWritingMode() const { return mWritingMode; }
+#else
+ WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
+#endif
+
+ nscoord IStart() const // inline-start edge
+ {
+ return mIStart;
+ }
+ nscoord IEnd() const // inline-end edge
+ {
+ return mIStart + mISize;
+ }
+ nscoord ISize() const // inline-size
+ {
+ return mISize;
+ }
+
+ nscoord BStart() const // block-start edge
+ {
+ return mBStart;
+ }
+ nscoord BEnd() const // block-end edge
+ {
+ return mBStart + mBSize;
+ }
+ nscoord BSize() const // block-size
+ {
+ return mBSize;
+ }
+
+ nscoord& IStart() // inline-start edge
+ {
+ return mIStart;
+ }
+ nscoord& ISize() // inline-size
+ {
+ return mISize;
+ }
+ nscoord& BStart() // block-start edge
+ {
+ return mBStart;
+ }
+ nscoord& BSize() // block-size
+ {
+ return mBSize;
+ }
+
+#ifdef DEBUG
+ WritingMode mWritingMode;
+#endif
+ // Inline- and block-geometry dimension
+ nscoord mIStart; // inline-start edge
+ nscoord mBStart; // block-start edge
+ nscoord mISize; // inline-size
+ nscoord mBSize; // block-size
+};
+
+template <typename T>
+const T& StyleRect<T>::Get(WritingMode aWM, LogicalSide aSide) const {
+ return Get(aWM.PhysicalSide(aSide));
+}
+
+template <typename T>
+const T& StyleRect<T>::GetIStart(WritingMode aWM) const {
+ return Get(aWM, eLogicalSideIStart);
+}
+
+template <typename T>
+const T& StyleRect<T>::GetBStart(WritingMode aWM) const {
+ return Get(aWM, eLogicalSideBStart);
+}
+
+template <typename T>
+const T& StyleRect<T>::GetIEnd(WritingMode aWM) const {
+ return Get(aWM, eLogicalSideIEnd);
+}
+
+template <typename T>
+const T& StyleRect<T>::GetBEnd(WritingMode aWM) const {
+ return Get(aWM, eLogicalSideBEnd);
+}
+
+template <typename T>
+T& StyleRect<T>::Get(WritingMode aWM, LogicalSide aSide) {
+ return Get(aWM.PhysicalSide(aSide));
+}
+
+template <typename T>
+T& StyleRect<T>::GetIStart(WritingMode aWM) {
+ return Get(aWM, eLogicalSideIStart);
+}
+
+template <typename T>
+T& StyleRect<T>::GetBStart(WritingMode aWM) {
+ return Get(aWM, eLogicalSideBStart);
+}
+
+template <typename T>
+T& StyleRect<T>::GetIEnd(WritingMode aWM) {
+ return Get(aWM, eLogicalSideIEnd);
+}
+
+template <typename T>
+T& StyleRect<T>::GetBEnd(WritingMode aWM) {
+ return Get(aWM, eLogicalSideBEnd);
+}
+
+template <typename T>
+const T& StyleRect<T>::Start(mozilla::LogicalAxis aAxis,
+ mozilla::WritingMode aWM) const {
+ return Get(aWM, aAxis == mozilla::eLogicalAxisInline
+ ? mozilla::eLogicalSideIStart
+ : mozilla::eLogicalSideBStart);
+}
+
+template <typename T>
+const T& StyleRect<T>::End(mozilla::LogicalAxis aAxis,
+ mozilla::WritingMode aWM) const {
+ return Get(aWM, aAxis == mozilla::eLogicalAxisInline
+ ? mozilla::eLogicalSideIEnd
+ : mozilla::eLogicalSideBEnd);
+}
+
+inline AspectRatio AspectRatio::ConvertToWritingMode(
+ const WritingMode& aWM) const {
+ return aWM.IsVertical() ? Inverted() : *this;
+}
+
+} // namespace mozilla
+
+// Definitions of inline methods for nsStylePosition, declared in
+// nsStyleStruct.h but not defined there because they need WritingMode.
+inline const mozilla::StyleSize& nsStylePosition::ISize(WritingMode aWM) const {
+ return aWM.IsVertical() ? mHeight : mWidth;
+}
+inline const mozilla::StyleSize& nsStylePosition::MinISize(
+ WritingMode aWM) const {
+ return aWM.IsVertical() ? mMinHeight : mMinWidth;
+}
+inline const mozilla::StyleMaxSize& nsStylePosition::MaxISize(
+ WritingMode aWM) const {
+ return aWM.IsVertical() ? mMaxHeight : mMaxWidth;
+}
+inline const mozilla::StyleSize& nsStylePosition::BSize(WritingMode aWM) const {
+ return aWM.IsVertical() ? mWidth : mHeight;
+}
+inline const mozilla::StyleSize& nsStylePosition::MinBSize(
+ WritingMode aWM) const {
+ return aWM.IsVertical() ? mMinWidth : mMinHeight;
+}
+inline const mozilla::StyleMaxSize& nsStylePosition::MaxBSize(
+ WritingMode aWM) const {
+ return aWM.IsVertical() ? mMaxWidth : mMaxHeight;
+}
+inline const mozilla::StyleSize& nsStylePosition::Size(
+ mozilla::LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == mozilla::eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
+}
+inline const mozilla::StyleSize& nsStylePosition::MinSize(
+ mozilla::LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == mozilla::eLogicalAxisInline ? MinISize(aWM) : MinBSize(aWM);
+}
+inline const mozilla::StyleMaxSize& nsStylePosition::MaxSize(
+ mozilla::LogicalAxis aAxis, WritingMode aWM) const {
+ return aAxis == mozilla::eLogicalAxisInline ? MaxISize(aWM) : MaxBSize(aWM);
+}
+
+inline bool nsStylePosition::ISizeDependsOnContainer(WritingMode aWM) const {
+ const auto& iSize = ISize(aWM);
+ return iSize.IsAuto() || ISizeCoordDependsOnContainer(iSize);
+}
+inline bool nsStylePosition::MinISizeDependsOnContainer(WritingMode aWM) const {
+ // NOTE: For a flex item, "min-inline-size:auto" is supposed to behave like
+ // "min-content", which does depend on the container, so you might think we'd
+ // need a special case for "flex item && min-inline-size:auto" here. However,
+ // we don't actually need that special-case code, because flex items are
+ // explicitly supposed to *ignore* their min-inline-size (i.e. behave like
+ // it's 0) until the flex container explicitly considers it. So -- since the
+ // flex container doesn't rely on this method, we don't need to worry about
+ // special behavior for flex items' "min-inline-size:auto" values here.
+ return ISizeCoordDependsOnContainer(MinISize(aWM));
+}
+inline bool nsStylePosition::MaxISizeDependsOnContainer(WritingMode aWM) const {
+ // NOTE: The comment above MinISizeDependsOnContainer about flex items
+ // applies here, too.
+ return ISizeCoordDependsOnContainer(MaxISize(aWM));
+}
+// Note that these functions count `auto` as depending on the container
+// since that's the case for absolutely positioned elements.
+// However, some callers do not care about this case and should check
+// for it, since it is the most common case.
+// FIXME: We should probably change the assumption to be the other way
+// around.
+inline bool nsStylePosition::BSizeDependsOnContainer(WritingMode aWM) const {
+ const auto& bSize = BSize(aWM);
+ return bSize.BehavesLikeInitialValueOnBlockAxis() ||
+ BSizeCoordDependsOnContainer(bSize);
+}
+inline bool nsStylePosition::MinBSizeDependsOnContainer(WritingMode aWM) const {
+ return BSizeCoordDependsOnContainer(MinBSize(aWM));
+}
+inline bool nsStylePosition::MaxBSizeDependsOnContainer(WritingMode aWM) const {
+ return BSizeCoordDependsOnContainer(MaxBSize(aWM));
+}
+
+inline bool nsStyleMargin::HasBlockAxisAuto(mozilla::WritingMode aWM) const {
+ return mMargin.GetBStart(aWM).IsAuto() || mMargin.GetBEnd(aWM).IsAuto();
+}
+
+inline bool nsStyleMargin::HasInlineAxisAuto(mozilla::WritingMode aWM) const {
+ return mMargin.GetIStart(aWM).IsAuto() || mMargin.GetIEnd(aWM).IsAuto();
+}
+inline bool nsStyleMargin::HasAuto(mozilla::LogicalAxis aAxis,
+ mozilla::WritingMode aWM) const {
+ return aAxis == mozilla::eLogicalAxisInline ? HasInlineAxisAuto(aWM)
+ : HasBlockAxisAuto(aWM);
+}
+
+inline mozilla::StyleAlignFlags nsStylePosition::UsedSelfAlignment(
+ mozilla::LogicalAxis aAxis, const mozilla::ComputedStyle* aParent) const {
+ return aAxis == mozilla::eLogicalAxisBlock ? UsedAlignSelf(aParent)._0
+ : UsedJustifySelf(aParent)._0;
+}
+
+inline mozilla::StyleContentDistribution nsStylePosition::UsedContentAlignment(
+ mozilla::LogicalAxis aAxis) const {
+ return aAxis == mozilla::eLogicalAxisBlock ? mAlignContent : mJustifyContent;
+}
+
+inline mozilla::StyleContentDistribution nsStylePosition::UsedTracksAlignment(
+ mozilla::LogicalAxis aAxis, uint32_t aIndex) const {
+ using T = mozilla::StyleAlignFlags;
+ const auto& tracksAlignment =
+ aAxis == mozilla::eLogicalAxisBlock ? mAlignTracks : mJustifyTracks;
+ if (MOZ_LIKELY(tracksAlignment.IsEmpty())) {
+ // An empty array encodes the initial value, 'normal', which behaves as
+ // 'start' for Grid containers.
+ return mozilla::StyleContentDistribution{T::START};
+ }
+
+ // If there are fewer values than tracks, then the last value is used for all
+ // the remaining tracks.
+ const auto& ta = tracksAlignment.AsSpan();
+ auto align = ta[std::min<size_t>(aIndex, ta.Length() - 1)];
+ if (align.primary == T::NORMAL) {
+ align = mozilla::StyleContentDistribution{T::START};
+ }
+ return align;
+}
+
+#endif // WritingModes_h_
diff --git a/layout/generic/broken-image.png b/layout/generic/broken-image.png
new file mode 100644
index 0000000000..69fee4745d
--- /dev/null
+++ b/layout/generic/broken-image.png
Binary files differ
diff --git a/layout/generic/crashtests/1001233.html b/layout/generic/crashtests/1001233.html
new file mode 100644
index 0000000000..aa877b5420
--- /dev/null
+++ b/layout/generic/crashtests/1001233.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+li::marker {
+ direction: rtl;
+ margin-right: 1em;
+}
+</style>
+</head>
+<body>
+Body
+<ul>
+<li>list item</li>
+</ul>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1001258-1.html b/layout/generic/crashtests/1001258-1.html
new file mode 100644
index 0000000000..fdb852351a
--- /dev/null
+++ b/layout/generic/crashtests/1001258-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+.r {
+ display: inline-block;
+ width: 40px;
+ height: 40px;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('willFloatLeft').style.cssFloat = 'left';">
+ <div style="width: 100px; background: lightgray;">
+ <div class="r" style="background: red; width: 10px; float: left;"></div>
+ <div class="r" style="background: violet; width: 120px;" id="willFloatLeft" ></div>
+ <div style="clear: left;"></div>
+ <div class="r" style="background: blue;"></div>
+ <div class="r" style="background: yellow; float: right;"></div>
+ <div class="r" style="background: orange; position: relative;"></div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/1001994.html b/layout/generic/crashtests/1001994.html
new file mode 100644
index 0000000000..7c9d165a35
--- /dev/null
+++ b/layout/generic/crashtests/1001994.html
@@ -0,0 +1,14 @@
+<html class="reftest-paged"><body>
+
+<div style="position: fixed;">
+<ul style="position: sticky;">
+<span>
+mmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmm
+mmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmm
+mmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmm
+mmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmm
+mmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmm
+mmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmm mmmmmm mmmmmmmmmmmmmmmmm mmmmm mmmmm mmmmmmmm mmmmmmmm
+<span style="position: sticky;">a
+
+</body></html>
diff --git a/layout/generic/crashtests/1003441.xhtml b/layout/generic/crashtests/1003441.xhtml
new file mode 100644
index 0000000000..998f5ca982
--- /dev/null
+++ b/layout/generic/crashtests/1003441.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom,0,1)">
+
+<splitter style="all: inherit;">
+ <box/>
+ <box/>
+ <box/>
+ <box/>
+ <iframe id="a" src="aaa"/>
+</splitter>
+
+<script id="script" xmlns="http://www.w3.org/1999/xhtml">
+
+ <![CDATA[//<![CDATA[
+
+var doc = document;
+function boom(i) {
+ if (i>6)
+ return;
+ var x=doc.getElementsByTagName('*');
+ if (x[i] && x[i+1]) {
+ var temp = x[i+1].getAttribute('style');
+ x[i+1].setAttribute('style', x[i].getAttribute('style'));
+ x[i].setAttribute('style', temp);
+ } else {
+ return;
+ }
+ i++;
+ setTimeout(boom,50,i);
+}
+
+//]]>
+
+ </script>
+
+ </window>
diff --git a/layout/generic/crashtests/1015562.html b/layout/generic/crashtests/1015562.html
new file mode 100644
index 0000000000..69fddcc7b2
--- /dev/null
+++ b/layout/generic/crashtests/1015562.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ document.getElementById("r").appendChild(document.createTextNode("B"));
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="r" style="display: grid;">A</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1015563-1.html b/layout/generic/crashtests/1015563-1.html
new file mode 100644
index 0000000000..5acfb644d4
--- /dev/null
+++ b/layout/generic/crashtests/1015563-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html style="display: inline-flex;">
+<body style="margin: -3642924795px; flex-grow: 1;"></body>
+</html>
diff --git a/layout/generic/crashtests/1015563-2.html b/layout/generic/crashtests/1015563-2.html
new file mode 100644
index 0000000000..0245bb45c3
--- /dev/null
+++ b/layout/generic/crashtests/1015563-2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <div style="display: flex">
+ <div style="margin: -3642924795px; flex-grow: 1;"></div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1015844.html b/layout/generic/crashtests/1015844.html
new file mode 100644
index 0000000000..5e265db994
--- /dev/null
+++ b/layout/generic/crashtests/1015844.html
@@ -0,0 +1,25 @@
+<style>
+.multicol {
+ width: 500px;
+ column-width: 100px;
+ height: 100px;
+ position: relative;
+}
+#clear {
+ position: absolute;
+}
+.float {
+ height: 300px;
+}
+</style>
+<div class="multicol">
+<div class="float">
+<div class="multicol">
+<div id="clear">
+<div class="float">
+<div class="multicol">
+<div class="float">&nbsp;
+</div>
+<div class="multicol">
+<div class="multicol">
+<div class="float">a
diff --git a/layout/generic/crashtests/1032450.html b/layout/generic/crashtests/1032450.html
new file mode 100644
index 0000000000..2e5678f96d
--- /dev/null
+++ b/layout/generic/crashtests/1032450.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+
+<body style="display: -moz-inline-box;">
+<div style="display: table;position: relative; float: left;">
+<button style="display: table-column-group;">
+ <iframe style="position: absolute;"></iframe>
+</button>
+</div>
+
+</body></html>
diff --git a/layout/generic/crashtests/1032613-1.svg b/layout/generic/crashtests/1032613-1.svg
new file mode 100644
index 0000000000..c1e8639806
--- /dev/null
+++ b/layout/generic/crashtests/1032613-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="tweak()">
+ <pattern>
+ <rect id="r" />
+ </pattern>
+ <script>
+ function tweak() {
+ document.getElementById("r").style.textDecoration = "underline";
+ }
+ </script>
+</svg>
diff --git a/layout/generic/crashtests/1032613-2.html b/layout/generic/crashtests/1032613-2.html
new file mode 100644
index 0000000000..9ba11b3142
--- /dev/null
+++ b/layout/generic/crashtests/1032613-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ function tweak() {
+ document.getElementById("c").style.textShadow = "3px 3px gray";
+ }
+ </script>
+ <body onload="tweak()">
+ <div id="c">hello
+ <svg height="0">
+ <clipPath>
+ <path d=""/>
+ </clipPath>
+ </svg>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/1037903.html b/layout/generic/crashtests/1037903.html
new file mode 100644
index 0000000000..684a46db52
--- /dev/null
+++ b/layout/generic/crashtests/1037903.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html style="column-width: calc(15px);">
+<body>
+<video></video><audio style="box-decoration-break: clone; display: block; direction: rtl;"></audio>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1039454-1.html b/layout/generic/crashtests/1039454-1.html
new file mode 100644
index 0000000000..b049cbfa31
--- /dev/null
+++ b/layout/generic/crashtests/1039454-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<body>
+<div id="outer" style="width:200px; height:200px; background:blue; transform:translateY(10px)">
+ <div id="inner" style="width:200px; height:100px; background:yellow; transform:translateX(100px)">
+ </div>
+</div>
+<script>
+console.log(outer.getBoundingClientRect());
+outer.style.width = "400px";
+console.log(outer.getBoundingClientRect());
+outer.style.transform = "translateY(100px)";
+</script>
diff --git a/layout/generic/crashtests/1042489.html b/layout/generic/crashtests/1042489.html
new file mode 100644
index 0000000000..50885a76d4
--- /dev/null
+++ b/layout/generic/crashtests/1042489.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html style="column-width: 1px;">
+<body style="column-width: 1px; box-decoration-break: clone;">
+<div>A B</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1054010-1.html b/layout/generic/crashtests/1054010-1.html
new file mode 100644
index 0000000000..52557340a1
--- /dev/null
+++ b/layout/generic/crashtests/1054010-1.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css">
+ .flexRow {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .flexColumn {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ flex-basis: 100%;
+ }
+
+ .flexBlock {
+ flex: 0;
+ display: flex;
+ flex-direction: column;
+ padding: 5px;
+ border: 1px solid blue;
+ }
+
+ .flexColumn > .flexBlock:last-child {
+ flex: 1;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 1
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 2
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 3
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 4
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 5
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 6
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 7
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 8
+ <div class="flexRow">
+ <div class="flexColumn">
+ <div class="flexBlock">
+ Nested layout 9
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/1058954-1.html b/layout/generic/crashtests/1058954-1.html
new file mode 100644
index 0000000000..4d989f7930
--- /dev/null
+++ b/layout/generic/crashtests/1058954-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+ <div style="display: -moz-box;">
+ <div style="position: relative; direction: rtl;">
+ <div style="position: absolute;">
+ </div>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1059138-1.html b/layout/generic/crashtests/1059138-1.html
new file mode 100644
index 0000000000..69055aa12e
--- /dev/null
+++ b/layout/generic/crashtests/1059138-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Testcase for bug 1059138</title>
+<template>
+ <div class="inner" style="border: 1px solid black; display:flex; width: 500px;">
+ <button class="action-button">
+ ThisIsAButton
+ </button>
+ <slot></slot>
+ </div>
+</template>
+
+<script>
+ // Gets content from <template>
+ var template = document.querySelector('template').content;
+
+ // Creates an object based in the HTML Element prototype
+ class MyElement extends HTMLElement {
+ // Fires when an instance of the element is connected
+ connectedCallback() {
+ // Creates the shadow root
+ var shadowRoot = this.attachShadow({ mode: "open" });
+
+ // Adds a template clone into shadow root
+ var clone = document.importNode(template, true);
+ shadowRoot.appendChild(clone);
+ }
+ };
+ // Registers <my-elem> in the main document
+ customElements.define('my-elem', MyElement);
+</script>
+</head>
+<body>
+ <my-elem><div>ThisIsADivFlexItem</div></my-elem>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1102175-2.html b/layout/generic/crashtests/1102175-2.html
new file mode 100644
index 0000000000..1f485f68d8
--- /dev/null
+++ b/layout/generic/crashtests/1102175-2.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>CSS-Writing Modes Test: propagation of the writing-mode property from body to root</title>
+ <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+ <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+ <link rel=help href="https://drafts.csswg.org/css-writing-modes-3/#principal-flow">
+ <link rel="match" href="wm-propagation-body-dynamic-change-002-ref.html">
+ <meta name=assert content="The writing mode of the newly inserted body must be propagated to the root.">
+
+ <script>
+ function runTest() {
+ document.body.offsetHeight;
+
+ var newBody = document.createElement("body");
+ newBody.id = "new-body";
+ var oldBody = document.getElementById("old-body");
+
+ /* Insert a new <body> before the old one, which should become the primary <body>. */
+ document.documentElement.insertBefore(newBody, oldBody);
+ }
+ </script>
+
+ <style>
+ #new-body {
+ /* This writing-mode should propagate to the root element. */
+ writing-mode: vertical-rl;
+ margin: 0;
+ }
+
+ #old-body {
+ writing-mode: horizontal-tb;
+ inline-size: 100px;
+ }
+
+ div {
+ background-color: blue;
+ height: 100px;
+ width: 100px;
+ }
+ </style>
+
+ <body id="old-body" onload="runTest();">
+ <div></div>
+ <p>Test passes if you see a blue square in the upper-right corner of the page</p>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/1134531.html b/layout/generic/crashtests/1134531.html
new file mode 100644
index 0000000000..35ddbb0606
--- /dev/null
+++ b/layout/generic/crashtests/1134531.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body onload="x.style.textTransform = 'capitalize';"><div id="x">b</div></body>
+</html>
diff --git a/layout/generic/crashtests/1134667.html b/layout/generic/crashtests/1134667.html
new file mode 100644
index 0000000000..e33eb99dd5
--- /dev/null
+++ b/layout/generic/crashtests/1134667.html
@@ -0,0 +1,2 @@
+x
+<ruby><x>
diff --git a/layout/generic/crashtests/1137723-1.html b/layout/generic/crashtests/1137723-1.html
new file mode 100644
index 0000000000..5d791adfff
--- /dev/null
+++ b/layout/generic/crashtests/1137723-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+ <meta charset="utf-8">
+<title>Testcase bug 1137723</title>
+
+<style>
+ .grid-comment{position:fixed;top:100%;}
+ a[href]:after{content:" (" attr(href) ")";}
+ </style>
+
+<style>
+@font-face { font-family: "slick"; src: url("non-existent-file.eot"); }
+</style>
+</head>
+
+<body>
+ <div style="height:7in"></div>
+
+ <div class="grid-comment">Artikel teilen
+
+<div>
+<a href="https://www.facebook.com/sharer/sharer.php?u=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">shares</a>
+<a href="https://twitter.com/share?text=Griechenland-Hilfe:Bundestag stimmt Verlängerung zu&amp;url=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">tweets</a>
+</div>
+
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1137723-2.html b/layout/generic/crashtests/1137723-2.html
new file mode 100644
index 0000000000..09ee495d8a
--- /dev/null
+++ b/layout/generic/crashtests/1137723-2.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+ <meta charset="utf-8">
+<title>Testcase bug 1137723</title>
+
+<style>
+ .grid-comment{position:fixed;top:100%;}
+ a[href]:after{content:" (" attr(href) ")";}
+ </style>
+
+<style>
+@font-face { font-family: "slick"; src: url("non-existent-file.eot"); }
+</style>
+</head>
+
+<body>
+ <div style="height:3in"></div>
+
+ <div class="grid-comment">Artikel teilen
+
+<div>
+<a href="https://www.facebook.com/sharer/sharer.php?u=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">shares</a>
+<a href="https://twitter.com/share?text=Griechenland-Hilfe:Bundestag stimmt Verlängerung zu&amp;url=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">tweets</a>
+</div>
+
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1140043-1.html b/layout/generic/crashtests/1140043-1.html
new file mode 100644
index 0000000000..202445c3b4
--- /dev/null
+++ b/layout/generic/crashtests/1140043-1.html
@@ -0,0 +1,7 @@
+<style>
+.justify-in-wide {
+ text-align-last: justify;
+ width: 3504270px;
+}
+</style>
+<div class="justify-in-wide">a b c d</div>
diff --git a/layout/generic/crashtests/1140043-2.html b/layout/generic/crashtests/1140043-2.html
new file mode 100644
index 0000000000..9a670f3e89
--- /dev/null
+++ b/layout/generic/crashtests/1140043-2.html
@@ -0,0 +1,7 @@
+<style>
+.justify-in-wide {
+ text-align-last: justify;
+ width: 3504270px;
+}
+</style>
+<div class="justify-in-wide"><span>a b c d</span> <span>e f g h</span></div>
diff --git a/layout/generic/crashtests/1140043-3.html b/layout/generic/crashtests/1140043-3.html
new file mode 100644
index 0000000000..c76920c8c8
--- /dev/null
+++ b/layout/generic/crashtests/1140043-3.html
@@ -0,0 +1,7 @@
+<style>
+.justify-in-wide {
+ text-align-last: justify;
+ width: 3504270px;
+}
+</style>
+<div class="justify-in-wide"><span><span>a b c d</span> d c b a</span> <span>e f g h <span>h g f e</span></span></div>
diff --git a/layout/generic/crashtests/1140268-1.html b/layout/generic/crashtests/1140268-1.html
new file mode 100644
index 0000000000..5e5510ba7f
--- /dev/null
+++ b/layout/generic/crashtests/1140268-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<script>
+function boom()
+{
+ var e = document.getElementsByTagName("mo")[0];
+ e.setAttribute("style", "position: absolute; top: 0px;");
+ document.documentElement.offsetHeight;
+ e.setAttribute("style", "position: absolute; top: 100px;");
+}
+</script>
+</head>
+<body onload="boom();">
+<math><mo>boom!</mo></math>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1145768.html b/layout/generic/crashtests/1145768.html
new file mode 100644
index 0000000000..b8e6938db6
--- /dev/null
+++ b/layout/generic/crashtests/1145768.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ var emptyTail = q.firstChild.splitText(2);
+ document.documentElement.offsetHeight;
+ emptyTail.splitText(0);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div style="width: 1px; height: 1px;"><div id="q" style="writing-mode: vertical-rl;">&#x07;R</div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/1145931.html b/layout/generic/crashtests/1145931.html
new file mode 100644
index 0000000000..b1607bf5cc
--- /dev/null
+++ b/layout/generic/crashtests/1145931.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html><head>
+<meta charset="UTF-8">
+<style>
+body { column-count: 2; }
+div::after { content: "A"; }
+</style>
+
+<script>
+function boom() {
+ x.style.display = "contents";
+}
+</script>
+</head>
+<body onload="boom();"><div></div><div><div id="x"></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/1145950-1.html b/layout/generic/crashtests/1145950-1.html
new file mode 100644
index 0000000000..8f4e40a59f
--- /dev/null
+++ b/layout/generic/crashtests/1145950-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ r.style.borderRadius = "4px";
+}
+
+</script>
+</head>
+<body onload="boom();">
+ <div style="display: table;" id="r">
+ <div style="display: -moz-popup;"></div>
+ <div style="display: contents;"><div style="display: table-caption;"></div></div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1146103.html b/layout/generic/crashtests/1146103.html
new file mode 100644
index 0000000000..876e08bd6a
--- /dev/null
+++ b/layout/generic/crashtests/1146103.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="display: ruby-text; margin: -47891343%"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1146107.html b/layout/generic/crashtests/1146107.html
new file mode 100644
index 0000000000..661fa876c4
--- /dev/null
+++ b/layout/generic/crashtests/1146107.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="display: ruby-base; margin: -47891343%"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1146114.html b/layout/generic/crashtests/1146114.html
new file mode 100644
index 0000000000..5f148a1679
--- /dev/null
+++ b/layout/generic/crashtests/1146114.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="display: ruby-text; margin: 288230376151711740%"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1153478-iframe.html b/layout/generic/crashtests/1153478-iframe.html
new file mode 100644
index 0000000000..aee79c811c
--- /dev/null
+++ b/layout/generic/crashtests/1153478-iframe.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+function main() {
+var elements = []
+elements = document.getElementsByTagName('time')
+setTimeout(function(){try{elements[11].innerHTML += "A"}catch(e){};}, 100)
+setTimeout(function(){try{elements[11].innerHTML += "A"}catch(e){};}, 200)
+}
+</script>
+</head>
+<body style="writing-mode:vertical-lr;direction:rtl;">
+<time><time><time> id="">>> <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time id=<time <time>>> id="">>> id="">>> id=<time <time id=<time <time>>
+</body>
+<script>
+document.addEventListener("DOMContentLoaded", function(event) {main()});
+</script>
+</html>
diff --git a/layout/generic/crashtests/1153478.html b/layout/generic/crashtests/1153478.html
new file mode 100644
index 0000000000..5bfb57ee59
--- /dev/null
+++ b/layout/generic/crashtests/1153478.html
@@ -0,0 +1,11 @@
+<html><head>
+ <title>Testcase for bug 1153478</title>
+</head>
+<body>
+
+<iframe width=550 height=900 src="1153478-iframe.html"></iframe>
+<iframe width=570 height=920 src="1153478-iframe.html"></iframe>
+<iframe width=950 height=550 src="1153478-iframe.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1153695.html b/layout/generic/crashtests/1153695.html
new file mode 100644
index 0000000000..2e2898e0ac
--- /dev/null
+++ b/layout/generic/crashtests/1153695.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <meta charset="UTF-8">
+ <script>
+ function boom()
+ {
+ document.documentElement.offsetHeight;
+ document.body.appendChild(document.getElementById("x"));
+ document.documentElement.offsetHeight;
+ }
+ </script>
+ </head>
+
+ <body style="column-count: 4;" onload="boom();">
+ <div style="float: left; height: 10px; width: 10px;">
+ <div id="x">
+ <div style="height: 80px;"></div>
+ <div style="float: left; height: 10px;"></div>
+ </div>
+ </div>
+ </body>
+
+</html>
diff --git a/layout/generic/crashtests/1156222.html b/layout/generic/crashtests/1156222.html
new file mode 100644
index 0000000000..75dcc85d2e
--- /dev/null
+++ b/layout/generic/crashtests/1156222.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div><span dir="rtl"></span><audio style="display: ruby-base;"></audio></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1156257.html b/layout/generic/crashtests/1156257.html
new file mode 100644
index 0000000000..4799926299
--- /dev/null
+++ b/layout/generic/crashtests/1156257.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("g").firstChild.remove();
+}
+</script>
+</head>
+<body onload="boom();">
+<div style="display: grid;" id="g">a b<br></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1157011.html b/layout/generic/crashtests/1157011.html
new file mode 100644
index 0000000000..c5fe133a87
--- /dev/null
+++ b/layout/generic/crashtests/1157011.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="display: inline-flex;"><div><div style="display: ruby-base;">f<br style="display: ruby-base-container;"></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/1157975-1.html b/layout/generic/crashtests/1157975-1.html
new file mode 100644
index 0000000000..8c398004f0
--- /dev/null
+++ b/layout/generic/crashtests/1157975-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom()
+{
+ document.getElementById("x").style.cssFloat = "none";
+}
+</script>
+</head>
+<body onload="boom();"><div style="height: 10px; position: fixed;"><span style="float: left; writing-mode: vertical-rl;"><span style="display: -moz-groupbox;"></span>
+<span style="float: right;" id="x">}</span></span></div></body>
+</html>
diff --git a/layout/generic/crashtests/1169420-1.html b/layout/generic/crashtests/1169420-1.html
new file mode 100644
index 0000000000..1ddbaea4f4
--- /dev/null
+++ b/layout/generic/crashtests/1169420-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html style="writing-mode: vertical-lr;">
+ <body>
+ <div style="display: inline-flex;">
+ <div style="margin-inline-start: auto; margin-inline-end: 75px; direction: rtl;"></div>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/1169420-2.html b/layout/generic/crashtests/1169420-2.html
new file mode 100644
index 0000000000..e096ed7f92
--- /dev/null
+++ b/layout/generic/crashtests/1169420-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html style="writing-mode: vertical-lr;">
+ <body>
+ <div style="display: inline-flex;">
+ <div style="margin-bottom: auto; margin-top: 75px; direction: rtl;"></div>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/1178783-1.html b/layout/generic/crashtests/1178783-1.html
new file mode 100644
index 0000000000..845e4e0b16
--- /dev/null
+++ b/layout/generic/crashtests/1178783-1.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+*
+{
+ margin: 0;
+ padding: 0;
+}
+html
+{
+ height: 100%;
+ display: flex;
+ flex-flow: row nowrap;
+}
+body
+{
+ flex: 1 1 0px;
+ min-width: 0;
+ display: flex;
+ flex-flow: row nowrap;
+}
+ul
+{
+ list-style: none;
+}
+.vertical
+{
+ flex: 1 1 0px;
+ min-width: 0;
+ display: flex;
+ flex-flow: column nowrap;
+}
+.vertical > li:first-child
+{
+ flex: 1 1 0px;
+ min-height: 0;
+ background-color: #0ff;
+}
+.vertical > li:last-child
+{
+ flex: 0 0 0px;
+ min-height: 0;
+ background-color: #f0f;
+ display: flex;
+ flex-flow: row nowrap;
+}
+.horizontal-separator
+{
+ flex: 0 0 5px;
+ cursor: row-resize;
+ background-color: #fff;
+}
+ </style>
+</head>
+<body>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+ <ul class="vertical">
+ <li></li>
+ <li class="horizontal-separator"></li>
+ <li>
+<!-- ... etc ... -->
diff --git a/layout/generic/crashtests/1183431.html b/layout/generic/crashtests/1183431.html
new file mode 100644
index 0000000000..e1d4c87c0c
--- /dev/null
+++ b/layout/generic/crashtests/1183431.html
@@ -0,0 +1,6 @@
+<!DOCTYPE>
+<html>
+<body>
+<div style="writing-mode: vertical-lr;"><div style="position: fixed;"></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1186147-1.html b/layout/generic/crashtests/1186147-1.html
new file mode 100644
index 0000000000..91ebc55f19
--- /dev/null
+++ b/layout/generic/crashtests/1186147-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="writing-mode: vertical-rl;"><div><span style="display: flex;">x</span></div>y</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1209952.html b/layout/generic/crashtests/1209952.html
new file mode 100644
index 0000000000..cf19c4f69c
--- /dev/null
+++ b/layout/generic/crashtests/1209952.html
@@ -0,0 +1,38 @@
+<style>
+.multicol-a {
+ width: 300px;
+ column-width: 100px;
+ column-gap: 0;
+ height: 100px;
+}
+.multicol-b {
+ border: 1px solid silver;
+ width: 200px;
+ column-width: 51px;
+ column-gap: 0;
+ height: 50px;
+}
+
+.step {
+ height: 1px;
+}
+.float-L {
+ width: 1px;
+ height: 1px;
+ float: left;
+}
+.float-R {
+ width: 1px;
+ height: 26px; /* 25 -> 26 crash */
+}
+</style>
+
+<div class="multicol-a">
+ <div class="float-R"></div>
+ <div class="multicol-b">
+ <div class="step"></div>
+ <div>
+ <div class="float-L"></div>
+ <div>
+ </div>
+</div>
diff --git a/layout/generic/crashtests/1221112-1.html b/layout/generic/crashtests/1221112-1.html
new file mode 100644
index 0000000000..24e60a37bf
--- /dev/null
+++ b/layout/generic/crashtests/1221112-1.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ .flexContainer {
+ display: flex;
+
+ /* Just for easier visualization: */
+ width: 600px;
+ justify-content: space-around;
+ border: 1px solid black;
+ }
+
+ .flexContainer:before {
+ position:absolute;
+ content:'before';
+ }
+ .flexContainer:after {
+ position:absolute;
+ content:'after'
+ }
+
+ .ordered-item {
+ position:relative;
+ order:5;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="flexContainer">
+ <div class="ordered-item">ItemWithOrderSet
+ <!-- It's important that this remain unclosed, for some reason. -->
diff --git a/layout/generic/crashtests/1221112-2.html b/layout/generic/crashtests/1221112-2.html
new file mode 100644
index 0000000000..2ae372a9d1
--- /dev/null
+++ b/layout/generic/crashtests/1221112-2.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ .flexContainer {
+ display: flex;
+
+ /* Just for easier visualization: */
+ width: 600px;
+ justify-content: space-around;
+ border: 1px solid black;
+ }
+
+ .flexContainer:before {
+ position:absolute;
+ content:'before';
+ }
+ .flexContainer:after {
+ position:absolute;
+ content:'after'
+ }
+ </style>
+ </head>
+ <body>
+ <div class="flexContainer">
+ <div class="ordered-item">NormalFlexItem
+ <!-- It's important that this remain unclosed, for some reason. -->
diff --git a/layout/generic/crashtests/1221874-1.html b/layout/generic/crashtests/1221874-1.html
new file mode 100644
index 0000000000..85d5bab3c0
--- /dev/null
+++ b/layout/generic/crashtests/1221874-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.style.textOrientation = "sideways";
+}
+
+</script>
+</head>
+<body onload="boom();" style="writing-mode: tb-rl; display: table;">Hello</body>
+</html>
diff --git a/layout/generic/crashtests/1221904.html b/layout/generic/crashtests/1221904.html
new file mode 100644
index 0000000000..e303234daa
--- /dev/null
+++ b/layout/generic/crashtests/1221904.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var x = "K\u0756 ";
+ var y = "K\u000A ";
+
+ var t = document.createTextNode(x);
+ d.appendChild(t);
+ document.documentElement.offsetHeight;
+ t.data = y;
+}
+
+
+</script>
+</head>
+<body onload="boom();">
+<div id="d"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1222783.xhtml b/layout/generic/crashtests/1222783.xhtml
new file mode 100644
index 0000000000..29cba980f9
--- /dev/null
+++ b/layout/generic/crashtests/1222783.xhtml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<title>Test, bug 1222783</title>
+<body>
+
+
+<div id="container" style="width: 400px">
+ <div id="float1" style="float: left; height: 50px; width: 50px"></div>
+ <div id="float2" style="float: left; clear: left; height: 50px; width: 200px"></div>
+
+ <frameset cols="50%,50%">
+ <frame></frame>
+ <frame></frame>
+ </frameset>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1223522.xhtml b/layout/generic/crashtests/1223522.xhtml
new file mode 100644
index 0000000000..cdcc69598b
--- /dev/null
+++ b/layout/generic/crashtests/1223522.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<td style="writing-mode: vertical-lr;"><col style="float: left; visibility: collapse;"><tr style="display: -moz-inline-box;"><div style="display: ruby-base-container;"><div style="display: list-item;"><td style="float: right;"></td></div></div></tr></col></td>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1223568-1.html b/layout/generic/crashtests/1223568-1.html
new file mode 100644
index 0000000000..8d09d878b7
--- /dev/null
+++ b/layout/generic/crashtests/1223568-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="display:flex; justify-content: stretch end">a
diff --git a/layout/generic/crashtests/1223568-2.html b/layout/generic/crashtests/1223568-2.html
new file mode 100644
index 0000000000..6b44e485ca
--- /dev/null
+++ b/layout/generic/crashtests/1223568-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<span style="justify-content: stretch end true; display: inline-flex;"><span style="writing-mode: vertical-rl; display: -moz-inline-box;">f</span></span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1224230-1.html b/layout/generic/crashtests/1224230-1.html
new file mode 100644
index 0000000000..2743b43aeb
--- /dev/null
+++ b/layout/generic/crashtests/1224230-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script>
+function boom() {
+ var div = document.querySelector("div");
+ div.offsetHeight;
+ div.style.fontSize = "120%";
+}
+</script>
+<body onload="boom()">
+<div style="float:left; position:relative;">
+ <div style="display:inline;">
+ <img src="foo.jpg" style="float:left;">
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam tortor nulla,
+eleifend eu eleifend eu, scelerisque sit amet sapien. Sed iaculis tellus ut quam
+pharetra consequat. Donec vitae nulla eu mi porta vulputate. In vestibulum, erat
+quis aliquam tempor, lectus augue viverra justo, vitae semper nibh neque tempor
+orci. Etiam luctus aliquet magna id pellentesque. Interdum et malesuada fames ac
+ante ipsum primis in faucibus. Suspendisse sit amet eros volutpat, convallis
+purus non, porta sapien. Duis fermentum at tortor nec ultricies. Morbi et lacus
+vitae risus elementum condimentum quis vitae justo. Cum sociis natoque penatibus
+et magnis dis parturient montes, nascetur ridiculus mus.<br><br>
diff --git a/layout/generic/crashtests/1225005.html b/layout/generic/crashtests/1225005.html
new file mode 100644
index 0000000000..8608e033de
--- /dev/null
+++ b/layout/generic/crashtests/1225005.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="padding: 810520769306363pt; column-count: 10; transform: translate(50%);"><div style="position: absolute;"></div></body>
+</html>
diff --git a/layout/generic/crashtests/1225118.html b/layout/generic/crashtests/1225118.html
new file mode 100644
index 0000000000..833596243f
--- /dev/null
+++ b/layout/generic/crashtests/1225118.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html style="width: -moz-fit-content; display: grid;">
+<body style="border: 1px solid green; padding-right: 56030668px;"></body>
+</html>
diff --git a/layout/generic/crashtests/1225376.html b/layout/generic/crashtests/1225376.html
new file mode 100644
index 0000000000..f6e35be3ee
--- /dev/null
+++ b/layout/generic/crashtests/1225376.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div style="display: inline-grid;">
+ <div style="display: flex; align-self: right safe;"></div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1225592.html b/layout/generic/crashtests/1225592.html
new file mode 100644
index 0000000000..e4b5e6e6d9
--- /dev/null
+++ b/layout/generic/crashtests/1225592.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="grid-template-columns: repeat(16384, auto); display: grid;"></div>
+<div style="grid-template-columns: repeat(9000, auto) repeat(9000, auto); display: grid;"></div>
+
+<div style="grid-template-columns: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ; display: grid;"></div>
+
+
+<div style='grid-template-areas: ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "; display: grid;'></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1229437-1.html b/layout/generic/crashtests/1229437-1.html
new file mode 100644
index 0000000000..a2ecadf455
--- /dev/null
+++ b/layout/generic/crashtests/1229437-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<style>
+ rbc { display: ruby-base-container; }
+</style>
+<div style="column-width: 1px">
+ <ruby><rbc></rbc><rb><div style="float: right"></div></rb></ruby>X
+</div>
diff --git a/layout/generic/crashtests/1229437-2.html b/layout/generic/crashtests/1229437-2.html
new file mode 100644
index 0000000000..429c857a16
--- /dev/null
+++ b/layout/generic/crashtests/1229437-2.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<div style="column-width: 1px">
+ <ruby><rb></rb><rb><div style="float: right"></div></rb></ruby>X
+</div>
diff --git a/layout/generic/crashtests/1230378.xhtml b/layout/generic/crashtests/1230378.xhtml
new file mode 100644
index 0000000000..46331df282
--- /dev/null
+++ b/layout/generic/crashtests/1230378.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+
+body { font-family: monospace; }
+.k { column-count: 2; height:2em; line-height:1em;}
+.fl { float: left; }
+
+</style>
+</head>
+<body>
+
+<caption class="k"><br /><br />A<div class="fl"><table>B C D E F</table><table></table></div></caption>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1231692-1.html b/layout/generic/crashtests/1231692-1.html
new file mode 100644
index 0000000000..0ed4b82d9f
--- /dev/null
+++ b/layout/generic/crashtests/1231692-1.html
@@ -0,0 +1,5 @@
+<dt oncanshowcurrentframe="event.target.parentNode.contentEditable = 'true')" style="white-space: initial;"><caption style="display: table-row-group;overflow: scroll;direction: rtl;unicode-bidi: embed;ime-mode: active;text-rendering: inherit;text-align: none;text-indent: -200px;width: min-content;line-height: 100px;white-space: nowrap;word-spacing: 80%;word-wrap: normal;letter-spacing: 20%;text-overflow: initial;text-decoration: line-through;"><dt style="display: table-footer-group;overflow: visible;direction: rtl;unicode-bidi: normal;ime-mode: initial;text-rendering: initial;text-align: -moz-center;text-indent: 200;width: initial;line-height: max-content;white-space: pre-wrap;word-spacing: -100px;word-wrap: normal;letter-spacing: 10em;text-overflow: clip;text-decoration: none;"></dt><ul style="display: table;overflow: auto;direction: initial;unicode-bidi: initial;ime-mode: inactive;text-rendering: optimizeLegibility;text-align: none;text-indent: 0;width: initial;line-height: 120%;white-space: initial;word-spacing: -100px;word-wrap: normal;letter-spacing: initial;text-overflow: moz-ui-ellipsis;text-decoration: initial;"></ul><ul style="display: table-header-group;overflow: initial;direction: initial;unicode-bidi: bidi-override;ime-mode: disabled;text-rendering: inherit;text-align: justify;text-indent: 50%;width: initial;line-height: 10em;white-space: pre-wrap;word-spacing: -100px;word-wrap: initial;letter-spacing: 80%;text-overflow: ellipsis-word;text-decoration: blink;"><form name=f style="display: inline-table;overflow: hidden;direction: ltr;unicode-bidi: inherit;ime-mode: active;text-rendering: initial;text-align: justify;text-indent: initial;width: initial;line-height: initial;white-space: nowrap;word-spacing: initial;word-wrap: break-word;letter-spacing: 10em;text-overflow: ellipsis-word;text-decoration: overline;"><tbody style="display: inline-table;overflow: clip;direction: auto;unicode-bidi: normal;ime-mode: initial;text-rendering: auto;text-align: none;text-indent: 20;width: 100px;line-height: -moz-available;white-space: nowrap;word-spacing: initial;word-wrap: initial;letter-spacing: 10em;text-overflow: initial;text-decoration: line-through;"></tbody><basefont style="display: initial;overflow: scroll;direction: ltr;unicode-bidi: initial;ime-mode: initial;text-rendering: -1;text-align: right;text-indent: inherit;width: 20%;line-height: -moz-fit-content;white-space: pre-wrap;word-spacing: 20%;word-wrap: normal;letter-spacing: -100px;text-overflow: initial;text-decoration: none;"></basefont>*::first-letter {text-transform: uppercase; background-color:red; font-size:600%;box-shadow: 2px 2pt 0px orange;}
+</style><td colspan="5" style="display: table-row;overflow: initial;direction: rtl;unicode-bidi: bidi-override;ime-mode: initial;text-rendering: optimizeLegibility;text-align: -moz-right;text-indent: inherit;width: min-content;line-height: 20%;white-space: pre-wrap;word-spacing: 80%;word-wrap: initial;letter-spacing: initial;text-overflow: clip;text-decoration: none;"><style id="e">*::first-letter {text-transform: uppercase; background-color:red; font-size:600%;box-shadow: 2px 2pt 0px orange;}
+</style></input><strike style="display: inline-block;overflow: hidden;direction: rtl;unicode-bidi: bidi-override;ime-mode: inactive;text-rendering: auto;text-align: justify;text-indent: -9999999999999999px;width: initial;line-height: 999999999px;white-space: pre-wrap;word-spacing: 120%;word-wrap: initial;letter-spacing: 80%;text-overflow: initial;text-decoration: underline;"><address style="display: block;overflow: clip;direction: rtl;unicode-bidi: normal;ime-mode: inactive;text-rendering: initial;text-align: char;text-indent: -20%;width: initial;line-height: initial;white-space: pre-wrap;word-spacing: 10em;word-wrap: initial;letter-spacing: 120%;text-overflow: ellipsis;text-decoration: initial;"></address><thead style="display: table-column;overflow: scroll;direction: auto;unicode-bidi: inherit;ime-mode: disabled;text-rendering: optimizeLegibility;text-align: -moz-left;text-indent: -200px;width: -moz-fit-content;line-height: initial;white-space: pre-wrap;word-spacing: -10%;word-wrap: initial;letter-spacing: -10%;text-overflow: initial;text-decoration: overline;"></thead></audio></td>a</label><audio type="application/ogg style="display: table-column;overflow: scroll;direction: rtl;unicode-bidi: bidi-override;ime-mode: inactive;text-rendering: -1;text-align: initial;text-indent: initial;width: inherit;line-height: 0;white-space: pre;word-spacing: 80%;word-wrap: normal;letter-spacing: -10%;text-overflow: ellipsis;text-decoration: none;"><abbr style="display: run-in;overflow: scroll;direction: initial;unicode-bidi: initial;ime-mode: initial;text-rendering: geometricPrecision;text-align: initial;text-indent: 0;width: 80%;line-height: initial;white-space: normal;word-spacing: 0;word-wrap: initial;letter-spacing: 10em;text-overflow: clip;text-decoration: blink;"><h2 style="display: run-in;overflow: auto;direction: initial;unicode-bidi: bidi-override;ime-mode: disabled;text-rendering: initial;text-align: right;text-indent: 200%;width: 10em;line-height: initial;white-space: pre-wrap;word-spacing: 20%;word-wrap: break-word;letter-spacing: 120%;text-overflow: initial;text-decoration: none;">a<style id="e">*::first-letter {float: right; text-transform: uppercase; background-color:red; font-size:600%;box-shadow: 2px 2pt 0px purple, -29px -1em 0px yellow;}
+</style></h2></input></nobr></hr><ol style="display: list-item;overflow: visible;direction: ltr;unicode-bidi: normal;ime-mode: auto;text-rendering: geometricPrecision;text-align: -moz-right;text-indent: -200px;width: initial;line-height: 120%;white-space: normal;word-spacing: 120%;word-wrap: normal;letter-spacing: -100px;text-overflow: clip;text-decoration: blink;"><kbd style="display: table-cell;overflow: auto;direction: ltr;unicode-bidi: embed;ime-mode: disabled;text-rendering: initial;text-align: -moz-right;text-indent: -20%;width: 999999999px;line-height: 80%;white-space: pre-line;word-spacing: -100px;word-wrap: break-word;letter-spacing: 20%;text-overflow: ellipsis-word;text-decoration: blink;"></kbd><style id="e">*::first-line { text-transform: uppercase; background-color:green; position:fixed; font-size:110%; height: 110%;}</style><input type="file" style="display: table-row-group;overflow: auto;direction: ltr;unicode-bidi: initial;ime-mode: auto;text-rendering: -1;text-align: none;text-indent: initial;width: initial;line-height: 80%;white-space: pre;word-spacing: 20%;word-wrap: normal;letter-spacing: 10em;text-overflow: clip;text-decoration: initial;"><font style="display: table-header-group;overflow: clip;direction: ltr;unicode-bidi: embed;ime-mode: disabled;text-rendering: inherit;text-align: initial;text-indent: -20%;width: 80%;line-height: -moz-available;white-space: pre;word-spacing: 0;word-wrap: break-word;letter-spacing: 10em;text-overflow: ellipsis;text-decoration: none;"><spacer type="block" style="display: table-footer-group;overflow: initial;direction: auto;unicode-bidi: inherit;ime-mode: inactive;text-rendering: initial;text-align: justify;text-indent: inherit;width: 80%;line-height: min-content;white-space: pre;word-spacing: -100px;word-wrap: initial;letter-spacing: 80%;text-overflow: clip;text-decoration: blink;"></strike><option style="display: table-row;overflow: auto;direction: auto;unicode-bidi: embed;ime-mode: disabled;text-rendering: geometricPrecision;text-align: left;text-indent: -200px;width: 80%;line-height: 0;white-space: pre-line;word-spacing: -100px;word-wrap: normal;letter-spacing: -10%;text-overflow: clip;text-decoration: line-through;"><object style="display: none;overflow: initial;direction: ltr;unicode-bidi: embed;ime-mode: initial;text-rendering: optimizeSpeed;text-align: -moz-center;text-indent: 50%;width: initial;line-height: 10em;white-space: nowrap;word-spacing: 20%;word-wrap: initial;letter-spacing: -100px;text-overflow: ellipsis;text-decoration: blink;">a</object>a</wbr></li></textarea><spacer style="display: inline-table;overflow: auto;direction: auto;unicode-bidi: initial;ime-mode: disabled;text-rendering: optimizeSpeed;text-align: none;text-indent: initial;width: inherit;line-height: 20%;white-space: pre;word-spacing: 10em;word-wrap: break-word;letter-spacing: 120%;text-overflow: ellipsis-word;text-decoration: initial;"><b style="display: none;overflow: hidden;direction: ltr;unicode-bidi: embed;ime-mode: enabled;text-rendering: -1;text-align: char;text-indent: -200px;width: min-content;line-height: min-content;white-space: pre-wrap;word-spacing: 120%;word-wrap: break-word;letter-spacing: 20%;text-overflow: clip;text-decoration: underline;"></b><iframe style="display: inline;overflow: hidden;direction: initial;unicode-bidi: normal;ime-mode: auto;text-rendering: inherit;text-align: -moz-right;text-indent: 0;width: 120%;line-height: 20%;white-space: nowrap;word-spacing: 120%;word-wrap: normal;letter-spacing: 10em;text-overflow: ellipsis;text-decoration: underline;">a</iframe>a</td></marquee></tr><abbr style="display: none;overflow: scroll;direction: initial;unicode-bidi: bidi-override;ime-mode: inactive;text-rendering: initial;text-align: inherit;text-indent: 200;width: initial;line-height: 120%;white-space: pre-wrap;word-spacing: 0;word-wrap: normal;letter-spacing: 120%;text-overflow: ellipsis;text-decoration: initial;"><textarea style="display: table-column;overflow: auto;direction: rtl;unicode-bidi: inherit;ime-mode: enabled;text-rendering: optimizeLegibility;text-align: inherit;text-indent: 99999999999999999999px;width: 20%;line-height: min-content;white-space: pre-wrap;word-spacing: -10%;word-wrap: normal;letter-spacing: -100px;text-overflow: moz-ui-ellipsis;text-decoration: none;"></textarea><style id="e">*::before { content:"before textbefore textbefore textbefore textbefore textbefore text"; float:right;border:3px solid black;font-size: 10px;width:80%; box-shadow: 20px 2pt 0px blue;}\</style></ul></textarea><font style="display: table-header-group;overflow: scroll;direction: rtl;unicode-bidi: inherit;ime-mode: active;text-rendering: auto;text-align: -moz-left;text-indent: -9999999999999999px;width: max-content;line-height: min-content;white-space: nowrap;word-spacing: 120%;word-wrap: initial;letter-spacing: 120%;text-overflow: ellipsis;text-decoration: initial;">a</abbr>a</input><tbody style="display: run-in;overflow: auto;direction: rtl;unicode-bidi: inherit;ime-mode: inactive;text-rendering: geometricPrecision;text-align: justify;text-indent: -200px;width: initial;line-height: -moz-fit-content;white-space: initial;word-spacing: 20%;word-wrap: break-word;letter-spacing: initial;text-overflow: moz-ui-ellipsis;text-decoration: none;">a</tbody></section></object></canvas><canvas style="display: list-item;overflow: initial;direction: rtl;unicode-bidi: bidi-override;ime-mode: enabled;text-rendering: geometricPrecision;text-align: justify;text-indent: -200px;width: initial;line-height: inherit;white-space: initial;word-spacing: 120%;word-wrap: normal;letter-spacing: -10%;text-overflow: initial;text-decoration: line-through;"><xmp style="display: none;overflow: visible;direction: rtl;unicode-bidi: bidi-override;ime-mode: initial;text-rendering: -1;text-align: left;text-indent: initial;width: 999999999px;line-height: min-content;white-space: pre;word-spacing: 10em;word-wrap: break-word;letter-spacing: initial;text-overflow: ellipsis-word;text-decoration: blink;"><fieldset style="display: inherit;overflow: auto;direction: rtl;unicode-bidi: initial;ime-mode: enabled;text-rendering: inherit;text-align: -moz-left;text-indent: initial;width: -moz-fit-content;line-height: 10em;white-space: pre;word-spacing: 80%;word-wrap: break-word;letter-spacing: 10em;text-overflow: moz-ui-ellipsis;text-decoration: overline;"><fieldset style="display: table-column;overflow: visible;direction: ltr;unicode-bidi: normal;ime-mode: auto;text-rendering: auto;text-align: -moz-center;text-indent: -9999999999999999px;width: initial;line-height: 120%;white-space: pre-wrap;word-spacing: -10%;word-wrap: break-word;letter-spacing: 0;text-overflow: ellipsis;text-decoration: blink;">a</fieldset><style id="e">*::first-line { text-transform: uppercase; background-color:green; font-size:110%; height: 110%;box-shadow: 2ex 2pt 0px blue;}</style>a</fieldset></section></small><code style="display: list-item;overflow: scroll;direction: rtl;unicode-bidi: bidi-override;ime-mode: active;text-rendering: optimizeLegibility;text-align: -moz-right;text-indent: 0;width: min-content;line-height: min-content;white-space: normal;word-spacing: 0;word-wrap: normal;letter-spacing: initial;text-overflow: clip;text-decoration: initial;"></option></basefont><area style="display: inline-table;overflow: hidden;direction: auto;unicode-bidi: embed;ime-mode: auto;text-rendering: optimizeLegibility;text-align: right;text-indent: 200;width: 120%;line-height: 120%;white-space: normal;word-spacing: 120%;word-wrap: initial;letter-spacing: initial;text-overflow: initial;text-decoration: underline;"></area><dd style="display: table-row;overflow: visible;direction: rtl;unicode-bidi: initial;ime-mode: disabled;text-rendering: optimizeLegibility;text-align: none;text-indent: -9999999999999999px;width: 100px;line-height: max-content;white-space: initial;word-spacing: initial;word-wrap: break-word;letter-spacing: -100px;text-overflow: clip;text-decoration: initial;"><textarea style="display: inline-block;overflow: auto;direction: rtl;unicode-bidi: initial;ime-mode: active;text-rendering: optimizeSpeed;text-align: char;text-indent: 50%;width: 20%;line-height: max-content;white-space: initial;word-spacing: -100px;word-wrap: break-word;letter-spacing: 120%;text-overflow: ellipsis-word;text-decoration: initial;"></form></abbr><blockquote style="display: table-row;overflow: scroll;direction: ltr;unicode-bidi: bidi-override;ime-mode: auto;text-rendering: geometricPrecision;text-align: left;text-indent: 99999999999999999999px;width: 0;line-height: 120%;white-space: pre-line;word-spacing: initial;word-wrap: initial;letter-spacing: -10%;text-overflow: ellipsis;text-decoration: initial;">a</p>a</tfoot></canvas></tr>*::first-letter {text-transform: uppercase; background-color:red; font-size:600%;box-shadow: 2px 2pt 0px orange;}
+</style><input type="file" style="display: inline-block;overflow: auto;direction: rtl;unicode-bidi: embed;ime-mode: inactive;text-rendering: optimizeSpeed;text-align: -moz-center;text-indent: -20%;width: min-content;line-height: 120%;white-space: inherit;word-spacing: -100px;word-wrap: initial;letter-spacing: 80%;text-overflow: ellipsis;text-decoration: line-through;"><a href="javascript:window.frameElement.parentNode.removeChild(window.frameElement)" style="display: inline-block;overflow: scroll;direction: rtl;unicode-bidi: normal;ime-mode: auto;text-rendering: geometricPrecision;text-align: initial;text-indent: inherit;width: initial;line-height: initial;white-space: nowrap;word-spacing: initial;word-wrap: break-word;letter-spacing: -100px;text-overflow: clip;text-decoration: initial;">a<style id="e">*::after { content:"before textbefore textbefore textbefore textbefore textbefore textbefore text"; position: fixed;border:3px solid black;font-size: 10px;width:80%;}\</style></body><hr style="display: block;overflow: auto;direction: initial;unicode-bidi: bidi-override;ime-mode: active;text-rendering: optimizeLegibility;text-align: initial;text-indent: 20;width: initial;line-height: 100px;white-space: pre;word-spacing: 120%;word-wrap: normal;letter-spacing: 80%;text-overflow: moz-ui-ellipsis;text-decoration: none;"><h3 style="display: none;overflow: hidden;direction: ltr;unicode-bidi: normal;ime-mode: initial;text-rendering: auto;text-align: right;text-indent: -20%;width: 100px;line-height: -moz-fit-content;white-space: pre;word-spacing: 20%;word-wrap: break-word;letter-spacing: 120%;text-overflow: ellipsis;text-decoration: initial;"><menu style="display: list-item;overflow: clip;direction: rtl;unicode-bidi: bidi-override;ime-mode: enabled;text-rendering: initial;text-align: none;text-indent: 20;width: initial;line-height: initial;white-space: pre-line;word-spacing: initial;word-wrap: initial;letter-spacing: -100px;text-overflow: moz-ui-ellipsis;text-decoration: initial;">a</menu>a</input><input type="reset" style="display: inline-table;overflow: auto;direction: rtl;unicode-bidi: initial;ime-mode: active;text-rendering: inherit;text-align: -moz-left;text-indent: -20%;width: inherit;line-height: initial;white-space: pre-wrap;word-spacing: -100px;word-wrap: break-word;letter-spacing: 10em;text-overflow: clip;text-decoration: initial;"><dt style="display: table-cell;overflow: hidden;direction: ltr;unicode-bidi: normal;ime-mode: auto;text-rendering: -1;text-align: initial;text-indent: -20%;width: initial;line-height: initial;white-space: pre-line;word-spacing: 10em;word-wrap: normal;letter-spacing: 20%;text-overflow: initial;text-decoration: line-through;"><input type="hidden" style="display: initial;overflow: initial;direction: auto;unicode-bidi: bidi-override;ime-mode: active;text-rendering: inherit;text-align: initial;text-indent: 20;width: 999999999px;line-height: 0;white-space: normal;word-spacing: -10%;word-wrap: break-word;letter-spacing: -10%;text-overflow: clip;text-decoration: line-through;"><section style="display: table;overflow: initial;direction: ltr;unicode-bidi: embed;ime-mode: initial;text-rendering: optimizeSpeed;text-align: justify;text-indent: inherit;width: initial;line-height: 0;white-space: nowrap;word-spacing: 120%;word-wrap: initial;letter-spacing: 0;text-overflow: ellipsis;text-decoration: underline;"><option style="display: inline;overflow: scroll;direction: ltr;unicode-bidi: embed;ime-mode: inactive;text-rendering: optimizeLegibility;text-align: justify;text-indent: -200px;width: initial;line-height: -moz-available;white-space: pre-wrap;word-spacing: -100px;word-wrap: initial;letter-spacing: 20%;text-overflow: initial;text-decoration: line-through;"><script style="display: block;overflow: hidden;direction: ltr;unicode-bidi: inherit;ime-mode: active;text-rendering: initial;text-align: -moz-left;text-indent: -200px;width: -moz-available;line-height: -moz-fit-content;white-space: pre;word-spacing: 10em;word-wrap: break-word;letter-spacing: 120%;text-overflow: ellipsis-word;text-decoration: blink;"></script><strong style="display: table-row-group;overflow: scroll;direction: ltr;unicode-bidi: inherit;ime-mode: enabled;text-rendering: optimizeLegibility;text-align: left;text-indent: 200;width: 100px;line-height: initial;white-space: inherit;word-spacing: 0;word-wrap: initial;letter-spacing: -10%;text-overflow: clip;text-decoration: overline;"></strong>a</em></textarea></script>a</u></audio><input type="radio" style="display: table-row;overflow: hidden;direction: initial;unicode-bidi: bidi-override;ime-mode: enabled;text-rendering: initial;text-align: initial;text-indent: 200%;width: 120%;line-height: -moz-available;white-space: pre-wrap;word-spacing: -100px;word-wrap: break-word;letter-spacing: 80%;text-overflow: ellipsis;text-decoration: initial;">
diff --git a/layout/generic/crashtests/1233191.html b/layout/generic/crashtests/1233191.html
new file mode 100644
index 0000000000..6a6a06edf9
--- /dev/null
+++ b/layout/generic/crashtests/1233191.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="width: 1px;"><fieldset style="display: grid;">y<legend></legend>x</fieldset></div>
+<div style="width: 1px;"><fieldset style="display: grid; overflow:hidden">y<legend></legend>x</fieldset></div>
+<div style="width: 1px;"><fieldset style="display: flex;">y<legend></legend>x</fieldset></div>
+<div style="width: 1px;"><fieldset style="display: flex; overflow:hidden">y<legend></legend>x</fieldset></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1233607.html b/layout/generic/crashtests/1233607.html
new file mode 100644
index 0000000000..bc3ecc852c
--- /dev/null
+++ b/layout/generic/crashtests/1233607.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html style="width: 1px;">
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="x.style.color='red';">
+<div id="x" style="overflow: scroll;"><input type="image" style="display: contents;"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1234701-1.html b/layout/generic/crashtests/1234701-1.html
new file mode 100644
index 0000000000..237ec295f9
--- /dev/null
+++ b/layout/generic/crashtests/1234701-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+#a { display: inline-block; height: 30px; }
+#b { float: left; writing-mode: vertical-lr; border-top-style: solid; }
+#c { float: left; height: 28px; }
+
+</style>
+</head>
+<body onload="a.appendChild(b)">
+
+<div id="a"></div>
+<div id="b"><span id="c"></span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1234701-2.html b/layout/generic/crashtests/1234701-2.html
new file mode 100644
index 0000000000..762427627f
--- /dev/null
+++ b/layout/generic/crashtests/1234701-2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+#a { display: inline-block; height: 30px; }
+#b { float: left; writing-mode: vertical-lr; border-top-style: solid; }
+#c { float: left; height: 28px; }
+
+</style>
+</head>
+<body>
+
+<div id="a"><div id="b"><span id="c"></span></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1248227.html b/layout/generic/crashtests/1248227.html
new file mode 100644
index 0000000000..2b2f3ff982
--- /dev/null
+++ b/layout/generic/crashtests/1248227.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+<div style="display: grid; grid-template-rows: subgrid repeat(auto-fill, []);"></div>
+
+<div style="display: grid">
+ <div style="display: grid; grid-template-rows: subgrid repeat(auto-fill, []);"></div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1271765.html b/layout/generic/crashtests/1271765.html
new file mode 100644
index 0000000000..729d2bd60b
--- /dev/null
+++ b/layout/generic/crashtests/1271765.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<body>
+<div id="content" class="entry">
+ <audio controls style="writing-mode: vertical-lr"></audio>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1272983-1.html b/layout/generic/crashtests/1272983-1.html
new file mode 100644
index 0000000000..32a5a302ee
--- /dev/null
+++ b/layout/generic/crashtests/1272983-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ div.foo {
+ margin-bottom: 50%;
+ display: -moz-box;
+ direction: rtl;
+ }
+ </style>
+</head>
+<body>
+ <div class="foo"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1272983-2.html b/layout/generic/crashtests/1272983-2.html
new file mode 100644
index 0000000000..cff041891a
--- /dev/null
+++ b/layout/generic/crashtests/1272983-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ div.foo {
+ padding-bottom: 50%;
+ display: -moz-box;
+ direction: rtl;
+ }
+ </style>
+</head>
+<body>
+ <div class="foo"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1275059.html b/layout/generic/crashtests/1275059.html
new file mode 100644
index 0000000000..bfcbfa6beb
--- /dev/null
+++ b/layout/generic/crashtests/1275059.html
@@ -0,0 +1,3 @@
+<div style="writing-mode: vertical-rl">
+<span style="text-combine-upright: all">
+<p>
diff --git a/layout/generic/crashtests/1278007.html b/layout/generic/crashtests/1278007.html
new file mode 100644
index 0000000000..f4e9e40da1
--- /dev/null
+++ b/layout/generic/crashtests/1278007.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title></title>
+ <style type="text/css">
+.grid {
+ display: grid;
+}
+
+.sfb { align-self:baseline; }
+.slb { align-self:last baseline; }
+
+.vl { writing-mode: vertical-lr; }
+
+</style>
+</head>
+<body>
+
+<div class="grid"><input class="slb"></div>
+<div class="grid"><input class="sfb"></div>
+
+<div class="grid"><input class="slb vl"></div>
+<div class="grid"><input class="sfb vl"></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1278080.html b/layout/generic/crashtests/1278080.html
new file mode 100644
index 0000000000..b9cb65fcbc
--- /dev/null
+++ b/layout/generic/crashtests/1278080.html
@@ -0,0 +1,29 @@
+<style>
+.columns {
+ columns: 5;
+ column-fill: auto;
+ height: 100px;
+}
+.grid {
+ display: grid;
+ max-height: 180px;
+ grid-auto-rows: 30px;
+ grid-gap: 12px;
+}
+span {
+ grid-row: 2;
+}
+i {
+ display: block;
+ height: 60px;
+}
+</style>
+<div class="columns"><div class="grid"><span><i></i></span></div></div>
+<script>
+window.onload = function(){
+ var x = document.createElementNS("http://www.w3.org/1999/xhtml", "x");
+ var r = new Range();
+ r.selectNode(document.getElementsByTagName('span')[0]);
+ setTimeout(function(){ r.surroundContents(x); }, 1);
+};
+</script>
diff --git a/layout/generic/crashtests/1278461-1.html b/layout/generic/crashtests/1278461-1.html
new file mode 100644
index 0000000000..acafb25907
--- /dev/null
+++ b/layout/generic/crashtests/1278461-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1252">
+<script>
+
+function boom()
+{
+ x.style.display = "contents";
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+ <div style="column-width: 1px;">
+ <div style="display: grid;">
+ <div style="height: 200px;">v</div>
+ <div style="height: 200px;" id="x">x</div>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1278461-2.html b/layout/generic/crashtests/1278461-2.html
new file mode 100644
index 0000000000..68f43ef27e
--- /dev/null
+++ b/layout/generic/crashtests/1278461-2.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait"><head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1252"></head><body>
+<style>
+html{columns:50px auto}
+body{display:grid}
+div{height:200px}
+</style>
+<script>
+window.onload=function(){
+var b = document.body, v;
+b.appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'div'));
+b.appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'div'));
+b.appendChild(v = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'));
+setTimeout(function(){
+ v.style.overflow = "hidden";
+ document.documentElement.className = '';
+ },100);
+};
+</script>
+
+<div></div><div></div><div style="overflow: hidden;"></div></body></html>
diff --git a/layout/generic/crashtests/1279814.html b/layout/generic/crashtests/1279814.html
new file mode 100644
index 0000000000..71a9a6e3b4
--- /dev/null
+++ b/layout/generic/crashtests/1279814.html
@@ -0,0 +1,35 @@
+<!-- 128 LRI / RLI -->
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+<!-- 64 PDI -->
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
+<!-- 64 LRI / RLI -->
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
+&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
diff --git a/layout/generic/crashtests/1281102.html b/layout/generic/crashtests/1281102.html
new file mode 100644
index 0000000000..5b1c753fb0
--- /dev/null
+++ b/layout/generic/crashtests/1281102.html
@@ -0,0 +1,30 @@
+<script>
+function start() {
+ o46=(new DOMParser()).parseFromString("<menu><li></li>a a",'text/html');
+ document.replaceChild(o46.documentElement,document.documentElement);
+ o66=document.createElement('form');
+ o100=document.createElement('table');
+ o119=document.createElement('iframe');
+ o66.appendChild(o119);
+ o135=document.createElement('tfoot');
+ o100.appendChild(o135);
+ document.body.appendChild(o100);
+ o66.style.display='grid';
+ o281=document.createElement('table');
+ o135.appendChild(o281);
+ o310=document.createTextNode("{ }");
+ o119.style.pageBreakBefore='left';
+ o418=document.createElement('th');
+ o100.appendChild(o310);
+ o281.appendChild(o418);
+ o135.style.display='table-row-group';
+ document.documentElement.style.columnCount='59';
+ setTimeout(f2, 4);
+}
+function f2() {
+ o100.setAttribute('style'," transition-delay: 128ms; padding-right: 5rem");
+ o418.appendChild(o66);
+ setTimeout("location.reload()",800);
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/generic/crashtests/1297427-non-equal-centers.html b/layout/generic/crashtests/1297427-non-equal-centers.html
new file mode 100644
index 0000000000..e51c8df324
--- /dev/null
+++ b/layout/generic/crashtests/1297427-non-equal-centers.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<meta charset="utf-8" />
+<style>
+#box {
+ border-radius: 335px;
+ width: 600px;
+ height: 401px;
+ border-style: dotted;
+ border-width: 41px 1px;
+}
+</style>
+<div id="box"></div>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1304441.html b/layout/generic/crashtests/1304441.html
new file mode 100644
index 0000000000..26d7dcdd6e
--- /dev/null
+++ b/layout/generic/crashtests/1304441.html
@@ -0,0 +1,9 @@
+<details>
+<summary>
+<li>
+<style>
+summary{
+all:initial
+}
+:first-child::first-line
+{}
diff --git a/layout/generic/crashtests/1308876-1.html b/layout/generic/crashtests/1308876-1.html
new file mode 100644
index 0000000000..2628e0aef6
--- /dev/null
+++ b/layout/generic/crashtests/1308876-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<style>
+* { margin: 0; padding: 0; }
+div.content { width: 370px; display: inline-block; }
+</style>
+<div class="content">
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+ <div class="content">hello
+</div>
diff --git a/layout/generic/crashtests/1316649.html b/layout/generic/crashtests/1316649.html
new file mode 100644
index 0000000000..cfef7d0f0d
--- /dev/null
+++ b/layout/generic/crashtests/1316649.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<script>
+addEventListener('DOMContentLoaded', function(){
+ obj1.style.writingMode = "rl";
+ obj2.style.writingMode = "rl";
+ //obj3.style.writingMode = "rl";
+ //obj4.style.writingMode = "rl";
+ obj5.style.writingMode = "rl";
+ sobj1.style.writingMode = "rl";
+ sobj2.style.writingMode = "rl";
+ //sobj3.style.writingMode = "rl";
+ //sobj4.style.writingMode = "rl";
+ sobj5.style.writingMode = "rl";
+});
+</script>
+<body style="writing-mode:tb">
+<button style="display:grid">
+<div id=obj1></div>
+</button>
+<button style="display:inline-grid">
+<div id=obj2></div>
+</button>
+<!--
+<button style="display:flex">
+<div id=obj3></div>
+</button>
+<button style="display:inline-flex">
+<div id=obj4></div>
+</button>
+-->
+<button style="columns:2">
+<div id=obj5></div>
+</button>
+
+<button style="display:grid; overflow:hidden">
+<div id=sobj1></div>
+</button>
+<button style="display:inline-grid; overflow:hidden">
+<div id=sobj2></div>
+</button>
+<!--
+<button style="display:flex; overflow:hidden">
+<div id=sobj3></div>
+</button>
+<button style="display:inline-flex; overflow:hidden">
+<div id=sobj4></div>
+</button>
+-->
+<button style="columns:2; overflow:hidden">
+<div id=sobj5></div>
+</button>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1316884-1.html b/layout/generic/crashtests/1316884-1.html
new file mode 100644
index 0000000000..edcca7626f
--- /dev/null
+++ b/layout/generic/crashtests/1316884-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="UTF-8">
+<script>
+addEventListener("DOMContentLoaded", function(){
+ setTimeout(function(){
+ o_0.appendChild(document.createElement("span"));
+ document.documentElement.removeAttribute("class");
+ }, 0);
+});
+</script>
+<body style="columns: 5; column-fill: auto; height: 100px;">
+<div style="display: grid; grid-auto-rows: 30px; grid-template-rows: 5px min-content 50px;">
+<span style="page-break-inside: avoid; grid-row: 2; height: 60px;">
+<div id=o_0 style="display: grid; grid-gap: 0; grid-template-rows: min-content 50px; grid-auto-rows: 30px;">
+<span style="grid-row: 1/span 4;"></span>
+</div>
+</span>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1343552-1.html b/layout/generic/crashtests/1343552-1.html
new file mode 100644
index 0000000000..fafe8930c7
--- /dev/null
+++ b/layout/generic/crashtests/1343552-1.html
@@ -0,0 +1,32 @@
+<style>
+.class1 { float: left; white-space: pre-line; }
+.class2 { border-bottom-style: solid; font-face: Arial; font-size: 7ex; }
+</style>
+<script>
+function go() {
+ menuitem.appendChild(document.body.firstChild);
+ canvas.toBlob(callback);
+}
+function callback() {
+ var s = menu.style;
+ s.setProperty("flex-direction", "row-reverse");
+ option.scrollBy();
+ document.implementation.createHTMLDocument("foo").adoptNode(progress);
+ s.setProperty("flex-direction", "column");
+ canvas.toBlob(callback);
+}
+</script>
+aaaaaaaaaaaaaaaaaa
+</head>
+<body onload=go()>
+<del class="class1">
+<span class="class2">
+<menu id="menu">
+<menuitem>
+</menu>
+<menuitem id="menuitem">
+<progress id="progress">
+</del>
+<ol dir="rtl">l+0</ol>
+<canvas id="canvas">
+<option id="option">
diff --git a/layout/generic/crashtests/1343552-2.html b/layout/generic/crashtests/1343552-2.html
new file mode 100644
index 0000000000..f4e548e58e
--- /dev/null
+++ b/layout/generic/crashtests/1343552-2.html
@@ -0,0 +1,31 @@
+<style>
+.class1 { float: left; white-space: pre-line; }
+.class2 { border-bottom-style: solid; font-face: Arial; font-size: 7ex; }
+</style>
+<script>
+function go() {
+ progress.remove();
+ menu.style.setProperty("flex-direction", "column");
+ setTimeout(callback,0);
+}
+function callback() {
+ menu.style.setProperty("flex-direction", "row-reverse");
+ option.scrollBy();
+}
+</script>
+<body onload=go()>
+<del class="class1">
+<span class="class2">
+<menu id="menu">
+<menuitem>
+</menuitem></menu>
+<menuitem id="menuitem">
+<progress id="progress">
+</progress>aaaaaaaaaaaaaaaaaa
+
+
+</menuitem></span></del>
+<ol dir="rtl"></ol>
+<canvas id="canvas">
+<option id="option">
+</option></canvas>
diff --git a/layout/generic/crashtests/1346454-1.html b/layout/generic/crashtests/1346454-1.html
new file mode 100644
index 0000000000..bf8eceee32
--- /dev/null
+++ b/layout/generic/crashtests/1346454-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+
+<style>
+ol {
+ list-style-position: inside;
+ column-width: calc(0px);
+}
+li {
+ float: left;
+}
+li:last-child {
+ padding-block-end: calc(50%);
+ float: none;
+}
+</style>
+
+<script>
+function boom(){
+ document.getElementById("tgt").style.display = "block";
+}
+</script>
+</head>
+
+<body onload=boom()>
+<ol>
+<li id=tgt></li>
+<li></li>
+<li></li>
+</ol>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1346454-2.html b/layout/generic/crashtests/1346454-2.html
new file mode 100644
index 0000000000..0e87f4c491
--- /dev/null
+++ b/layout/generic/crashtests/1346454-2.html
@@ -0,0 +1,12 @@
+<style>
+#x { float: left; }
+dir {
+ list-style-position: inside;
+ column-width: 3px;
+}
+</style>
+<dir>
+<div>
+<li>
+<li id=x>
+<li> \ No newline at end of file
diff --git a/layout/generic/crashtests/1349650.html b/layout/generic/crashtests/1349650.html
new file mode 100644
index 0000000000..c225d02863
--- /dev/null
+++ b/layout/generic/crashtests/1349650.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body style="display: grid;">
+<div style="display:flex; position:fixed;"></div>
+<div style="order:-1;"></div>
+</body>
+</html>
+
diff --git a/layout/generic/crashtests/1349816-1.html b/layout/generic/crashtests/1349816-1.html
new file mode 100644
index 0000000000..48aa48b381
--- /dev/null
+++ b/layout/generic/crashtests/1349816-1.html
@@ -0,0 +1,6 @@
+<textarea dir='rtl' style='white-space:nowrap' draggable='true'>
+LFIodxNi
+&#x9C;&#x102C;&#xEC;&#x13;&#xB7;&#x8D;&#x210F;&#xBA;
+&#xAD;&#x1A;
+</textarea>
+<noscript hidden contenteditable='true' title='&#x289;' itemscope>
diff --git a/layout/generic/crashtests/1350372.html b/layout/generic/crashtests/1350372.html
new file mode 100644
index 0000000000..58858b3518
--- /dev/null
+++ b/layout/generic/crashtests/1350372.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom(){
+ let root = document.documentElement,
+ inp = document.createElement("input"),
+ aud = document.createElement("audio"),
+ vid = document.createElement("video");
+ inp.type = "image";
+ document.body.appendChild(inp);
+ document.body.appendChild(aud);
+ document.body.appendChild(vid);
+ root.style.columnWidth = "0px";
+ setTimeout(function(){
+ inp.style.display = "contents";
+ setTimeout(function(){
+ inp.remove();
+ inp.appendChild(vid);
+ setTimeout(function(){
+ root.style.strokeWidth = "0px";
+ }, 10);
+ }, 10);
+ }, 10);
+}
+addEventListener("DOMContentLoaded", boom);
+</script>
+</head>
+<body></body>
+</html>
diff --git a/layout/generic/crashtests/1364361-1.html b/layout/generic/crashtests/1364361-1.html
new file mode 100644
index 0000000000..5056a52df7
--- /dev/null
+++ b/layout/generic/crashtests/1364361-1.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<style>
+details {
+ column-count: 3;
+ column-width: 5em;
+ background-color: yellow;
+ overflow: scroll;
+ width: 300px; height: 300px;
+}
+</style>
+<details>
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+abcde
+</details>
diff --git a/layout/generic/crashtests/1367413-1.html b/layout/generic/crashtests/1367413-1.html
new file mode 100644
index 0000000000..1b77e9c52a
--- /dev/null
+++ b/layout/generic/crashtests/1367413-1.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<title>Test for dynamic re-pagination of absolutely positioned elements</title>
+<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<style>
+
+#multicol {
+ columns: 3;
+ column-fill: auto;
+ column-gap: 15px;
+ height: 500px;
+ width: 300px;
+ background: yellow;
+}
+
+#relpos {
+ position: relative;
+ background: aqua;
+ height: 250px;
+}
+
+#abspos {
+ position: absolute;
+ top: 60px;
+ right: 0;
+ height: 80px;
+ width: 50px;
+ background: blue;
+ transform: scale(0.9);
+}
+
+#overflow {
+ height: 100px;
+ width: 30px;
+ background: grey;
+}
+</style>
+<body style="width: 700px">
+
+<div id="multicol">
+ <div id="relpos">
+ <div id="abspos"><div id="overflow"></div></div>
+ </div>
+</div>
+
+<script>
+
+var mc = document.getElementById("multicol");
+mc.offsetHeight; // flush layout
+mc.style.height = "140px";
+mc.offsetHeight; // flush layout
+mc.parentNode.style.width = "800px";
+
+</script>
diff --git a/layout/generic/crashtests/1368617-1.html b/layout/generic/crashtests/1368617-1.html
new file mode 100644
index 0000000000..656829d68c
--- /dev/null
+++ b/layout/generic/crashtests/1368617-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+.x:first-line { letter-spacing: 1px; }
+.x:first-letter { float: left; }
+</style>
+<script>document.documentElement.offsetTop;</script>
+<p class=x>&nbsp;</p>
+<script>document.documentElement.offsetTop;</script>
+<a href=""><svg></svg></a>
diff --git a/layout/generic/crashtests/1373586.html b/layout/generic/crashtests/1373586.html
new file mode 100644
index 0000000000..3e5a47cfd2
--- /dev/null
+++ b/layout/generic/crashtests/1373586.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<script>
+// This is the constant in nsTextFrame.cpp of the number of lines which
+// a text run can be built up to.
+const NUM_LINES_TO_BUILD_TEXT_RUNS = 200;
+// Push the affecting line to be the last line in the text run.
+for (let i = 0; i < NUM_LINES_TO_BUILD_TEXT_RUNS - 1; i++) {
+ document.write('x<br>');
+}
+// The exact number here isn't important. It just needs to be large
+// enough that '\n' would be inside text after a line break.
+for (let i = 0; i < 2000; i++) {
+ document.write('ã‚');
+}
+document.write('\nã‚<br>');
+// Then this ruby would not get its text run.
+document.write('x<ruby>x</ruby>');
+</script>
diff --git a/layout/generic/crashtests/1375858.html b/layout/generic/crashtests/1375858.html
new file mode 100644
index 0000000000..b6d89f6201
--- /dev/null
+++ b/layout/generic/crashtests/1375858.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style>
+ *::marker, * {
+ transform-style:preserve-3d;
+ }
+</style>
+</head>
+<body>
+ <li></li>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1381134-2.html b/layout/generic/crashtests/1381134-2.html
new file mode 100644
index 0000000000..d3ac73507a
--- /dev/null
+++ b/layout/generic/crashtests/1381134-2.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+addEventListener("DOMContentLoaded", () => {
+ [d1, d2] = document.getElementsByTagName("div");
+ [s1, s2] = document.getElementsByTagName("span")
+ d3 = document.createElement("div")
+ d4 = document.createElement("div")
+ d4.setAttribute("class", "grid")
+ d3.appendChild(d4)
+ d1.appendChild(document.createElement("span"))
+ setTimeout(() => {
+ d2.removeChild(s2)
+ setTimeout(() => {
+ d2.insertBefore(d3, s1)
+ }, 100)
+ }, 100)
+})
+</script>
+<style>
+.columns {
+ columns: 3;
+}
+.grid {
+ border:5px solid;
+ counter-reset: item;
+}
+.grid * { display:block; }
+span { display:contents; }
+span::before { content: counter(item) ":before"; }
+span::after { content: counter(item) ":after"; }
+</style>
+</head>
+<body>
+<div class=columns>
+<div class=grid>
+<c></c>
+<span><c></c></span>
+<span><c></c></span>
+</div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1381134.html b/layout/generic/crashtests/1381134.html
new file mode 100644
index 0000000000..a45fa04ecb
--- /dev/null
+++ b/layout/generic/crashtests/1381134.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+addEventListener("DOMContentLoaded", () => {
+ [d1, d2] = document.getElementsByTagName("div");
+ [s1, s2] = document.getElementsByTagName("span")
+ d3 = document.createElement("div")
+ d4 = document.createElement("div")
+ d4.setAttribute("class", "grid")
+ d3.appendChild(d4)
+ d1.appendChild(document.createElement("span"))
+ setTimeout(() => {
+ d2.removeChild(s2)
+ setTimeout(() => {
+ d2.insertBefore(d3, s1)
+ }, 100)
+ }, 100)
+})
+</script>
+<style>
+.columns {
+ columns: 3;
+}
+.grid {
+ display: grid;
+ border:5px solid;
+ counter-reset: item;
+}
+span { display:contents; }
+span::before { content: counter(item) ":before"; }
+span::after { content: counter(item) ":after"; }
+</style>
+</head>
+<body>
+<div class=columns>
+<div class=grid>
+<c></c>
+<span><c></c></span>
+<span><c></c></span>
+</div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1401420-1.html b/layout/generic/crashtests/1401420-1.html
new file mode 100644
index 0000000000..114f0f0ba6
--- /dev/null
+++ b/layout/generic/crashtests/1401420-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style> rbc { display: ruby-base-container; } </style>
+<div style="column-width: 1px">
+x <ruby><rbc>aaaaaaaaaaaaaa</rbc><rbc><div style="float: left"></div></rbc></ruby>
+</div>
diff --git a/layout/generic/crashtests/1401709.html b/layout/generic/crashtests/1401709.html
new file mode 100644
index 0000000000..6ad364c3b1
--- /dev/null
+++ b/layout/generic/crashtests/1401709.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <ruby>
+ <rtc style="writing-mode: vertical-rl">
+ <div style="float: left"></div>
+ </rtc>
+ </ruby>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1401807.html b/layout/generic/crashtests/1401807.html
new file mode 100644
index 0000000000..835e14b048
--- /dev/null
+++ b/layout/generic/crashtests/1401807.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <style></style>
+ <script>
+ try { o1 = document.createElement('acronym') } catch(e) { }
+ try { o2 = document.createElement('video') } catch(e) { }
+ try { o3 = document.createElement('progress') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { o1.appendChild(o3) } catch(e) { }
+ try { o3.style.writingMode = 'sideways-lr' } catch(e) { }
+ try { document.styleSheets[0].insertRule('* { column-width: calc(15px); display: flow-root}', 0); } catch(e) { }
+ </script>
+ </head>
+</html>
diff --git a/layout/generic/crashtests/1404222-empty-shape.html b/layout/generic/crashtests/1404222-empty-shape.html
new file mode 100644
index 0000000000..0f4a62c2cc
--- /dev/null
+++ b/layout/generic/crashtests/1404222-empty-shape.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="float: left; shape-outside: linear-gradient(to top, green 50%, transparent 50%);"></div>
diff --git a/layout/generic/crashtests/1405443.html b/layout/generic/crashtests/1405443.html
new file mode 100644
index 0000000000..79313ae1c4
--- /dev/null
+++ b/layout/generic/crashtests/1405443.html
@@ -0,0 +1,19 @@
+<style>
+#htmlvar00009 { page-break-inside: avoid; }
+* { padding-left: 1vw; border-right: solid green 3em; }
+#htmlvar00001 { columns: 1px; )
+</style>
+<script>
+function jsfuzzer() {
+try { htmlvar00009.appendChild(htmlvar00013); } catch(e) { }
+try { var var00143 = htmlvar00009.x; } catch(e) { }
+try { htmlvar00009.appendChild(document.createElement("table").createCaption()); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<dl id="htmlvar00001">
+A
+<img id="htmlvar00009" align="left"></img>
+<menu id="htmlvar00013">
+<menuitem>
+<hr>
diff --git a/layout/generic/crashtests/1405813.html b/layout/generic/crashtests/1405813.html
new file mode 100644
index 0000000000..f349520b06
--- /dev/null
+++ b/layout/generic/crashtests/1405813.html
@@ -0,0 +1,10 @@
+<style>
+* { height: 0vmin; padding-bottom: 1vmax; display: grid; columns: 0px }
+</style>
+<details>
+<summary>
+V
+<link>
+<content>
+>
+<video>
diff --git a/layout/generic/crashtests/1405896.html b/layout/generic/crashtests/1405896.html
new file mode 100644
index 0000000000..0d3b6ddb24
--- /dev/null
+++ b/layout/generic/crashtests/1405896.html
@@ -0,0 +1,31 @@
+<!-- a -->
+<style>
+* {
+ height: 0vmin;
+ display: grid
+}
+.class1 {
+ columns: 0px;
+ page-break-before: always;
+ grid-row-start: last
+}
+</style>
+<script>
+function jsfuzzer() {
+ try { htmlvar00045.replaceChild(htmlvar00013,htmlvar00045.childNodes[6]); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<details id="htmlvar00013" class="class1"></details>
+<details class="class1" open="">
+<form>
+<h6 id="htmlvar00045">
+>
+<link>
+<content>
+<embed>
+>
+#
+K
+</content>
+<!-- a -->
diff --git a/layout/generic/crashtests/1406252-1.html b/layout/generic/crashtests/1406252-1.html
new file mode 100644
index 0000000000..df0f4fcdd7
--- /dev/null
+++ b/layout/generic/crashtests/1406252-1.html
@@ -0,0 +1,13 @@
+<style>
+#htmlvar00006 {
+ display: flex; cellspacing: 1;
+ writing-mode: vertical-lr;
+}
+* { -webkit-flex: auto; }
+#htmlvar00007 { flex-grow: 0.614207684486; }
+#htmlvar00006 { flex-flow: column nowrap; }
+</style>
+<output id="htmlvar00006" marginheight="0">
+<select id="htmlvar00007" scrollamount="-1">
+</select>
+<dl>
diff --git a/layout/generic/crashtests/1415185.html b/layout/generic/crashtests/1415185.html
new file mode 100644
index 0000000000..c1ef181abf
--- /dev/null
+++ b/layout/generic/crashtests/1415185.html
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <style>
+ * { column-count: 17 }
+ </style>
+ <script>
+ try { o1 = document.createElement('p') } catch(e) { }
+ try { o2 = document.createElement('iframe') } catch(e) { }
+ try { o3 = document.createElement('details') } catch(e) { }
+ try { o4 = document.createElement('track') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { o1.appendChild(o3) } catch(e) { }
+ try { o2.contentWindow.location.reload() } catch(e) { }
+ try { o3.appendChild(o4) } catch(e) { }
+ </script>
+ </head>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1416544.html b/layout/generic/crashtests/1416544.html
new file mode 100644
index 0000000000..713568d107
--- /dev/null
+++ b/layout/generic/crashtests/1416544.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+
+ body {
+ columns: 2;
+ -webkit-columns: 2;
+ }
+ span {
+ float: right;
+ }
+
+ </style>
+</head>
+<body>
+
+ <div>
+ a
+ <span>1</span>
+ </div>
+
+ <div>
+ <div>
+ b
+ <span>2</span>
+ <div></div>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1427824.html b/layout/generic/crashtests/1427824.html
new file mode 100644
index 0000000000..d69fcb6cff
--- /dev/null
+++ b/layout/generic/crashtests/1427824.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<div id="container"></div>
+<script>
+let contents = document.createElement('div');
+contents.style.display = "contents";
+container.appendChild(contents);
+container.appendChild(document.createElement('colgroup'));
+container.offsetTop;
+contents.appendChild(document.createElement('colgroup'));
+</script>
diff --git a/layout/generic/crashtests/1431781-2.html b/layout/generic/crashtests/1431781-2.html
new file mode 100644
index 0000000000..2a29c0b07d
--- /dev/null
+++ b/layout/generic/crashtests/1431781-2.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+<style>
+body { column-width: 1px; }
+dd { height: 13px }
+</style>
+<script>
+ function go() {
+ document.body.style.width = "1240px";
+ document.body.offsetHeight;
+ }
+</script>
+</head>
+<body onload="go()">
+<span>
+<span>
+ a
+ <meter></meter>
+ <dl>
+ <dd>
+ b
+ c
+ d
+ <menu></menu>
+ e
+ f
diff --git a/layout/generic/crashtests/1431781.html b/layout/generic/crashtests/1431781.html
new file mode 100644
index 0000000000..cd0733be48
--- /dev/null
+++ b/layout/generic/crashtests/1431781.html
@@ -0,0 +1,22 @@
+<style>
+body { column-width: 0px }
+dd { height: 1vmax }
+</style>
+<script>
+function eh1() {
+ a.clientHeight;
+ window.document.linkColor = "1";
+}
+</script>
+<content id="a">
+</div>
+<data>
+a
+<meter></meter>
+<dl>
+<dd>
+<style onload="eh1()"></style>
+<m>a
+a
+a<menu>a
+a
diff --git a/layout/generic/crashtests/1458028.html b/layout/generic/crashtests/1458028.html
new file mode 100644
index 0000000000..4bf2db4db6
--- /dev/null
+++ b/layout/generic/crashtests/1458028.html
@@ -0,0 +1,13 @@
+<style>
+.cl { page-break-inside: avoid; }
+summary::first-letter { float: right; }
+</style>
+<script>
+function go() {
+ a.remove();
+}
+</script>
+<body onload=go()>
+<details>
+<summary id="a" class="cl">
+T
diff --git a/layout/generic/crashtests/1459697.html b/layout/generic/crashtests/1459697.html
new file mode 100644
index 0000000000..0276a3f1a2
--- /dev/null
+++ b/layout/generic/crashtests/1459697.html
@@ -0,0 +1,13 @@
+<style>
+* {
+ shape-margin: 33%;
+ shape-outside: ellipse(20% 47% at 66% 79%)
+}
+.cl {
+ border-right-style: dashed;
+ float: right;
+ border-bottom-style: dashed;
+}
+</style>
+<button>
+<del class="cl">
diff --git a/layout/generic/crashtests/1460158-1.html b/layout/generic/crashtests/1460158-1.html
new file mode 100644
index 0000000000..b3b5ec6f0f
--- /dev/null
+++ b/layout/generic/crashtests/1460158-1.html
@@ -0,0 +1,15 @@
+<style>
+.cl {
+ display: list-item;
+ list-style-position: inside;
+}
+</style>
+<script>
+function go() {
+ a.appendChild(b);
+}
+</script>
+<body onload=go()>
+<progress id="a"></progress>
+<details class="cl">
+<summary id="b">
diff --git a/layout/generic/crashtests/1460158-2.html b/layout/generic/crashtests/1460158-2.html
new file mode 100644
index 0000000000..0272168368
--- /dev/null
+++ b/layout/generic/crashtests/1460158-2.html
@@ -0,0 +1,17 @@
+<style>
+.cl {
+ display: list-item;
+ list-style-position: inside;
+ position: relative;
+}
+#b { position: absolute; }
+</style>
+<script>
+function go() {
+ a.appendChild(b);
+}
+</script>
+<body onload=go()>
+<progress id="a"></progress>
+<details class="cl">
+<summary id="b">
diff --git a/layout/generic/crashtests/1460158-3.html b/layout/generic/crashtests/1460158-3.html
new file mode 100644
index 0000000000..6bbd4a7d23
--- /dev/null
+++ b/layout/generic/crashtests/1460158-3.html
@@ -0,0 +1,14 @@
+<style>
+.cl {
+ display: list-item;
+ list-style-position: inside;
+}
+</style>
+<script>
+function go() {
+ a.removeChild(b);
+}
+</script>
+<body onload=go()>
+<details class="cl" id="a">
+<summary id="b">
diff --git a/layout/generic/crashtests/1461039.html b/layout/generic/crashtests/1461039.html
new file mode 100644
index 0000000000..7d687908bc
--- /dev/null
+++ b/layout/generic/crashtests/1461039.html
@@ -0,0 +1,15 @@
+<style>
+.cl {
+ overflow-y: auto;
+ text-overflow: ellipsis;
+}
+</style>
+<script>
+function go() {
+ a.appendChild(b);
+}
+</script>
+<body onload=go()>
+<li id="b">
+<li class="cl">
+<ruby id="a">)
diff --git a/layout/generic/crashtests/1461979-1.html b/layout/generic/crashtests/1461979-1.html
new file mode 100644
index 0000000000..3250a922d1
--- /dev/null
+++ b/layout/generic/crashtests/1461979-1.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <script>
+ function start () {
+ try { o1 = document.createElementNS('http://www.w3.org/2000/svg', 'style') } catch (e) {}
+ try { document.head.appendChild(o1) } catch (e) {}
+ try { o1.sheet.insertRule('* { padding-top:calc(439804in) }', 0) } catch (e) {}
+ try { o1.sheet.insertRule('* { position:absolute}', 0) } catch (e) {}
+ }
+ window.addEventListener('load', start)
+ </script>
+ </head>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1463977.html b/layout/generic/crashtests/1463977.html
new file mode 100644
index 0000000000..6569c8c476
--- /dev/null
+++ b/layout/generic/crashtests/1463977.html
@@ -0,0 +1,5 @@
+<table cellpadding='89181367.20'>
+<td>
+<button>
+<q>
+<meter>
diff --git a/layout/generic/crashtests/1466224.html b/layout/generic/crashtests/1466224.html
new file mode 100644
index 0000000000..a0343eef12
--- /dev/null
+++ b/layout/generic/crashtests/1466224.html
@@ -0,0 +1,27 @@
+<script>
+window.requestIdleCallback(function() {
+ document.documentElement.style.display="none";
+ document.documentElement.getBoundingClientRect();
+ document.documentElement.style.display="";
+});
+</script>
+<style>
+#a {
+ display: table-caption;
+}
+:not(altGlyphDef) {
+ text-indent: 1vw;
+ overflow-x: scroll;
+}
+#b {
+ bottom: 0em;
+ font: small/7% cursive;
+ transform: translate3d(0px, 1px, -1px);
+ max-height: 3mm;
+ filter: brightness(0);
+}
+</style>
+<button id="a">
+<dialog id="b" open="">
+<h5>&
+
diff --git a/layout/generic/crashtests/1467239.html b/layout/generic/crashtests/1467239.html
new file mode 100644
index 0000000000..3f49a66c56
--- /dev/null
+++ b/layout/generic/crashtests/1467239.html
@@ -0,0 +1,14 @@
+<style>
+.b {
+ display: grid;
+ contain: strict;
+}
+</style>
+<script>
+function go() {
+ a.data = "A";
+}
+</script>
+<body onload=go()>
+<form class="b">
+<object id="a">
diff --git a/layout/generic/crashtests/1472403.html b/layout/generic/crashtests/1472403.html
new file mode 100644
index 0000000000..fabfc5431c
--- /dev/null
+++ b/layout/generic/crashtests/1472403.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<div id="target"></div>
+<script>
+ let element = document.createElement('_moz_generated_content_image');
+ target.appendChild(element);
+</script>
diff --git a/layout/generic/crashtests/1474768.html b/layout/generic/crashtests/1474768.html
new file mode 100644
index 0000000000..0735cc64cc
--- /dev/null
+++ b/layout/generic/crashtests/1474768.html
@@ -0,0 +1,22 @@
+<style>
+* {
+ max-height: 0mm;
+}
+:not(script) {
+ columns: 0px;
+ border-right-style: dashed;
+}
+</style>
+<script>
+function go() {
+ a.replaceWith(b);
+}
+</script>
+<body onload=go()>
+<data>
+<video></video>
+<br></br>
+<span>
+<ol>
+<li id="a"></li>
+<form id="b">
diff --git a/layout/generic/crashtests/1478178.html b/layout/generic/crashtests/1478178.html
new file mode 100644
index 0000000000..08a9dfc723
--- /dev/null
+++ b/layout/generic/crashtests/1478178.html
@@ -0,0 +1,6 @@
+<p style="text-align-last: justify">
+A
+<wbr/>
+&#x0C;
+<!-- A -->
+</p>
diff --git a/layout/generic/crashtests/1483972.html b/layout/generic/crashtests/1483972.html
new file mode 100644
index 0000000000..1a31be16cc
--- /dev/null
+++ b/layout/generic/crashtests/1483972.html
@@ -0,0 +1,10 @@
+<script>
+window.onload=function(){
+ window.frames[0].document.body.appendChild(a);
+ var o=window.frames[0].document.getElementById('a');
+ document.getElementById('b').appendChild(o.parentNode.removeChild(o));
+}
+</script>
+<audio id='a' controls autobuffer='true'></audio>
+<time id='b'>
+<iframe hidden>
diff --git a/layout/generic/crashtests/1486457.html b/layout/generic/crashtests/1486457.html
new file mode 100644
index 0000000000..cf1999cb96
--- /dev/null
+++ b/layout/generic/crashtests/1486457.html
@@ -0,0 +1,19 @@
+<style>
+* { display: ruby-text-container }
+div { float: right }
+</style>
+<script>
+function start() {
+ try { o1 = document.createElement('div') } catch (e) {}
+ try { o2 = document.createElement('img') } catch (e) {}
+ try { o3 = document.createElement('img') } catch (e) {}
+ try { o4 = document.createElement('img') } catch (e) {}
+ try { o3.innerText = '%\n' } catch (e) {}
+ try { o4.align = 'right' } catch (e) {}
+ try { o3.appendChild(o4) } catch (e) {}
+ try { o2.appendChild(o3) } catch (e) {}
+ try { document.documentElement.appendChild(o1) } catch (e) {}
+ try { document.documentElement.appendChild(o2) } catch (e) {}
+}
+window.addEventListener('load', start)
+</script>
diff --git a/layout/generic/crashtests/1488762-1.html b/layout/generic/crashtests/1488762-1.html
new file mode 100644
index 0000000000..2f3a30f02f
--- /dev/null
+++ b/layout/generic/crashtests/1488762-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <title>testcase</title>
+ <style>
+ * {
+ display: flex;
+ flex-flow: wrap;
+ min-width: -moz-fit-content;
+
+ flex-grow: 0.05;
+ margin: 0 88em 0 65170px;
+ font-size: calc(59902%);
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1488910-1.html b/layout/generic/crashtests/1488910-1.html
new file mode 100644
index 0000000000..62fd8c1974
--- /dev/null
+++ b/layout/generic/crashtests/1488910-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+ <style id='style'></style>
+ <script>
+ function start() {
+ o1 = document.createElement('a')
+ o2 = document.createElement('a')
+ o1.textContent = '-=–ð¢'
+ o1.setAttribute('style', 'block-size: calc(3px) !important')
+ o1.appendChild(o2)
+ document.documentElement.appendChild(o1)
+ document.getElementById('style').textContent = 'HTML * { -moz-columns: 0px !important } * { float: right} :first-letter,H{\'}:not(c),* { overflow: hidden !important }'
+ window.scrollByPages(4096, {})
+ o2.insertAdjacentHTML('beforebegin', '<d>')
+ }
+ window.addEventListener('load', start)
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1488910-2.html b/layout/generic/crashtests/1488910-2.html
new file mode 100644
index 0000000000..4ed8439d63
--- /dev/null
+++ b/layout/generic/crashtests/1488910-2.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+ <style id='style'></style>
+ <script>
+ function start() {
+ o1 = document.createElement('a')
+ o2 = document.createElement('a')
+ o1.textContent = '-=–ð¢'
+ o1.setAttribute('style', 'block-size: calc(3px) !important')
+ o1.appendChild(o2)
+ document.documentElement.appendChild(o1)
+ document.getElementById('style').textContent = 'HTML * { columns: 0px !important } * { float: right} :first-letter,H{\'}:not(c),* { overflow: hidden !important }'
+ window.scrollByPages(4096, {})
+ o2.insertAdjacentHTML('beforebegin', '<d>')
+ }
+ window.addEventListener('load', start)
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1489287.html b/layout/generic/crashtests/1489287.html
new file mode 100644
index 0000000000..4d511e83a0
--- /dev/null
+++ b/layout/generic/crashtests/1489287.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+ <script>
+ function start() {
+ o1 = document.createElement('u')
+ o2 = document.createElement('li')
+ o3 = document.createElement('style')
+ o2.textContent = '\nr'
+ o1.appendChild(o2)
+ document.documentElement.appendChild(o1)
+ o3.textContent = 'html { white-space: pre } :first-letter { float: left }'
+ document.documentElement.appendChild(o3)
+ }
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1489770.html b/layout/generic/crashtests/1489770.html
new file mode 100644
index 0000000000..0873d0743c
--- /dev/null
+++ b/layout/generic/crashtests/1489770.html
@@ -0,0 +1,23 @@
+<style>
+#a {
+ grid-auto-rows: min-content;
+ display: grid;
+}
+:not(html) {
+ margin-bottom: -1vmin;
+ column-width: 0;
+}
+</style>
+<script>
+function go() {
+ a.appendChild(b);
+ document.documentElement.style.display = "none"
+ document.documentElement.getBoundingClientRect()
+ document.documentElement.style.display = ""
+}
+</script>
+<a id="a">A</a>
+<ul>
+<li>
+<audio onloadstart="go()" src="">
+<keygen id="b">
diff --git a/layout/generic/crashtests/1489863.html b/layout/generic/crashtests/1489863.html
new file mode 100644
index 0000000000..dd110d8459
--- /dev/null
+++ b/layout/generic/crashtests/1489863.html
@@ -0,0 +1,15 @@
+<script>
+window.onload=function(){
+ document.getElementById('a').style='white-space:pre'
+}
+</script>
+<style>
+:last-child::first-letter {
+ float:right;
+}
+:empty {
+ padding-left:82%;
+}
+</style>
+<embed>
+<data id='a'>A
diff --git a/layout/generic/crashtests/1490032.html b/layout/generic/crashtests/1490032.html
new file mode 100644
index 0000000000..45123665b0
--- /dev/null
+++ b/layout/generic/crashtests/1490032.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <title>AddressSanitizer: SEGV /builds/worker/workspace/build/src/obj-firefox/dist/include/nsTArray.h in Length</title>
+ <style class="">
+ @namespace math url(http://www.w3.org/1998/Math/MathML);
+ *>* {
+ display: grid;
+ max-block-size: calc(3*25px + 50%);
+ -webkit-align-self: start;
+ grid-template-rows: repeat(auto-fill, minmax(1ch, min-content)) minmax(min-content, 0);
+ }
+
+ *,
+ HTML {
+ columns: 2 10px;
+ page-break-inside: avoid !important;
+
+ </style>
+</head>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1490685.html b/layout/generic/crashtests/1490685.html
new file mode 100644
index 0000000000..6a41850643
--- /dev/null
+++ b/layout/generic/crashtests/1490685.html
@@ -0,0 +1 @@
+<img class="" srcset="data:;base64,R0lGODlhAQA//wAAACwAAAAAAQABADs 0x"/>
diff --git a/layout/generic/crashtests/1493708.html b/layout/generic/crashtests/1493708.html
new file mode 100644
index 0000000000..e4615e1e66
--- /dev/null
+++ b/layout/generic/crashtests/1493708.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <style>
+ * {
+ column-width: calc(-15px);
+ transform-style: preserve-3d ! important;
+ }
+ </style>
+</head>
+
+<body>
+ <iframe></iframe>
+ <textarea autofocus minlength="">
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1493710.html b/layout/generic/crashtests/1493710.html
new file mode 100644
index 0000000000..738b2af9be
--- /dev/null
+++ b/layout/generic/crashtests/1493710.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+ <style id="id_3">
+ * {
+ position: absolute !important
+ </style>
+ <script>
+ function start() {
+ window.CustomElement0 = class extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({
+ mode: 'open'
+ })
+ }
+ }
+ customElements.define('custom-element-0', CustomElement0)
+ o1 = document.createElement('input')
+ o2 = document.createElement('custom-element-0')
+ document.documentElement.appendChild(o2)
+ o2.shadowRoot.prepend('', undefined, o1, '', undefined, document.getElementById('id_3'))
+ }
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+</head>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1493741.html b/layout/generic/crashtests/1493741.html
new file mode 100644
index 0000000000..59ba5df1c4
--- /dev/null
+++ b/layout/generic/crashtests/1493741.html
@@ -0,0 +1,6 @@
+<style>
+ video::after {
+ content: "";
+ }
+</style>
+<video></video>
diff --git a/layout/generic/crashtests/1494380.html b/layout/generic/crashtests/1494380.html
new file mode 100644
index 0000000000..ac43c4a528
--- /dev/null
+++ b/layout/generic/crashtests/1494380.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+div {
+ text-combine-upright: all;
+ writing-mode: vertical-rl;
+ display: contents;
+ text-emphasis: dot;
+}
+</style>
+<div>test</div>
diff --git a/layout/generic/crashtests/1505817.html b/layout/generic/crashtests/1505817.html
new file mode 100644
index 0000000000..185a210514
--- /dev/null
+++ b/layout/generic/crashtests/1505817.html
@@ -0,0 +1,27 @@
+<style>
+ html {
+ word-spacing: calc(-47em + 255px);
+ }
+
+ * {
+ float: left;
+ column-count: 14;
+ }
+
+ *::first-letter {
+ -moz-margin-start: calc(25px*3);
+ }
+</style>
+<script>
+ function start() {
+ o1 = document.createElement('c')
+ audio = document.getElementById('audio_0')
+ audio.insertAdjacentText('beforebegin', 'Ù\n-†­ð†3ê')
+ audio.getClientRects()
+ audio.insertAdjacentElement('beforebegin', o1)
+ audio.getClientRects()
+ }
+
+ window.addEventListener('load', start)
+</script>
+<audio id="audio_0" muted>
diff --git a/layout/generic/crashtests/1506216.html b/layout/generic/crashtests/1506216.html
new file mode 100644
index 0000000000..b09dca1eb7
--- /dev/null
+++ b/layout/generic/crashtests/1506216.html
@@ -0,0 +1,4 @@
+<style>
+:root { columns: 5px }
+</style>
+<hr style="column-span:all">
diff --git a/layout/generic/crashtests/1506306.html b/layout/generic/crashtests/1506306.html
new file mode 100644
index 0000000000..0296c3e25f
--- /dev/null
+++ b/layout/generic/crashtests/1506306.html
@@ -0,0 +1,8 @@
+<script>
+function go() {
+ document.linkColor = "0";
+}
+</script>
+<body onload=go()>
+<li style="columns: 1px">
+<title style="column-span: all; display: table">A</title>
diff --git a/layout/generic/crashtests/1507196.html b/layout/generic/crashtests/1507196.html
new file mode 100644
index 0000000000..65f4943ec7
--- /dev/null
+++ b/layout/generic/crashtests/1507196.html
@@ -0,0 +1,30 @@
+<html>
+
+<head>
+ <style class="">
+ * {
+ border-block-start-style: ridge;
+ margin-block-end: calc(27914%) !important;
+ float: left;
+ margin-inline-start: 3em;
+ column-width: 15px
+ }
+
+ HTML {
+ width: calc(25px* 3);
+ font: condensed bold italic small-caps 19769px/calc(1 + 2*188/4) Times New Roman, serif ! important;
+ height: calc(153%) !important;
+ }
+ </style>
+ <script>
+ function start() {
+ o1 = document.createElementNS('http://www.w3.org/1999/xhtml', 'c')
+ document.documentElement.appendChild(o1)
+ o1.scrollTop = 100
+ o1.dir = ''
+ }
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+</head>
+
+</html>
diff --git a/layout/generic/crashtests/1513275.html b/layout/generic/crashtests/1513275.html
new file mode 100644
index 0000000000..1abb5137a5
--- /dev/null
+++ b/layout/generic/crashtests/1513275.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<div style="width: 1ex">
+<ruby>
+hello
+<math style="display: ruby-base-container">
+<mfenced>a</mfenced>
+</math>
+</ruby>
+</div>
diff --git a/layout/generic/crashtests/1513282.html b/layout/generic/crashtests/1513282.html
new file mode 100644
index 0000000000..367a9afb6a
--- /dev/null
+++ b/layout/generic/crashtests/1513282.html
@@ -0,0 +1,16 @@
+<html>
+<style id="style">
+ * {
+ display: contents;
+ }
+ *::first-line,
+ #id_3 {
+</style>
+<script>
+ function start() {
+ const style = document.getElementById('style')
+ style.prepend('�')
+ }
+ document.addEventListener('DOMContentLoaded', start)
+</script>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1515124.html b/layout/generic/crashtests/1515124.html
new file mode 100644
index 0000000000..027cec5b4b
--- /dev/null
+++ b/layout/generic/crashtests/1515124.html
@@ -0,0 +1,26 @@
+<style>
+* {
+ columns: 0;
+ break-before: page;
+ overflow-wrap: break-word;
+}
+</style>
+<script>
+function go() {
+try { b.appendChild(a); } catch(e) { }
+try { c.style.setProperty("border-collapse", "collapse"); } catch(e) { }
+}
+</script>
+<body onload=go()>
+<menu>
+<menuitem>
+<div style="display: grid">
+<table>
+A
+<li>
+<th id="a">
+</table>
+<time id="b">
+</menu>
+<dl id="c">
+AA
diff --git a/layout/generic/crashtests/1517033.html b/layout/generic/crashtests/1517033.html
new file mode 100644
index 0000000000..f882ec2931
--- /dev/null
+++ b/layout/generic/crashtests/1517033.html
@@ -0,0 +1,24 @@
+<!-- COMMENT -->
+<style id="style"></style>
+<script>
+ function start() {
+ try { o = [] } catch (e) {}
+ try { window = window } catch (e) {}
+ try { document = document } catch (e) {}
+ try { header_0 = document.getElementById('header') } catch (e) {}
+ try { style_0 = document.getElementById('style') } catch (e) {}
+ try { style_1 = document.createElement('style') } catch (e) {}
+ try { computed = window.getComputedStyle(style_0) } catch (e) {}
+ try { style_1.textContent = '#header, i { column-span:all } c,* { columns: 2 !important }' } catch (e) {}
+ try { document.firstElementChild.appendChild(style_1) } catch (e) {}
+ try { computed.getPropertyValue('quotes') } catch (e) {}
+ try { header_0.style.setProperty('scroll-behavior', 'smooth', undefined) } catch (e) {}
+ try { window.scrollBy(256, 1) } catch (e) {}
+ try { header_0.style.setProperty('scroll-snap-type', 'both mandatory', '') } catch (e) {}
+ try { header_0.getClientRects() } catch (e) {}
+ try { style_1.sheet.insertRule('i { }', undefined) } catch (e) {}
+ }
+ window.addEventListener('load', start)
+</script>
+<header id="header"></header>
+<!-- COMMENT -->
diff --git a/layout/generic/crashtests/1517297.html b/layout/generic/crashtests/1517297.html
new file mode 100644
index 0000000000..f729d130f6
--- /dev/null
+++ b/layout/generic/crashtests/1517297.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+ <style>
+ * {
+ column-count: 1;
+ min-width: -moz-min-content;
+ column-span: all;
+ }
+
+ ins {
+ display: grid;
+ }
+ </style>
+</head>
+<body>
+ <a>
+ <ins>MJ]yYfC&gt;;M|</ins>
+ <keygen></keygen>
+ </a>
+</body>
+</html>
+
diff --git a/layout/generic/crashtests/1520798-1.xhtml b/layout/generic/crashtests/1520798-1.xhtml
new file mode 100644
index 0000000000..af694305a8
--- /dev/null
+++ b/layout/generic/crashtests/1520798-1.xhtml
@@ -0,0 +1,10 @@
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="go()">
+<div id="tweakMe">abc</div>
+<script>
+ function go() {
+ document.getElementById("tweakMe").style.overflowAnchor = "none";
+ }
+</script>
+</window>
diff --git a/layout/generic/crashtests/1520798-2.html b/layout/generic/crashtests/1520798-2.html
new file mode 100644
index 0000000000..83a27ab4ca
--- /dev/null
+++ b/layout/generic/crashtests/1520798-2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<head>
+ <script>
+ function go() {
+ document.getElementById("tweakMe").style.overflowAnchor = "none";
+ }
+ </script>
+</head>
+<body onload="go()">
+ <div style="position:fixed">
+ <div id="tweakMe">Hi</div>
+ </div>
+</body>
diff --git a/layout/generic/crashtests/1528771.html b/layout/generic/crashtests/1528771.html
new file mode 100644
index 0000000000..40357f791e
--- /dev/null
+++ b/layout/generic/crashtests/1528771.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <style>
+ * {
+ display: inline-grid;
+ grid-template-columns: repeat(9999, minmax(1%, 25054ex) minmax(1%, 2%) 1px) repeat(auto-fit, minmax(1%, 2%));
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1539656.html b/layout/generic/crashtests/1539656.html
new file mode 100644
index 0000000000..7d4ac06948
--- /dev/null
+++ b/layout/generic/crashtests/1539656.html
@@ -0,0 +1,14 @@
+<script>
+document.addEventListener("DOMContentLoaded", function() {
+ var o=document.getElementById('b');
+ o.parentNode.removeChild(o);
+ window.frames[0].document.body.appendChild(document.getElementById('a'));
+})
+</script>
+<ol>
+<li>
+<table id='a'>
+</table>
+<iframe></iframe>
+</li>
+<ul id='b'>
diff --git a/layout/generic/crashtests/1542441.html b/layout/generic/crashtests/1542441.html
new file mode 100644
index 0000000000..8b9644edf5
--- /dev/null
+++ b/layout/generic/crashtests/1542441.html
@@ -0,0 +1,37 @@
+<style>
+.multicol-a {
+ width: 300px;
+ column-width: 100px;
+ column-gap: 0;
+ height: 100px;
+}
+.multicol-b {
+ border: 1px solid silver;
+ width: 200px;
+ column-width: 51px;
+ column-gap: 0;
+ height: 50px;
+}
+
+.step {
+ height: 1px;
+}
+.float-L {
+ width: 1px;
+ height: 1px;
+ float: left;
+}
+.float-R {
+ width: 1px;
+ height: 26px; /* 25 -> 26 crash */
+}
+</style>
+
+<div class="multicol-a">
+ <div class="float-R"></div>
+ <div class="multicol-b">
+ <div class="step"></div>
+ <div class="float-L"></div>
+ </div>
+</div>
+
diff --git a/layout/generic/crashtests/1543140-1.html b/layout/generic/crashtests/1543140-1.html
new file mode 100644
index 0000000000..010316c796
--- /dev/null
+++ b/layout/generic/crashtests/1543140-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ overflow: auto;
+ perspective: 1px;
+ padding-inline: 50000ex;
+ margin: inherit;
+ font-size-adjust: 30000 !important;
+ direction: rtl;
+ }
+ #id_0 {
+ inline-size: 1%;
+ position: absolute;
+ }
+ </style>
+
+</head>
+<body>
+<wbr id="id_0"/>
+<wbr/>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1544060-1.html b/layout/generic/crashtests/1544060-1.html
new file mode 100644
index 0000000000..87fc333f7b
--- /dev/null
+++ b/layout/generic/crashtests/1544060-1.html
@@ -0,0 +1,3 @@
+<html>
+<body hidden style="scroll-snap-type:both proximity!important">
+</html>
diff --git a/layout/generic/crashtests/1544060-2.html b/layout/generic/crashtests/1544060-2.html
new file mode 100644
index 0000000000..17db889e64
--- /dev/null
+++ b/layout/generic/crashtests/1544060-2.html
@@ -0,0 +1 @@
+<html hidden style="scroll-snap-type:both proximity!important"></html>
diff --git a/layout/generic/crashtests/1553824.html b/layout/generic/crashtests/1553824.html
new file mode 100644
index 0000000000..afa0fb0a8e
--- /dev/null
+++ b/layout/generic/crashtests/1553824.html
@@ -0,0 +1,86 @@
+<body onload="test()">
+<script>
+function test() {
+ document.body.offsetWidth;
+ let grid = document.querySelector(".container");
+ grid.style = "grid-template-columns:none";
+}
+</script>
+
+<style>
+body {
+ font: 1.2em Arial, Verdana, sans-serif;
+ background-color: #fff;
+}
+
+.container {
+ display: grid;
+ gap: 10px;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-template-rows: auto auto auto;
+}
+
+header,
+footer,
+aside,
+li,.box {
+ background-color: rgb(120, 70, 123);
+ border: 5px solid rgb(88, 55, 112);
+ color: #fff;
+ border-radius: 5px;
+ padding: 20px;
+}
+
+aside {
+ grid-row: 1 / -1;
+ grid-column: 1;
+}
+
+header {
+ grid-column: 2 / -2;
+}
+
+ul {
+ gap: 10px;
+ grid-row: 2;
+ grid-column: 2 / -1;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: grid;
+ grid-template-columns: subgrid;
+}
+
+footer {
+ grid-row: 3;
+ grid-column: 2 / -1;
+}
+
+.box {
+ grid-column: -2;
+ grid-row:1;
+}
+
+</style>
+
+
+<div class="container">
+ <header>This is my header</header>
+ <div class="box"></div>
+ <aside>I should stretch from the top to the bottom of the grid</aside>
+ <ul>
+ <li>A</li>
+ <li>B</li>
+ <li>C</li>
+ <li>D</li>
+ <li>E</li>
+ <li>F</li>
+ <li>G</li>
+ <li>H</li>
+ <li>I</li>
+ <li>J</li>
+ <li>K</li>
+ </ul>
+ <footer>I am a footer</footer>
+</div>
+
diff --git a/layout/generic/crashtests/1554824.html b/layout/generic/crashtests/1554824.html
new file mode 100644
index 0000000000..ba5dd26da0
--- /dev/null
+++ b/layout/generic/crashtests/1554824.html
@@ -0,0 +1,25 @@
+<style>
+* {
+ min-height: 4em;
+ height: 0.238em;
+ white-space: pre;
+ column-width: 0em;
+ -webkit-transform-style: preserve-3d;
+}
+</style>
+<script>
+function go() {
+ a.after("x")
+}
+</script>
+</head>
+<body onload=go()>
+a
+<a style="direction: rtl">
+</a>
+</pre>
+<ul>
+<li id="a">
+</ul>
+</canvas>
+</font>
diff --git a/layout/generic/crashtests/1555142.html b/layout/generic/crashtests/1555142.html
new file mode 100644
index 0000000000..20c371bfea
--- /dev/null
+++ b/layout/generic/crashtests/1555142.html
@@ -0,0 +1,15 @@
+<style>
+#a {
+ display: -webkit-inline-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+ width: 0;
+}
+span {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+}
+</style>
+<body onload="b.remove()">
+<div id="a"><span id="b"></span>x</div>
diff --git a/layout/generic/crashtests/1560349.html b/layout/generic/crashtests/1560349.html
new file mode 100644
index 0000000000..a2cfe2c032
--- /dev/null
+++ b/layout/generic/crashtests/1560349.html
@@ -0,0 +1,12 @@
+<style>
+ #id_0 {
+ grid-template-rows: [ line_name_37 ] repeat(18022, [ line_name_38 ] 60% [ line_name_39 line_name_40 line_name_41 ] minmax(max-content, 24ex) [ line_name_42 ]) [ line_name_43 ] repeat(auto-fit, [ line_name_44 ] minmax(61%, min-content) [ line_name_45 line_name_46 ]) [ line_name_47 ] minmax(min-content, 25%) [ line_name_48 ];
+ }
+
+ .class_0 {
+ grid-row-end: line_name_19;
+ display: inline-grid;
+ }
+</style>
+<ol class="class_0" id="id_0">
+ <li class="class_0">
diff --git a/layout/generic/crashtests/1560397-2.html b/layout/generic/crashtests/1560397-2.html
new file mode 100644
index 0000000000..6e9305189e
--- /dev/null
+++ b/layout/generic/crashtests/1560397-2.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+ <style>
+
+ .class_2, .class_3 {
+ display: inline-grid;
+ grid: repeat(3, 6%) repeat(1, minmax(1em, 3%))/repeat(1, auto);
+ grid-row: i;
+ grid-auto-columns: 1px;
+ }
+ .class_2 {
+ grid-column: span 999999;
+ grid-template-columns: subgrid repeat(99999, [a]) repeat(auto-fill,[b]);
+ }
+ </style>
+</head>
+<body>
+<big class="class_3">
+ <em class="class_2">
+ <x style="grid-column:b">A</x>
+ <x style="grid-column:a -1">A</x>
+ </em>
+</big>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1560397.html b/layout/generic/crashtests/1560397.html
new file mode 100644
index 0000000000..416616d3bd
--- /dev/null
+++ b/layout/generic/crashtests/1560397.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+ <style>
+ BIG, .class_2 {
+ position: fixed;
+ rotate: 4.201792242287117rad z;
+ }
+
+ .class_2, .class_3 {
+ display: inline-grid;
+ grid: repeat(25976, 2vh) repeat(auto-fit, minmax(0, max-content)) repeat(3, 6%) repeat(1, minmax(1em, 3%))/repeat(1, auto);
+ grid-row: i
+ }
+ </style>
+</head>
+<body>
+<big class="class_3">
+ <em class="class_2"></em>
+</big>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1562105.html b/layout/generic/crashtests/1562105.html
new file mode 100644
index 0000000000..cc0a8eb98e
--- /dev/null
+++ b/layout/generic/crashtests/1562105.html
@@ -0,0 +1,6 @@
+<script>
+document.addEventListener("DOMContentLoaded", function(){
+ document.documentElement.hidden = 'true'
+ window.scrollBy(9, 13)
+})
+</script>
diff --git a/layout/generic/crashtests/1563131.html b/layout/generic/crashtests/1563131.html
new file mode 100644
index 0000000000..086cc9e12d
--- /dev/null
+++ b/layout/generic/crashtests/1563131.html
@@ -0,0 +1,17 @@
+<style>
+#c {
+ margin-right: 24vmin;
+}
+#b {
+ column-count: 1;
+ max-width: 18%;
+ writing-mode: tb;
+}
+#a {
+ translate: 0px 9px 0px;
+ white-space: pre-line;
+}
+</style>
+<fieldset id="a">
+<fieldset id="b">
+<select id="c">a</select>
diff --git a/layout/generic/crashtests/1568001-1.html b/layout/generic/crashtests/1568001-1.html
new file mode 100644
index 0000000000..b60ab99437
--- /dev/null
+++ b/layout/generic/crashtests/1568001-1.html
@@ -0,0 +1,19 @@
+<style>
+* {
+ margin: 69;
+ width: 1vw;
+ font-size: 0.002rem;
+ height: 0;
+ columns: 9px;
+ column-span: all;
+}
+</style>
+<script>
+window.onload = () => {
+ a.click()
+}
+</script>
+<t>
+<br style="margin: auto 1"></br>
+<details style="display: -webkit-inline-flex">
+<summary id="a">
diff --git a/layout/generic/crashtests/1568001-2.html b/layout/generic/crashtests/1568001-2.html
new file mode 100644
index 0000000000..5679d12b8f
--- /dev/null
+++ b/layout/generic/crashtests/1568001-2.html
@@ -0,0 +1,13 @@
+<style>
+* {
+ margin: 69px;
+ width: 1vw;
+ font-size: 0;
+ height: 0;
+ columns: 9px;
+ column-span: all;
+}
+</style>
+
+<br style="margin: auto 1"><br>
+<div>
diff --git a/layout/generic/crashtests/1569639.html b/layout/generic/crashtests/1569639.html
new file mode 100644
index 0000000000..88e50aa58c
--- /dev/null
+++ b/layout/generic/crashtests/1569639.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+ <style>
+ #id_0 {
+ display: inline-grid;
+ grid-template-rows: [ l0 ] repeat(58321, [ l1 ] minmax(26571ch, max-content) [ l2 ]) [ l3 ] repeat(auto-fill, [ l4 l5 ] minmax(min-content, 46.15128007109454em) [ l6 ])
+ }
+ </style>
+</head>
+<body>
+ <abbr id="id_0"></abbr>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1571239.html b/layout/generic/crashtests/1571239.html
new file mode 100644
index 0000000000..bf0bccd586
--- /dev/null
+++ b/layout/generic/crashtests/1571239.html
@@ -0,0 +1,22 @@
+<style>
+.b {
+ font-size: 0.0136in;
+ writing-mode: tb;
+}
+* {
+ column-count: 7;
+ width: 7pt;
+}
+</style>
+<script>
+function go() {
+ a.style.setProperty("column-span", "all")
+}
+</script>
+<body onload=go()>
+<q>
+<ins class="b">
+<pre></pre>
+<q id="a">A</q>
+</ins>
+<!-- a -->
diff --git a/layout/generic/crashtests/1571460.html b/layout/generic/crashtests/1571460.html
new file mode 100644
index 0000000000..fc662faaaa
--- /dev/null
+++ b/layout/generic/crashtests/1571460.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+ <style>
+ BDO {
+ shape-outside: url(data:image/bmp;base64,Qk0IBQAAAAAAAGoBAAAoAAAAIAAAACAAAAABAAgAAQAAAJ4DAADDDgAAww4AAE0AAABNAAAAAAAAAP///wBAIMAAQCCgAEAA4ABAAMAAQCCAAEAAoACAAKAAgACAAIAggACAAGAAgCBgAMAAYACAQGAAwABAAIAgQADAIEAAwAAgAP8AAADAICAAgEBAAMAgAADAQCAAwEAAAIBAIADAYAAAgGBAAIBgIACAYAAAgIAAAICAIABAoAAAQMAAAECAIABAoCAAQMAgAAD/AABAoEAAQIBAAEBgQAAA4CAAAOBAAADAQAAA4GAAAMBgAECAYAAAoGAAAOCgAACAYAAAwKAAAODAAAD//wAA4OAAAMDAAACAgABAYGAAAMDgAACAoAAAYIAAAKDgAACAwAAAYKAAAIDgAABgwABAYIAAAGDgAABAoAAAQMAAAEDgAAAgwAAAIOAAAAD/AEBAoABAQIAAQEBgAP///wAESAAFBAQFBQUABAcBCAQJAAQLCwsNBA8ABhISExMTEwAABEgBBAEEBAUABAcHBwgECQAECwsLDQQPAAYSEhITExMAAAAKSEhIR0dHAgIFAgQDBAoADgwMDBAPERERFBQUFhMTAAABSAFIBEcABEYCAgIEAwAMBgYKCgwMDBAQEBERBBQBFgETAAAGRwAERkYCAgQDAAQGBgoKBAwABBAQEREEFAIWAAAACkdHRUdHR0ZGRgIEAwAEBgYGCgQMAAQQEBARBBQCFgAABkUABEZEREQESQRKBA4EFQQXAhgAAAZFAAhERERDQ0lJSQRKAANLDg4ABRUABhkXFxgYGAAABkUERAAEQ0NJSQRKAARLSw4OBBUABhkZFxcYGAAABkUERAAHQ0NDSUpKSgAESwEOBBUABhkZGRgaGAAABkIEQAAEQz4+PgRBAARLODg4BBsABhkcHBwaGgAABkIEQAQ+AAQ7QUFBBDgABCgbGxsEHAEdARoAAAZCBEAEPgAEOztBQQQ4AAQoKBsbBBwCHQAABkIAB0BAQD0+Oj4ABDsBQQQ4AAQoKCgbBBwCHQAABj8EPQQ6BDcELgAKJygnKCIfHx8eHgAABj8EPQQ6BDcAAzEuLgAEJwAHIiIfHx4eHgAAAAAEPDw/PAQ/AAU9PTo9OgAENwAEMTExLgQnAAciIiIfHh4eAAAABDwEPwQ9BDcEMQAMLycnJyYiIiIjHh4eAAAEPAAEPzk/PwQ9AAQ2Nzc3BDEADC8vLycmJiIiIyMeHgAAABM8PDk8Pz8/PT82PT03NzcyMTExAAQvAAkmJiMmIiAjIyMAAAAAAzk5PAAFOQAYPTY2NjIyNzIyLS0tKysvKyYjJiMgICMgAAAIOQQ2BDIELQUrAAcjIyMgICAgAAAACTkAAzY2NgAEMgQtBysABSMkJCQgAAAAACA5NTk1OTk5MzY2MzYwNjIsLSwtLSorKyorKyshIyEkIAAAACA1OTU5NTU5NTM2MzYwMDIwLSwsLSwqKyorKSsrISEhIQAACDUAAzMzMwAFMAQsBCoFKQMhAAABNQE0BzUEMwQwAAMsLCwABCoGKQIhAAABNAc1BDMAAzAwMAAFLAQqCCkAAAQ0BDUEMwQwBCwEKgQpAAQlJSklAAAENAQ1AAMzMzMABTAAAywsLAAFKgQpBCUAAAU0BDUEMwQwBCwEKgAHKSkpJSUlJQAAAAQ0BDUEMwQwBCwEKgQpBCUAAQ==) ! important;
+ }
+
+ *, TITLE {
+ columns: 57 ! important;
+ }
+ </style>
+ <script>
+ function start () {
+ document.body.offsetTop;
+ const style = document.createElement('style')
+ document.head.appendChild(style)
+ style.sheet.insertRule('*{ float: left!important }', 0)
+ }
+
+ window.addEventListener('load', start)
+ </script>
+</head>
+<bdo>
+ f蓜𖹽𖻄ð…»ó ™³-=‌ð©¬Û°ó ¯â˜†AÛ¹âŸ*á©¿RÛ°ð¨£áð†«ð‡½ð¢ “ð…¯ð…¨ð‰‚
/ð¿Ž8é´£ÌÍ៞𪜎٩Ⱥ-ðŸã‚š0𯴘​󠱧n‪ðº¹ó °§9𯵙壆녩٪㫙ð‰ƒð‘“ A٫𧲨𯌭󠂉𩾃󠓕0è• ð‘«¢7𯕔0%=/*
+ <abbr>
+ <i> ÍŒ?𛩰𥴠剘٩𖷕٩á·A߭ 󠠟+=𛔯]ó ¹¢vك𠩒🻆ð–¹ð¹êšó ž ó ª ð¯¨Žà¯Œy</i>
+ </abbr>
+</bdo>
+</html>
diff --git a/layout/generic/crashtests/1571598.html b/layout/generic/crashtests/1571598.html
new file mode 100644
index 0000000000..0096a8252d
--- /dev/null
+++ b/layout/generic/crashtests/1571598.html
@@ -0,0 +1,14 @@
+<style>
+* {
+ width: 66%;
+ quotes: none;
+ padding-right: 1px;
+ column-width: 0;
+}
+</style>
+<menuitem>A</menu>
+<font style="writing-mode: sideways-rl">
+<link>
+<data>A</command>
+</font>
+<q>
diff --git a/layout/generic/crashtests/1571897.html b/layout/generic/crashtests/1571897.html
new file mode 100644
index 0000000000..af7b9d4a7a
--- /dev/null
+++ b/layout/generic/crashtests/1571897.html
@@ -0,0 +1,15 @@
+<style>
+#a {
+ font-size: 0.013em;
+ max-height: 1px;
+ padding-top: 94vw;
+}
+* {
+ word-break: break-all;
+ columns: 0px;
+}
+:root {
+ display: -webkit-inline-box;
+}
+</style>
+<menu id="a">AAAA</menu>
diff --git a/layout/generic/crashtests/1572901.html b/layout/generic/crashtests/1572901.html
new file mode 100644
index 0000000000..72317955dc
--- /dev/null
+++ b/layout/generic/crashtests/1572901.html
@@ -0,0 +1,19 @@
+<style id="a">
+* {
+ font: 0px/0px serif;
+ columns: 0;
+ display: inherit;
+}
+</style>
+<script>
+function go() {
+ a.appendChild(b)
+}
+</script>
+<body onload=go()>
+<details style="height: 1vw; display: inline" open="">
+<summary></br>
+<s dir="">
+<svg>
+</summary>
+<optgroup id="b">
diff --git a/layout/generic/crashtests/1573216.html b/layout/generic/crashtests/1573216.html
new file mode 100644
index 0000000000..3be0921533
--- /dev/null
+++ b/layout/generic/crashtests/1573216.html
@@ -0,0 +1,20 @@
+<style>
+body::first-letter {}
+#c { display: initial }
+* {
+ columns: 0;
+ contain: size layout;
+ writing-mode: vertical-rl
+}
+</style>
+<script>
+function go() {
+ a.size = "0"
+ var x = b.offsetWidth
+ document.title = "a"
+}
+</script>
+<body onload=go()>
+<title id="c">A</title>
+<hr id="b">
+<font id="a">
diff --git a/layout/generic/crashtests/1574552.html b/layout/generic/crashtests/1574552.html
new file mode 100644
index 0000000000..8a29aeb80d
--- /dev/null
+++ b/layout/generic/crashtests/1574552.html
@@ -0,0 +1,6 @@
+<style>
+* { float: right }
+</style>
+<ruby>
+<table>
+A
diff --git a/layout/generic/crashtests/1574993.html b/layout/generic/crashtests/1574993.html
new file mode 100644
index 0000000000..c41761674a
--- /dev/null
+++ b/layout/generic/crashtests/1574993.html
@@ -0,0 +1,21 @@
+<style>
+* {
+ font: 0px/0px serif;
+ columns: 0;
+ display: inherit;
+}
+.x {
+ height: 1vw;
+ display: inline;
+}
+</style>
+<script>
+function go() {
+ a.prepend(null)
+}
+</script>
+<body onload=go()>
+<details class="x" open="">
+<s dir="rtl">
+<table id="a">
+<svg></svg>
diff --git a/layout/generic/crashtests/1582019.html b/layout/generic/crashtests/1582019.html
new file mode 100644
index 0000000000..9486c44a6f
--- /dev/null
+++ b/layout/generic/crashtests/1582019.html
@@ -0,0 +1,22 @@
+<style>
+.x {
+ font-family: serif;
+ max-width: 0vmin;
+ top: 1vh
+}
+* {
+ border-right: blue 1px solid;
+ column-count: 9;
+}
+#a {
+ column-span: all;
+}
+#b {
+ position: relative;
+</style>
+<h6 id="a">C</h6>
+<l>q
+p f
+<pre id="b" wrap>
+<dialog style="word-break:break-all" open class="x">Cp</pre>
+<q>6/N_s/"eve"
diff --git a/layout/generic/crashtests/1586470.html b/layout/generic/crashtests/1586470.html
new file mode 100644
index 0000000000..5ba0af8380
--- /dev/null
+++ b/layout/generic/crashtests/1586470.html
@@ -0,0 +1,9 @@
+<style>
+.a {
+ max-height: 0vw;
+ writing-mode: vertical-rl;
+ box-decoration-break: clone
+}
+</style>
+<dl style="column-width:3em">
+<dd class="a" dir="RTL">A</dd>
diff --git a/layout/generic/crashtests/1588955-very-large-frameset.html b/layout/generic/crashtests/1588955-very-large-frameset.html
new file mode 100644
index 0000000000..17568ffbda
--- /dev/null
+++ b/layout/generic/crashtests/1588955-very-large-frameset.html
@@ -0,0 +1,9 @@
+<style>
+* { scale: 64 }
+</style>
+<script>
+window.onload = () => {
+ a.appendChild(document.createElement("frameset"))
+}
+</script>
+<ol id="a">
diff --git a/layout/generic/crashtests/1590569.html b/layout/generic/crashtests/1590569.html
new file mode 100644
index 0000000000..c3243532e5
--- /dev/null
+++ b/layout/generic/crashtests/1590569.html
@@ -0,0 +1,24 @@
+<style>
+html {
+ word-wrap: break-word;
+ contain: size layout;
+ columns: 0;
+}
+</style>
+<script>
+function go() {
+ window.scrollBy()
+ a.insertCell(0)
+}
+</script>
+<video height="0"></video>
+<datalist></datalist>
+<map>
+<ol style="height: 4vmin">
+A-A A
+</li>
+</ol>
+<style onload="go()"></style>
+<marquee></marquee>
+<table>
+<tr id="a">&gt;AAAA</tr>
diff --git a/layout/generic/crashtests/1596310.html b/layout/generic/crashtests/1596310.html
new file mode 100644
index 0000000000..3d471046b2
--- /dev/null
+++ b/layout/generic/crashtests/1596310.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+ <style>
+@page { size:5in 3in; margin:0; }
+html,body { padding:0; margin:0; height:100%; }
+div {
+ margin-bottom: 1em;
+ height: 100%;
+ page-break-after: always;
+}
+</style>
+</head>
+<body>
+ <div>A</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1601819-1.html b/layout/generic/crashtests/1601819-1.html
new file mode 100644
index 0000000000..2b63fc6c30
--- /dev/null
+++ b/layout/generic/crashtests/1601819-1.html
@@ -0,0 +1,11 @@
+<style>
+* {
+ page-break-before: left;
+ display: grid;
+}
+</style>
+<details style="column-width: 0em" open>
+<form>
+<fieldset>
+<x>
+<datalist>
diff --git a/layout/generic/crashtests/1608851-1.html b/layout/generic/crashtests/1608851-1.html
new file mode 100644
index 0000000000..2333a9b150
--- /dev/null
+++ b/layout/generic/crashtests/1608851-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+ <style>
+ * {
+ display: inline-grid;
+ position: fixed;
+ grid-template-columns: subgrid [ line_name_15 ];
+ scale: 38873 !important;
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1608851-2.html b/layout/generic/crashtests/1608851-2.html
new file mode 100644
index 0000000000..73f9b055e5
--- /dev/null
+++ b/layout/generic/crashtests/1608851-2.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+ <style>
+ * {
+ display: inline-grid;
+ position: fixed;
+ grid-template-columns: subgrid [ line_name_15 ];
+ scale: 1 !important;
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1613210.html b/layout/generic/crashtests/1613210.html
new file mode 100644
index 0000000000..bb45ce6d7e
--- /dev/null
+++ b/layout/generic/crashtests/1613210.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+ <style>
+ #id_0 {
+ padding-block-end: 88%;
+ }
+
+ * {
+ columns: auto 5543 !important;
+ font-size: 196vh;
+ border-inline-end: outset 27314em hsla(224.63915115406618deg 47% 78% / 5%);
+ margin-bottom: 56%;
+ grid-template: none / subgrid [ line_name_1 line_name_2 ];
+ max-block-size: 39821vw;
+ border-style: dashed;
+ }
+
+ RUBY {
+ min-width: max-content;
+ display: block grid;
+ }
+ </style>
+</head>
+<body>
+<ruby>
+ <ruby id="id_0">
+ </ruby>
+ <rt></rt>
+</ruby>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1614101.html b/layout/generic/crashtests/1614101.html
new file mode 100644
index 0000000000..8490fb9c62
--- /dev/null
+++ b/layout/generic/crashtests/1614101.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1614101</title>
+ <style>
+ @keyframes w {
+ 0%, 33% { width:10% }
+ 33%, 50% { width:50% }
+ 50%, 99% { width:10% }
+ 99%, 100% { display:none }
+ }
+
+ .a {
+ column-count: 2;
+ width: 1%;
+ writing-mode: vertical-lr;
+ }
+ </style>
+</head>
+<body>
+
+ <div style="width:10%; border:solid; animation: w .01s infinite">
+ <span>
+ zzzzzzzz
+ <x class="a"><div style="height:30px">A <br> a <br> a <br> a <br> a <br> a <br> B</div></x>
+ <textarea style="width:10px"></textarea>y
+ <canvas>
+ </span>
+ </div>
+ <script>
+ function destroy() {
+ document.body.style.display = 'none';
+ document.documentElement.removeAttribute('class');
+ }
+
+ document.body.getBoundingClientRect();
+ setTimeout(destroy, 500)
+ </script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1618312.html b/layout/generic/crashtests/1618312.html
new file mode 100644
index 0000000000..b0cc32b427
--- /dev/null
+++ b/layout/generic/crashtests/1618312.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+ <style class=''>
+ DIV {
+ grid: subgrid repeat(7806, [ line_name_1 line_name_2 ]) / auto-flow dense minmax(min-content, 90fr)
+ }
+
+ * {
+ overflow: scroll;
+ position: absolute;
+ display: inline-grid !important;
+ }
+ </style>
+</head>
+<body>
+ó ­‰R5¿󠽿𓗺ð†­%/*3êš·ä늗𛸌󠂀󠥄J
+<div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1618564.html b/layout/generic/crashtests/1618564.html
new file mode 100644
index 0000000000..2eb619c82b
--- /dev/null
+++ b/layout/generic/crashtests/1618564.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<style>
+ * {
+ border-image-source: url(solidblue.gif);
+ }
+</style>
+<script>
+ window.addEventListener('load', () => {
+ const mtd = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mtd')
+ document.documentElement.appendChild(mtd)
+ })
+</script>
diff --git a/layout/generic/crashtests/1625051-1.html b/layout/generic/crashtests/1625051-1.html
new file mode 100644
index 0000000000..78bf13616f
--- /dev/null
+++ b/layout/generic/crashtests/1625051-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+div { width: 150000px; }
+span {
+ display: grid;
+ min-width: min-content;
+ grid-template-columns: repeat(auto-fit, minmax(0%, auto));
+}
+</style>
+
+<span class="class3">
+ <div></div>
+</span>
diff --git a/layout/generic/crashtests/1625051-2.html b/layout/generic/crashtests/1625051-2.html
new file mode 100644
index 0000000000..2f9dfb557a
--- /dev/null
+++ b/layout/generic/crashtests/1625051-2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+div { width: 150000px; }
+span {
+ display: grid;
+ min-width: min-content;
+ grid-template-columns: repeat(auto-fit, minmax(0%, auto) 1px);
+}
+</style>
+
+<span class="class3">
+ <div></div>
+</span>
diff --git a/layout/generic/crashtests/1626970.html b/layout/generic/crashtests/1626970.html
new file mode 100644
index 0000000000..96e59656c5
--- /dev/null
+++ b/layout/generic/crashtests/1626970.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+ <style>
+ * {
+ padding-block-end: 46% ! important;
+ }
+ </style>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ window.scrollByLines(536870912, {})
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1628804.html b/layout/generic/crashtests/1628804.html
new file mode 100644
index 0000000000..937a3474d1
--- /dev/null
+++ b/layout/generic/crashtests/1628804.html
@@ -0,0 +1,21 @@
+<style>
+.c {
+ box-decoration-break: clone;
+ padding-bottom: 41vmax;
+}
+</style>
+<script>
+go = () => {
+ try { b.appendChild(a) } catch(e) { }
+}
+</script>
+<body onload=go()>
+<button style="columns: 72 0px">
+<dl style="columns: 1">
+<dd>-</dd>
+<dt id="b" style="float: left" class="c">x</dt>
+</dl>
+<dl>
+<map id="a">
+<canvas></canvas>
+<details open="">x</details>
diff --git a/layout/generic/crashtests/1629575-1.html b/layout/generic/crashtests/1629575-1.html
new file mode 100644
index 0000000000..e8eaf3d250
--- /dev/null
+++ b/layout/generic/crashtests/1629575-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <style>
+ .grid-container {
+ display: grid;
+ /* Repeat auto-fill which is less than the number of elements in the grid. */
+ grid-template-columns: 1px 1px 1px 1px repeat(auto-fill, 1px 1px 1px);
+ width: 13px;
+ }
+ div > div {
+ /* Any name will work */
+ grid-column-start: x;
+ }
+ </style>
+ <div class="grid-container">
+ <div>x</div>
+ <div>y</div>
+ <div>z</div>
+ <div>w</div>
+ </div>
+</html>
diff --git a/layout/generic/crashtests/1629575-2.html b/layout/generic/crashtests/1629575-2.html
new file mode 100644
index 0000000000..e8b5df190a
--- /dev/null
+++ b/layout/generic/crashtests/1629575-2.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html><head>
+
+
+ <style>
+ .grid-container {
+ display: grid;
+ border: solid thick;
+ margin: 10px;
+ }
+
+ .columns {
+ grid-template-columns: repeat(auto-fill, 50px 50px);
+ grid-auto-rows: 25px;
+ grid-column-gap: 100px;
+ width: 300px;
+ }
+
+ .rows {
+ grid-auto-flow: column;
+ grid-template-rows: repeat(auto-fill, 50px 50px);
+ grid-auto-columns: 25px;
+ grid-row-gap: 100px;
+ width: min-content;
+ height: 300px;
+ }
+
+ .grid-container>div {
+ background: lime;
+ }
+ </style>
+
+<style>
+ div { grid-column-start: first;}
+</style>
+
+</head>
+
+<body>
+
+ <div class="grid-container columns">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ </div>
+ <div class="grid-container columns"
+ style="grid-template-columns: repeat(auto-fill, 50px 50px 50px)">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ </div>
+ <div class="grid-container columns"
+ style="grid-template-columns: repeat(auto-fill, 50px 50px 50px 50px)">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ </div>
+ <div class="grid-container rows">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ </div>
+
+
+</body></html>
diff --git a/layout/generic/crashtests/1630385.html b/layout/generic/crashtests/1630385.html
new file mode 100644
index 0000000000..2fcb2f155e
--- /dev/null
+++ b/layout/generic/crashtests/1630385.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+<script>
+window.setTimeout(() => {
+ try{ c.style.cssText="margin-top:78%" }catch(e){}
+ try{ b.style.cssText="padding-right:100em" }catch(e){}
+ try{ b.scrollIntoView() }catch(e){}
+ try{ b.setAttribute('style', "filter:opacity()drop-shadow(2px 8vmax hsl(2,9%,6%))opacity(") }catch(e){}
+ try{ b.scrollIntoView() }catch(e){}
+ try{ a.setAttribute('style', "") }catch(e){}
+ try{ a.offsetHeight }catch(e){}
+ try{ a.textContent="" }catch(e){}
+ setTimeout(() => {
+ document.documentElement.className = "";
+ }, 0)
+}, 0)
+</script>
+<style>
+:last-of-type {
+ overflow-y:clip;
+}
+</style>
+<body id='a'>
+e
+<samp id='b'>
+</samp>
+<canvas id='c'/>
diff --git a/layout/generic/crashtests/1633434.html b/layout/generic/crashtests/1633434.html
new file mode 100644
index 0000000000..8a60b2072c
--- /dev/null
+++ b/layout/generic/crashtests/1633434.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+ svg.setAttribute('height', '6')
+ svg.setAttribute('width', '1pc')
+ document.documentElement.appendChild(svg)
+ svg.style.setProperty('height', '5%', undefined)
+ svg.width.baseVal.valueInSpecifiedUnits = 1.988164037240853e+38
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1633737-1.html b/layout/generic/crashtests/1633737-1.html
new file mode 100644
index 0000000000..d42258dc73
--- /dev/null
+++ b/layout/generic/crashtests/1633737-1.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <style>* {
+ grid-template: none/repeat(58, minmax(19443em, max-content) minmax(2vh, max-content)) repeat(auto-fill, 1vw minmax(3%, 1in)) 1px repeat(25817, minmax(25in, min-content))
+ </style>
+ <button style='display:inline-grid'></button>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1633737-2.html b/layout/generic/crashtests/1633737-2.html
new file mode 100644
index 0000000000..1e2274f8b4
--- /dev/null
+++ b/layout/generic/crashtests/1633737-2.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <style>
+div { grid-template-columns: 9919443em repeat(auto-fill, 1in 1in 1in) repeat(25817, 25in); }
+ </style>
+ <div style='display:inline-grid'></div>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1633737-3.html b/layout/generic/crashtests/1633737-3.html
new file mode 100644
index 0000000000..19fc6d78be
--- /dev/null
+++ b/layout/generic/crashtests/1633737-3.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<!--
+ any copyright is dedicated to the public domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <title>Testcase for Bug 1633737</title>
+ <style>
+html,body {
+ color:black; background-color:white; font:15px/1 monospace; padding:0; margin:0;
+}
+
+.grid {
+ display: grid;
+ width: min-content;
+ grid-template-columns: repeat(9998,0) repeat(auto-fill, 5px 10px 15px 20px);
+ grid-auto-columns: 500px; /* should not be used */
+ border: 1px solid;
+ margin-bottom: 2px;
+}
+
+x { grid-column: 1 / foo; }
+
+ </style>
+</head>
+<body>
+
+<div class="grid"><x></x></div>
+<script>
+ document.body.offsetHeight;
+ window.getComputedStyle(document.querySelectorAll('.grid')[0]).gridTemplateColumns;
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1633737-4.html b/layout/generic/crashtests/1633737-4.html
new file mode 100644
index 0000000000..804837dce5
--- /dev/null
+++ b/layout/generic/crashtests/1633737-4.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <title>Testcase for Bug 1633737</title>
+ <style>
+html,body {
+ color:black; background-color:white; font:15px/1 monospace; padding:0; margin:0;
+}
+
+.grid {
+ display: grid;
+ width: min-content;
+ grid-auto-columns: 500px; /* should not be used */
+ border: 1px solid;
+ margin-bottom: 2px;
+}
+
+x { grid-column: 1 / foo; }
+
+ </style>
+</head>
+<body>
+
+<script>
+const MAX_LINE_NUMBER = 10000;
+const sz = ["5px", "10px", "15px", "20px", "25px"];
+const cols = [9997, 9998, 9999, 10000, 10001];
+for (let len = 1; len <= sz.length; ++len) {
+ for (const c of cols) {
+ if (c + len + 1 < MAX_LINE_NUMBER) {
+ continue;
+ }
+ let grid = document.createElement("div");
+ grid.className = "grid";
+ let s = "repeat(" + c + ",0) " + "repeat(auto-fill,";
+ for (let i = 0; i < len; ++i) {
+ s += " " + sz[i];
+ }
+ s += ")";
+ grid.style.gridTemplateColumns = s;
+ console.log(s);
+ let item = document.createElement("x");
+ grid.appendChild(item);
+ document.body.appendChild(grid);
+ }
+}
+</script>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/1633737-5.html b/layout/generic/crashtests/1633737-5.html
new file mode 100644
index 0000000000..889a53836a
--- /dev/null
+++ b/layout/generic/crashtests/1633737-5.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<!--
+ any copyright is dedicated to the public domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <title>Testcase for Bug 1633737</title>
+ <style>
+html,body {
+ color:black; background-color:white; font:15px/1 monospace; padding:0; margin:0;
+}
+
+.grid {
+ display: grid;
+ width: min-content;
+ grid-template-columns: repeat(9998,0) repeat(auto-fit, 5px [b] 10px [c] 15px [d] 20px [e] 25px);
+ grid-auto-columns: 500px; /* should not be used */
+ border: 1px solid;
+ margin-bottom: 2px;
+}
+
+x { grid-column: b / e; }
+
+ </style>
+</head>
+<body>
+
+<div class="grid"><x style="grid-column: b / e"></x></div>
+<div class="grid"><x style="grid-column: 1 / e"></x></div>
+<div class="grid"><x style="grid-column: c / foo"></x></div>
+<div class="grid"><x style="grid-column: b / d"></x></div>
+<script>
+ document.body.offsetHeight;
+ document.querySelectorAll('.grid').forEach(function(grid){
+ window.getComputedStyle(grid).gridTemplateColumns;
+ });
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1633828.html b/layout/generic/crashtests/1633828.html
new file mode 100644
index 0000000000..f0db68e853
--- /dev/null
+++ b/layout/generic/crashtests/1633828.html
@@ -0,0 +1,36 @@
+<script>
+var limit = 0
+function go() {
+ part1()
+ part1()
+ window.find("foo",true,true,true,true,false)
+}
+function part2() {
+ if(++limit > 2) { return; }
+ g.appendChild(a)
+ d.insertAdjacentText("afterEnd", "foo")
+ f.addEventListener("DOMAttrModified", () => {
+ window.getSelection().deleteFromDocument()
+ b.select()
+ })
+}
+function part1() {
+ var x = window.getSelection()
+ g.appendChild(c)
+ g.addEventListener("DOMSubtreeModified", part2)
+ c.border = "1"
+ document.createElement("rp").prepend(x.focusNode)
+ window.find("foo")
+ a.value = ""
+ x.collapseToEnd()
+}
+</script>
+<body onload=go()>
+<button id="a"></button>
+<li>
+<textarea id="b"></textarea>
+</li>
+<image id="c"></image>
+<br id="d">
+<font id="f">
+<font id="g">
diff --git a/layout/generic/crashtests/1638860-1.html b/layout/generic/crashtests/1638860-1.html
new file mode 100644
index 0000000000..150fc742f6
--- /dev/null
+++ b/layout/generic/crashtests/1638860-1.html
@@ -0,0 +1,24 @@
+<html>
+<body>
+ <style>
+ html {
+ transform-style: preserve-3d;
+ }
+ div,html {
+ display: inline-grid;
+ grid-template-columns: subgrid;
+ }
+ div {
+ position: fixed;
+ }
+ </style>
+ <script>
+ window.addEventListener('load', () => {
+ const div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div')
+ const text = document.createTextNode('BAR')
+ div.appendChild(text)
+ document.documentElement.appendChild(div)
+ })
+ </script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1638860-2.html b/layout/generic/crashtests/1638860-2.html
new file mode 100644
index 0000000000..a04861a662
--- /dev/null
+++ b/layout/generic/crashtests/1638860-2.html
@@ -0,0 +1,26 @@
+<html>
+<body>
+ <style>
+ div {
+ position: relative;
+ }
+ span,div {
+ display: inline-grid;
+ grid-template-columns: subgrid;
+ }
+ span {
+ position: absolute;
+ }
+ </style>
+ <script>
+ window.addEventListener('load', () => {
+ const span = document.createElementNS('http://www.w3.org/1999/xhtml', 'span')
+ const text = document.createTextNode('BAR')
+ span.appendChild(text)
+ const div = document.getElementsByTagName('div')[0];
+ div.appendChild(span)
+ })
+ </script>
+<div>FOO</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1638906.html b/layout/generic/crashtests/1638906.html
new file mode 100644
index 0000000000..39b65e32f4
--- /dev/null
+++ b/layout/generic/crashtests/1638906.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+ <style>
+ *, LI {
+ display: inline-grid;
+ grid-row: line_name_1 / auto;
+ }
+ HTML {
+ scale: -102 63;
+ }
+ * {
+ grid: subgrid [ line_name_5 ] / auto-flow dense fit-content(40%);
+ }
+ </style>
+
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const style = document.createElement('style')
+ document.head.appendChild(style)
+ const sheet = style.sheet
+ sheet.addRule('script', 'position:absolute!important', (820656062 % sheet.cssRules.length))
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1640028.html b/layout/generic/crashtests/1640028.html
new file mode 100644
index 0000000000..6ada5791d3
--- /dev/null
+++ b/layout/generic/crashtests/1640028.html
@@ -0,0 +1,31 @@
+<style>
+* {
+ column-width: 15em;
+ display: flex;
+}
+#a {
+ writing-mode: tb;
+ display: inline-grid;
+ font-size-adjust: 5;
+}
+</style>
+<script>
+window.onload = () => {
+ b.align = "ABSMIDDLE"
+ c.scrollBy(0.6938726108254376,0.19469668495287307)
+ d.insertCell(1)
+}
+</script>
+<data id="a">
+<details style="width: 0pt">
+<summary style="max-width: 0vh">
+<table>
+<tr id="d">
+<th></th>
+<col id="b">
+</table>
+aaaaaa
+aaaaaaaaaaaaa
+</details>
+aaaaaaaaaaaaaaaaaaaa
+<fePointLight id="c">
diff --git a/layout/generic/crashtests/1640051.html b/layout/generic/crashtests/1640051.html
new file mode 100644
index 0000000000..b9766b459b
--- /dev/null
+++ b/layout/generic/crashtests/1640051.html
@@ -0,0 +1,20 @@
+<style>
+* {
+ column-width: 0em;
+ font-weight: bold;
+ overflow-wrap: break-word
+}
+</style>
+<script>
+window.onload = () => {
+ a.style.setProperty("display", "flex")
+}
+</script>
+<time>AAAAAAAAAAAAAAAAAA</time>
+<content style="font-size: large">AAAAAAAAAAAAAA</content>
+<layer>AAAAAAAAAAA</layer>
+<pre id="a" wrap="">
+<dir>
+<li>AAAAAAAAAAAAAAA</li>
+</pre>
+<svg>
diff --git a/layout/generic/crashtests/1640275.html b/layout/generic/crashtests/1640275.html
new file mode 100644
index 0000000000..a28d08d981
--- /dev/null
+++ b/layout/generic/crashtests/1640275.html
@@ -0,0 +1,14 @@
+<style>
+* {
+ display: -webkit-inline-flex;
+ margin-top: 7vw;
+ column-count: 6;
+}
+</style>
+<script>
+window.onload = () => {
+ document.createElement("select").add(a)
+}
+</script>
+<details open="true"><datalist>
+<option id="a">
diff --git a/layout/generic/crashtests/1644819.html b/layout/generic/crashtests/1644819.html
new file mode 100644
index 0000000000..b08a411835
--- /dev/null
+++ b/layout/generic/crashtests/1644819.html
@@ -0,0 +1,20 @@
+<style>
+#x {
+ -webkit-user-select: none;
+ word-break: break-word;
+ width: 0vh;
+ -webkit-perspective: 89px;
+ display: flex;
+ height: 1ch;
+}
+</style>
+<script>
+window.onload = () => {
+ document.execCommand("selectAll");
+ document.execCommand("selectAll");
+ document.execCommand("backColor", false, "r");
+ document.execCommand("superscript");
+}
+</script>
+<dl style="columns:1px">
+<dt id="x" contenteditable>aaa</dt>
diff --git a/layout/generic/crashtests/1645549-1.html b/layout/generic/crashtests/1645549-1.html
new file mode 100644
index 0000000000..647373800d
--- /dev/null
+++ b/layout/generic/crashtests/1645549-1.html
@@ -0,0 +1,19 @@
+<style>
+#a {
+ display: flex;
+ flex-flow: column-reverse;
+}
+:not(wbr) {
+ column-width: 1px;
+}
+</style>
+<script>
+window.onload = () => {
+ a.insertBefore(b, a.childNodes[0])
+ document.documentElement.style.display = "none"
+ document.documentElement.getBoundingClientRect()
+ document.documentElement.style.display = ""
+}
+</script>
+<label id="a">x</label>
+<details id="b">
diff --git a/layout/generic/crashtests/1648577.html b/layout/generic/crashtests/1648577.html
new file mode 100644
index 0000000000..f2244a56c1
--- /dev/null
+++ b/layout/generic/crashtests/1648577.html
@@ -0,0 +1,18 @@
+<style>
+#a {
+ flex-wrap: wrap-reverse;
+ float: right;
+ display: inline-flex;
+}
+#b {
+ order: 4;
+ break-after: page;
+}
+body:last-child {
+ columns: 1px;
+}
+</style>
+<time id="a">
+<video></video>
+<ol id="b"></ol>
+<link>x</link>
diff --git a/layout/generic/crashtests/1652618.html b/layout/generic/crashtests/1652618.html
new file mode 100644
index 0000000000..385c18ee62
--- /dev/null
+++ b/layout/generic/crashtests/1652618.html
@@ -0,0 +1,15 @@
+<style>
+* {
+ float: left !important;
+ all: initial;
+ block-size: 247ch;
+ columns: 251ch auto;
+}
+</style>
+<script>
+window.addEventListener('load', () => {
+ var x = document.createElementNS('http://www.w3.org/1999/xhtml', 'audio')
+ try { x.innerHTML = '<marquee>' } catch (e) {}
+ try { document.documentElement.appendChild(x) } catch (e) {}
+})
+</script>
diff --git a/layout/generic/crashtests/1652897.html b/layout/generic/crashtests/1652897.html
new file mode 100644
index 0000000000..bc96f46e73
--- /dev/null
+++ b/layout/generic/crashtests/1652897.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+// DO NOT move this script to the end of this file
+// as the bug depends on this specific tree structure
+document.addEventListener("DOMContentLoaded", () => {
+ window.getSelection().selectAllChildren(document.body)
+ window.getSelection().modify('extend','left','word')
+});
+</script>
+<table>
+<caption>
+<caption>
diff --git a/layout/generic/crashtests/1654925.html b/layout/generic/crashtests/1654925.html
new file mode 100644
index 0000000000..410a42e773
--- /dev/null
+++ b/layout/generic/crashtests/1654925.html
@@ -0,0 +1,16 @@
+<script>
+function go() {
+ a.appendChild(b)
+}
+</script>
+<style>
+#b {
+ display: unset;
+ position: fixed;
+}
+</style>
+<body onload=go()>
+<ul id="a">
+<audio id="b">
+<marquee></marquee>
+<span>x</span>
diff --git a/layout/generic/crashtests/1663222.html b/layout/generic/crashtests/1663222.html
new file mode 100644
index 0000000000..132436c3fc
--- /dev/null
+++ b/layout/generic/crashtests/1663222.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<style>
+.a {
+ letter-spacing: 0.635em;
+ float: left;
+}
+.b {
+ column-span: all;
+ line-height: 89vmax;
+ padding-bottom: 6vmax;
+}
+:not(animateTransform) {
+ word-break: break-word;
+ columns: 15px;
+}
+</style>
+<legend class="b">
+<input itemprop="">
+<o>s.ykED*-U6p]6</>8&quotFX7wpo&mK`</><details class="a"><summary>cx.6W</>.2;3\52A
diff --git a/layout/generic/crashtests/1666592.html b/layout/generic/crashtests/1666592.html
new file mode 100644
index 0000000000..9f4518c0c4
--- /dev/null
+++ b/layout/generic/crashtests/1666592.html
@@ -0,0 +1,2 @@
+<style>#a{display:flex;flex-flow:column-reverse}:not(w){column-width:1px</style><l id="a"><<label id="a">x<l>x</label>a i
+
diff --git a/layout/generic/crashtests/1670336.html b/layout/generic/crashtests/1670336.html
new file mode 100644
index 0000000000..34e5fa2d76
--- /dev/null
+++ b/layout/generic/crashtests/1670336.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<style>
+.c1 {
+ column-span: all;
+ border-bottom-style: groove;
+ float: left;
+ border-width: 0em 0em 9em 1em;
+ box-decoration-break: clone;
+ height: 21vmin;
+}
+.c2 {
+ height: 74vmin;
+}
+:not(param) {
+ column-width: 1px;
+}
+</style>
+<script>
+function go() {
+ a.lastElementChild.appendChild(b)
+ window.requestIdleCallback(window.close)
+}
+function fuzz() {
+ var x = document.createElementNS("", "f")
+ x.prepend("1")
+ b.insertAdjacentHTML("afterEnd", x.outerHTML)
+}
+</script>
+<body onload=go()>
+<aside id="a">
+<data id="b" class="c1">U-.9zA</shadow>
+</aside>
+<details ontoggle="fuzz()" open="" class="c2">
diff --git a/layout/generic/crashtests/1676970.html b/layout/generic/crashtests/1676970.html
new file mode 100644
index 0000000000..fd3af85db2
--- /dev/null
+++ b/layout/generic/crashtests/1676970.html
@@ -0,0 +1,20 @@
+<script>
+function go() {
+ a.appendChild(c)
+ a.getRootNode().addEventListener("DOMSubtreeModified", eh, { once: true })
+ b.setAttribute("oninvalid", "eh()")
+}
+function eh() {
+ b.setAttribute("role", "dialog")
+ window.scrollBy(0.802, 0.384)
+ var x = document.getSelection()
+ x.extend(a)
+ x.modify("move", "forward", "lineboundary")
+ a.style.setProperty("column-span", "all")
+}
+</script>
+<body onload=go()>
+<select>
+<option id="a" contenteditable="true">x</span>
+<input id="b">
+<textarea id="c">
diff --git a/layout/generic/crashtests/1677518-1.html b/layout/generic/crashtests/1677518-1.html
new file mode 100644
index 0000000000..cbf6161c44
--- /dev/null
+++ b/layout/generic/crashtests/1677518-1.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<head>
+<script>
+function finish() {
+ document.documentElement.className = "";
+}
+ window.addEventListener('load', () => {
+ const video = document.createElementNS('http://www.w3.org/1999/xhtml', 'video')
+ document.documentElement.appendChild(video)
+ setTimeout(async () => { video.poster = '1677518-1.jpg'; setTimeout(finish, 500); }, 165 )
+ video.setAttribute('poster', '1677518-1.svg')
+ })
+ </script>
+</head>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/1677518-1.jpg b/layout/generic/crashtests/1677518-1.jpg
new file mode 100644
index 0000000000..ab1a9b1165
--- /dev/null
+++ b/layout/generic/crashtests/1677518-1.jpg
Binary files differ
diff --git a/layout/generic/crashtests/1677518-1.svg b/layout/generic/crashtests/1677518-1.svg
new file mode 100644
index 0000000000..3f7b668b02
--- /dev/null
+++ b/layout/generic/crashtests/1677518-1.svg
@@ -0,0 +1,29 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ class="reftest-wait"
+ onload="setTimeAndSnapshot(2, true)">
+ <script xlink:href="../smil-util.js" type="text/javascript"/>
+ <!--
+ Test that ends are sampled first.
+ -->
+ <rect width="100" height="100" fill="red">
+ <animate attributeName="y" attributeType="XML" from="0" to="0" id="a"
+ begin="-3s; 1.5s" dur="20s"/>
+ <animate attributeName="fill" attributeType="CSS"
+ values="orange; green; purple"
+ begin="a.begin-0.5s; 1.5s" dur="2s" restart="whenNotActive"/>
+ <!--
+ So initially we have:
+ a: -3->17s
+ (b): -3.5->-1.5s (instance times: -3.5s, 1.5s)
+
+ At t=1.5s we get an early end on 'a', giving us:
+ a: 1.5->21.5s
+ (b): 1.0->3.0s (instance times: 1.0s, 1.5s)
+
+ If, at t=1.5s, we sample the second animation first, we'll start an
+ interval from 1.5s instead of 1.0s. So this is a test that ends are
+ actually sampled first.
+ -->
+ </rect>
+</svg>
diff --git a/layout/generic/crashtests/1679794.html b/layout/generic/crashtests/1679794.html
new file mode 100644
index 0000000000..dbc83a3483
--- /dev/null
+++ b/layout/generic/crashtests/1679794.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+ <style>
+ * {
+ writing-mode: sideways-rl;
+ scale: 99 calc(88.16690992045048 * 43820.370300978146)
+ }
+
+ TIME,
+ *:-moz-full-screen {
+ inset: -3732em;
+ font-size: 62235vh ! important;
+ }
+
+ * ~ * {
+ position: absolute;
+ padding-left: 1520280669.8216546cm ! important;
+ }
+ </style>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const time = document.createElementNS('http://www.w3.org/1999/xhtml', 'time')
+ document.documentElement.appendChild(time)
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1680406.html b/layout/generic/crashtests/1680406.html
new file mode 100644
index 0000000000..d86973a056
--- /dev/null
+++ b/layout/generic/crashtests/1680406.html
@@ -0,0 +1,16 @@
+<style>
+.a {
+ column-count: 4;
+ display: block;
+}
+* {
+ height: 1vmax;
+ grid-row-gap: 7em;
+ display: -webkit-flex;
+ -webkit-flex-direction: column
+}
+</style>
+<time class="a"></x>
+<label>
+x
+<q>
diff --git a/layout/generic/crashtests/1681788.html b/layout/generic/crashtests/1681788.html
new file mode 100644
index 0000000000..c6c732caef
--- /dev/null
+++ b/layout/generic/crashtests/1681788.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+ <style>
+ * {
+ display: inline-flex;
+ padding-bottom: 28%;
+ block-size: 86%;
+ box-sizing: border-box;
+ border-block: ridge 3267675504.837444em currentcolor;
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1682032.html b/layout/generic/crashtests/1682032.html
new file mode 100644
index 0000000000..2974e5ec0d
--- /dev/null
+++ b/layout/generic/crashtests/1682032.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script>
+ window.addEventListener('load', () => {
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'style')
+ document.documentElement.appendChild(svg)
+ svg.textContent = `
+ HTML { column-rule-color: rgba( 4294967295%, 0%, 0%, 0% );
+ inset-block: 4294967295% 0%;
+ min-block-size: 4294967295%;
+ position: fixed;
+ aspect-ratio: 4294967295 / 4294967295;`
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1682686-1.html b/layout/generic/crashtests/1682686-1.html
new file mode 100644
index 0000000000..10bbf5077c
--- /dev/null
+++ b/layout/generic/crashtests/1682686-1.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<body onload="document.body.style.width = '95%'">
+<div style="padding: 10px; display: grid; grid-template-rows: min-content auto;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<div style="display: grid;">
+<span>TEST</span>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1682686-2.html b/layout/generic/crashtests/1682686-2.html
new file mode 100644
index 0000000000..3aef0e7c1f
--- /dev/null
+++ b/layout/generic/crashtests/1682686-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<style>
+ .grid {
+ display: grid;
+ border-left: 1px solid blue;
+ margin-left: 0.5em;
+ }
+</style>
+<body onload="document.body.style.width = '95%'">
+ <div class="grid">1
+ <div class="grid">2
+ <div class="grid">3
+ <div class="grid">4
+ <div class="grid">5
+ <div class="grid">6
+ <div class="grid">7
+ <div class="grid">8
+ <div class="grid">9
+ <div class="grid">10
+ <div class="grid">11
+ <div class="grid">12
+ <div class="grid">13
+ <div class="grid">14
+ <div class="grid">15
+ <div class="grid">16
+ <div class="grid">17
+ <div class="grid">18
+ <div class="grid">19
+ <div class="grid">20
+ <div class="grid">21
+ <div class="grid">22
+ <div class="grid">23
+ <div class="grid">24
+ <div class="grid">25
+ <div class="grid">26
diff --git a/layout/generic/crashtests/1682882.html b/layout/generic/crashtests/1682882.html
new file mode 100644
index 0000000000..ad6ba94375
--- /dev/null
+++ b/layout/generic/crashtests/1682882.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+ <style id='style'>
+ * {
+ all: revert;
+ }
+ </style>
+ <script>
+ window.addEventListener('load', () => {
+ const style_0 = document.getElementById('style')
+ const style_1 = document.createElement('style')
+ document.head.appendChild(style_1)
+ style_1.sheet.insertRule(`* { position: absolute; scroll-padding-inline: 3195984415.1022196vh }`, 0)
+ style_1.sheet.insertRule(`*::first-line { border-left-color: transparent }`, 0)
+ style_1.sheet.insertRule(`* ~ * { display: contents }`, 0)
+ const xhr = new XMLHttpRequest()
+ xhr.open('POST', 'FOOBAR', false)
+ xhr.send()
+ style_0.innerHTML = '<div></div>'
+ })
+ </script>
+</head>
+<ol hidden>
+ <li>
+ <li contenteditable='true'></li>
+</ol>
+</html>
diff --git a/layout/generic/crashtests/1683126.html b/layout/generic/crashtests/1683126.html
new file mode 100644
index 0000000000..8d08ab3127
--- /dev/null
+++ b/layout/generic/crashtests/1683126.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+ <style>
+ * {
+ border-block-width: 1151557106.9598303in ! important;
+ border-block-end-style: double;
+ }
+
+ * * {
+ position: fixed;
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1697262-1.html b/layout/generic/crashtests/1697262-1.html
new file mode 100644
index 0000000000..784ebd18c7
--- /dev/null
+++ b/layout/generic/crashtests/1697262-1.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<script>
+let pp;
+window.addEventListener("MozReftestInvalidate", finish);
+window.onload = () => {
+ try { document.getElementById('a').setAttribute('style', 'left:76%') } catch (e) {}
+ pp = SpecialPowers.wrap(self).printPreview();
+ pp?.print()
+ setTimeout(window.close, 250)
+}
+function finish() {
+ setTimeout(function() {
+ pp.close();
+ document.documentElement.className = "";
+ }, 400);
+}
+
+</script>
+<style>
+:last-of-type {
+ position: relative;
+}
+</style>
+<select>
+<optgroup label='y'>
+<option>
+</select>
+<video id='a'>
+</html>
diff --git a/layout/generic/crashtests/1699263.html b/layout/generic/crashtests/1699263.html
new file mode 100644
index 0000000000..5a1aefa083
--- /dev/null
+++ b/layout/generic/crashtests/1699263.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ BODY {
+ position: fixed ! important;
+ }
+
+ * {
+ aspect-ratio: auto 3962283067.99873 / 2091098098.4395208;
+ filter: url(#id_1) ! important;
+ inset: 49% auto;
+ height: 261118874%
+ }
+ </style>
+</head>
+<body></body>
+</html>
diff --git a/layout/generic/crashtests/1699468.html b/layout/generic/crashtests/1699468.html
new file mode 100644
index 0000000000..6f86e4cd41
--- /dev/null
+++ b/layout/generic/crashtests/1699468.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ HTML {
+ flex-direction: column;
+ }
+
+ EMBED {
+ inline-size: min-content;
+ width: 621490006%;
+ }
+
+ HTML {
+ display: inline-flex !important;
+ }
+
+ * {
+ aspect-ratio: 2044245302.5699975 / 2382682379.6661625;
+ writing-mode: vertical-lr;
+ }
+ </style>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const embed = document.createElement('embed')
+ document.documentElement.appendChild(embed)
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1728319.html b/layout/generic/crashtests/1728319.html
new file mode 100644
index 0000000000..1ef3bf1554
--- /dev/null
+++ b/layout/generic/crashtests/1728319.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ padding-right: 2589893033.302024Q;
+ display: inline-flex;
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/generic/crashtests/1730506.html b/layout/generic/crashtests/1730506.html
new file mode 100644
index 0000000000..9910e5e016
--- /dev/null
+++ b/layout/generic/crashtests/1730506.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ grid-column-gap: 3970820911.345316ex !important;
+ }
+ </style>
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const style = document.createElement("style")
+ document.head.appendChild(style)
+ style.sheet.insertRule(`* { display: inline-flex; }`, 0)
+ })
+ </script>
+</head>
+<img>
+<script></script>
+<b></b>
+<table></table>
+</html>
diff --git a/layout/generic/crashtests/1730570.html b/layout/generic/crashtests/1730570.html
new file mode 100644
index 0000000000..79193375c2
--- /dev/null
+++ b/layout/generic/crashtests/1730570.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ padding-left: 385945468.9776893vmin ! important;
+ flex-direction: column-reverse;
+ }
+
+ HTML {
+ writing-mode: sideways-lr;
+ }
+
+ #id_0 * {
+ padding-right: 4243575909.3360567ch !important;
+ border-left: inset rgb(27% 15% 81% / 1%);
+ }
+
+ #id_1 {
+ display: inline flex ! important;
+ }
+
+ #id_2 {
+ aspect-ratio: 321484271.5137855 / 2731818911.2819076;
+ }
+ </style>
+</head>
+<h1 id="id_0">
+ <sup id="id_1">
+ <select id="id_2"></select>
+ </sup>
+</h1>
+</html>
diff --git a/layout/generic/crashtests/1734015.html b/layout/generic/crashtests/1734015.html
new file mode 100644
index 0000000000..950a3f51cc
--- /dev/null
+++ b/layout/generic/crashtests/1734015.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ all: initial;
+ }
+ </style>
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const selection = document.getSelection()
+ const option = document.getElementById("id_0")
+ selection.setPosition(option)
+ selection.modify("move", "backward", "line")
+ })
+ </script>
+</head>
+<select>
+ <option id="id_0"></option>
+</select>
+</html>
diff --git a/layout/generic/crashtests/1776079.html b/layout/generic/crashtests/1776079.html
new file mode 100644
index 0000000000..e9c66092b0
--- /dev/null
+++ b/layout/generic/crashtests/1776079.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<style>
+html {
+ column-width: 0px;
+}
+.a {
+ break-inside: avoid;
+ word-wrap: break-word;
+ visibility: collapse;
+ display: flex;
+}
+</style>
+<li>
+<time>a</time>
+</li>
+<base></base>
+<q>
+<li class="a">aaaaaaaaaaaa</li>
+<ul style="writing-mode: sideways-lr">aaa</ul>
diff --git a/layout/generic/crashtests/1791606.html b/layout/generic/crashtests/1791606.html
new file mode 100644
index 0000000000..f748d5b6d1
--- /dev/null
+++ b/layout/generic/crashtests/1791606.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <style>
+ * {
+ white-space: pre;
+ padding: 1px 30% 35%;
+ scroll-snap-type: block proximity;
+ overflow-y: hidden !important;
+ scroll-snap-align: center;
+ scroll-margin-block-end: 100vmax;
+ }
+ </style>
+</head>
+<body>
+𛵺𫗡<\n\r󠼘0𛇶)0á©¿\rð†¬ÙƒÙ©ã»¡\r\n\uDCFFNá·ó ¿°ð¥œ´&=0䱜󠖰괚۰å¶Nó ‹ á·“\n\rð¯ºð–¡‚🇚󠄢2ð €®\r0󠾎𯇫⭲<\uDB3A-ó ž„ð’´™ð¯¸ð’»Dó šµ4꙯ká·¸Ù«â¡ð††' hidden draggable='false' title='󠙘᷾𖄨ð¡¾]{+ä¤ð¯›ž-​ð†‰cH\náž\uDC1D\uDC1DÒ‡ð…©ä¡´I+٠𛽋?ë°¼ó ˆ….𛾺9𛼨𯽢.\r𯩃(\r^ó ’𯮑󠯶𡥳 2\f\n\ró ’‮۰\n󠋇𩙅4R𦤔⛨ﰸÎÙ ]+=ó ®+ð£ŠÛ°Òƒ90a\uDC1Dó ¥–á·±cᆹ۹迅b𖑎㤜᭱i\u202F᧪ᷭ-ð‡½äŒ½\uDC1D٠颇\r0è·ð…¥ï»¿' itemid='ð–ˆ·\r\n倂2R𯽄%Ù«!𛣑󠱸肨0𯩋󠉲ð…±ð¯ˆªU%ì º\0' translate='no' autocapitalize='words' spellcheck='false' accesskey='&'>
+<i id='id_0' tabindex='32'>
+ </del>
+</i>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ document.getElementById('id_0').focus({});
+ setTimeout(() => {
+ document.documentElement.className = "";
+ }, 1000);
+});
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/1799749.html b/layout/generic/crashtests/1799749.html
new file mode 100644
index 0000000000..8107058d48
--- /dev/null
+++ b/layout/generic/crashtests/1799749.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<div style="overflow: auto;">
+<canvas style="margin-left: auto; filter: url(#htmlvar00005); display: list-item;" width="1">a</canvas>
+<div id="htmlvar00005" style="bottom: 0; display: list-item; overflow: auto;"></div>
+</div>
diff --git a/layout/generic/crashtests/1807958.html b/layout/generic/crashtests/1807958.html
new file mode 100644
index 0000000000..3eb1023b09
--- /dev/null
+++ b/layout/generic/crashtests/1807958.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ #id_1 {
+ writing-mode: sideways-rl;
+ columns: 3364184556 118834207.58399546em !important;
+ }
+
+ * {
+ padding-block-start: 1150693220%;
+ content-visibility: hidden;
+ border-top-style: groove;
+ overflow-block: scroll;
+ min-block-size: 2744448288.222785px;
+ display: flex;
+ padding-inline-end: 3966002714.7525673Q !important;
+ }
+
+ SLOT {
+ display: inline;
+ }
+ </style>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ document.documentElement.appendChild(document.getElementById('id_0'))
+ })
+ </script>
+</head>
+<slot id='id_0'>
+ <slot id='id_1'></slot>
+</slot>
+</html>
diff --git a/layout/generic/crashtests/1816574.html b/layout/generic/crashtests/1816574.html
new file mode 100644
index 0000000000..de5d440d90
--- /dev/null
+++ b/layout/generic/crashtests/1816574.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ grid-gap: 164.70272054344653vw 68%;
+ }
+ </style>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const style = document.createElement("style")
+ document.documentElement.appendChild(style)
+ style.textContent = `
+ @font-face { }
+ * {
+ column-width: 161.0069566994682Q;
+ offset-anchor: left 0ex top -32ch;
+ break-inside: avoid;
+ }`
+ style.sheet.insertRule(`* {
+ aspect-ratio: 2334586695.701634 / 653082496.7273545 ! important;
+ margin-top: -84em;
+ min-inline-size: 162em;
+ font-size-adjust: 8198;
+ }`, 0)
+ })
+ </script>
+</head>
+<h4></h4>
+<svg></svg>
+<fieldset>
+ <legend>
+ <cite>
+  e\r𠪢0ið…¯ó ‡©0*=ð¤µ*=\nâ€ï¿½' lang='ar'>
+ <h5></h5>
+ </cite>
+ </legend>
+</fieldset>
+</html>
diff --git a/layout/generic/crashtests/1821603.html b/layout/generic/crashtests/1821603.html
new file mode 100644
index 0000000000..5935121cde
--- /dev/null
+++ b/layout/generic/crashtests/1821603.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<div style="display: flex">
+ <div style="visibility: collapse; direction: rtl"></div>
+</div>
diff --git a/layout/generic/crashtests/1822118.html b/layout/generic/crashtests/1822118.html
new file mode 100644
index 0000000000..d4776fa6b7
--- /dev/null
+++ b/layout/generic/crashtests/1822118.html
@@ -0,0 +1,11 @@
+<style>
+.a {
+ min-width: 641em;
+ display: list-item inline;
+}
+</style>
+A
+<ruby>
+<rp class="a">
+<rt>
+<fieldset class="a">
diff --git a/layout/generic/crashtests/1825434.html b/layout/generic/crashtests/1825434.html
new file mode 100644
index 0000000000..a7d6adba57
--- /dev/null
+++ b/layout/generic/crashtests/1825434.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+.rel {
+ position: relative;
+}
+
+.abs {
+ width: 100%;
+ position: absolute;
+ padding-top: 50%;
+}
+</style>
+
+<div class="rel">
+ <div class="abs"></div>
+</div>
diff --git a/layout/generic/crashtests/225868-1-inner.html b/layout/generic/crashtests/225868-1-inner.html
new file mode 100644
index 0000000000..db8fbc73c9
--- /dev/null
+++ b/layout/generic/crashtests/225868-1-inner.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+
+html { overflow: hidden; }
+
+</style>
+</head>
+<body onload="setTimeout(function(){document.write('<body onload=&quot;parent.document.documentElement.removeAttribute(\'class\');&quot;>2</body>'); document.close();}, 0);">
+
+1
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/225868-1.html b/layout/generic/crashtests/225868-1.html
new file mode 100644
index 0000000000..55f221a7cc
--- /dev/null
+++ b/layout/generic/crashtests/225868-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head></head>
+<body>
+<iframe src="225868-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/generic/crashtests/255468.xhtml b/layout/generic/crashtests/255468.xhtml
new file mode 100644
index 0000000000..62a859511e
--- /dev/null
+++ b/layout/generic/crashtests/255468.xhtml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>Iridescence</title>
+<style type="text/css" title="Crashme" disabled="disabled">
+ul.tree ul {
+ list-style-position: inside;
+}
+
+span.type:after {
+ content: ": ";
+}
+
+</style>
+</head>
+<body>
+<h1>Crash Test</h1>
+<table><tbody>
+<tr><td><ul class="struct tree"><li><span class="type">A</span> X<ul><li><span class="type">L</span> <var>Size</var></li><li><var>data</var></li></ul></li></ul></td></tr>
+</tbody></table>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/255982-1.html b/layout/generic/crashtests/255982-1.html
new file mode 100644
index 0000000000..42ab38a761
--- /dev/null
+++ b/layout/generic/crashtests/255982-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html class="reftest-paged">
+<body>
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+</style>
+<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
+<div style="width: 60%; float: left; height: 1.6in; border:1px solid black; ">overlaps page break</div>
+<br />
+<div style="width: 60%; float: left;">3rd float</div>
+<div style="width: 60%; float: left;">4th float</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/255982-2.html b/layout/generic/crashtests/255982-2.html
new file mode 100644
index 0000000000..f595621139
--- /dev/null
+++ b/layout/generic/crashtests/255982-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html class="reftest-paged">
+<body>
+<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
+<div style="width: 60%; float: left; height: 1.6in; border:1px solid black; ">overlaps page break</div>
+Some text
+<div style="width: 60%; float: left;">3rd float</div>
+<div style="width: 60%; float: left;">4th float</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/255982-3.html b/layout/generic/crashtests/255982-3.html
new file mode 100644
index 0000000000..766dffa177
--- /dev/null
+++ b/layout/generic/crashtests/255982-3.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html class="reftest-paged">
+<body>
+<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
+<div style="width: 60%; float: left; height: 1.6in; border:1px solid black;">overlaps page break</div>
+Some text
+<div style="width: 60%; float: left;">3rd float</div>
+<div style="width: 60%; float: left;">4th float</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/255982-4.html b/layout/generic/crashtests/255982-4.html
new file mode 100644
index 0000000000..6fb1ff2c62
--- /dev/null
+++ b/layout/generic/crashtests/255982-4.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html class="reftest-paged">
+<body>
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+</style>
+<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
+<div style="width: 60%; float: left; height: 1.6in; border:1px solid black; ">overlaps page break</div>
+This is enough text to trigger a line break.
+<div style="width: 60%; float: left;">3rd float</div>
+<div style="width: 60%; float: left;">4th float</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/25888-1.html b/layout/generic/crashtests/25888-1.html
new file mode 100644
index 0000000000..bfe0f79e51
--- /dev/null
+++ b/layout/generic/crashtests/25888-1.html
@@ -0,0 +1,6 @@
+<title>Hang while developing patch for bug 25888</title>
+
+<div style="width:500px">
+<div style="float:left;width:600px;height:30px"></div>
+Hello
+</div>
diff --git a/layout/generic/crashtests/25888-2.html b/layout/generic/crashtests/25888-2.html
new file mode 100644
index 0000000000..065218f31d
--- /dev/null
+++ b/layout/generic/crashtests/25888-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<title>Testcase for hang while developing bug 25888 (hit on www.flightaware.com)</title>
+
+<div style="border: solid 1em; width: 500px; height: 500px">
+ <div style="float:right; width: 300px; height: 3px;background:yellow;"></div>
+ <div style="float:left; width: 220px; height: 100px;background:aqua;"></div>
+ hi
+</div>
diff --git a/layout/generic/crashtests/264937-1.html b/layout/generic/crashtests/264937-1.html
new file mode 100644
index 0000000000..fe0291481e
--- /dev/null
+++ b/layout/generic/crashtests/264937-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 264937</title>
+
+<style type="text/css">
+ p:first-letter {
+ background-color: lime;
+ }
+</style>
+
+</head>
+<body>
+
+ <p>"Test word</p>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/265867-1.html b/layout/generic/crashtests/265867-1.html
new file mode 100644
index 0000000000..e9da8c7f6f
--- /dev/null
+++ b/layout/generic/crashtests/265867-1.html
@@ -0,0 +1,11 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<BODY STYLE="FLOAT:RIGHT;"></BODY>
+<MARQUEE STYLE="MARGIN:99999999999px;"></MARQUEE>
+<B STYLE="FLOAT:RIGHT; PADDING:99999999999px;"></B>
+</BODY>
+</HTML>
+
+
diff --git a/layout/generic/crashtests/265867-2.html b/layout/generic/crashtests/265867-2.html
new file mode 100644
index 0000000000..c0ece7c279
--- /dev/null
+++ b/layout/generic/crashtests/265867-2.html
@@ -0,0 +1,3 @@
+<DIV STYLE="display:table-column-group;">
+ <DIV>Hello</DIV>
+</DIV>
diff --git a/layout/generic/crashtests/286491.html b/layout/generic/crashtests/286491.html
new file mode 100644
index 0000000000..5b7e632f05
--- /dev/null
+++ b/layout/generic/crashtests/286491.html
@@ -0,0 +1,26 @@
+<html><head><title>Testcase bug 286491 - Crash with evil testcase with iframe and flash inside it</title>
+<style>a:hover{display:block;}</style>
+<script>
+function doe(){
+document.links[1].style.display='block';
+setTimeout(doe2,0);
+}
+function doe2(){
+document.links[1].style.display='';
+document.links[0].style.display='block';
+setTimeout(doe3,0);
+}
+function doe3(){
+document.links[0].style.display='';
+}
+</script>
+</head>
+<body onload="setInterval(doe,20)">
+<button onclick="doe()">doe()</button>
+<button onclick="setInterval(doe,20)">setInterval(doe,20)</button><br>
+<span><a href="#">link1</a><a href="#">link2</a></span>
+<br>
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%0A%3Cobject%20classid%3D%22clsid%3AD27CDB6E-AE6D-11cf-96B8-444553540000%22%20codebase%3D%22https%3A//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab%23version%3D3%2C0%2C0%2C0%22%3E%3Cembed%20src%3D%22data%3Aapplication/x-shockwave-flash%2CFWS%2505d%2500%2500%2500%2560%2500%253E%2580%2500%2519%2500%2500%250C%2501%2500C%2502%25FF%25CC%2500%253F%250C%250E%2500%2500%2500%2501%2500%2511%2500%2505Arial%2500%2500%2502%2500%257F%2509%2523%2500%2500%2500%2502%2500g%25EC%252Fg%25EC%250F0%250D0%2501%2500%2518%2501%2500%2500%2500%25FF%2502%2500%2500%2500%2500%2500%2500%28%2500mytext%2500%2589%2506%2506%2501%2500%2502%2500%2514%2583%2516%2520%2540%2500%2500%2500%22%20pluginspage%3D%22http%3A//www.macromedia.com/go/getflashplayer%22%20type%3D%22application/x-shockwave-flash%22%20height%3D%2240%22%20width%3D%22100%22%3E%3C/object%3E%0A%3C/body%3E%3C/html%3E"></iframe>
+
+
+</body></html>
diff --git a/layout/generic/crashtests/289864-1.html b/layout/generic/crashtests/289864-1.html
new file mode 100644
index 0000000000..ed8299a9a0
--- /dev/null
+++ b/layout/generic/crashtests/289864-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<img src="289864-1.jpg" height="9999999" width="9999999" />
+</body>
+</html>
diff --git a/layout/generic/crashtests/289864-1.jpg b/layout/generic/crashtests/289864-1.jpg
new file mode 100644
index 0000000000..6337fc5717
--- /dev/null
+++ b/layout/generic/crashtests/289864-1.jpg
Binary files differ
diff --git a/layout/generic/crashtests/295292-1.html b/layout/generic/crashtests/295292-1.html
new file mode 100644
index 0000000000..1961db5364
--- /dev/null
+++ b/layout/generic/crashtests/295292-1.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <style type="text/css">
+ div{overflow:hidden;}
+ div.menubar{position:fixed;width:100%;}
+ </style>
+ </head>
+ <body>
+ <div class="menubar">
+ </div>
+ if you can't see this, it crashed.
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/295292-2.html b/layout/generic/crashtests/295292-2.html
new file mode 100644
index 0000000000..15d070f83b
--- /dev/null
+++ b/layout/generic/crashtests/295292-2.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Test Page</title>
+<style>
+#problem {
+ left:0;
+ top:0;
+ width: 0;
+ overflow: hidden;
+ position: fixed;
+}
+</style>
+</head>
+<body>
+
+<div id="problem">
+this should be hidden
+</div>
+
+page loaded
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/302260-1.html b/layout/generic/crashtests/302260-1.html
new file mode 100644
index 0000000000..ce4e0fb7a7
--- /dev/null
+++ b/layout/generic/crashtests/302260-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+
+<style type="text/css">
+
+body {
+ text-indent:20px;
+}
+
+span {
+ float:right;
+}
+</style>
+</head>
+
+<body><span>What's Clicking Now</span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/307979-1.html b/layout/generic/crashtests/307979-1.html
new file mode 100644
index 0000000000..7a9a10a1f9
--- /dev/null
+++ b/layout/generic/crashtests/307979-1.html
@@ -0,0 +1,27 @@
+<html class="reftest-wait">
+<head>
+
+<title>Crash testcase</title>
+
+<script>
+
+function foo()
+{
+ document.body.appendChild(document.createElement('frameset'));
+ setTimeout(bar, 30);
+}
+
+function bar()
+{
+ document.body.style.display = '-moz-box';
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(foo, 30);">
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/309322-1.html b/layout/generic/crashtests/309322-1.html
new file mode 100644
index 0000000000..4382061748
--- /dev/null
+++ b/layout/generic/crashtests/309322-1.html
@@ -0,0 +1,56 @@
+<html><head>
+<title>Testcase1 bug 309322 - Evil testcase using multiple display:table-caption causes crash</title>
+<style>
+*[toggle_style],*[toggle_style1],*[toggle_style2],*[toggle_style3],*[toggle_style4]{
+display:table-caption;
+}
+</style>
+<script>
+function doe(i){
+var x=document.body.getElementsByTagName('*');
+var xl=x.length;i=i+1;
+x[i-1].removeAttribute('toggle_style');
+if ((i)<xl) x[i].setAttribute('toggle_style','toggle_style');
+if ((i+1)<xl) {x[i+1].setAttribute('toggle_style1','toggle_style');
+x[i].removeAttribute('toggle_style1');
+}
+if ((i+2)<xl) {x[i+2].setAttribute('toggle_style2','toggle_style');
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+3)<xl) {x[i+3].setAttribute('toggle_style3','toggle_style');
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+4)<xl) {x[i+4].setAttribute('toggle_style4','toggle_style');
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+4)==xl) {
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+3)==xl) {
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+2)==xl) {
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+1)==xl) {
+x[i].removeAttribute('toggle_style1');
+}
+setTimeout(doe,20,i);
+}
+
+function doe2(){
+var x=document.links[0].cloneNode(true);
+document.links[0].appendChild(x);
+}
+</script>
+</head>
+<body onload="doe(1)">
+<button onclick="doe(1)">Clicking on this button should not crash Mozilla</button>
+<table><tbody><tr><td>
+
+<span><br></span>
+<a href="#">Galloway<span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span></a>
+
+</td></tr></tbody></table>
+
+</body></html>
diff --git a/layout/generic/crashtests/309322-2.html b/layout/generic/crashtests/309322-2.html
new file mode 100644
index 0000000000..f1450a0473
--- /dev/null
+++ b/layout/generic/crashtests/309322-2.html
@@ -0,0 +1,56 @@
+<html><head>
+<title>Testcase2 bug 309322 - Evil testcase using multiple display:table-caption causes crash</title>
+<style>
+*[toggle_style],*[toggle_style1],*[toggle_style2],*[toggle_style3],*[toggle_style4]{
+display:table-caption;
+}
+</style>
+<script>
+function doe(i){
+var x=document.body.getElementsByTagName('*');
+var xl=x.length;i=i+1;
+x[i-1].removeAttribute('toggle_style');
+if ((i)<xl) x[i].setAttribute('toggle_style','toggle_style');
+if ((i+1)<xl) {x[i+1].setAttribute('toggle_style1','toggle_style');
+x[i].removeAttribute('toggle_style1');
+}
+if ((i+2)<xl) {x[i+2].setAttribute('toggle_style2','toggle_style');
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+3)<xl) {x[i+3].setAttribute('toggle_style3','toggle_style');
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+4)<xl) {x[i+4].setAttribute('toggle_style4','toggle_style');
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+4)==xl) {
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+3)==xl) {
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+2)==xl) {
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+1)==xl) {
+x[i].removeAttribute('toggle_style1');
+}
+setTimeout(doe,20,i);
+}
+
+function doe2(){
+var x=document.links[0].cloneNode(true);
+document.links[0].appendChild(x);
+}
+</script>
+</head>
+<body onload="doe(1)">
+<button onclick="doe(1)">Clicking on this button should not crash Mozilla</button>
+<table><tbody><tr><td>
+
+<span><br></span>
+<a href="#">Galloway<span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span><span>Galloway</span></a>
+
+</td></tr></tbody></table>
+
+</body></html>
diff --git a/layout/generic/crashtests/309322-3.html b/layout/generic/crashtests/309322-3.html
new file mode 100644
index 0000000000..96148ffe4f
--- /dev/null
+++ b/layout/generic/crashtests/309322-3.html
@@ -0,0 +1,48 @@
+<html><head>
+<title>Testcase3 bug 309322 - Evil testcase using multiple display:table-caption causes crash</title>
+<style>
+*[toggle_style],*[toggle_style1],*[toggle_style2],*[toggle_style3],*[toggle_style4]{
+display:table-caption;
+}
+</style>
+<script>
+function doe(i){
+var x=document.body.getElementsByTagName('*');
+var xl=x.length;i=i+1;
+x[i-1].removeAttribute('toggle_style');
+x[i].setAttribute('toggle_style','toggle_style');
+if ((i+1)<xl) {x[i+1].setAttribute('toggle_style1','toggle_style');
+x[i].removeAttribute('toggle_style1');
+}
+if ((i+2)<xl) {x[i+2].setAttribute('toggle_style2','toggle_style');
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+3)<xl) {x[i+3].setAttribute('toggle_style3','toggle_style');
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+4)<xl) {x[i+4].setAttribute('toggle_style4','toggle_style');
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+4)==xl) {
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+3)==xl) {
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+2)==xl) {
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+1)==xl) {
+x[i].removeAttribute('toggle_style1');
+}
+setTimeout(doe,20,i);
+}
+
+
+</script></head><body onload="doe(3)">
+<button onclick="doe(3)">Clicking on this button should not create extra "Galloway" text</button>
+<table><tbody><tr><td>
+<span><br></span>
+<a href="#">Galloway</a>
+</td></tr></tbody></table>
+</body></html>
diff --git a/layout/generic/crashtests/309322-4.html b/layout/generic/crashtests/309322-4.html
new file mode 100644
index 0000000000..9b9b6a5bbf
--- /dev/null
+++ b/layout/generic/crashtests/309322-4.html
@@ -0,0 +1,48 @@
+<html><head>
+<title>Testcase4 bug 309322 - Evil testcase using multiple display:table-caption causes crash</title>
+<style>
+*[toggle_style],*[toggle_style1],*[toggle_style2],*[toggle_style3],*[toggle_style4]{
+display:table-caption;
+}
+</style>
+<script>
+function doe(i){
+var x=document.body.getElementsByTagName('*');
+var xl=x.length;i=i+1;
+x[i-1].removeAttribute('toggle_style');
+x[i].setAttribute('toggle_style','toggle_style');
+if ((i+1)<xl) {x[i+1].setAttribute('toggle_style1','toggle_style');
+x[i].removeAttribute('toggle_style1');
+}
+if ((i+2)<xl) {x[i+2].setAttribute('toggle_style2','toggle_style');
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+3)<xl) {x[i+3].setAttribute('toggle_style3','toggle_style');
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+4)<xl) {x[i+4].setAttribute('toggle_style4','toggle_style');
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+4)==xl) {
+x[i+3].removeAttribute('toggle_style4');
+}
+if ((i+3)==xl) {
+x[i+2].removeAttribute('toggle_style3');
+}
+if ((i+2)==xl) {
+x[i+1].removeAttribute('toggle_style2');
+}
+if ((i+1)==xl) {
+x[i].removeAttribute('toggle_style1');
+}
+setTimeout(doe,20,i);
+}
+
+
+</script></head><body onload="doe(3)">
+<button onclick="doe(3)">Clicking on this button and then closing this tab/window should not crash Mozilla</button>
+<table><tbody><tr><td>
+<span><br></span>
+<a href="#"><img src="data:image/gif;base64,R0lGODlhAQABAIABAAD/AP///ywAAAAAAQABAAACAkQBADs="></a>
+</td></tr></tbody></table>
+</body></html>
diff --git a/layout/generic/crashtests/310556-1.xhtml b/layout/generic/crashtests/310556-1.xhtml
new file mode 100644
index 0000000000..260960008e
--- /dev/null
+++ b/layout/generic/crashtests/310556-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+
+<style type="text/css">
+
+span:before {
+ content: "";
+}
+
+</style>
+
+</head>
+
+<body>
+
+X<span/>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/321224.xhtml b/layout/generic/crashtests/321224.xhtml
new file mode 100644
index 0000000000..a21c4b7b0a
--- /dev/null
+++ b/layout/generic/crashtests/321224.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <tabpanels>
+ <nativescrollbar/>
+ </tabpanels>
+</window> \ No newline at end of file
diff --git a/layout/generic/crashtests/322780-1.xhtml b/layout/generic/crashtests/322780-1.xhtml
new file mode 100644
index 0000000000..cf914cfaf0
--- /dev/null
+++ b/layout/generic/crashtests/322780-1.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <label>
+ <foopy style="display: block; float:left;" />
+ </label>
+</window>
diff --git a/layout/generic/crashtests/323381-1.html b/layout/generic/crashtests/323381-1.html
new file mode 100644
index 0000000000..3f2c17f9c0
--- /dev/null
+++ b/layout/generic/crashtests/323381-1.html
@@ -0,0 +1 @@
+<iframe src="data:text/html,foo" width="808080"></iframe>
diff --git a/layout/generic/crashtests/323381-2.html b/layout/generic/crashtests/323381-2.html
new file mode 100644
index 0000000000..ab28e6bb54
--- /dev/null
+++ b/layout/generic/crashtests/323381-2.html
@@ -0,0 +1 @@
+<div style="width: 808080px;">foo</div>
diff --git a/layout/generic/crashtests/323386-1.html b/layout/generic/crashtests/323386-1.html
new file mode 100644
index 0000000000..437b9a16b9
--- /dev/null
+++ b/layout/generic/crashtests/323386-1.html
@@ -0,0 +1 @@
+<TEXTAREA COLS="381762666"> \ No newline at end of file
diff --git a/layout/generic/crashtests/323389-1.html b/layout/generic/crashtests/323389-1.html
new file mode 100644
index 0000000000..591ebb539e
--- /dev/null
+++ b/layout/generic/crashtests/323389-1.html
@@ -0,0 +1,7 @@
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
+
+<TEXTAREA>
+ ˆˆˆˆˆˆ
+
diff --git a/layout/generic/crashtests/323389-2.html b/layout/generic/crashtests/323389-2.html
new file mode 100644
index 0000000000..74fea379a3
--- /dev/null
+++ b/layout/generic/crashtests/323389-2.html
@@ -0,0 +1,8 @@
+<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1"></head>
+
+<body>
+
+blocking&#8209;aviary1.0.8
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/323493-1.html b/layout/generic/crashtests/323493-1.html
new file mode 100644
index 0000000000..12e14f6a8d
--- /dev/null
+++ b/layout/generic/crashtests/323493-1.html
@@ -0,0 +1,16 @@
+<html><head><style>
+* {
+ display: table;
+ position: absolute;
+}
+</style>
+
+</head>
+
+<body>
+<table><tbody><tr><td>One</td><td>Two</td></tr><tr><td>Three</td><td>Four</td></tr></tbody></table>
+<ul><li>One</li><li>Two</li><li>Three</li></ul>
+
+<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius. Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum. Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima. Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.</p>
+
+</body></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/323495-1.html b/layout/generic/crashtests/323495-1.html
new file mode 100644
index 0000000000..fcffe0166e
--- /dev/null
+++ b/layout/generic/crashtests/323495-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+
+<style>
+select { width: 1000000px; }
+</style>
+
+</head>
+
+<body>
+
+<select><option>a</option></select>
+
+</body></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/324318-1.html b/layout/generic/crashtests/324318-1.html
new file mode 100644
index 0000000000..c9946caba9
--- /dev/null
+++ b/layout/generic/crashtests/324318-1.html
@@ -0,0 +1,29 @@
+<script>
+
+var tr;
+
+function init() {
+
+ tr = one.document.getElementsByTagName("tr")[0];
+ tr = document.adoptNode(tr);
+ document.getElementsByTagName("html")[0].appendChild(tr);
+
+ setTimeout(
+ function(){
+ var frameset = document.getElementsByTagName("frameset")[0];
+ document.getElementsByTagName("td")[0].appendChild(frameset);
+ document.documentElement.removeAttribute("class");
+ },
+ 100);
+
+}
+
+window.addEventListener("load", init);
+document.documentElement.setAttribute("class", "reftest-wait");
+
+</script>
+
+<frameset resizable="yes" rows="50%,*">
+ <frame name="one" src="file_324318-1.html">
+ <frame name="two" src="empty.html">
+</frameset>
diff --git a/layout/generic/crashtests/328946-1.html b/layout/generic/crashtests/328946-1.html
new file mode 100644
index 0000000000..75345ce5de
--- /dev/null
+++ b/layout/generic/crashtests/328946-1.html
@@ -0,0 +1 @@
+<table> \ No newline at end of file
diff --git a/layout/generic/crashtests/331284-1.xhtml b/layout/generic/crashtests/331284-1.xhtml
new file mode 100644
index 0000000000..43bea83860
--- /dev/null
+++ b/layout/generic/crashtests/331284-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body>
+ <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <ttt style="display: block; float: right;">
+ <img src="../../../testing/crashtest/images/animfish.gif" xmlns="http://www.w3.org/1999/xhtml" />
+ </ttt>
+ </hbox>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/331292.html b/layout/generic/crashtests/331292.html
new file mode 100644
index 0000000000..dfc4cf60be
--- /dev/null
+++ b/layout/generic/crashtests/331292.html
@@ -0,0 +1,258 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head>
+<title>Testcase bug 331292 - Loading page causes freeze, no Talkback, both WindowsXP & Linux</title>
+<style>
+.floatA{
+ width:110px;
+ float: left;
+ border:1px solid red;
+}
+.floatB{
+ width:430px;
+ float: left;
+ border:1px solid green;
+}
+.floatC{
+ width:430px;
+ float: left;
+ border:1px solid blue;
+}
+.floatclear{
+clear: both;
+}
+</style>
+</head>
+<body>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatB"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatC"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear">
+<div class="floatA"></div>
+<div class="floatclear"></div>
+</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
+</body></html>
diff --git a/layout/generic/crashtests/334105-1.xhtml b/layout/generic/crashtests/334105-1.xhtml
new file mode 100644
index 0000000000..6b5c135f89
--- /dev/null
+++ b/layout/generic/crashtests/334105-1.xhtml
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<style>
+html { width: 2em; border: 1px solid red; }
+body { display: inline; }
+.padded { padding: 1em; border: 1px solid black; }
+#float1 { background: lightgreen; } /* float is added to this one dynamically */
+#float2 { float: right; background: lightblue; }
+</style>
+
+<script>
+
+function foo()
+{
+ document.getElementById("float1").style.cssFloat = "right";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function() { setTimeout(foo, 30); }, false);
+
+</script>
+
+</head>
+
+<body>
+
+ X
+ <span id="float1">FFFFFF</span>
+ <span id="float2">GGGGGG</span>
+ <span class="padded"/>
+
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/334107-1.xhtml b/layout/generic/crashtests/334107-1.xhtml
new file mode 100644
index 0000000000..3e6b25a5c5
--- /dev/null
+++ b/layout/generic/crashtests/334107-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<p style="letter-spacing: -2em;">Foo</p>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/334147-1.xhtml b/layout/generic/crashtests/334147-1.xhtml
new file mode 100644
index 0000000000..5490157224
--- /dev/null
+++ b/layout/generic/crashtests/334147-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+.x:first-letter { }
+.x { direction: rtl; }
+</style>
+
+</head>
+
+<body>
+
+<p class="x">2 3</p>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/334148-1.xhtml b/layout/generic/crashtests/334148-1.xhtml
new file mode 100644
index 0000000000..dab5083246
--- /dev/null
+++ b/layout/generic/crashtests/334148-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+* { float: right; }
+* { display: list-item; }
+</style>
+</head>
+
+<body>
+
+<menupopup xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" />
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/334602-1.html b/layout/generic/crashtests/334602-1.html
new file mode 100644
index 0000000000..2bb820ebcf
--- /dev/null
+++ b/layout/generic/crashtests/334602-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<title>Testcase bug 334602 - ASSERTION: Reparenting something that has no usable parent? Shouldn't happen!: 'Not Reached'</title>
+<style>
+html::first-line { }
+html::before { content:"This should not give an assertion in Mozilla";}
+</style>
+</head>
+<body>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/337412-1.html b/layout/generic/crashtests/337412-1.html
new file mode 100644
index 0000000000..14f99c4017
--- /dev/null
+++ b/layout/generic/crashtests/337412-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+
+.columns {
+ column-width: 20em;
+ column-gap: 3em;
+}
+
+
+</style>
+
+</head>
+
+<body>
+
+<div class="columns">
+
+<ul>
+<li>XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX!</li>
+</ul>
+
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/337883-1.html b/layout/generic/crashtests/337883-1.html
new file mode 100644
index 0000000000..b0c280ae2e
--- /dev/null
+++ b/layout/generic/crashtests/337883-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html class="reftest-wait">
+<head>
+<script>
+
+function foop()
+{
+ document.getElementById("xxx").style.height = "10%";
+ document.getElementById("yyy").style.position = "relative";
+
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function(){setTimeout(foop, 30)});
+
+</script>
+</head>
+
+<body>--<a id="yyy"><IMG align="right"></a><br id="xxx">==</body></html>
+
diff --git a/layout/generic/crashtests/337883-2.html b/layout/generic/crashtests/337883-2.html
new file mode 100644
index 0000000000..bc9d532908
--- /dev/null
+++ b/layout/generic/crashtests/337883-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html class="reftest-wait">
+<head>
+<style id="s"></style>
+<script>
+
+function foop()
+{
+ var stylesheet = document.getElementById("s");
+ stylesheet.textContent = "#xxx { height: 10%; } #yyy { position: relative; }";
+
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function(){setTimeout(foop, 30)});
+
+</script>
+</head>
+
+<body>--<a id="yyy"><IMG align="right"></a><br id="xxx">==</body></html>
+
diff --git a/layout/generic/crashtests/339769-1.html b/layout/generic/crashtests/339769-1.html
new file mode 100644
index 0000000000..cf445be793
--- /dev/null
+++ b/layout/generic/crashtests/339769-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function foo()
+{
+ var div = document.getElementById("div");
+ div.remove();
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(foo, 0);">
+
+ <div id="div" style="display: inline;"><p>x</p></div>
+
+ <iframe src='data:text/html,<html><body><input value="q">'></iframe>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/342322-1.html b/layout/generic/crashtests/342322-1.html
new file mode 100644
index 0000000000..9d0d082616
--- /dev/null
+++ b/layout/generic/crashtests/342322-1.html
@@ -0,0 +1,28 @@
+<html class="reftest-wait">
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var q1 = document.getElementById("q1");
+ var q2 = document.getElementById("q2");
+
+ q1.style.cssFloat = "right"
+ q2.style.cssFloat = "right"
+
+ setTimeout(function(){
+ q1.style.cssFloat = "none";
+ document.documentElement.removeAttribute("class");
+ }, 30);
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 30);">
+ <b id="q1">AAA</b>
+ <b id="q2">BBB</b>
+</body>
+</html>
diff --git a/layout/generic/crashtests/343206-1.xhtml b/layout/generic/crashtests/343206-1.xhtml
new file mode 100644
index 0000000000..5be6fc2a6a
--- /dev/null
+++ b/layout/generic/crashtests/343206-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<style id="sty">
+html::before {
+ content: "before text";
+ float: right;
+}
+</style>
+
+<script>
+function boom()
+{
+ var sty = document.getElementById("sty");
+ sty.appendChild(document.createTextNode(" "));
+ document.documentElement.removeAttribute("class");
+}
+
+setTimeout(boom, 30);
+</script>
+
+</html>
diff --git a/layout/generic/crashtests/344557-1.html b/layout/generic/crashtests/344557-1.html
new file mode 100644
index 0000000000..cb65a67641
--- /dev/null
+++ b/layout/generic/crashtests/344557-1.html
@@ -0,0 +1,32 @@
+<html class="reftest-wait"><head>
+<title>Testcase bug 344557 - [columns] Crash [@ nsLineBox::DeleteLineList] with moz-column-count and generated content</title>
+<script>
+function removestyles(i){
+document.getElementsByTagName('div')[0].removeAttribute('style');
+document.documentElement.removeAttribute("class");
+}
+window.resizeTo(1440, 600);
+setTimeout(removestyles,200);
+</script>
+
+<style>
+ div::first-line, ul::first-line { text-transform: uppercase; font-size:110%;}
+sub::after, div::after { content:"anonymous text"; border:3px solid black;text-transform: uppercase;}
+sub::before, div::before { content:"before text"; font-size: 10px;}
+</style>
+</head>
+<body>
+
+<ul>
+ <sub>t</sub>
+ <div style="column-count: 3;">
+ <sub>t-column-councode&gt;text</sub>
+ <div style="column-count: 3;"><sub>text styletttmoztcolumntcountt 2;ttext</sub>
+ <div>
+ <sub>text</sub>
+ </div>
+ </div>
+ </div>
+</ul>
+
+</body></html>
diff --git a/layout/generic/crashtests/345139-1.xhtml b/layout/generic/crashtests/345139-1.xhtml
new file mode 100644
index 0000000000..1dfbe8f239
--- /dev/null
+++ b/layout/generic/crashtests/345139-1.xhtml
@@ -0,0 +1,53 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head style="display: none ! important;">
+
+<style>
+#a { direction: rtl; }
+#a { position: absolute; }
+#a { right: 5px; }
+#a { left: -10em; }
+#b { direction: ltr; }
+#b { width: 10%; }
+#b { display: -moz-inline-block; }
+#c { display: -moz-inline-block; }
+#e { display: -moz-inline-block; }
+#d { display: -moz-inline-block; }
+#d { padding: 5px 10px; }
+#d dl, #d dt, #d dd { display: inline; }
+#d dt { margin-left: 5px; }
+#d dd { margin-left: 2px; }
+#d dd:before { content: "="; }
+</style>
+
+<style id="newstyle"></style>
+<script>
+function change()
+{
+ document.getElementById("newstyle").appendChild(document.createTextNode("#b { white-space: pre; }"));
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+</head>
+
+<body onload="setTimeout(change, 1);">
+
+<div id="a"><div id="b">
+
+<div id="c"></div>
+
+<div id="d">
+<dl>
+<dt>0</dt><dd><span id="e">Accessibility Statement</span></dd>
+<dt>1</dt><dd>Main Page</dd>
+<dt>2</dt><dd>Skip to Content</dd>
+<dt>3</dt><dd>List of Posts</dd>
+<dt>4</dt><dd>Search</dd>
+<dt>p</dt><dd>Previous (individual/monthly archive page)</dd>
+</dl>
+</div>
+
+</div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/345617-1.html b/layout/generic/crashtests/345617-1.html
new file mode 100644
index 0000000000..03de639582
--- /dev/null
+++ b/layout/generic/crashtests/345617-1.html
@@ -0,0 +1,8 @@
+<html>
+<body style="line-height: 30760827em;">
+
+x
+<p>y
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/348510-1.html b/layout/generic/crashtests/348510-1.html
new file mode 100644
index 0000000000..6e00e71f1b
--- /dev/null
+++ b/layout/generic/crashtests/348510-1.html
@@ -0,0 +1,7 @@
+<marquee>
+<a>
+<object>
+<dd>
+<form>
+</object>
+aaaaaaa
diff --git a/layout/generic/crashtests/348510-2.html b/layout/generic/crashtests/348510-2.html
new file mode 100644
index 0000000000..8f8c998cfd
--- /dev/null
+++ b/layout/generic/crashtests/348510-2.html
@@ -0,0 +1,7 @@
+<listing>
+<marquee>
+<aa>
+<object>
+<fieldset>
+</object>
+a \ No newline at end of file
diff --git a/layout/generic/crashtests/348887-1-inner.html b/layout/generic/crashtests/348887-1-inner.html
new file mode 100644
index 0000000000..ce296666d2
--- /dev/null
+++ b/layout/generic/crashtests/348887-1-inner.html
@@ -0,0 +1,21 @@
+<html><head>
+<style>
+blockquote::first-letter {float: right;}
+</style>
+<title>
+Testcase bug - Crash [@ nsFrameList::DestroyFrames] on reload with column-count, -moz-inline-block and blockquote::first-letter
+</title>
+</head>
+<body>
+This page should not crash on reload
+<div style=" column-count: 2;">
+<span style="display: table;"></span>
+<blockquote style="display: -moz-inline-block;">anonymous text</blockquote>
+</div>
+
+<script>
+document.location.reload();
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/348887-1.html b/layout/generic/crashtests/348887-1.html
new file mode 100644
index 0000000000..b865b4beb7
--- /dev/null
+++ b/layout/generic/crashtests/348887-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="348887-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/generic/crashtests/350370.html b/layout/generic/crashtests/350370.html
new file mode 100644
index 0000000000..6ab607e207
--- /dev/null
+++ b/layout/generic/crashtests/350370.html
@@ -0,0 +1,42 @@
+<html><head>
+<title>Testcase bug 350370 - Crash [@ ComputedStyle::FindChildWithRules] with ::first-line, appending rows and table-cells, etc</title>
+<style>
+#b td::first-line { font-size:110%;}
+nobr::first-line { font-size:110%;}
+
+#b td::after { content:"anonymous text"; }
+nobr::after{ content:"anonymous text"; }
+
+#b::before { content:"before text";}
+#b td::before { content:"before text";}
+</style>
+</head>
+<body>
+<table style="display: table-row;"></table><nobr style="display: list-item; column-count: 2;">
+<table id="b" style="display: inline;"></table>
+</nobr>
+<br>
+This page should not crash Mozilla
+<script>
+function doe(){
+ var td = document.createElement('td');;
+ td.setAttribute('height', '50%');
+ var tr = document.createElement('tr');;
+ tr.setAttribute('height', '50%');
+ tr.appendChild(td);
+ document.getElementsByTagName('table')[1].appendChild(tr);
+ document.body.offsetHeight;
+
+ var td = document.createElement('td');;
+ td.setAttribute('height', '50%');
+ document.getElementsByTagName('tr')[0].appendChild(td);
+ document.body.offsetHeight;
+
+ var td = document.createElement('td');;
+ td.setAttribute('height', '50%');
+ document.getElementsByTagName('tr')[0].appendChild(td);
+}
+setTimeout(doe, 60);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/354458-1.html b/layout/generic/crashtests/354458-1.html
new file mode 100644
index 0000000000..69b8b2dca5
--- /dev/null
+++ b/layout/generic/crashtests/354458-1.html
@@ -0,0 +1,10 @@
+<html>
+
+<body>
+ <div style="height: 5px; column-count: 3;">
+ Text!
+ <span style="float: left">Float!</span>
+ </div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/354458-2.html b/layout/generic/crashtests/354458-2.html
new file mode 100644
index 0000000000..8e1d0a9710
--- /dev/null
+++ b/layout/generic/crashtests/354458-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style type="text/css">
+
+ p { margin: 0; }
+
+ </style>
+</head>
+<body>
+
+<div style="column-width: 10em; height: 2.5em">
+ text
+ <img src="../../../testing/crashtest/images/tree.gif" width="197" height="200" style="float:left">
+ <p>text</p>
+ <p>text</p>
+ <p>text</p>
+ <p>text</p>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/355426-1.html b/layout/generic/crashtests/355426-1.html
new file mode 100644
index 0000000000..87abaae088
--- /dev/null
+++ b/layout/generic/crashtests/355426-1.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+
+<script>
+
+function foo()
+{
+ document.getElementById("navish").style.display = "inline-block";
+}
+</script>
+
+</head>
+
+
+<body onload="foo()">
+
+<div style="column-count: 2;">
+ <ol id="ol1" style="height: 30px;">
+ <li>A</li>
+ <li><p id="p3" style="width: 1em;">Foo Foo Foo Foo Foo Foo Foo Foo Foo Foo Foo</p></li>
+ </ol>
+</div>
+
+<p id="navish">E</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/359371-1.html b/layout/generic/crashtests/359371-1.html
new file mode 100644
index 0000000000..8134a36cbb
--- /dev/null
+++ b/layout/generic/crashtests/359371-1.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body {
+ width: 500px;
+}
+
+p {
+ font-family: monospace;
+ font-size: 12px;
+}
+.columns {
+ column-count: 3;
+}
+
+.toppadded
+{
+ padding-top: 20px;
+}
+
+.floatbox {
+ float: left;
+ margin: 3px 10px 0 0;
+ padding: 8px 3px;
+ width: 55px;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('foo').appendChild(document.createTextNode('aaaaaaa'));">
+
+<div class="columns">
+
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+
+ <p id="foo">xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx </p>
+
+ <div class="toppadded">
+ T
+ <p class="floatbox">W W W W W W</p>
+ </div>
+
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/359371-2.html b/layout/generic/crashtests/359371-2.html
new file mode 100644
index 0000000000..98e2917c3c
--- /dev/null
+++ b/layout/generic/crashtests/359371-2.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body {
+ width: 500px;
+}
+
+p {
+ font-family: monospace;
+ font-size: 12px;
+}
+.columns {
+ column-count: 3;
+}
+
+.toppadded
+{
+ padding-top: 18px;
+}
+
+.floatbox {
+ float: left;
+ margin: 20px 0px 0 0;
+ width: 90px;
+}
+
+</style>
+</head>
+
+<!-- <body onload="document.getElementById('foo').appendChild(document.createTextNode('aaaaaaa'));"> -->
+<div class="columns">
+
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+ <p>C
+
+ <p id="foo">xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx </p>
+
+ <div class="toppadded">
+ T
+ <p class="floatbox">W W W W W W W W Waaaaaaa</p>
+ </div>
+
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/360599.html b/layout/generic/crashtests/360599.html
new file mode 100644
index 0000000000..210add23d8
--- /dev/null
+++ b/layout/generic/crashtests/360599.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<style>
+#b::first-letter { }
+#c::first-line { }
+</style>
+<title>Testcase bug 360599 - Crash [@ nsFrameList::DestroyFrames] with first-letter/first-line css and position: fixed</title>
+</head>
+<body>
+This page should not crash Mozilla
+<div id="c">
+ <table>
+ <div id="b" style="display:table-header-group;">
+ <q>
+ text
+ <div style="position:fixed;">
+ <q>y</q>
+ </div>
+ </q>
+ </div>
+ <span style="display: table;"></span>
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/361109.html b/layout/generic/crashtests/361109.html
new file mode 100644
index 0000000000..fef9c6be83
--- /dev/null
+++ b/layout/generic/crashtests/361109.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Testcase bug 361109 - Crash [@ nsBlockBandData::Init] with position:fixed on select</title>
+</head>
+<body>
+This page should not crash Mozilla
+<select style="position: fixed;"><select>
+</body>
+</html>
diff --git a/layout/generic/crashtests/363448.html b/layout/generic/crashtests/363448.html
new file mode 100644
index 0000000000..960aa5445c
--- /dev/null
+++ b/layout/generic/crashtests/363448.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Testcase bug 363448 - Crash [@ nsCachedStyleData::GetStyleData] with testcase, using floating and absolutely positioned iframes</title>
+</head>
+<body>
+This page should not crash Mozilla
+<div style="width:300px;">
+ <span id="b">
+ <iframe style="float: left;" id="a"></iframe>
+ <wbr>text
+ <iframe id="c" style="position: absolute;"></iframe>
+ </span>
+</div>
+
+<script>
+function stirdom(){
+ document.body.appendChild(document.getElementById('a'));
+ document.getElementById('b').appendChild(document.getElementById('c'));
+}
+setTimeout(stirdom,200);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/363722-1.html b/layout/generic/crashtests/363722-1.html
new file mode 100644
index 0000000000..f83671c5ab
--- /dev/null
+++ b/layout/generic/crashtests/363722-1.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+ <marquee style="background: yellow;">
+ <marquee style="background: lightgreen;">
+ I am a double-marquee.
+ </marquee>
+ </marquee>
+</body>
+</html>
diff --git a/layout/generic/crashtests/363722-2.html b/layout/generic/crashtests/363722-2.html
new file mode 100644
index 0000000000..1a12a227e8
--- /dev/null
+++ b/layout/generic/crashtests/363722-2.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+ <marquee style="background: yellow;">
+ [inside OUTER marquee]
+ <marquee style="background: lightgreen;">
+ [inside INNER marquee]
+ </marquee>
+ </marquee>
+</body>
+</html>
diff --git a/layout/generic/crashtests/364220.html b/layout/generic/crashtests/364220.html
new file mode 100644
index 0000000000..63a927b450
--- /dev/null
+++ b/layout/generic/crashtests/364220.html
@@ -0,0 +1,17 @@
+<html><head>
+<title>Testcase bug 364220 - [reflow branch][columns] Crash [@ nsLineLayout::ReflowFrame] using moz-column-count, floats, generated content and first-line</title>
+<style>
+body > span::first-line { }
+span::before { content:"before text"; border:3px solid black;}
+</style>
+</head>
+<body>
+
+<span style=" float: left; column-count: 2;">
+ <span style="float: right;">
+ <span style=" float: right;column-count: 2;"></span>
+ </span>
+</span>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/364407-1.html b/layout/generic/crashtests/364407-1.html
new file mode 100644
index 0000000000..2ae181886b
--- /dev/null
+++ b/layout/generic/crashtests/364407-1.html
@@ -0,0 +1,44 @@
+<html>
+<body>
+
+<div style="overflow: scroll">
+ <table border="1">
+ <tr>
+ <td>
+ <select style="height: 200%">
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ <option>Option</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/364686-1.xhtml b/layout/generic/crashtests/364686-1.xhtml
new file mode 100644
index 0000000000..93a1eeaa4b
--- /dev/null
+++ b/layout/generic/crashtests/364686-1.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:math="http://www.w3.org/1998/Math/MathML">
+
+<body>
+
+<math:merror>
+ <img/>
+</math:merror>
+
+</body>
+</html>
+
diff --git a/layout/generic/crashtests/366021-1.xhtml b/layout/generic/crashtests/366021-1.xhtml
new file mode 100644
index 0000000000..5bbc7fe185
--- /dev/null
+++ b/layout/generic/crashtests/366021-1.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("xulwin").appendChild(document.getElementById("sss"));
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+
+<body onload="setTimeout(boom, 10)">
+
+<span id="sss"><tr><td style="position: absolute;">td</td></tr></span>
+
+<window id="xulwin" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"></window>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/366667-1.html b/layout/generic/crashtests/366667-1.html
new file mode 100644
index 0000000000..a50ce84e48
--- /dev/null
+++ b/layout/generic/crashtests/366667-1.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ <span><span style="display: -moz-inline-box"><img></span></span>
+ </body>
+</html>
+
diff --git a/layout/generic/crashtests/366952-1.html b/layout/generic/crashtests/366952-1.html
new file mode 100644
index 0000000000..c210b77b78
--- /dev/null
+++ b/layout/generic/crashtests/366952-1.html
@@ -0,0 +1,17 @@
+<html>
+
+<head>
+</head>
+
+<body>
+
+<div style="display: -moz-inline-box;">
+ <div style="padding-right: 3%;">
+ <div style="position: fixed;">
+ Foo
+ </div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/367246-1.html b/layout/generic/crashtests/367246-1.html
new file mode 100644
index 0000000000..5a5a845650
--- /dev/null
+++ b/layout/generic/crashtests/367246-1.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+
+<div style="direction: rtl">
+ <div style="padding: 10px; width: 1px; overflow: scroll;">Foopy</div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/367360.html b/layout/generic/crashtests/367360.html
new file mode 100644
index 0000000000..21a4f5e750
--- /dev/null
+++ b/layout/generic/crashtests/367360.html
@@ -0,0 +1,30 @@
+<html><head>
+<title>Testcase bug - Crash [@ nsHTMLButtonControlFrame::GetContentInsertionFrame] with column-count and display: list-item</title>
+</head>
+<body>
+<div style="display: table;">
+text
+<listing style="column-count: 2;">
+<dl>
+
+
+
+
+
+
+
+
+tesxt
+
+
+<menu>
+<span style="display: list-item; column-count: 1;">
+<span style="display: list-item; column-count: 1;">
+text
+</span></span>
+</menu>
+<div>text</div>
+</dl>
+</listing>
+</div>
+</body></html>
diff --git a/layout/generic/crashtests/368330-1.html b/layout/generic/crashtests/368330-1.html
new file mode 100644
index 0000000000..1a4c93e39f
--- /dev/null
+++ b/layout/generic/crashtests/368330-1.html
@@ -0,0 +1,15 @@
+<html>
+
+<body>
+
+<div style="width: 1px">
+ <h3 style="float: right;">Foo bar</h3>
+ <ul>
+ <li style="float: right;">E</li>
+ <li style="float: right;">PPP XXXXXX</li>
+ </ul>
+</div>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/368461-1.xhtml b/layout/generic/crashtests/368461-1.xhtml
new file mode 100644
index 0000000000..d5baccf523
--- /dev/null
+++ b/layout/generic/crashtests/368461-1.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+
+<head>
+</head>
+
+<body>
+
+<p><math:msubsup><span>Foo bar baz<td></td></span></math:msubsup></p>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/368568.html b/layout/generic/crashtests/368568.html
new file mode 100644
index 0000000000..337f84b4bf
--- /dev/null
+++ b/layout/generic/crashtests/368568.html
@@ -0,0 +1,14 @@
+<html><head>
+<style>
+*::first-line { }
+*::after { content:"anonymous text"; }
+*::before { content:"before text"; }
+</style>
+</head>
+<body>
+
+<ol style="overflow: hidden; float: right; column-count: 3;">
+<span style="overflow: auto; float: left;"></span>
+</ol>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/368752.html b/layout/generic/crashtests/368752.html
new file mode 100644
index 0000000000..07d5c7002a
--- /dev/null
+++ b/layout/generic/crashtests/368752.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait"><head>
+<title>Testcase bug - Crash [@ nsIFrame::Invalidate] with display: inherit, large margin and padding and generated content</title>
+<script>
+function addstyle(){
+var x=document.createElementNS('http://www.w3.org/1999/xhtml','style');
+x.innerHTML='body::before {content: "text"; }';
+document.documentElement.appendChild(x);
+document.documentElement.offsetHeight;
+document.documentElement.removeAttribute("class");
+}
+</script>
+
+</head>
+<body onload="setTimeout(addstyle,0);">
+<div style="display: table;">
+<div style="display: inherit;float: left;margin-bottom: -9999999px;padding-top: 9999999999px;">
+<span style="position: fixed;">t</span>
+</div>
+</div>
+</body></html>
diff --git a/layout/generic/crashtests/368860-1.html b/layout/generic/crashtests/368860-1.html
new file mode 100644
index 0000000000..f38b5b3c43
--- /dev/null
+++ b/layout/generic/crashtests/368860-1.html
@@ -0,0 +1,12 @@
+<html>
+
+<head></head>
+
+<body>
+
+<table border="1"><tr><td>
+ <span>&rlm;X</span>
+</td></tr></table>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/368863-1.html b/layout/generic/crashtests/368863-1.html
new file mode 100644
index 0000000000..b4551e41df
--- /dev/null
+++ b/layout/generic/crashtests/368863-1.html
@@ -0,0 +1,5 @@
+<style>
+*::first-line { }
+*::before { content:"before text";}
+</style>
+<object style="position: fixed;column-count: 100;"><ol style="float: right;"> \ No newline at end of file
diff --git a/layout/generic/crashtests/369150-1.html b/layout/generic/crashtests/369150-1.html
new file mode 100644
index 0000000000..b492c0a06d
--- /dev/null
+++ b/layout/generic/crashtests/369150-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+
+<script>
+
+function boom()
+{
+ document.getElementById("frameset").appendChild(document.createTextNode(""));
+ document.getElementById("frameset").setAttribute("rows", "100%, *");
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+ <frameset id="frameset" cols="130, *" onload="setTimeout(boom, 30);">
+ <frame src="data:text/html,foo">
+ <frame src="data:text/html,bar">
+ </frameset>
+
+</html>
diff --git a/layout/generic/crashtests/369150-2.html b/layout/generic/crashtests/369150-2.html
new file mode 100644
index 0000000000..59215b14a6
--- /dev/null
+++ b/layout/generic/crashtests/369150-2.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+<title>Bug 369150 - Crash [@ nsHTMLFramesetFrame::GetNoResize] with dynamic changes</title>
+<script>
+
+function boom()
+{
+ document.getElementById("frameset").appendChild(document.createElement("frame"));
+ document.getElementById("frameset").setAttribute("rows", "100%, *");
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+ <frameset id="frameset" cols="130, *" onload="setTimeout(boom, 30);">
+ <frame src="data:text/html,foo">
+ <frame src="data:text/html,bar">
+ </frameset>
+
+</html>
diff --git a/layout/generic/crashtests/369227-1.xhtml b/layout/generic/crashtests/369227-1.xhtml
new file mode 100644
index 0000000000..7a61e4c202
--- /dev/null
+++ b/layout/generic/crashtests/369227-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>document.documentElement.offsetHeight</script>
+</head>
+<body>
+
+<div style="height: 3000px; background: lightgreen;"/>
+
+<div style="width: 4px; float: left; background: yellow;">
+ A
+ <span style="padding: 5px">
+ <span style="float: left;" />
+ </span>
+ Z
+</div>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/369542-1.html b/layout/generic/crashtests/369542-1.html
new file mode 100644
index 0000000000..ff62a3d05c
--- /dev/null
+++ b/layout/generic/crashtests/369542-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+<style>
+body::first-letter { float: right; }
+body::before { content:"before text"; }
+</style>
+</head><body></body></html>
diff --git a/layout/generic/crashtests/369542-2.html b/layout/generic/crashtests/369542-2.html
new file mode 100644
index 0000000000..b2c2085919
--- /dev/null
+++ b/layout/generic/crashtests/369542-2.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style>
+html::first-letter { float: right; }
+html::before { content:"before text"; float:right; }
+span::before { content:"before text"; float:right; }
+</style>
+</head>
+<body>
+<span>
+ <div></div>
+</span>
+<button onclick="document.body.style.width='100px'">Click</button>
+</body>
+</html>
diff --git a/layout/generic/crashtests/369547-1.html b/layout/generic/crashtests/369547-1.html
new file mode 100644
index 0000000000..2f7516d350
--- /dev/null
+++ b/layout/generic/crashtests/369547-1.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait"><head><script>
+function doe2() {
+document.getElementById('a').setAttribute('style', 'display: inline-block;');
+document.body.offsetHeight;
+document.getElementById('b').removeAttribute('style');
+document.body.offsetHeight;
+document.documentElement.className = '';
+}
+setTimeout(doe2,200,0);
+</script>
+</head>
+<body style="display: -moz-inline-box;"><span style="display: inline-block;"><span style="display: inline-block;"></span></span><span id="a">
+<iframe></iframe>
+<div id="b" style="display: table-footer-group;"></div>
+</span></body>
+</html>
diff --git a/layout/generic/crashtests/370174-1.html b/layout/generic/crashtests/370174-1.html
new file mode 100644
index 0000000000..57493a7d3d
--- /dev/null
+++ b/layout/generic/crashtests/370174-1.html
@@ -0,0 +1,566 @@
+<html>
+<head>
+<style style="display: none; direction: ltr;">
+<label style="display: table-column-group;direction: rtl;">
+</big>
+</textarea>
+</q>
+<q style="display: table-row;direction: ltr;">
+</span>
+</ol>
+</dir>
+<html style="display: table-row;direction: rtl;">
+</strong>
+<basefont style="display: inline-table;direction: auto;">
+<map style="display: table-footer-group;direction: ltr;">
+<dir="rtl" style="display: inline-block;direction: ltr;">
+<center style="display: none;direction: ltr;">
+<style style="display: block;direction: auto;">
+<samp style="display: -moz-inline-box;direction: rtl;">
+<noframes style="display: inline;direction: rtl;">
+<code style="display: table-header-group;direction: ltr;">
+</li>
+</body>
+</div>
+<embed style="display: table-header-group;direction: auto;">
+</table>
+</fieldset>
+<legend style="display: table-row;direction: auto;">
+</ul>
+<canvas style="display: table;direction: ltr;">
+<font style="display: inline-table;direction: auto;">
+</option>
+</code>
+</menu>
+<thead style="display: inline-table;direction: rtl;">
+</button>
+</div>
+</ol>
+</isindex>
+<code style="display: table-row-group;direction: rtl;">
+<dl style="display: table-row;direction: ltr;">
+<style style="display: table-footer-group;direction: ltr;">
+<marquee style="display: table-row;direction: auto;">
+</object>
+</hx>
+<nobr style="display: table-cell;direction: rtl;">
+<a style="display: table-header-group;direction: ltr;">
+</thead>
+<tr style="display: table-caption;direction: ltr;">
+<tr style="display: table-row;direction: rtl;">
+<base style="display: inline;direction: auto;">
+</input>
+<hx style="display: inline-block;direction: auto;">
+<link style="display: table-column;direction: rtl;">
+<code style="display: inline;direction: ltr;">
+<li style="display: table-column-group;direction: ltr;">
+</dd>
+</spacer>
+<address style="display: table-header-group;direction: auto;">
+</object>
+</hx>
+</th>
+</sup>
+<u style="display: -moz-inline-box;direction: auto;">
+</del>
+<bdo style="display: table-column-group;direction: ltr;">
+<param style="display: block;direction: auto;">
+<td rowspan="5" style="display: inline-table;direction: rtl;">
+<head style="display: none;direction: ltr;">
+</abbr>
+<address style="display: table-row;direction: rtl;">
+</input type="password">
+</li>
+<area style="display: -moz-inline-box;direction: ltr;">
+</noframes>
+</b>
+</input type="image">
+<title style="display: -moz-inline-box;direction: ltr;">
+</font>
+<blockquote style="display: table-column-group;direction: auto;">
+<area style="display: none;direction: auto;">
+</script>
+<colgroup style="display: -moz-inline-box;direction: auto;">
+<font style="display: list-item;direction: auto;">
+<isindex style="display: table-cell;direction: ltr;">
+<sup style="display: table-footer-group;direction: ltr;">
+<abbr style="display: block;direction: auto;">
+<address style="display: table-caption;direction: rtl;">
+</span>
+</code>
+</strong>
+<select style="display: table-cell;direction: auto;">
+<dl style="display: inline-table;direction: rtl;">
+</small>
+</listing>
+<colgroup style="display: table-header-group;direction: rtl;">
+<title style="display: none;direction: auto;">
+</input type="submit">
+<tbody style="display: table-column-group;direction: rtl;">
+</blink>
+<i style="display: table;direction: ltr;">
+</td>
+</dir>
+</tt>
+</a<bgsound style="display: inline;direction: auto;">
+<multicol style="display: -moz-inline-box;direction: auto;">
+</th>
+</font>
+</base>
+<textarea style="display: inline-block;direction: ltr;">
+<frameset style="display: list-item;direction: rtl;">
+</span>
+<blockquote style="display: table-caption;direction: auto;">
+</code>
+<dir style="display: inline-table;direction: rtl;">
+<fieldset style="display: table-column;direction: ltr;">
+</kbd>
+<noframes style="display: none;direction: rtl;">
+</spacer type="block">
+</i>
+<area style="display: table-column-group;direction: ltr;">
+<form style="display: none;direction: rtl;">
+<tt style="display: table-row;direction: auto;">
+</bdo>
+</th>
+<cite style="display: none;direction: auto;">
+</td colspan="5">
+</dir>
+</noscript>
+<spacer style="display: table-caption;direction: rtl;">
+</legend>
+<ins style="display: table-row-group;direction: rtl;">
+</multicol>
+</base>
+</img>
+<nobr style="display: table-header-group;direction: auto;">
+</map>
+<br style="display: none;direction: rtl;">
+</spacer type="block">
+<table style="display: table-row-group;direction: ltr;">
+</td rowspan="5">
+</caption>
+<embed style="display: inline-table;direction: auto;">
+</script>
+<style style="display: list-item;direction: rtl;">
+</nobr>
+<button style="display: table-column;direction: auto;">
+</select>
+</strike>
+<hx style="display: table-header-group;direction: ltr;">
+</bdo>
+<td rowspan="5" style="display: none;direction: ltr;">
+</code>
+<input type="hidden" style="display: table;direction: auto;">
+<spacer type="block" style="display: inline-block;direction: ltr;">
+</marquee>
+</dir="rtl">
+</html>
+<le</i>gend style="display: table-caption;direction: ltr;">
+<sup style="display: table-column-group;direction: ltr;">
+</address>
+<title style="display: none;direction: rtl;">
+<font style="display: table-row;direction: ltr;">
+<ol style="display: inline-table;direction: rtl;">
+</th>
+</link>
+</isindex>
+</iframe>
+<th style="display: table;direction: rtl;">
+</hx>
+<object style="display: table-caption;direction: auto;">
+</embed>
+<small style="display: block;direction: auto;">
+</abbr>
+<object style="display: inline-table;direction: ltr;">
+<div style="display: table-column-group;direction: auto;">
+</i>
+</u>
+</wbr>
+<abbr style="display: inline;direction: ltr;">
+</sub>
+<dir style="display: block;direction: auto;">
+</small>
+</code>
+<style style="display: table-column;direction: ltr;">
+<fieldset style="display: table-cell;direction: rtl;">
+</b>
+<blockquote style="display: table-header-group;direction: rtl;">
+<q style="display: inline;direction: auto;">
+<iframe style="display: block;direction: rtl;">
+<button style="display: table-footer-group;direction: auto;">
+</s>
+</caption>
+<input type="submit" style="display: inline-block;direction: auto;">
+</wbr>
+</link>
+</sub>
+<dt style="display: table-column-group;direction: auto;">
+<noscript style="display: table-cell;direction: ltr;">
+<spacer type="block" style="display: inline-table;direction: rtl;">
+<dl style="display: inline;direction: auto;">
+<thead style="display: table-row;direction: auto;">
+</td>
+</strike>
+<body style="display: inline;direction: auto;">
+<img style="display: table-column;direction: rtl;">
+</input type="hidden">
+</label>
+<img src="fish.gif" style="display: inline-table;direction: auto;">
+</ins>
+<img src="fish.gif" style="display: block;direction: rtl;">
+</caption>
+<abbr style="display: table-header-group;direction: auto;">
+</meta>
+<area style="display: list-item;direction: auto;">
+<ul style="display: table-row-group;direction: rtl;">
+<img style="display: table-cell;direction: rtl;">
+</nobr>
+<blockquote style="display: table;direction: ltr;">
+<a style="display: table-header-group;direction: auto;">
+</basefont>
+</tbody>
+<dt style="display: table-footer-group;direction: auto;">
+</address>
+<strong style="display: table-header-group;direction: auto;">
+<strong style="display: table-row;direction: rtl;">
+<u style="display: table;direction: rtl;">
+</font>
+</dir>gend style="display: ta<samp style="display: table-row-group;direction: rt<kbd style="display: table-column;direction: auto;">
+<acronym style="display: table-footer-group;direction: auto;">
+</html>
+</cite>
+</keygen>
+<kbd style="display: none;direction: ltr;">
+</spacer>
+<div style="display: inline;direction: rtl;">
+</marquee>
+</code>
+<select style="display: table-cell;direction: rtl;">
+<i style="display: none;direction: rtl;">
+<bgsound style="display: table-header-group;direction: ltr;">
+</isindex>
+<table style="display: none;direction: rtl;">
+</td>
+<bgsound style="display: inline-table;direction: rtl;">
+<canvas style="display: inline-table;direction: ltr;">
+<textarea style="display: table-header-group;direction: auto;">
+</tbody>
+</li>
+</th>
+</body>
+<th style="display: inline;direction: ltr;">
+</sub>
+</sup>
+<li style="display: table;direction: ltr;">
+<l<sup style="display: none;direction: ltr;">
+</bgsound>
+<select style="display: table-row;direction: auto;">
+</del>
+</cite>
+</img>
+<abbr style="display: table-row;direction: ltr;">
+<multicol style="display: table;direction: ltr;">
+<style style="display: inline-table;direction: ltr;">
+<code style="display: table-caption;direction: ltr;">
+</tbody>
+<button style="display: -moz-inline-box;direction: rtl;">
+<basefont style="display: table-column-group;direction: auto;">
+<img style="display: inline;direction: auto;">
+<wbr style="display: table-row;direction: rtl;">
+</isindex>
+<html style="display: table-footer-group;direction: rtl;">
+</nobr>
+<col style="display: table-column;direction: ltr;">
+</li>
+<fieldset style="display: table-row-group;direction: auto;">
+<a style="display: inline;direction: rtl;">
+</textarea>
+<input type="hidden" style="display: table;direction: auto;">
+</td rowspan="5">
+</wbr>
+</script>
+</optgroup>
+</noframes>
+<le</i>
+</label>
+</li>
+<frame style="display: table-footer-group;direction: ltr;">
+<noscript style="display: table-column;direction: ltr;">
+<thead style="display: none;direction: rtl;">
+</blink>
+<style style="display: -moz-inline-box;direction: ltr;">
+</strike>
+<noframes style="display: -moz-inline-box;direction: auto;">
+</u>
+</frame>
+</big>
+</strike>
+<label style="display: table-cell;direction: rtl;">
+</p>
+</optgroup>
+<kbd style="display: table-row-group;direction: rtl;">
+<hx style="display: block;direction: ltr;">
+</spacer type="block">
+<strike style="display: -moz-inline-box;direction: rtl;">
+</td>
+<input type="submit" style="display: inline-block;direction: auto;">
+</fieldset>
+</dir="rtl">
+<label style="display: list-item;direction: rtl;">
+<base style="display: -moz-inline-box;direction: ltr;">
+<iframe style="display: table-footer-group;direction: ltr;">
+</link>
+</s>
+<sup style="display: table-row;direction: rtl;">
+</colgroup>
+<map style="display: inline;direction: rtl;">
+<script style="display: -moz-inline-box;direction: auto;">
+<spacer style="display: list-item;direction: auto;">
+</acronym>
+<spacer style="display: none;direction: rtl;">
+</spacer>
+</menu>
+</blockquote>
+<a style="display: table;direction: ltr;">
+</big>
+<script style="display: table-column;direction: auto;">
+</dd>
+<div style="display: table-caption;direction: rtl;">
+</object>
+<label style="display: block;direction: ltr;">
+<colgroup style="display: table;direction: rtl;">
+</cite>
+<ol style="display: inline-table;direction: auto;">
+</cite>
+</small>
+</big>
+<input type="hidden" style="display: inline-table;direction: auto;">
+<big style="display: -moz-inline-box;direction: rtl;">
+<strong style="display: table-row-group;direction: rtl;">
+<dd style="display: block;direction: ltr;">
+</font>
+<dt style="display: list-item;direction: rtl;">
+<cite style="display: table-header-group;direction: rtl;">
+<em style="display: table-row-group;direction: rtl;">
+<basefont style="display: table-column;direction: rtl;">
+<legend style="display: -moz-inline-box;direction: auto;">
+</img>
+<tt style="display: table-row-group;direction: ltr;">
+<link style="display: table-caption;direction: ltr;">
+<q style="display: block;direction: ltr;">
+</title>
+</bdo>
+</meta>
+</iframe>
+<b style="display: table-header-group;direction: rtl;">
+<script style="display: block;direction: auto;">
+</link>
+</embed>
+<code style="display: -moz-inline-box;direction: rtl;">
+</select>
+</label>
+</hx>
+</td>
+</textarea>
+<q style="display: none;direction: rtl;">
+<del style="display: table-cell;direction: auto;">
+<input type="submit" style="display: -moz-inline-box;direction: rtl;">
+<tfoot style="display: inline;direction: rtl;">
+</abbr>
+</tr>
+<strike style="display: inline;direction: rtl;">
+</big>
+<input type="hidden" style="display: inline-block;direction: rtl;">
+</strong>
+<q style="display: table-header-group;direction: rtl;">
+</big>
+<tr style="display: list-item;direction: auto;">
+</object>
+</input type="hidden">
+</bdo>
+</thead>
+</span>
+</tt>
+</listing>
+</blink>
+</dfn>
+</b>
+</a>
+</tt>
+</dir>
+</a<nobr</keygen> styl<sup style="display: -moz-inline-box;direction: ltr<ol style="display: table;direction: auto;">
+</span>
+</ins>
+<div style="display: table-row-group;direction: auto;">
+<pre style="display: -moz-inline-box;direction: rtl;">
+</tfoot>
+<frameset style="display: table-footer-group;direction: auto;">
+</img>
+</keygen>
+<span style="display: table-caption;direction: auto;">
+</samp>
+</object>
+<code style="display: table-row-group;direction: ltr;">
+<sup style="display: table-column-group;direction: ltr;">
+<frame style="display: table-cell;direction: ltr;">
+</spacer type="block">
+<button style="display: table-caption;direction: rtl;">
+<td style="display: table;direction: ltr;">
+<td colspan="5" style="display: inline-table;direction: ltr;">
+<pre style="display: inline-table;direction: ltr;">
+<object style="display: table-row-group;direction: auto;">
+</a>
+</tt>
+</optgroup>
+</span>
+<style style="display: table-footer-group;direction: auto;">
+<hx style="display: block;direction: rtl;">
+<spacer style="display: list-item;direction: auto;">
+</title>
+<ins style="display: table-column;direction: auto;">
+</object>
+</br>
+<frameset style="display: inline-block;direction: rtl;">
+<span style="display: block;direction: auto;">
+<kbd style="display: list-item;direction: auto;">
+<dir style="display: table;direction: rtl;">
+</address>
+</cite>
+</big>
+<optgroup style="display: inline-block;direction: ltr;">
+</fieldset>
+</base>
+<input type="image" style="display: inline-block;direction: ltr;">
+</object>
+<col style="display: table-row-group;direction: auto;">
+<noscript style="display: none;direction: auto;">
+</blink>
+</script>
+<input type="hidden" style="display: -moz-inline-box;direction: rtl;">
+<ul style="display: -moz-inline-box;direction: rtl;">
+</menu>
+<sup style="display: table-row-group;direction: ltr;">
+<tr style="display: table-column;direction: auto;">
+<ul style="display: table-row;direction: ltr;">
+<noscript style="display: table;direction: ltr;">
+</i>
+</dir="rtl">
+</input type="submit">
+</cite>
+</code>
+<title style="display: table-footer-group;direction: rtl;">
+<sub style="display: block;direction: rtl;">
+</code>
+</acronym>
+</caption>
+</code>
+</font>
+</em>
+</optgroup>
+<multicol style="display: table-row-group;direction: auto;">
+<bdo style="display: list-item;direction: auto;">
+<canvas style="display: table-header-group;direction: rtl;">
+</form>
+<tt style="display: table-footer-group;direction: ltr;">
+<frame style="display: -moz-inline-box;direction: rtl;">
+</multicol>
+</acronym>
+<em style="display: -moz-inline-box;direction: ltr;">
+</wbr>
+<legend style="display: inline-block;direction: auto;">
+</samp>
+</var>
+<dir style="display: table-footer-group;direction: auto;">
+</title>
+</input type="submit">
+<spacer style="display: inline-block;direction: auto;">
+<ol style="display: table-header-group;direction: ltr;">
+</option>
+<i style="display: list-item;direction: rtl;">
+</caption>
+</spacer>
+</noframes>
+</sub>
+</tfoot>
+</legend>
+</select>
+<marquee style="display: table-caption;direction: ltr;">
+<script style="display: table-footer-group;direction: rtl;">
+</q>
+</strong>
+</code>
+</div>
+</code>
+</td>
+</optgroup>
+<map style="display: table-row-group;direction: ltr;">
+</marquee>
+<optgroup style="display: none;direction: ltr;">
+<hr style="display: table-row-group;direction: rtl;">
+</br>
+<param style="display: table-row;direction: ltr;">
+</dfn>
+</keygen>
+<param style="display: none;direction: ltr;">
+<legend style="display: list-item;direction: ltr;">
+<b style="display: list-item;direction: ltr;">
+</bdo>
+</dfn>
+</option>
+</input>
+</u>
+</code>
+<ul style="display: table-header-group;direction: ltr;">
+</thead>
+</span>
+</link>
+<img src="fish.gif" style="display: inline-table;direction: auto;">
+</menu>
+<u style="display: table-row;direction: rtl;">
+</cite>
+<listing style="display: inline-block;direction: auto;">
+</style>
+</head>
+<body>
+<code style="display: table-row-group; direction: ltr;">
+<button style="display: list-item; direction: ltr;">
+<kbd style="display: table-row; direction: rtl;">
+<input style="display: list-item; direction: rtl;" type="hidden">
+<iframe style="display: table-column-group;">
+</iframe>gend style="display: ta<samp style="display: table-row-group;" display:="" table-column;direction:="" auto;="">
+</samp>
+</kbd>
+</button>
+</code>
+<script>
+//<![CDATA[
+/*template*/
+var doc = document;
+if (document.getElementById('content'))
+ doc = document.getElementById('content').contentDocument;var tt;
+var l=0;
+function replacestyles(i){
+l++;
+window.status=l+':'+i;
+if (l>70)
+ return;
+var x=doc.getElementsByTagName('*');
+
+if (x[i] && x[i+1])
+ {
+var temp = x[i+1].getAttribute('style');
+x[i+1].setAttribute('style', x[i].getAttribute('style'));
+x[i].setAttribute('style', temp);
+}
+else { i = 0;}
+ i++;
+tt=setTimeout(replacestyles,10,i);
+}
+//window.onmousedown=function(){clearTimeout(tt);}
+tt=setTimeout(replacestyles,100,0);
+/*template*/
+//]]>
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/370174-2.html b/layout/generic/crashtests/370174-2.html
new file mode 100644
index 0000000000..fd96496364
--- /dev/null
+++ b/layout/generic/crashtests/370174-2.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>Bug 370174 – Crash [@ FindBlockFrameOrBR] with unminimised testcase triple-clicking at the bottom of the page</title>
+</head>
+<body>
+<script>
+function doe() {
+document.documentElement.innerHTML = '';
+}
+setTimeout(doe, 300);
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/370174-3.html b/layout/generic/crashtests/370174-3.html
new file mode 100644
index 0000000000..dbcc4595d7
--- /dev/null
+++ b/layout/generic/crashtests/370174-3.html
@@ -0,0 +1,25 @@
+<HTML><HEAD style="display: table-row;"></HEAD>
+<BODY style="display: table-row;">
+<CODE>
+<span>
+<IFRAME style="display:table-column-group;"></IFRAME>
+</span>
+</CODE>
+<span style="display:table-column-group;"></span>
+<SCRIPT>
+function doe2() {
+document.body.setAttribute('style', '');
+document.getElementsByTagName('head')[0].setAttribute('style', '');
+document.body.offsetHeight;
+document.getElementsByTagName('span')[0].setAttribute('style', 'display: table-row;');
+}
+setTimeout(doe2,100);
+
+function tripleclick(){
+var wu = SpecialPowers.DOMWindowUtils;
+wu.sendMouseEvent('mousedown', 500, 500, 0, 3, 0);
+setTimeout(tripleclick,20);
+}
+setTimeout(tripleclick,200);
+</SCRIPT>
+</BODY></HTML>
diff --git a/layout/generic/crashtests/370699-1.html b/layout/generic/crashtests/370699-1.html
new file mode 100644
index 0000000000..d977c937ab
--- /dev/null
+++ b/layout/generic/crashtests/370699-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+</head>
+
+<body style="width: 100px; border: 2px solid yellow;">
+
+<span style="position: relative;">
+ <img width="200" height="100" align="left">
+ <span style="position:absolute; float:left;"></span>
+ Foo
+</span>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/370794-1.html b/layout/generic/crashtests/370794-1.html
new file mode 100644
index 0000000000..d4f489d146
--- /dev/null
+++ b/layout/generic/crashtests/370794-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+
+<div style="overflow: hidden; padding-right: 3%;">
+ <h2 style="position: fixed;">H2</h2>
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/370884-1.xhtml b/layout/generic/crashtests/370884-1.xhtml
new file mode 100644
index 0000000000..3959d4b178
--- /dev/null
+++ b/layout/generic/crashtests/370884-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+</head>
+
+<body>
+
+<math:mroot>
+ <div>
+ <div style="position: fixed;">Y</div>
+ </div>
+</math:mroot>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/371348-1.xhtml b/layout/generic/crashtests/371348-1.xhtml
new file mode 100644
index 0000000000..1b2252f11b
--- /dev/null
+++ b/layout/generic/crashtests/371348-1.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ var span = document.getElementById("span");
+ var table = document.getElementById("table");
+
+ table.appendChild(span);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+
+<body onload="setTimeout(boom, 20)">
+
+<span id="span"></span>
+
+<table id="table">
+ <tbody>
+ <tr>
+ <td style="float: left">
+ Table
+ <xul:box>
+ <input type="checkbox"/>
+ </xul:box>
+ cell
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/371561-1.html b/layout/generic/crashtests/371561-1.html
new file mode 100644
index 0000000000..bce6075c48
--- /dev/null
+++ b/layout/generic/crashtests/371561-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <div style="position: relative; display: inline;">
+ <table style="position: absolute;"><tr><td></td></tr></table>
+ </div>
+ <body>
+</html>
diff --git a/layout/generic/crashtests/371566-1.xhtml b/layout/generic/crashtests/371566-1.xhtml
new file mode 100644
index 0000000000..e85cc9f08d
--- /dev/null
+++ b/layout/generic/crashtests/371566-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body>
+ <div style="display: -moz-inline-box;">
+ <div style="float: left; padding-right: 3%">
+ <div style="position: absolute;"></div>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/372376-1.xhtml b/layout/generic/crashtests/372376-1.xhtml
new file mode 100644
index 0000000000..ddb7d2d179
--- /dev/null
+++ b/layout/generic/crashtests/372376-1.xhtml
@@ -0,0 +1,39 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+<![CDATA[
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function boom()
+{
+ var $table = document.getElementById('table');
+ var $oldtd = document.getElementById('oldtd');
+
+ $table.style.outline = "1px solid green";
+
+ var $tr = document.createElementNS(HTML_NS, 'tr');
+ $tr.style.display = "inherit";
+ $table.insertBefore($tr, $oldtd);
+
+ var $td = document.createElementNS(HTML_NS, 'td');
+ $tr.appendChild($td);
+
+ var $anothertr = document.createElementNS(HTML_NS, 'tr');
+ $table.insertBefore($anothertr, $tr);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30)">
+
+<table border="1" id="table">
+<td id="oldtd"></td>
+</table>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/373859-1.html b/layout/generic/crashtests/373859-1.html
new file mode 100644
index 0000000000..f9e4aa0214
--- /dev/null
+++ b/layout/generic/crashtests/373859-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ document.getElementById("k").style.display = "-moz-inline-box";
+}
+</script>
+
+<body onload="boom();">
+
+<div style="display: table;"><div id="k" style="height: 18000000px;">x</div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/373868-1.xhtml b/layout/generic/crashtests/373868-1.xhtml
new file mode 100644
index 0000000000..712569386b
--- /dev/null
+++ b/layout/generic/crashtests/373868-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body>
+<span style="float: left; height: 18000000px"/>
+<p style="clear: left;"/>
+<div>
+ <span style="float: right; height: 100px;">a</span>
+ b
+ <span style="float: right; height: 18000000px;"/>
+ <p/>
+ <span style="float: right; height: 18000000px;">c</span>
+ <table style="height: 20000000px;"/>
+ <p style="float: right;"/>
+</div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/375462-1.html b/layout/generic/crashtests/375462-1.html
new file mode 100644
index 0000000000..a0d9dca75d
--- /dev/null
+++ b/layout/generic/crashtests/375462-1.html
@@ -0,0 +1,781 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+ lang="en">
+
+ <head>
+ <meta http-equiv="Content-Type"
+ content="text/html;charset=utf-8" />
+
+ <title>Testcase #1 for bug 375462</title>
+
+<!-- Reduced from http://www.bethelmaine.com/events/index_events_bugy -->
+
+
+ </head>
+
+ <body class="section-events" dir="ltr">
+
+
+
+ <div>
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">"Remembering Sis Post"</h3>
+ <h3 style="float: right;">
+ <span>07/01</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">BHS members and friends will gather to share stories and memories in celebration of the life of the late Persis G. Post, who was an outstanding volunteer at the Society for more than 20 years.</div>
+ <br />
+ <div style="position: relative;" class="stx">1:00 PM, Mason House Exhibit Hall</div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Dr. Moses Mason House</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>Bethel Historical Society</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:info@bethelhistorical.org"><span>info@bethelhistorical.org</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207-824-2908</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">"An Androscoggin Sampler"</h3>
+ <h3 style="float: right;">
+ <span>07/01</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">In conjunction with the Society's newest exhibit, "A River's Journey: The Story of the Androscoggin," a series of slides of old photos taken in various locations throughout the Androscoggin valley will be shown. Free and open to the public.</div>
+ <br />
+ <div style="position: relative;" class="stx">2:00 PM, Mason House Exhibit Hall (the exhibit will be open at the Robinson House following the program)</div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Bethel Historical Society</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>Bethel Historical Society</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:info@bethelhistorical.org"><span>info@bethelhistorical.org</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207-824-2908</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">Summer Season Mason House Period Room Tours</h3>
+ <h3 style="float: right;">
+ <span>07/02</span>
+ <span>- 08/01</span>
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">Summer season guided tours of the Mason House period rooms begin (Tuesday through Sunday, 1:00 to 4:00 PM, until Labor Day); tours may be arranged during the remainder of the year by calling 207-824-2908.</div>
+ <br />
+ <div style="position: relative;" class="stx"></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Dr. Moses Mason House</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>Bethel Historical Society</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:info@bethelhistorical.org"><span>info@bethelhistorical.org</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207-824-2908</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">4th of July Community Picnic</h3>
+ <h3 style="float: right;">
+ <span>07/04</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;"></div>
+ <br />
+ <div style="position: relative;" class="stx"><p>In the 1850s, Dr. Moses Mason began hosting a Fourth of July community picnic in "the grove" behind his residence. Today, the Bethel Historical Society proudly carries on this tradition. </p><p>This year's event will start at noon on the lawn beside the Mason house, and after the presentation of colors and the National Anthem, a two-hour concert will be presented by the Portland Brass Quintet. </p><p>In case of rain, the picnic and concert will be held in the historic Middle Intervale Meetinghouse (1816) on Intervale Road, approximately four miles down river from Bethel Hill village. Bring your lunch and a lawn chair or blanket. Free and open to the public (donations accepted).</p></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>14 Broad Street</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>Bethel Historical Society</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:info@bethelhistorical.org"><span>info@bethelhistorical.org</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207-824-2908</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">Mama's Night Out</h3>
+ <h3 style="float: right;">
+ <span>07/05</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">You don't have to be a mother to enjoy the hilarious comedy of these three finalists in Nick at Nite's “Search for the Funniest Mom in America.†The uproarious views on life and laughter of these ladies’ – a southerner living in Maine, a sassy Brit, and a New Yorker - are for everyone. Okay, perhaps they are not really for children, but that's why it's called a night out. For further information and reservations please call the Deertrees Box Office at 207 583 6747 or visit www.deertreestheatre.org</div>
+ <br />
+ <div style="position: relative;" class="stx"><p>8pm $18 <br /> <br />The Box Office is open from 10 AM until 5 PM Tuesday through Saturday and one hour before performances. <br /> <br />Tickets are also available at: <br />Books-N-Things, Bethel <br />The Cool Moose, Bridgton <br />Center Lovell Market, Lovell <br />The Country Sleigh, Naples <br />Fare Share, Norway <br /> <br />Deertrees Theatre is handicap accessible. <br />Free parking on the grounds.</p></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Deertrees Theatre</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>C.Randolph Parker (house manager)</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:deertrees@usa.net"><span>deertrees@usa.net</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207 583 6747</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">The Casco Bay Tummlers</h3>
+ <h3 style="float: right;">
+ <span>07/06</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">Klezmer music is music that speaks, it's Balkans and blues, ancient Jewish culture and prayer and history, spirit and jazz all mixed together. Emotionally charged and played with abandon by musicians who continue to change, expand, and morph musical and cultural boundaries. For further information and reservations please call the Deertrees Box Office at 207 583 6747 or visit www.deertreestheatre.org
+ </div>
+ <br />
+ <div style="position: relative;" class="stx"><p>8pm $18 <br /> <br />Box Office open, 10 AM until 5 PM, Tuesday through Saturday, and one hour before performances. <br /> <br />Tickets are also available at: <br />Books-N-Things, Bethel <br />The Cool Moose, Bridgton <br />Center Lovell Market, Lovell <br />The Country Sleigh, Naples <br />Fare Share, Norway <br /> <br />Deertrees Theatre is handicapped accessible. <br />Free parking on the grounds.</p></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Deertrees Theatre</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>C.Randolph Parker (house manager)</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:deertrees@usa.net"><span>deertrees@usa.net</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207 583 6747</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">18th Annual Bethel Art Fair</h3>
+ <h3 style="float: right;">
+ <span>07/07</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">Fifty artists &amp; fine artisans display and sell their creations -- </div>
+ <br />
+ <div style="position: relative;" class="stx"><div><div>Scroll down for complete schedule...<br /></div></div></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Bethel Town Common</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>Bethel Area Chamber of Commerce</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:info@bethelmaine.com"><span>info@bethelmaine.com</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207.824.2282</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">The Hunger Mountain Boys with The Wiyos</h3>
+ <h3 style="float: right;">
+ <span>07/07</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">This double dose of traditional music may be resolutely old-school but it is also resolutely eclectic. With their ears tuned to music from the 30’s, 40’s and 50’s, the Massachusetts bluegrass band, The Hunger Mountain Boys, serve up a hard-hittin’, high energy sound that pays homage to the soulfulness and sincerity of old-time country while subtly incorporating rock, jazz and ska influences. The opening set by Brooklyn based “the WIYOS†can only be called Vaudevillian Ragtime-Blues and HillBilly Swing. For further information and reservations please call the Deertrees Box Office at 207 583 6747 or visit www.deertreestheatre.org
+
+</div>
+ <br />
+ <div style="position: relative;" class="stx"><p>8pm $16 <br /> <br />The Box Office is open from 10 AM until 5 PM Tuesday through Saturday and one hour before performances. <br /> <br />Tickets are also available at: <br />Books-N-Things, Bethel <br />The Cool Moose, Bridgton <br />Center Lovell Market, Lovell <br />The Country Sleigh, Naples <br />Fare Share, Norway <br /> <br />Deertrees Theatre is handicap accessible. <br />Free parking on the grounds.</p></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Deertrees Theatre</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>C.Randolph Parker (house manager)</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:deertrees@usa.net"><span>deertrees@usa.net</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207 583 6747</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">Summer Piano Festival at Gould Academy</h3>
+ <h3 style="float: right;">
+ <span>07/07</span>
+
+ </h3>
+ <div style="clear: both;" />
+
+ <div style="position: relative;">Free piano recitals, July 1-20, 2007 at Gould Academy, an all-Steinway school!
+</div>
+ <br />
+ <div style="position: relative;" class="stx"><p>
+
+</p><p>Saturday, July 7, 7 p.m.,
+Bingham Hall</p><p>Distinguished Russian pianist and Master Teacher, Tamara
+Poddubnaya, and her students present free public recitals.</p>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<p>FULL SCHEDULE:<br />Saturday, July 7, 7 p.m.,
+Bingham Hall<br />Wednesday, July 11, 7 p.m.,
+Bingham Hall<br />Friday, July 13, 7 p.m.,
+Bingham Hall<br />Wednesday, July 18, 7 p.m.,
+Bingham Hall<br />Friday, July 20, 7 p.m.,
+<strong>McLaughlin Science Center, Trustees' Auditorium</strong></p><p><br /></p><p><br /></p><br /><p><br />
+</p>
+
+</div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Bingham Hall, Gould Academy, Bethel, Maine</span>
+ </td>
+ </tr>
+
+
+
+ </table>
+
+ </div>
+ <hr />
+
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">5th Annual Skunk Run</h3>
+ <h3 style="float: right;">
+ <span>07/28</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">SATURDAY, July 28,2007 -- Benefiting the Sandon A. Morgan Memorial Scholarship Fund awarded exclusively at Telstar Regional High School, Bethel, Maine. The scholarship is awarded each year to a graduating senior at Telstar to attend CMCC in Auburn for 2 full years, all academics are covered. </div>
+ <br />
+ <div style="position: relative;" class="stx"><p>
+
+</p>
+
+<p>MOTORCYCLE RUN</p>
+
+
+
+
+
+
+
+
+
+<p>$10.00 includes run, bottled water, sandwich, chips &amp;
+FUN!<br />Registration 8:30am-9:45am<br />Vertical Outlaws Stunt Team to perform in the morning.<br />Run leaves at 10:00 am </p>
+
+<p>Ride through Bethel to Grafton Notch State Park, into New
+Hampshire and back to Greenwood (Locke Mills). Designated bathroom/rest &amp;
+gas stops. Great scenery and roads! All of our traffic is taken care of by our
+traffic control volunteers, so the run stays together for safety reasons. We
+also have a red Hummer leading the pack of bikes, as well as a tail vehicle.</p>
+
+
+
+<p> </p>
+
+
+
+
+
+
+
+
+
+
+
+<p>PIG ROAST/TURKEY FRY<br />3:00-5:00 PM<br />$10:00<br />Includes pig, turkey, summer salads, bean hole beans, rolls,
+watermelon, homemade desserts, and assorted beverages. BYOB.<br />Music, vendor showcase, Skunk Run T's and assorted items for
+sale. </p>
+
+
+
+<p>Directions:<br />From the south - go north from Gray on Route 26 to Bryant
+Pond, after sharp "S" turn with light, follow Rt. 26 for a few more
+miles. You will see North Pond on your right, take the 2nd right onto Gore
+Road.</p>
+
+
+
+<p>From the north - go west on Route 2 through Rumford to Route
+232 south. Turn right and travel north on Route 26 to Bryant Pond, after sharp
+"S" turn with light, follow Rt. 26 for a few more miles. You will see
+North Pond on your right, take the 2nd right onto Gore Road.</p>
+
+
+
+<p> From the west - take Route 2 east to Route 26 south to
+Greenwood (Locke Mills). Past Mt. Mica Rarities (purple building) take Gore
+Road on left.</p>
+
+
+
+<p>From the east - from Augusta, take Route 202 west to
+Route 133 to Route 219 to Route 26 North to Greenwood (Locke Mills). Past Mt.
+Mica Rarities (purple building) take Gore Road on left.</p><p><br /></p></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Gore Road, Greenwood, ME, just off Rt. 26</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>Mark</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:mdmorgandesigns@yahoo.com"><span>mdmorgandesigns@yahoo.com</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207.743.8254 or 207.890.6767</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">The Bill &amp; Bo Winiker Quartet</h3>
+ <h3 style="float: right;">
+ <span>07/28</span>
+
+ </h3>
+ <div style="clear: both;" />
+ <div style="float: right; padding: 1px; margin-left: 1em; margin-bottom: 1em; border: 1px solid black;">
+ </div>
+ <div style="position: relative;">Featuring jazz savant pianist Tony Deblois, this Boston based quartet embarks on an eclectic journey through the many styles of jazz including traditional, swing, bebop, jazzy takes on Broadway classics, world music and even some original compositions. Making music an uplifting and joyous experience. Sponsored by Gordon Dexter and Barbara Grandolfo. For further information and reservations please call the Deertrees Box Office at 207 583 6747 or visit www.deertreestheatre.org </div>
+ <br />
+ <div style="position: relative;" class="stx"><p>Saturday, July 28 at 8:00 pm <br /> <br />$20 <br /> <br />The Box Office is open from 10 AM until 5 PM Tuesday through Saturday and one hour before performances. <br /> <br />Tickets are also available at: <br />Books-N-Things, Bethel <br />The Cool Moose, Bridgton <br />Center Lovell Market, Lovell <br />The Country Sleigh, Naples <br />Fare Share, Norway <br /> <br />Deertrees Theatre is handicap accessible. <br />Free parking on the grounds.</p></div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Location:
+ </td>
+ <td>
+ <span>Deertrees Theatre</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>C.Randolph Parker (house manager)</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:deertrees@usa.net"><span>deertrees@usa.net</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207 583 6747</span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+ <hr />
+
+
+ <div>
+ <div style="clear: both;" />
+ <h3 style="float: left;">Tenth Annual Canoe and Kayak Outdoor Adventure Day Camp </h3>
+ <h3 style="float: right;">
+ <span>07/30</span>
+ <span>- 08/03</span>
+ </h3>
+ <div style="clear: both;" />
+
+ <div style="position: relative;">Designed with young people in mind, all aspects of canoeing and kayaking will be taught, as well as some other adventure activities. This is a hands on active program! June 23rd - July 27th from 9am-3pm for ages 8-13. $175.00 for the week session.</div>
+ <br />
+ <div style="position: relative;" class="stx">Included in the session will be basic canoe and kayak strokes, rescue techniques, expeditioning by canoe, simple camping skills, simple white water techniques and other adventure activities. Participants will learn how to skillfully maneuver canoes and kayaks while exploring the many channels and islands on the river, and develop proficiency and confidence in all types of outdoor skills. Meet at Bethel Outdoor Adventure every morning and travel to local lakes , ponds, islands, and the Androscoggin River. To sign up call 207.824.4224 or info@betheloutdooradventure.com
+Event Starts: July 30, 2007
+Event Ends: August 3, 2007
+
+</div>
+ <table style="border: 1px solid grey; margin-top: 1em; margin-bottom: 1em;" cellspacing="0" cellpadding="2px">
+
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Name:
+ </td>
+ <td>
+ <span>Jeff and Pattie Parsons</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact E-mail:
+ </td>
+ <td>
+ <a href="mailto:info@betheloutdooradventure.com"><span>info@betheloutdooradventure.com</span></a>
+ </td>
+ </tr>
+ <tr>
+ <td style="border-right: 1px solid grey;">
+ Contact Phone:
+ </td>
+ <td>
+ <span>207.824.4224 </span>
+ </td>
+ </tr>
+ </table>
+
+ </div>
+
+
+ </div>
+
+ <hr />
+
+ </div>
+
+
+ <div class="discussion">
+
+</div>
+
+
+ </div>
+
+ </div>
+
+
+ </td>
+
+
+
+
+
+ </tr>
+ </tbody>
+ </table>
+
+
+ <div class="visualClear"><!-- --></div>
+
+
+ <hr class="netscape4" />
+
+
+
+ <div id="portal-footer">
+
+<p>
+</p>
+
+</div>
+
+ <div id="portal-colophon">
+ <p>
+ Bethel Area Chamber of Commerce<br />
+ 8 Station Place<br />
+ PO Box 1247<br />
+ Bethel, ME 04217<br />
+ Tel: 207.824.2282 or 800.442.5826 Fax: 207.824.7123<br />
+ Email: info@bethelmaine.com
+ </p>
+
+
+ </div>
+
+
+ </div>
+
+</body>
+</html>
+
+
diff --git a/layout/generic/crashtests/375831.html b/layout/generic/crashtests/375831.html
new file mode 100644
index 0000000000..79c334738a
--- /dev/null
+++ b/layout/generic/crashtests/375831.html
@@ -0,0 +1,11 @@
+<html>
+<head style="overflow: scroll; display: block; float: right; ">
+</head>
+<body>
+
+<script id="script">
+document.documentElement.setAttribute('style', 'overflow: scroll; ');
+document.getElementsByTagName('head')[0].removeAttribute('style');
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/376419.html b/layout/generic/crashtests/376419.html
new file mode 100644
index 0000000000..b5cdca1ed6
--- /dev/null
+++ b/layout/generic/crashtests/376419.html
@@ -0,0 +1,28 @@
+<html><head>
+<style>
+*::first-line { }
+*::after { content:"anonymous text"; border:3px solid black;}
+*::before { content:"before text"; border:3px solid black;font-size: 10px;}
+</style>
+</head>
+<body>
+<div style="column-count: 2; width: 1400px;">
+ &#1593; tesxt
+ <span>
+ &#1593; tesxt &#1593; tesxt &#1593; tesxt
+ <span>
+ &#1593; tesxt &#1593; tesxt &#1593; tesxt &#1593; tesxt
+ </span>
+ </span>
+<div style="column-count: 2;">
+<div style="column-count: 2; white-space: nowrap;">
+<div style="column-count: 2;">
+<span>
+&#1593; tesxt &#1593; tesxt &#1593; tesxt
+</span>
+</div>
+</div>
+</div>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/377522.html b/layout/generic/crashtests/377522.html
new file mode 100644
index 0000000000..418fb85521
--- /dev/null
+++ b/layout/generic/crashtests/377522.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+
+function boom() {
+ var div = document.getElementById('div');
+ div.appendChild(document.createTextNode(String.fromCharCode(8238)));
+ div.appendChild(document.createTextNode(String.fromCharCode(50377)));
+ div.appendChild(document.createTextNode(String.fromCharCode(50)));
+ div.textContent = div.textContent.slice(1);
+}
+
+</script>
+</head>
+<body onload="boom()">
+<div id="div" style="word-spacing:2px; text-align:right">x</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/37757-1.html b/layout/generic/crashtests/37757-1.html
new file mode 100644
index 0000000000..9a7ddab5d0
--- /dev/null
+++ b/layout/generic/crashtests/37757-1.html
@@ -0,0 +1 @@
+<p style="font-size: 1px">Text</p>
diff --git a/layout/generic/crashtests/379217-1.xhtml b/layout/generic/crashtests/379217-1.xhtml
new file mode 100644
index 0000000000..0614b9a540
--- /dev/null
+++ b/layout/generic/crashtests/379217-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<button style="width: 3em;"><b>aaa<u>bbb<p style="float: left"/></u></b></button>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/379217-2.xhtml b/layout/generic/crashtests/379217-2.xhtml
new file mode 100644
index 0000000000..8a8b32693d
--- /dev/null
+++ b/layout/generic/crashtests/379217-2.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body style="width: 1px;">
+
+<span>x
+<span style="border: 1px dotted red;"><span style="float: left;"></span></span></span>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/379917-1.xhtml b/layout/generic/crashtests/379917-1.xhtml
new file mode 100644
index 0000000000..a99bd7f4ae
--- /dev/null
+++ b/layout/generic/crashtests/379917-1.xhtml
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:math="http://www.w3.org/1998/Math/MathML"
+ class="reftest-wait">
+<head>
+<script>
+
+// This testcase uses long timeouts to make sure the marquee has a chance to animate.
+
+function boom()
+{
+ var div1 = document.getElementById("div1");
+ var marquee = document.getElementById("marquee");
+
+ div1.parentNode.removeChild(div1);
+ marquee.width = 4;
+
+ setTimeout(done, 100);
+}
+
+function done()
+{
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 100);">
+
+<math:math><div id="div1"/>x&#1506;</math:math>
+<marquee id="marquee">m</marquee>
+<div/>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/380012-1.html b/layout/generic/crashtests/380012-1.html
new file mode 100644
index 0000000000..6cd7e1018e
--- /dev/null
+++ b/layout/generic/crashtests/380012-1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+
+<style>
+ .fl:first-line { }
+ .inh { position: inherit; }
+ .abs { position: absolute; }
+</style>
+
+<script>
+
+var x, y;
+
+function boom()
+{
+ x = document.getElementById("x");
+ y = document.getElementById("y");
+
+ x.setAttribute('class', "fl abs");
+ y.setAttribute('class', "inh");
+ setTimeout(boom2, 5);
+}
+
+function boom2()
+{
+ y.setAttribute('class', "abs");
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 5);">
+<div id="x">
+ <p id="y">foo</p>
+</div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/381152-1.html b/layout/generic/crashtests/381152-1.html
new file mode 100644
index 0000000000..23ea97e6c8
--- /dev/null
+++ b/layout/generic/crashtests/381152-1.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title>Bug 381152 - Hang with float, large padding and margin</title>
+</head>
+<body>
+<div style="float: left;padding-top: 9999999999px;">
+<div style="float: left;margin-top: 9999999999px;margin-bottom: -9999999px;">
+</div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/382129-1.xhtml b/layout/generic/crashtests/382129-1.xhtml
new file mode 100644
index 0000000000..0d52309f35
--- /dev/null
+++ b/layout/generic/crashtests/382129-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<p style="display: -moz-box;"><img style="-moz-appearance: checkbox" src="../../../testing/crashtest/images/tree.gif"/></p>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/382131-1.html b/layout/generic/crashtests/382131-1.html
new file mode 100644
index 0000000000..82941f2cba
--- /dev/null
+++ b/layout/generic/crashtests/382131-1.html
@@ -0,0 +1,25 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function crash()
+{
+ try {
+ window.getSelection().containsNode([], false);
+ } catch(e) { }
+
+ try {
+ window.getSelection().containsNode(null, false);
+ } catch(e) { }
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(crash, 10);">
+Foo
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/382199-1.html b/layout/generic/crashtests/382199-1.html
new file mode 100644
index 0000000000..07aab13813
--- /dev/null
+++ b/layout/generic/crashtests/382199-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+
+<html style="display: table;">
+ <body>
+ <div style="float: left; border: 1px solid blue;">float</div>
+ <div style="position: absolute; border: 1px solid red;">absolute</div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/382208-1.xhtml b/layout/generic/crashtests/382208-1.xhtml
new file mode 100644
index 0000000000..5264b8845d
--- /dev/null
+++ b/layout/generic/crashtests/382208-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<body>
+
+<div><math:mfrac><math:mmultiscripts/><math:mi/></math:mfrac></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/382262-1.html b/layout/generic/crashtests/382262-1.html
new file mode 100644
index 0000000000..67ef591a48
--- /dev/null
+++ b/layout/generic/crashtests/382262-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div style="column-count: 2;">
+a<span style="float: right">e<select></select></span> r
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/382396-1.xhtml b/layout/generic/crashtests/382396-1.xhtml
new file mode 100644
index 0000000000..f334bbfdf5
--- /dev/null
+++ b/layout/generic/crashtests/382396-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<body>
+
+<p style="text-indent: 0%">a<math:ms/></p>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/383089-1.html b/layout/generic/crashtests/383089-1.html
new file mode 100644
index 0000000000..27d4435adc
--- /dev/null
+++ b/layout/generic/crashtests/383089-1.html
@@ -0,0 +1,85 @@
+<html style="width: 1px;" class="reftest-wait"><head style="float: left; position: fixed; display: initial;" id="head">
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+
+
+
+<script style="display: none;" type="text/javascript">
+
+var iter;
+var interv;
+
+function olo()
+{
+ iter = foo();
+ interv = setInterval(neext, 30);
+}
+
+function neext()
+{
+ let {done} = iter.next();
+ if (done) {
+ clearInterval(interv);
+ }
+}
+
+function* foo()
+{
+ var docElem = document.documentElement;
+ var head = document.getElementById("head");
+ var br1 = document.getElementById("br1");
+ var br2 = document.getElementById("br2");
+ var br3 = document.getElementById("br3");
+ var br4 = document.getElementById("br4");
+ var br5 = document.getElementById("br5");
+ var br6 = document.getElementById("br6");
+ var br7 = document.getElementById("br7");
+ var tableRow = document.getElementById("tableRow");
+
+ br6.style.width = "1px";
+ br1.style.overflow = "visible";
+ head.style.cssFloat = "left";
+ br4.style.position = "static";
+ br7.style.color = "green";
+ br3.style.height = "auto";
+ yield;
+
+ br7.style.width = "2px";
+ yield;
+
+ br5.style.background = "yellow";
+ br7.style.color = "black";
+ yield;
+
+ br7.style.display = "table-cell";
+ head.style.position = "fixed";
+ br5.style.display = "inline";
+ br3.style.clear = "both";
+ br6.style.visibility = "visible";
+ yield;
+
+ tableRow.style.display = "list-item";
+ br3.style.width = "auto";
+ br2.style.clear = "none";
+ head.style.display = "initial"; // doesn't seem to crash when this is "block"!
+ docElem.style.width = "1px";
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<style>
+</style>
+
+</head><body onload="setTimeout(olo, 30);">
+
+<br style="overflow: visible;" id="br1">
+<br style="clear: none;" id="br2">
+<br style="height: auto; clear: both; width: auto;" id="br3">
+<br style="position: static;" id="br4">
+<br style="background: yellow none repeat scroll 0% 0%; background-clip: initial; background-origin: initial; display: inline;" id="br5">
+<br style="width: 1px; visibility: visible;" id="br6">
+<br style="color: black; width: 2px; display: table-cell;" id="br7">
+<table border="1"><tbody><tr style="display: list-item;" id="tableRow"><td>x</td></tr></tbody></table>
+
+</body></html>
diff --git a/layout/generic/crashtests/385265-1.xhtml b/layout/generic/crashtests/385265-1.xhtml
new file mode 100644
index 0000000000..7994653ffa
--- /dev/null
+++ b/layout/generic/crashtests/385265-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mtable>
+ <mtr>
+ <mtd><mi>x</mi></mtd>
+ </mtr>
+ </mtable>
+</math>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/385295-1.xhtml b/layout/generic/crashtests/385295-1.xhtml
new file mode 100644
index 0000000000..734270d756
--- /dev/null
+++ b/layout/generic/crashtests/385295-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="letter-spacing: 1px; -moz-appearance: checkbox;">
+<body onload="document.getElementById('s').style.cssFloat = 'left';">
+<p style="float: left; direction: rtl">zzz yyy <span id="s">foo</span></p>
+</body>
+</html>
diff --git a/layout/generic/crashtests/385344-1.html b/layout/generic/crashtests/385344-1.html
new file mode 100644
index 0000000000..7fbb7a73da
--- /dev/null
+++ b/layout/generic/crashtests/385344-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style id="script">
+ body::first-letter {float: right; }
+</style>
+</head>
+<body>
+<dd style="white-space: -moz-pre-wrap;">m
+ <dt>m</dt>
+</dd>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/385344-2.html b/layout/generic/crashtests/385344-2.html
new file mode 100644
index 0000000000..f22336d1b3
--- /dev/null
+++ b/layout/generic/crashtests/385344-2.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style>body::first-letter {float: right; }
+</style>
+</head>
+<body>
+<span style="direction: rtl; unicode-bidi: embed;">*::first
+m</span>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/385414-1.html b/layout/generic/crashtests/385414-1.html
new file mode 100644
index 0000000000..1b86f7bdcd
--- /dev/null
+++ b/layout/generic/crashtests/385414-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<div style="font-variant: small-caps">&shy;</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/385414-2.html b/layout/generic/crashtests/385414-2.html
new file mode 100644
index 0000000000..d2d8c5e285
--- /dev/null
+++ b/layout/generic/crashtests/385414-2.html
@@ -0,0 +1,5 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+</head><body>
+<div style="font-variant: small-caps;">&shy; </div>
+</body></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/385426-1.html b/layout/generic/crashtests/385426-1.html
new file mode 100644
index 0000000000..534e1cef21
--- /dev/null
+++ b/layout/generic/crashtests/385426-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<div><span>&#8238;</span><span>&shy;</span></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/385526.html b/layout/generic/crashtests/385526.html
new file mode 100644
index 0000000000..629f7cfe3b
--- /dev/null
+++ b/layout/generic/crashtests/385526.html
@@ -0,0 +1,116 @@
+<html class="reftest-wait"><head>
+<style>
+*::first-line { font-size:310%; }
+</style>
+</head>
+<body style="display: inline; white-space: nowrap;">
+<span>mmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm•mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmÈmmmmmmmmmmmmÞmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm!mmmmmmm
+
+mmmmmmmm mmmmmmmmmmmmmmmm
+m
+ mm mmmmmmm m mmmmmmmm mmmmmm mmmm mmmmmm mmmmmmm m mmmmmmmmmmm mmm m mmmm mmmmmmmmmmm mmmm mmmmmmmmmmmmm mmm mmmm m mmmm m mmmmmmmm
+ mm mmmmmmm mm mm mmmmmm mm mmmm mmmmmm m mmmmmmmmmmmm mmmm
+ mmmmmmmm mmmmmmmmm m
+ mmmm m mmmmmmmmmm m mmmmmm m mmmmmmmmm
+ mmmmmm mmmmmmmmmmmmmmmm
+ mm
+
+ mm mmmmmmm m mmmmmmmm mmmmmm mmmmmmm mm mmm mmmmm mmm mm mm mmmm mmmmmmmmm
+ mmmmmmmm mmmmmmmmmmmmmm m
+ mmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+ mm
+
+ mmmmmm mmmmmmm
+m
+
+
+mmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmm mmm m mmmmmmmmmmmmmmmmmmmmmmm
+
+mmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmømmmmmmmmmmmmÍmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmm§mmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmÌmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmŸmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmÃmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmm mmmmmmmmmmm ?mmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm m mmmmmmmmmmmmmmm mmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm\mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmÍmmmmmmmmmmmmmmmmmùmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmÂmmmmmmmmmmmmÖmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmÞmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmm mmmmmmmmmmm mmmmmm!mmmmmmm
+
+mmmmmmmm mmmmmmmmmmmmmmmm
+m
+ mm mmmmmmm m mmmmmmmm mmmmmm mmmm mmmmmm mmmmmmm m mmmmmmmmmmm mmm m mmmm mmmmmmmmmmm mmmm mmmmmmmmmmmmm mmm mmmm m mmmm m mmmmmmmm
+ mm mmmmmmm mm mm mmmmmm mm mmmm mmmmmm m mmmmmmmmmmmm mmmm
+ mmmmmmmm mmmmmmmmm m
+ mmmm m mmmmmmmmmm m mmmmmm m mmmmmmmmm
+ mmmmmm mmmmmmmmmmmmmmmm
+ mm
+
+ mm mmmmmmm m mmmmmmmm mmmmmm mmmmmmm mm mmm mmmmm mmm mm mm mmmm mmmmmmmmm
+ mmmmmmmm mmmmmmmmmmmmmm m
+ mmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+ mm
+
+ mmmmmm mmmmmmm
+m
+
+
+mmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmm mmm m mmmmmmmmmmmmmmmmmmmmmmm
+
+mmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmÔmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmœmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmËmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmÁmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmm m mmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm»mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmÀmmmmmmmmmm½mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmm mmmmmmmmmmmmm^mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm!mmmmmmm
+
+mmmmmmmm mmmmmmmmmmmmmmmm
+m
+ mm mmmmmmm m mmmmmmmm mmmmmm mmmm mmmmmm mmmmmmm m mmmmmmmmmmm mmm m mmmm mmmmmmmmmmm mmmm mmmmmmmmmmmmm mmm mmmm m mmmm m mmmmmmmm
+ mm mmmmmmm mm mm mmmmmm mm mmmm mmmmmm m mmmmmmmmmmmm mmmm
+ mmmmmmmm mmmmmmmmm m
+ mmmm m mmmmmmmmmm m mmmmmm m mmmmmmmmm
+ mmmmmm mmmmmmmmmmmmmmmm
+ mm
+
+ mm mmmmmmm m mmmmmmmm mmmmmm mmmmmmm mm mmm mmmmm mmm mm mm mmmm mmmmmmmmm
+ mmmmmmmm mmmmmmmmmmmmmm m
+ mmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+ mm
+
+ mmmmmm mmmmmmm
+m
+
+
+mmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmm mmm m mmmmmmmmmmmmmmmmmmmmmmm
+
+mmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mm
+mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmm mmmm mmmmmmmmmmmmm mmmmmmmm mmmmmmmmmmmm mmmmmmmmmmmmmÒ°mÞmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmm mmmmmmmmmm mmmm mmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmm mmmmmmm mmmmmmmmm mmmmmmmmmmmm˜mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmmmmmmmmmmmmmmmmmmmmmmmmm
+</span>
+<script>
+function finish() {
+ document.documentElement.removeAttribute('class');
+}
+function doe(){
+ document.body.removeAttribute('style');
+ setTimeout(finish, 100);
+}
+setTimeout(doe,300,0);
+</script>
+</body></html>
diff --git a/layout/generic/crashtests/385681.html b/layout/generic/crashtests/385681.html
new file mode 100644
index 0000000000..43707f65c5
--- /dev/null
+++ b/layout/generic/crashtests/385681.html
@@ -0,0 +1,34 @@
+<html><head>
+<title></title>
+<style>
+a {
+ font-variant: small-caps;
+}
+a:hover {
+ color: red;
+}
+</style>
+</head>
+<body>
+<a href="#">home</a><br>
+<a href="#">login</a><br>
+<a href="#">signup</a><br>
+
+<script>
+function doe(i) {
+if (!i) {
+ document.links[1].style.color='red';
+ document.links[0].offsetHeight;
+ document.links[0].style.color = 'red';
+ }
+else {
+ document.links[1].style.color='blue';
+ document.links[0].style.color = 'blue';
+ }
+setTimeout(doe, 100, !i);
+}
+setTimeout(doe, 500, true);
+</script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/386799-1.html b/layout/generic/crashtests/386799-1.html
new file mode 100644
index 0000000000..2fd2eb1307
--- /dev/null
+++ b/layout/generic/crashtests/386799-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <canvas style="padding: 6px; width: 0px;"></canvas>
+ <div style="overflow: scroll;"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/386807-1.html b/layout/generic/crashtests/386807-1.html
new file mode 100644
index 0000000000..af8169d8a5
--- /dev/null
+++ b/layout/generic/crashtests/386807-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ s = document.getElementById("s");
+ document.body.insertBefore(document.createTextNode("\n"), s);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+<span id="s" style="text-transform: uppercase;">
+</span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/386812-1.html b/layout/generic/crashtests/386812-1.html
new file mode 100644
index 0000000000..52a4526fef
--- /dev/null
+++ b/layout/generic/crashtests/386812-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom() {
+ document.body.style.textIndent = "0%";
+}
+</script>
+
+<style>
+body { float: left; }
+.c { padding-left: 60%; padding-right: 60%; white-space: pre; }
+.f { float: left; height: 2em; }
+</style>
+</head>
+
+<body onload="boom()">
+
+<div class="c"> <div class="f">a</div>
+ <div class="f">b</div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/386827-1.html b/layout/generic/crashtests/386827-1.html
new file mode 100644
index 0000000000..e1f345c6cd
--- /dev/null
+++ b/layout/generic/crashtests/386827-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ document.getElementById("d1").appendChild(document.createElement("span"));
+ document.getElementById("d2").style.direction = "rtl";
+}
+</script>
+</head>
+<body onload="boom()">
+<div id="d1" style="column-width: 1em;">a b c d</div>
+<div id="d2"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/387058-1.html b/layout/generic/crashtests/387058-1.html
new file mode 100644
index 0000000000..5a3eebcaa5
--- /dev/null
+++ b/layout/generic/crashtests/387058-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ document.getElementById("div").style.letterSpacing = "";
+}
+</script>
+</head>
+<body onload="boom()" style="font-family: monospace; width: 20em;">
+<div id="div" style="white-space: pre-wrap; letter-spacing: 3em;">
+ x x x x x x
+ x x x x x x
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/387058-2.html b/layout/generic/crashtests/387058-2.html
new file mode 100644
index 0000000000..eb5087aed4
--- /dev/null
+++ b/layout/generic/crashtests/387058-2.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ document.getElementById("div").style.letterSpacing = "";
+}
+</script>
+</head>
+
+<body onload="boom()" style="font-family: monospace; width: 3em;">
+<div id="div" style="white-space: pre-wrap; letter-spacing: 1em;">
+ x
+
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/387088-1.html b/layout/generic/crashtests/387088-1.html
new file mode 100644
index 0000000000..a927081885
--- /dev/null
+++ b/layout/generic/crashtests/387088-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<div style="line-height: 30760827em;">x</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/387209-1.html b/layout/generic/crashtests/387209-1.html
new file mode 100644
index 0000000000..c6ac101d97
--- /dev/null
+++ b/layout/generic/crashtests/387209-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="display: table-header-group; overflow: scroll; top: 10%;"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/387213-1.html b/layout/generic/crashtests/387213-1.html
new file mode 100644
index 0000000000..1b3fd1041d
--- /dev/null
+++ b/layout/generic/crashtests/387213-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+ <div style="position: fixed; overflow: scroll;">
+ <div style="position: fixed;"></div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/387215-1.xhtml b/layout/generic/crashtests/387215-1.xhtml
new file mode 100644
index 0000000000..ba14e67668
--- /dev/null
+++ b/layout/generic/crashtests/387215-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body>
+<div style="float: left; height: 18000000px"></div>
+<p style="clear: left;"/>
+<div>
+ <div style="float: right; height: 100px;">a</div>
+ <div style="float: right; height: 18000000px;"></div>
+ <p/>
+</div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/387219-1.xhtml b/layout/generic/crashtests/387219-1.xhtml
new file mode 100644
index 0000000000..0439324079
--- /dev/null
+++ b/layout/generic/crashtests/387219-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<style id="s"></style>
+</head>
+<body onload="document.getElementById('s').appendChild(document.createTextNode('#sb { display: list-item; }'));">
+<xul:scrollbox id="sb"/>
+</body>
+</html>
diff --git a/layout/generic/crashtests/387233-1.html b/layout/generic/crashtests/387233-1.html
new file mode 100644
index 0000000000..3ea2c05443
--- /dev/null
+++ b/layout/generic/crashtests/387233-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+
+<div style="display: -moz-inline-box;">
+ <table style="height: 200%;">
+ <tr>
+ <td></td>
+ </tr>
+ </table>
+ <div>
+ c
+ <div style="height: 200%;">
+ </div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/387233-2.html b/layout/generic/crashtests/387233-2.html
new file mode 100644
index 0000000000..fdad7e872a
--- /dev/null
+++ b/layout/generic/crashtests/387233-2.html
@@ -0,0 +1,18 @@
+<html>
+<body>
+
+<div style="display: -moz-inline-box; background: yellow;">
+ <table style="height: 101%; background: lightgreen;">
+ <tr>
+ <td></td>
+ </tr>
+ </table>
+ <div>
+ c
+ <div style="height: 100%; background: lightblue;">
+ </div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/387282-1.html b/layout/generic/crashtests/387282-1.html
new file mode 100644
index 0000000000..a82983d091
--- /dev/null
+++ b/layout/generic/crashtests/387282-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<div id="b" style="float: left; height: 18000000px; overflow: scroll;"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/388049.html b/layout/generic/crashtests/388049.html
new file mode 100644
index 0000000000..c324dc225b
--- /dev/null
+++ b/layout/generic/crashtests/388049.html
@@ -0,0 +1,43 @@
+<html>
+ <head>
+ <style type="text/css">
+ p:first-line { color: teal; }
+ p:first-letter { color: aqua; }
+ .one:first-line { font-size: 300%; }
+ .one:first-letter { font-size: 300%; }
+ p.two:first-letter { font-size: 200%; }
+ p.two:first-line { font-variant: small-caps; }
+ /* three uses the default styles */
+ </style>
+ </head>
+ <body>
+ <p class="one">
+ The <strong>first letter of this paragraph, and only that
+ one, should be 600% bigger than the normal text
+ (300% bigger than the rest of first line of this paragraph) and
+ aqua, while the entire first line
+ should be 300% bigger than normal and teal. If this precise combination does not occur,
+ then the user agent has failed this test. Remember that in order to
+ ensure a complete test, the paragraph must be displayed on more
+ than one line. (TEST1)
+ </p>
+ <p class="two">
+ The first two characters in this paragraph
+ (a double-quote mark and a capital 'T') should be 200%
+ bigger than the rest of the paragraph, and
+ aqua. In addition, the entire first
+ line should be in a small-caps font and
+ teal. Remember that in order to ensure a complete test,
+ the paragraph must be displayed on more than one line. (TEST2)
+ </p>
+ <p>
+ The first letter of this paragraph, and only that
+ one, should be aqua, while the entire
+ <strong>first line should be teal. If
+ this precise combination does not occur, then the user agent has
+ failed this test. Remember that in order to ensure a complete test,
+ the paragraph must be displayed on more than one line. (TEST3)
+ </p>
+
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/388175-1.html b/layout/generic/crashtests/388175-1.html
new file mode 100644
index 0000000000..9e9a2879f0
--- /dev/null
+++ b/layout/generic/crashtests/388175-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("tr").focus();
+ document.getElementById("b").focus();
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(boom, 30);">
+
+<table border="1">
+ <tbody dir="rtl">
+ <tr id="tr" contenteditable="true"><td>a</td><td contenteditable="false">
+ <div id="b" contenteditable="true">b</div>
+ </td></tr>
+ </tbody>
+</table>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/388367-1.html b/layout/generic/crashtests/388367-1.html
new file mode 100644
index 0000000000..6c368fcc4b
--- /dev/null
+++ b/layout/generic/crashtests/388367-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<pre style="direction: rtl;">&#12;</pre>
+</body>
+</html>
diff --git a/layout/generic/crashtests/388709-1.html b/layout/generic/crashtests/388709-1.html
new file mode 100644
index 0000000000..29d01955ba
--- /dev/null
+++ b/layout/generic/crashtests/388709-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<style>
+#d:after { content: 'a'; }
+#d:first-letter { float: right; }
+</style>
+</head>
+
+<body>
+<div id="d"></div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/389635-1.html b/layout/generic/crashtests/389635-1.html
new file mode 100644
index 0000000000..dffd61a4a5
--- /dev/null
+++ b/layout/generic/crashtests/389635-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+
+<div style="position: fixed; overflow-x: scroll;">
+ <div style="padding: 0%;">
+ <div style="position: fixed;"></div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/390050-1.html b/layout/generic/crashtests/390050-1.html
new file mode 100644
index 0000000000..1a1f34e550
--- /dev/null
+++ b/layout/generic/crashtests/390050-1.html
@@ -0,0 +1,48 @@
+<html>
+<head>
+<style type="text/css">
+/*<![CDATA[*/
+
+body {
+ font-size: 62.5%; /* Resets 1em to 10px */
+}
+
+#content { font-size: 1.2em; }
+
+.entry p { font-size: 1.05em; }
+
+.entry {
+ column-width: 25em;
+ column-gap: 3em;
+}
+
+.entry img {
+ float: left;
+ margin: 3px 10px;
+}
+
+.alt {
+ padding: 10px;
+}
+
+#content {
+ margin-left: 20px;
+ margin-right: 260px;
+ padding: 0 17px 20px 17px;
+}
+
+
+/*]]>*/
+</style>
+</head>
+<body>
+<div id="content" class="entry">
+
+<img width="222" height="164">
+
+ <p class="alt">
+, .You can follow any responses the
+ </p>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/390050-2.html b/layout/generic/crashtests/390050-2.html
new file mode 100644
index 0000000000..f22b55e343
--- /dev/null
+++ b/layout/generic/crashtests/390050-2.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<style type="text/css">
+.entry {
+ column-width: 25em;
+ column-gap: 3em;
+}
+</style>
+
+</script>
+
+</head>
+<body><div class="entry">
+<p>Edit: Wow, quick response there! Thanks for the pointers guys, looks like the dbginfo
+packages are there, just take a bit to get to. I had installed the dbg packages (or at
+least some of them, the ones that I saw with apt-cache search dbg that were relevant), but
+there wasn&#8217;t coverage for some packages &#8212; in particular the x server. I&#8217;m
+going to try Travis&#8217;s suggestion, thanks!</p>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/390050-3.html b/layout/generic/crashtests/390050-3.html
new file mode 100644
index 0000000000..99f6455da3
--- /dev/null
+++ b/layout/generic/crashtests/390050-3.html
@@ -0,0 +1,4 @@
+<div class="entry" style="margin-right: 260px; padding: 0 17px; column-width: 25em; column-gap: 3em">
+<img width="222" style="float: left; margin: 3px 10px;">
+<p style="padding: 10px; font-size: 1.05em;">ABCDEF GHI JKLMNOPQR</p>
+</div>
diff --git a/layout/generic/crashtests/390052.html b/layout/generic/crashtests/390052.html
new file mode 100644
index 0000000000..bf5b4e20ad
--- /dev/null
+++ b/layout/generic/crashtests/390052.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Testcase for bug 390052</title>
+</head>
+<body>
+
+'¡''i''ǃ''!''α''a'' '' ''­''''Û''''Ü''''á †''''á Ž''''​''''‌''''â€''''
''''
''''â ''''â¡''''â¢''''â£''''âª''''â«''''â¬''''â­''''â®''''â¯''''''''''''''''ï¿»''''''''ð…³''''ð…´''''ð…µ''''ð…¶''''ð…·''''ð…¸''''ð…¹''''ð…º''''Û¬''ÛŸ''̓''Ì“''Ù''Ì“''Öœ''Ì''Í''Ì''݇''Ì''॔''Ì''Í€''Ì€''॓''Ì€''ÌŒ''̆''Ì‘''Ì‚''Ö¯''ÌŠ''ஂ''ÌŠ''à¹''ÌŠ''à»''ÌŠ''ံ''ÌŠ''ំ''ÌŠ''៓''ÌŠ''ã‚š''ÌŠ''゚''ÌŠ''ͦ''ÌŠ''Í‚''̃''ׄ''̇''Ö¹''̇''ׂ''̇''×''̇''Ý''̇''ं''̇''ਂ''̇''ં''̇''à¯''̇''Ì…''Ì„''〬''̉''̱''Ì ''॒''Ì ''̧''Ì¡''̦''Ì¡''̨''Ì¢''़''Ì£''়''Ì£''਼''Ì£''઼''Ì£''଼''Ì£''͇''̳''̶''̵''ﱞ''ï¹²Ù‘''ﱟ''ï¹´Ù‘''ï³²''ï¹·Ù‘''ï± ''ﹶّ''ï³³''ï¹¹Ù‘''ﱡ''ﹸّ''ï³´''ï¹»Ù‘''ï±¢''ﹺّ''ï±£''ï¹¼Ù°''Ù´''Ù”''Ý‚''ܼ''౦''o''೦''o''゙''ã‚™'' '' '' '' ''â€'' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ''âŸ'' '' '' '' '' '' '' ''`''`''ï½€''`''á¿€''Ëœ''ï¼¾''^''︿''^''_''_''ï¹''_''﹎''_''ï¹''_''⌇''︴''ï¼''-''â€''-''‑''-''‒''-''–''-''﹘''-''∼''â“''ï½¥''・''•''・'','',''‚'',''Ù¬''ØŒ''、''ã€'';'';''ï¼›'';'':'':''Ö‰'':''︰'':''׃'':''â©´''::=''.''.''․''.''Ü‚''.''‥''..''…''...''。''。''·''·''‧''·''∙''·''â‹…''·''á§''·''ᔯ''·4''áŒ''·á''áŽ''·áƒ''á''·á„''á’''·á…''á”''·á†''á—''·áŠ''á™''·á‹''á·''·á³''á‘€''·á³''á‘‚''·á´''á‘„''·á¸''ᑆ''·á¹''á‘—''·ᑌ''á‘™''·ᑎ''á‘›''·á‘''á‘”''·á‘''á‘''·á‘''á‘Ÿ''·ᑑ''á‘¡''·ᑕ''á‘£''·ᑖ''á‘´''·ᑫ''ᑸ''·ᑮ''ᑼ''·ᑰ''ᑾ''·ᑲ''á’€''·ᑳ''á’’''·ᒉ''á’”''·ᒋ''á’–''·ᒌ''á’š''·ᒎ''á’œ''·á’''á’ž''·ᒑ''á’¬''·ᒣ''á’®''·ᒥ''á’°''·ᒦ''á’²''·ᒧ''á’´''·ᒨ''á’¶''·L''á’¸''·ᒫ''ᓉ''·ᓀ''á“‹''·ᓇ''á“''·ᓈ''á“œ''·ᓓ''á“ž''·ᓕ''á“ ''·ᓖ''á“¢''·ᓗ''ᓤ''·ᓘ''ᓦ''·ᓚ''ᓨ''·ᓛ''ᓶ''·ᓭ''ᓸ''·ᓯ''ᓺ''·ᓰ''ᓼ''·ᓱ''ᓾ''·ᓲ''ᔀ''·ᓴ''ᔂ''·ᓵ''á”—''·á”''á”™''·ᔑ''á”›''·ᔒ''á”''·ᔓ''ᔟ''·ᔔ''ᔡ''·ᔕ''ᔣ''·ᔖ''á”±''·ᔨ''ᔳ''·ᔩ''ᔵ''·ᔪ''á”·''·ᔫ''ᔹ''·ᔭ''á”»''·ᔮ''á•Ž''·ᕌ''á•›''·ᕚ''ᕨ''·ᕧ''(''(''â‘´''(1)''â’§''(l)''⑽''(10)''⑾''(11)''â‘¿''(12)''â’€''(13)''â’''(14)''â’‚''(15)''â’ƒ''(16)''â’„''(17)''â’…''(18)''â’†''(19)''⑵''(2)''â’‡''(20)''⑶''(3)''â‘·''(4)''⑸''(5)''⑹''(6)''⑺''(7)''â‘»''(8)''⑼''(9)''â’œ''(a)''â’''(b)''â’ž''(c)''â’Ÿ''(d)''â’ ''(e)''â’¡''(f)''â’¢''(g)''â’£''(h)''â’¤''(i)''â’¥''(j)''â’¦''(k)''â’¨''(m)''â’©''(n)''â’ª''(o)''â’«''(p)''â’¬''(q)''â’­''(r)''â’®''(s)''â’¯''(t)''â’°''(u)''â’±''(v)''â’²''(w)''â’³''(x)''â’´''(y)''â’µ''(z)''㈀''(á„€)''㈎''(ê°€)''ãˆ''(á„‚)''ãˆ''(나)''㈂''(ᄃ)''ãˆ''(다)''㈃''(á„…)''㈑''(ë¼)''㈄''(ᄆ)''㈒''(마)''㈅''(ᄇ)''㈓''(ë°”)''㈆''(ᄉ)''㈔''(사)''㈇''(á„‹)''㈕''(ì•„)''ãˆ''(오전)''㈞''(오후)''㈈''(á„Œ)''㈖''(ìž)''㈜''(주)''㈉''(á„Ž)''㈗''(ì°¨)''㈊''(á„)''㈘''(ì¹´)''㈋''(á„)''㈙''(타)''㈌''(á„‘)''㈚''(파)''ãˆ''(á„’)''㈛''(하)''㈠''(一)''㈦''(七)''㈢''(三)''㈨''(ä¹)''㈡''(二)''㈤''(五)''㈹''(代)''㈽''(ä¼)''ã‰''(休)''㈧''(å…«)''㈥''(å…­)''㈸''(労)''㈩''(å)''㈿''(å”)''㈴''(å)''㈺''(呼)''㈣''(å››)''㈯''(土)''㈻''(å­¦)''㈰''(æ—¥)''㈪''(月)''㈲''(有)''㈭''(木)''㈱''(æ ª)''㈬''(æ°´)''㈫''(ç«)''㈵''(特)''㈼''(監)''㈳''(社)''㈷''(ç¥)''㉀''(祭)''㉂''(自)''㉃''(至)''㈶''(財)''㈾''(資)''㈮''(金)'')'')''ï¼»''[''〔''[''ï¼½'']''〕'']''ï½›''{''ï½''}''⦅''⦅''ï½ ''⦆''ï½¢''「''ï½£''ã€''ï¼ ''&#64;''*''*''ï¼''/''â„''/''∕''/''ï¼¼''\\''&''&amp;''#''#''ï¼…''%''‶''‵‵''‷''‵‵‵''༌''་''´''ʹ''΄''ʹ''´''ʹ''\'''ʹ'''''ʹ''′''ʹ''׳''ʹ''Í´''ʹ''ËŠ''ʹ''&quot;''ʹʹ''"''ʹʹ''″''ʹʹ''〃''ʹʹ''×´''ʹʹ''ʺ''ʹʹ''‴''ʹʹʹ''â—''ʹʹʹʹ''¯''ˉ''ï¿£''ˉ''‾''ˉ''﹉''ˉ''﹊''ˉ''﹋''ˉ''﹌''ˉ''Ëš''°''௵''௳''ï¿©''â†''ï¿«''→''↑''↑''↓''↓''↵''↲''⨡''↾''ð›Â›''∂''ðœÂ•''∂''ðÂ''∂''ðžÂ‰''∂''ðŸÂƒ''∂''ð›Â''∇''ð›»''∇''ðœµ''∇''ð¯''∇''ðž©''∇''+''+''﬩''+''‹''&lt;''<''&lt;''ï¼''=''⩵''==''⩶''===''›''&gt;''>''&gt;''ï¿¢''¬''¦''¦''〜''~''~''~''﹨''∖''â‹€''∧''â‹''∨''â‹‚''∩''⋃''∪''∯''∮∮''∰''∮∮∮''≣''≡''â™''⊕''☉''⊙''⟂''⊥''â–·''⊲''â¨''⋈''⨽''⌙''☸''⎈''⎮''⎥''│''│''â–''â–Œ''ï¿­''â– ''â˜''â–¡''ï¿®''â—‹''⦾''â—Ž''〛''⟧''〈''⟨''〈''⟨''〉''⟩''〉''⟩''⧙''⦚''〶''〒''ï½°''ー''ï¿ ''¢''$''$''ï¿¡''£''ï¿¥''Y̵''₩''W̵''ï¼''0''ðŸÂŽ''0''ðŸÂ˜''0''ðŸ¢''0''ðŸ¬''0''ðŸ¶''0''০''0''à­¦''0''௦''0''á ''0''〇''0''ðÂŽ''0''ð‘‚''0''ð‘¶''0''ð’ª''0''ð“ž''0''ð”Â’''0''ð•Â†''0''ð•º''0''ð–®''0''ð—¢''0''ð˜Â–''0''ð™ÂŠ''0''ð™¾''0''ðš¶''0''ð›°''0''ðœª''0''ð¤''0''ðžÂž''0''âµ”''0''à´ ''0''⊖''0̵''ðš¯''0̵''ðš¹''0̵''ð›©''0̵''ð›³''0̵''ðœ£''0̵''ðœ­''0̵''ðÂ''0̵''ð§''0̵''ðžÂ—''0̵''ðž¡''0̵''â´±''0̵''Ꮎ''0̵''Û°''Ù ''á­œ''á­''ã˜''0点''1''1''ðŸÂ''1''ðŸÂ™''1''ðŸ£''1''ðŸ­''1''ðŸ·''1''â„''1''â„‘''1''ðˆ''1''ð¼''1''ð‘°''1''ð“˜''1''ð•Â€''1''ð•´''1''ð–¨''1''ð—Âœ''1''ð˜Â''1''ð™Â„''1''ð™¸''1''l''l''l''l''â…¼''1''â„“''l''ð¥''l''ð‘™''l''ð’Â''l''ð“Â''l''ð“µ''l''ð”©''l''ð•Â''l''ð–‘''l''ð—Â…''l''ð—¹''l''ð˜­''l''ð™¡''l''ðšÂ•''l''ðš°''l''ð›ª''l''ðœ¤''l''ðž''l''ðžÂ˜''l''â‘ ''➀''É­''lÌ¢''É«''lÌ´''Æš''l̵''Å‚''lÌ·''Û±''Ù¡''â’ˆ''1.''Å€''l·''á’·''1·''â‘©''➉''â’‘''10.''ã©''10æ—¥''㋉''10月''ã¢''10点''â’’''11.''ãª''11æ—¥''ã‹Š''11月''ã£''11点''â’“''12.''ã«''12æ—¥''ã‹‹''12月''ã¤''12点''â’”''13.''ã¬''13æ—¥''ã¥''13点''â’•''14.''ã­''14æ—¥''ã¦''14点''â’–''15.''ã®''15æ—¥''ã§''15点''â’—''16.''ã¯''16æ—¥''ã¨''16点''â’˜''17.''ã°''17æ—¥''ã©''17点''â’™''18.''ã±''18æ—¥''ãª''18点''â’š''19.''ã²''19æ—¥''ã«''19点''lj''lj''ã ''1æ—¥''ã‹€''1月''ã™''1点''ï¼’''2''ðŸÂ''2''ðŸÂš''2''ðŸ¤''2''ðŸ®''2''ðŸ¸''2''á’¿''2''â‘¡''âž''Û²''Ù¢''â’‰''2.''â’›''20.''ã³''20æ—¥''ã¬''20点''ã´''21æ—¥''ã­''21点''ãµ''22æ—¥''ã®''22点''ã¶''23æ—¥''ã¯''23点''ã·''24æ—¥''ã°''24点''ã¸''25æ—¥''ã¹''26æ—¥''ãº''27æ—¥''ã»''28æ—¥''ã¼''29æ—¥''ã¡''2æ—¥''ã‹''2月''ãš''2点''3''3''ðŸÂ‘''3''ðŸÂ›''3''ðŸ¥''3''ðŸ¯''3''ðŸ¹''3''â‘¢''âž‚''Û³''Ù£''â’Š''3.''ã½''30æ—¥''ã¾''31æ—¥''ã¢''3æ—¥''ã‹‚''3月''ã›''3点''ï¼”''4''ðŸÂ’''4''ðŸÂœ''4''ðŸ¦''4''ðŸ°''4''ðŸº''4''áŽ''4''â‘£''➃''â’‹''4.''á”°''4·''ã£''4æ—¥''㋃''4月''ãœ''4点''5''5''ðŸÂ“''5''ðŸÂ''5''ðŸ§''5''ðŸ±''5''ðŸ»''5''⑤''âž„''â’Œ''5.''ã¤''5æ—¥''ã‹„''5月''ã''5点''ï¼–''6''ðŸÂ”''6''ðŸÂž''6''ðŸ¨''6''ðŸ²''6''ðŸ¼''6''б''6''â‘¥''âž…''â’''6.''ã¥''6æ—¥''ã‹…''6月''ãž''6点''ï¼—''7''ðŸÂ•''7''ðŸÂŸ''7''ðŸ©''7''ðŸ³''7''ðŸ½''7''⑦''➆''Û·''Ù§''â’Ž''7.''ã¦''7æ—¥''㋆''7月''ãŸ''7点''ଃ''8''৪''8''੪''8''8''8''ðŸÂ–''8''ðŸ ''8''ðŸª''8''ðŸ´''8''ðŸ¾''8''È£''8''⑧''➇''Û¸''Ù¨''â’''8.''ã§''8æ—¥''㋇''8月''ã ''8点''੧''9''à­¨''9''৭''9''ï¼™''9''ðŸÂ—''9''ðŸ¡''9''ðŸ«''9''ðŸµ''9''ðŸ¿''9''⑨''➈''Û¹''Ù©''â’''9.''ã¨''9æ—¥''㋈''9月''ã¡''9点''ï½''a''ðš''a''ð‘ÂŽ''a''ð’‚''a''ð’¶''a''ð“ª''a''ð”ž''a''ð•Â’''a''ð–†''a''ð–º''a''ð—®''a''ð˜¢''a''ð™Â–''a''ðšÂŠ''a''â„€''a/c''â„''a/s''æ''ae''b''b''ð›''b''ð‘Â''b''ð’ƒ''b''ð’·''b''ð“«''b''ð”Ÿ''b''ð•Â“''b''ð–‡''b''ð–»''b''ð—¯''b''ð˜£''b''ð™Â—''b''ðšÂ‹''b''É“''bÌ”''ƃ''bÌ„''Æ€''b̵''c''c''â…½''c''ðÂœ''c''ð‘Â''c''ð’„''c''ð’¸''c''ð“¬''c''ð” ''c''ð•Â”''c''ð–ˆ''c''ð–¼''c''ð—°''c''ð˜¤''c''ð™Â˜''c''ðšÂŒ''c''ð›Â“''c''ðœÂ''c''ð‡''c''ðžÂ''c''ðž»''c''â„…''c/o''℆''c/u''d''d''â…¾''d''â…†''d''ðÂ''d''ð‘‘''d''ð’Â…''d''ð’¹''d''ð“­''d''ð”¡''d''ð•Â•''d''ð–‰''d''ð–½''d''ð—±''d''ð˜¥''d''ð™Â™''d''ðšÂ''d''É—''dÌ”''ÆŒ''dÌ„''É–''dÌ¢''Ä‘''d̵''dz''dz''dž''dž''ï½…''e''ℯ''e''â…‡''e''ðž''e''ð‘Â’''e''ð’†''e''ð“®''e''ð”¢''e''ð•Â–''e''ð–Š''e''ð–¾''e''ð—²''e''ð˜¦''e''ð™Âš''e''ðšÂŽ''e''â´¹''E''É™''Ç''Éš''ÇËž''â‹´''É›''ð›Â†''É›''ð›Âœ''É›''ðœÂ€''É›''ðœÂ–''É›''ðœº''É›''ðÂ''É›''ð´''É›''ðžÂŠ''É›''ðž®''É›''ðŸÂ„''É›''f''f''ðŸ''f''ð‘“''f''ð’‡''f''ð’»''f''ð“¯''f''ð”£''f''ð•Â—''f''ð–‹''f''ð–¿''f''ð—³''f''ð˜§''f''ð™Â›''f''ðšÂ''f''Æ’''fÌ¡''g''g''â„Š''g''ð ''g''ð‘”''g''ð’ˆ''g''ð“°''g''ð”¤''g''ð•Â˜''g''ð–ÂŒ''g''ð—€''g''ð—´''g''ð˜¨''g''ð™Âœ''g''ðšÂ''g''É¡''g''É ''gÌ”''Ç¥''g̵''h''h''â„Ž''h''ð¡''h''ð’‰''h''ð’½''h''ð“±''h''ð”¥''h''ð•Â™''h''ð–Â''h''ð—Â''h''ð—µ''h''ð˜©''h''ð™Â''h''ðšÂ‘''h''ɦ''hÌ”''ħ''h̵''â„''h̵''῾''Ê»''‘''Ê»''‛''Ê»''ʽ''Ê»''â³''i''i''i''â…°''i''ℹ''i''â…ˆ''i''ð¢''i''ð‘–''i''ð’Š''i''ð’¾''i''ð“²''i''ð”¦''i''ð•Âš''i''ð–ÂŽ''i''ð—‚''i''ð—¶''i''ð˜ª''i''ð™Âž''i''ðšÂ’''i''ı''i''ðš¤''i''ɪ''i''É©''i''ð›ÂŠ''i''ðœÂ„''i''ðœ¾''i''ð¸''i''ðž²''i''ɨ''i̵''â…±''ii''â…²''iii''ij''ij''â…³''iv''â…¸''ix''j''j''â…‰''j''ð£''j''ð‘—''j''ð’‹''j''ð’¿''j''ð“³''j''ð”§''j''ð•Â›''j''ð–Â''j''ð—ƒ''j''ð—·''j''ð˜«''j''ð™ÂŸ''j''ðšÂ“''j''ϳ''j''ðš¥''È·''k''k''ð¤''k''ð‘˜''k''ð’ÂŒ''k''ð“€''k''ð“´''k''ð”¨''k''ð•Âœ''k''ð–Â''k''ð—„''k''ð—¸''k''ð˜¬''k''ð™ ''k''ðšÂ”''k''Æ™''kÌ”''ï½''m''â…¿''m''ð¦''m''ð‘š''m''ð’ÂŽ''m''ð“‚''m''ð“¶''m''ð”ª''m''ð•Âž''m''ð–Â’''m''ð—†''m''ð—º''m''ð˜®''m''ð™¢''m''ðšÂ–''m''ɱ''mÌ¡''n''n''ð§''n''ð‘›''n''ð’Â''n''ð“ƒ''n''ð“·''n''ð”«''n''ð•ÂŸ''n''ð–“''n''ð—‡''n''ð—»''n''ð˜¯''n''ð™£''n''ðšÂ—''n''ðÂ''N''ð‘Â''N''ð‘µ''N''ð’©''N''ð“Â''N''ð”‘''N''ð•¹''N''ð–­''N''ð—¡''N''ð˜Â•''N''ð™Â‰''N''ð™½''N''ðš´''N''ð›®''N''ðœ¨''N''ð¢''N''ðžÂœ''N''ɲ''ņ''ɳ''nÌ¢''Æž''nÌ©''ð›Âˆ''nÌ©''ðœÂ‚''nÌ©''ðœ¼''nÌ©''ð¶''nÌ©''ðž°''nÌ©''ÇŒ''nj''ï½''o''â„´''o''ð¨''o''ð‘Âœ''o''ð’Â''o''ð“¸''o''ð”¬''o''ð• ''o''ð–”''o''ð—ˆ''o''ð—¼''o''ð˜°''o''ð™¤''o''ðšÂ˜''o''á´''o''ð›Â''o''ðœÂŠ''o''ð„''o''ð¾''o''ðž¸''o''ɵ''o̵''Ç¿''o̵Ì''ø''oÌ·''Å“''oe''Æ¡''oʼ''â´''p''ï½''p''ð©''p''ð‘Â''p''ð’‘''p''ð“Â…''p''ð“¹''p''ð”­''p''ð•¡''p''ð–•''p''ð—‰''p''ð—½''p''ð˜±''p''ð™¥''p''ðšÂ™''p''ð›Â’''p''ð› ''p''ðœÂŒ''p''ðœÂš''p''ð†''p''ð”''p''ðžÂ€''p''ðžÂŽ''p''ðžº''p''ðŸÂˆ''p''Æ¥''pÌ”''q''q''ðª''q''ð‘ž''q''ð’Â’''q''ð“†''q''ð“º''q''ð”®''q''ð•¢''q''ð––''q''ð—Š''q''ð—¾''q''ð˜²''q''ð™¦''q''ðšÂš''q''ðÂ''Q''ð‘„''Q''ð‘¸''Q''ð’¬''Q''ð“ ''Q''ð””''Q''ð•¼''Q''ð–°''Q''ð—¤''Q''ð˜Â˜''Q''ð™ÂŒ''Q''ðšÂ€''Q''Ê ''qÌ”''ð›Â‹''ĸ''ð›Âž''ĸ''ðœÂ…''ĸ''ðœÂ˜''ĸ''ðœ¿''ĸ''ðÂ’''ĸ''ð¹''ĸ''ðžÂŒ''ĸ''ðž³''ĸ''ðŸÂ†''ĸ''ï½’''r''ð«''r''ð‘Ÿ''r''ð’“''r''ð“‡''r''ð“»''r''ð”¯''r''ð•£''r''ð–—''r''ð—‹''r''ð—¿''r''ð˜³''r''ð™§''r''ðšÂ›''r''ɽ''rÌ¢''ɼ''rÌ©''s''s''ð¬''s''ð‘ ''s''ð’”''s''ð“ˆ''s''ð“¼''s''ð”°''s''ð•¤''s''ð–˜''s''ð—ÂŒ''s''ð˜Â€''s''ð˜´''s''ð™¨''s''ðšÂœ''s''ƽ''s''Ê‚''sÌ¢''∫''ʃ''∬''ʃʃ''∭''ʃʃʃ''⨌''ʃʃʃʃ''ï½”''t''ð­''t''ð‘¡''t''ð’•''t''ð“‰''t''ð“½''t''ð”±''t''ð•¥''t''ð–™''t''ð—Â''t''ð˜Â''t''ð˜µ''t''ð™©''t''ðšÂ''t''ð‘‡''T''ð‘»''T''ð’¯''T''ð“£''T''ð”—''T''ð•Â‹''T''ð•¿''T''ð–³''T''ð—§''T''ð˜Â›''T''ð™Â''T''ðšÂƒ''T''ðš»''T''ð›µ''T''ðœ¯''T''ð©''T''ðž£''T''Æ­''tÌ”''È›''Å£''Æ«''Å£''ŧ''t̵''u''u''ð®''u''ð‘¢''u''ð’–''u''ð“Š''u''ð“¾''u''ð”²''u''ð•¦''u''ð–š''u''ð—ÂŽ''u''ð˜Â‚''u''ð˜¶''u''ð™ª''u''ðšÂž''u''ÊŠ''u''Ê‹''u''ð›Â–''u''ðœÂ''u''ðŠ''u''ðžÂ„''u''ðž¾''u''ð‘ˆ''U''ð‘¼''U''ð’°''U''ð“¤''U''ð”˜''U''ð•ÂŒ''U''ð–€''U''ð–´''U''ð—¨''U''ð˜Âœ''U''ð™Â''U''ðšÂ„''U''ï½–''v''â…´''v''ð¯''v''ð‘£''v''ð’—''v''ð“‹''v''ð“¿''v''ð”³''v''ð•§''v''ð–›''v''ð—Â''v''ð˜Âƒ''v''ð˜·''v''ð™«''v''ðšÂŸ''v''ð›ÂŽ''v''ðœÂˆ''v''ð‚''v''ð¼''v''ðž¶''v''â…µ''vi''â…¶''vii''â…·''viii''ɯ''w''ï½—''w''ð°''w''ð‘¤''w''ð’˜''w''ð“ÂŒ''w''ð”€''w''ð”´''w''ð•¨''w''ð–Âœ''w''ð—Â''w''ð˜Â„''w''ð˜¸''w''ð™¬''w''ðš ''w''ð‘Š''W''ð‘¾''W''ð’²''W''ð“¦''W''ð”š''W''ð•ÂŽ''W''ð–‚''W''ð–¶''W''ð—ª''W''ð˜Âž''W''ð™Â’''W''ðšÂ†''W''×''x''x''x''â…¹''x''ð±''x''ð‘¥''x''ð’™''x''ð“Â''x''ð”Â''x''ð”µ''x''ð•©''x''ð–Â''x''ð—‘''x''ð˜Â…''x''ð˜¹''x''ð™­''x''ðš¡''x''á™­''X''ð‘‹''X''ð‘¿''X''ð’³''X''ð“§''X''ð”›''X''ð•Â''X''ð–ƒ''X''ð–·''X''ð—«''X''ð˜ÂŸ''X''ð™Â“''X''ðšÂ‡''X''ðš¾''X''ð›¸''X''ðœ²''X''ð¬''X''ðž¦''X''â…º''xi''â…»''xii''ï½™''y''ð²''y''ð‘¦''y''ð’š''y''ð“ÂŽ''y''ð”‚''y''ð”¶''y''ð•ª''y''ð–ž''y''ð—Â’''y''ð˜Â†''y''ð˜º''y''ð™®''y''ðš¢''y''Æ´''yÌ”''z''z''ð³''z''ð‘§''z''ð’›''z''ð“Â''z''ð”ƒ''z''ð”·''z''ð•«''z''ð–Ÿ''z''ð—“''z''ð˜Â‡''z''ð˜»''z''ð™¯''z''ðš£''z''È¥''zÌ¡''Ê''zÌ¢''ƶ''z̵''È''Ê’''?''Ê”''?''Ê”''â‡''ʔʔ''âˆ''ʔǃ''á¾½''ʼ''᾿''ʼ''’''ʼ''ʾ''ʼ''!''ǃ''ï¼''ǃ''â‰''ǃʔ''‼''ǃǃ''âº''α''ð›Â‚''α''ð›¼''α''ðœ¶''α''ð°''α''ðžª''α''ð›Âƒ''β''ð›½''β''ðœ·''β''ð±''β''ðž«''β''ℽ''γ''ð›Â„''γ''ð›¾''γ''ðœ¸''γ''ð²''γ''ðž¬''γ''ð›Â…''δ''ð›¿''δ''ðœ¹''δ''ð³''δ''ðž­''δ''ðŸÂ‹''Ï''ð›Â‡''ζ''ðœÂ''ζ''ðœ»''ζ''ðµ''ζ''ðž¯''ζ''â¬''θ''ð›Â‰''θ''ð›Â''θ''ðœÂƒ''θ''ðœÂ—''θ''ðœ½''θ''ð‘''θ''ð·''θ''ðžÂ‹''θ''ðž±''θ''ðŸÂ…''θ''ð›ÂŒ''λ''ðœÂ†''λ''ð€''λ''ðº''λ''ðž´''λ''ð›¬''Λ''ðœ¦''Λ''ð ''Λ''ðžÂš''Λ''ð›Â''μ''ðœÂ‡''μ''ðÂ''μ''ð»''μ''ðžµ''μ''ð›Â''ξ''ðœÂ‰''ξ''ðƒ''ξ''ð½''ξ''ðž·''ξ''ð›¯''Ξ''ðœ©''Ξ''ð£''Ξ''ðžÂ''Ξ''ℼ''Ï€''ð›Â‘''Ï€''ð›¡''Ï€''ðœÂ‹''Ï€''ðœÂ›''Ï€''ðÂ…''Ï€''ð•''Ï€''ð¿''Ï€''ðžÂ''Ï€''ðž¹''Ï€''ðŸÂ‰''Ï€''á´¨''Ï€''âˆ''Π''ðš·''Π''ð›±''Π''ðœ«''Π''ð¥''Π''ðžÂŸ''Π''ð›Â”''σ''ðœÂŽ''σ''ðˆ''σ''ðžÂ‚''σ''ðž¼''σ''ð›Â•''Ï„''ðœÂ''Ï„''ð‰''Ï„''ðžÂƒ''Ï„''ðž½''Ï„''ð˜''Y''ð‘ÂŒ''Y''ð’€''Y''ð’´''Y''ð“¨''Y''ð”Âœ''Y''ð•Â''Y''ð–„''Y''ð–¸''Y''ð—¬''Y''ð˜ ''Y''ð™Â”''Y''ðšÂˆ''Y''ðš¼''Y''ð›¶''Y''ðœ°''Y''ðª''Y''ðž¤''Y''ð›Â—''φ''ð›ÂŸ''φ''ðœÂ‘''φ''ðœÂ™''φ''ð‹''φ''ð“''φ''ðžÂ…''φ''ðžÂ''φ''ðž¿''φ''ðŸÂ‡''φ''ð›·''Φ''ðœ±''Φ''ð«''Φ''ðž¥''Φ''ð›Â˜''χ''ðœÂ’''χ''ðÂŒ''χ''ðžÂ†''χ''ðŸÂ€''χ''ð›Â™''ψ''ðœÂ“''ψ''ðÂ''ψ''ðžÂ‡''ψ''ðŸÂ''ψ''ð›¹''Ψ''ðœ³''Ψ''ð­''Ψ''ðž§''Ψ''âµ''ω''ð›Âš''ω''ðœÂ”''ω''ðÂŽ''ω''ðžÂˆ''ω''ðŸÂ‚''ω''Ó•''ae''Ò“''r̵''Ò‘''rá‘Š''Ò—''ж̩''Ò™''з̡''Ó''i''Ò‹''й̡''Ò›''ĸ̩''ÒŸ''ĸ̵''á´«''л''Ó†''л̡''ÓŽ''м̡''ÓŠ''н̡''Óˆ''н̡''Ò£''н̩''Ó©''o̵''ѳ''o̵''Ò«''cÌ¡''Ò­''Ñ‚Ì©''Ò¯''y''Ò±''y̵''Ñ›''h̵''ѽ''Ñ¡Òƒ''ÓŒ''Ò·''Ò¿''ҽ̢''Ò''Ь̵''Õ¦''q''Õ¼''n''ℵ''×''ﬡ''×''אָ''אַ''אּ''אַ''ï­''×ל''ℶ''ב''â„·''×’''ℸ''ד''ﬢ''ד''ﬣ''×”''ﬤ''×›''ﬥ''ל''ﬦ''×''ﬠ''×¢''ﬧ''ר''ﬨ''ת''ﺀ''Ø¡''ﺂ''Ø¢''ïº''Ø¢''ﺄ''Ø£''ﺃ''Ø£''Ùµ''أ''ï­‘''Ù±''ï­''Ù±''ﺆ''ؤ''ﺅ''ؤ''Ù¶''ÙˆÙ”''ﺈ''Ø¥''ﺇ''Ø¥''ﺋ''ئ''ﺌ''ئ''ﺊ''ئ''ﺉ''ئ''ﯫ''ئا''ﯪ''ئا''ﯸ''ئٻ''ﯷ''ئٻ''ﯶ''ئٻ''ï²—''ئج''ï°€''ئج''ﲘ''ئح''ï°''ئح''ï²™''ئخ''ﱤ''ئر''ï±¥''ئز''ﲚ''ئم''ﳟ''ئم''ﱦ''ئم''ï°‚''ئم''ﱧ''ئن''ï²›''ئه''ï³ ''ئه''ﯭ''ئه''ﯬ''ئه''ﯯ''ئو''ﯮ''ئو''ﯳ''ئۆ''ﯲ''ئۆ''ﯱ''ئۇ''ﯰ''ئۇ''ﯵ''ئۈ''ﯴ''ئۈ''ﯻ''ئى''ﯺ''ئى''ﱨ''ئى''ﯹ''ئى''ï°ƒ''ئى''ﱩ''ئى''ï°„''ئى''ﺎ''ا''ïº''ا''ï´¼''اً''ï´½''اً''ï·³''اكبر''ï·²''الله''ﺑ''ب''ﺒ''ب''ïº''ب''ïº''ب''ﲜ''بج''ï°…''بج''ï²''بح''ï°†''بح''ï·‚''بحى''ﲞ''بخ''ï°‡''بخ''ﶞ''بخى''ﱪ''بر''ﱫ''بز''ﲟ''بم''ﳡ''بم''ﱬ''بم''ï°ˆ''بم''ï±­''بن''ï² ''به''ï³¢''به''ï±®''بى''ï°‰''بى''ﱯ''بى''ï°Š''بى''ï­”''Ù»''ï­•''Ù»''ï­“''Ù»''ï­’''Ù»''Û''Ù»''ﯦ''Ù»''ﯧ''Ù»''ﯥ''Ù»''ﯤ''Ù»''ï­˜''Ù¾''ï­™''Ù¾''ï­—''Ù¾''ï­–''Ù¾''ï­œ''Ú€''ï­''Ú€''ï­›''Ú€''ï­š''Ú€''ﺔ''Ø©''ﺓ''Ø©''ﺗ''ت''ﺘ''ت''ﺖ''ت''ﺕ''ت''ﲡ''تج''ï°‹''تج''ïµ''تجم''ﶠ''تجى''ﶟ''تجى''ï²¢''تح''ï°Œ''تح''ïµ’''تحج''ﵑ''تحج''ﵓ''تحم''ï²£''تخ''ï°''تخ''ïµ”''تخم''ﶢ''تخى''ﶡ''تخى''ï±°''تر''ï±±''تز''ﲤ''تم''ï³£''تم''ï±²''تم''ï°Ž''تم''ﵕ''تمج''ïµ–''تمح''ïµ—''تمخ''ﶤ''تمى''ﶣ''تمى''ï±³''تن''ï²¥''ته''ﳤ''ته''ï±´''تى''ï°''تى''ï±µ''تى''ï°''تى''ﺛ''Ø«''ﺜ''Ø«''ﺚ''Ø«''ﺙ''Ø«''ï°‘''ثج''ﱶ''ثر''ï±·''ثز''ﲦ''ثم''ï³¥''ثم''ﱸ''ثم''ï°’''ثم''ï±¹''ثن''ﳦ''ثه''ﱺ''ثى''ï°“''ثى''ï±»''ثى''ï°”''ثى''ï­¨''Ù¹''ï­©''Ù¹''ï­§''Ù¹''ï­¦''Ù¹''Ú»''Ù¹''ﮢ''Ù¹''ﮣ''Ù¹''ﮡ''Ù¹''ï® ''Ù¹''ï­ ''Ùº''ï­¡''Ùº''ï­Ÿ''Ùº''ï­ž''Ùº''ï­¤''Ù¿''ï­¥''Ù¿''ï­£''Ù¿''ï­¢''Ù¿''ﺟ''ج''ﺠ''ج''ﺞ''ج''ïº''ج''ﲧ''جح''ï°•''جح''ﶦ''جحى''ﶾ''جحى''ï·»''جل جلاله''ﲨ''جم''ï°–''جم''ïµ™''جمح''ﵘ''جمح''ﶧ''جمى''ﶥ''جمى''ï´''جى''ï´''جى''ï´ž''جى''ï´‚''جى''ï­¸''Úƒ''ï­¹''Úƒ''ï­·''Úƒ''ï­¶''Úƒ''ï­´''Ú„''ï­µ''Ú„''ï­³''Ú„''ï­²''Ú„''ï­¼''Ú†''ï­½''Ú†''ï­»''Ú†''ï­º''Ú†''ﮀ''Ú‡''ï®''Ú‡''ï­¿''Ú‡''ï­¾''Ú‡''ﺣ''Ø­''ﺤ''Ø­''ﺢ''Ø­''ﺡ''Ø­''ﲩ''حج''ï°—''حج''ﶿ''حجى''ﲪ''حم''ï°˜''حم''ïµ›''حمى''ﵚ''حمى''ï´›''حى''ﳿ''حى''ï´œ''حى''ï´€''حى''ﺧ''Ø®''ﺨ''Ø®''ﺦ''Ø®''ﺥ''Ø®''ﲫ''خج''ï°™''خج''ï°š''خح''ﲬ''خم''ï°›''خم''ï´Ÿ''خى''ï´ƒ''خى''ï´ ''خى''ï´„''خى''ﺪ''د''ﺩ''د''ﺬ''Ø°''ﺫ''Ø°''ï±›''ذٰ''ﮉ''Úˆ''ﮈ''Úˆ''ï®…''ÚŒ''ﮄ''ÚŒ''ﮃ''Ú''ﮂ''Ú''ﮇ''ÚŽ''ﮆ''ÚŽ''ﺮ''ر''ﺭ''ر''ﱜ''رٰ''ï·¶''رسول''ï·¼''رىال''ﺰ''ز''ﺯ''ز''ï®''Ú‘''ﮌ''Ú‘''ﮋ''Ú˜''ﮊ''Ú˜''ﺳ''س''ﺴ''س''ﺲ''س''ﺱ''س''ï²­''سج''ï´´''سج''ï°œ''سج''ïµ''سجح''ﵞ''سجى''ï²®''سح''ï´µ''سح''ï°''سح''ﵜ''سحج''ﲯ''سخ''ï´¶''سخ''ï°ž''سخ''ﶨ''سخى''ï·†''سخى''ï´ª''سر''ï´Ž''سر''ï²°''سم''ﳧ''سم''ï°Ÿ''سم''ﵡ''سمج''ïµ ''سمح''ﵟ''سمح''ïµ£''سمم''ïµ¢''سمم''ï´±''سه''ﳨ''سه''ï´—''سى''ï³»''سى''ï´˜''سى''ï³¼''سى''ﺷ''Ø´''ﺸ''Ø´''ﺶ''Ø´''ﺵ''Ø´''ï´­''شج''ï´·''شج''ï´¥''شج''ï´‰''شج''ﵩ''شجى''ï´®''شح''ï´¸''شح''ï´¦''شح''ï´Š''شح''ﵨ''شحم''ﵧ''شحم''ﶪ''شحى''ï´¯''شخ''ï´¹''شخ''ï´§''شخ''ï´‹''شخ''ï´©''شر''ï´''شر''ï´°''شم''ﳩ''شم''ï´¨''شم''ï´Œ''شم''ﵫ''شمخ''ﵪ''شمخ''ïµ­''شمم''ﵬ''شمم''ï´²''شه''ﳪ''شه''ï´™''شى''ï³½''شى''ï´š''شى''ï³¾''شى''ﺻ''ص''ﺼ''ص''ﺺ''ص''ﺹ''ص''ï²±''صح''ï° ''صح''ïµ¥''صحح''ﵤ''صحح''ﶩ''صحى''ï²²''صخ''ï´«''صر''ï´''صر''ï·µ''صلعم''ï·¹''صلى''ï·º''صلى الله علىه وسلم''ï·°''صلے''ï²³''صم''ï°¡''صم''ï·…''صمم''ﵦ''صمم''ï´¡''صى''ï´…''صى''ï´¢''صى''ï´†''صى''ﺿ''ض''ﻀ''ض''ﺾ''ض''ﺽ''ض''ï²´''ضج''ï°¢''ضج''ï²µ''ضح''ï°£''ضح''ïµ®''ضحى''ﶫ''ضحى''ﲶ''ضخ''ï°¤''ضخ''ïµ°''ضخم''ﵯ''ضخم''ï´¬''ضر''ï´''ضر''ï²·''ضم''ï°¥''ضم''ï´£''ضى''ï´‡''ضى''ï´¤''ضى''ï´ˆ''ضى''ﻃ''Ø·''ﻄ''Ø·''ﻂ''Ø·''ï»''Ø·''ﲸ''طح''ï°¦''طح''ï´³''طم''ï´º''طم''ï°§''طم''ïµ²''طمح''ïµ±''طمح''ïµ³''طمم''ïµ´''طمى''ï´‘''طى''ï³µ''طى''ï´’''طى''ﳶ''طى''ﻇ''ظ''ﻈ''ظ''ﻆ''ظ''ï»…''ظ''ï²¹''ظم''ï´»''ظم''ï°¨''ظم''ﻋ''ع''ﻌ''ع''ﻊ''ع''ﻉ''ع''ﲺ''عج''ï°©''عج''ï·„''عجم''ïµµ''عجم''ï··''علىه''ï²»''عم''ï°ª''عم''ïµ·''عمم''ﵶ''عمم''ﵸ''عمى''ﶶ''عمى''ï´“''عى''ï³·''عى''ï´”''عى''ﳸ''عى''ï»''غ''ï»''غ''ﻎ''غ''ï»''غ''ï²¼''غج''ï°«''غج''ï²½''غم''ï°¬''غم''ïµ¹''غمم''ïµ»''غمى''ﵺ''غمى''ï´•''غى''ï³¹''غى''ï´–''غى''ﳺ''غى''ﻓ''Ù''ï»”''Ù''ï»’''Ù''ﻑ''Ù''ï²¾''Ùج''ï°­''Ùج''ﲿ''ÙØ­''ï°®''ÙØ­''ï³€''ÙØ®''ï°¯''ÙØ®''ïµ½''Ùخم''ïµ¼''Ùخم''ï³''ÙÙ…''ï°°''ÙÙ…''ï·''Ùمى''ï±¼''ÙÙ‰''ï°±''ÙÙ‰''ï±½''ÙÙ‰''ï°²''ÙÙ‰''ï­¬''Ú¤''ï­­''Ú¤''ï­«''Ú¤''ï­ª''Ú¤''ï­°''Ú¦''ï­±''Ú¦''ï­¯''Ú¦''ï­®''Ú¦''ï»—''Ù‚''ﻘ''Ù‚''ï»–''Ù‚''ﻕ''Ù‚''ﳂ''قح''ï°³''قح''ï·±''قلے''ﳃ''قم''ï°´''قم''ﶴ''قمح''ïµ¾''قمح''ﵿ''قمم''ﶲ''قمى''ï±¾''قى''ï°µ''قى''ﱿ''قى''ï°¶''قى''ï»›''Ùƒ''ﻜ''Ùƒ''ﻚ''Ùƒ''ï»™''Ùƒ''Ú©''Ùƒ''ï®''Ùƒ''ﮑ''Ùƒ''ï®''Ùƒ''ﮎ''Ùƒ''ï²€''كا''ï°·''كا''ﳄ''كج''ï°¸''كج''ï³…''كح''ï°¹''كح''ﳆ''كخ''ï°º''كخ''ﳇ''كل''ﳫ''كل''ï²''كل''ï°»''كل''ﳈ''كم''ﳬ''كم''ﲂ''كم''ï°¼''كم''ï·ƒ''كمم''ﶻ''كمم''ﶷ''كمى''ﲃ''كى''ï°½''كى''ﲄ''كى''ï°¾''كى''ﯕ''Ú­''ﯖ''Ú­''ﯔ''Ú­''ﯓ''Ú­''ï®”''Ú¯''ﮕ''Ú¯''ﮓ''Ú¯''ï®’''Ú¯''ﮜ''Ú±''ï®''Ú±''ï®›''Ú±''ﮚ''Ú±''ﮘ''Ú³''ï®™''Ú³''ï®—''Ú³''ï®–''Ú³''ﻟ''Ù„''ï» ''Ù„''ﻞ''Ù„''ï»''Ù„''ﻶ''لآ''ﻵ''لآ''ﻸ''لأ''ï»·''لأ''ﻺ''لإ''ﻹ''لإ''ﻼ''لا''ï»»''لا''ﳉ''لج''ï°¿''لج''ﶃ''لجج''ﶄ''لجج''ﶺ''لجم''ﶼ''لجم''ﶬ''لجى''ﳊ''لح''ï±€''لح''ﶵ''لحم''ﶀ''لحم''ﶂ''لحى''ï¶''لحى''ﳋ''لخ''ï±''لخ''ﶆ''لخم''ﶅ''لخم''ﳌ''لم''ï³­''لم''ï²…''لم''ﱂ''لم''ﶈ''لمح''ﶇ''لمح''ﶭ''لمى''ï³''له''ﲆ''لى''ﱃ''لى''ﲇ''لى''ﱄ''لى''ﻣ''Ù…''ﻤ''Ù…''ﻢ''Ù…''ﻡ''Ù…''ﲈ''ما''ﳎ''مج''ï±…''مج''ﶌ''مجح''ﶒ''مجخ''ï¶''مجم''ï·€''مجى''ï³''مح''ﱆ''مح''ﶉ''محج''ﶊ''محم''ï·´''محمد''ﶋ''محى''ï³''مخ''ﱇ''مخ''ﶎ''مخج''ï¶''مخم''ﶹ''مخى''ﳑ''مم''ﲉ''مم''ﱈ''مم''ﶱ''ممى''ﱉ''مى''ﱊ''مى''ﻧ''Ù†''ﻨ''Ù†''ﻦ''Ù†''ﻥ''Ù†''ï³’''نج''ﱋ''نج''ﶸ''نجح''ﶽ''نجح''ﶘ''نجم''ﶗ''نجم''ﶙ''نجى''ï·‡''نجى''ﳓ''نح''ﱌ''نح''ﶕ''نحم''ﶖ''نحى''ﶳ''نحى''ï³”''نخ''ï±''نخ''ﲊ''نر''ﲋ''نز''ﳕ''نم''ï³®''نم''ﲌ''نم''ﱎ''نم''ﶛ''نمى''ﶚ''نمى''ï²''نن''ï³–''نه''ﳯ''نه''ﲎ''نى''ï±''نى''ï²''نى''ï±''نى''ﮟ''Úº''ﮞ''Úº''ﻫ''Ù‡''ﻬ''Ù‡''ﻪ''Ù‡''ﻩ''Ù‡''Ú¾''Ù‡''ﮬ''Ù‡''ï®­''Ù‡''ﮫ''Ù‡''ﮪ''Ù‡''Û''Ù‡''ﮨ''Ù‡''ﮩ''Ù‡''ﮧ''Ù‡''ﮦ''Ù‡''Û•''Ù‡''ï³™''هٰ''ï³—''هج''ﱑ''هج''ﳘ''هم''ï±’''هم''ﶓ''همج''ﶔ''همم''ﱓ''هى''ï±”''هى''ﮥ''Û€''ﮤ''Û€''ï»®''Ùˆ''ï»­''Ùˆ''ï·¸''وسلم''ﯡ''Û…''ﯠ''Û…''ﯚ''Û†''ﯙ''Û†''ﯘ''Û‡''ﯗ''Û‡''Ù·''Û‡Ù”''ï¯''Û‡Ù”''ﯜ''Ûˆ''ﯛ''Ûˆ''ﯣ''Û‰''ﯢ''Û‰''ﯟ''Û‹''ﯞ''Û‹''ﯨ''Ù‰''ﯩ''Ù‰''ï»°''Ù‰''ﻯ''Ù‰''ÙŠ''Ù‰''ﻳ''Ù‰''ï»´''Ù‰''ﻲ''Ù‰''ï»±''Ù‰''ÛŒ''Ù‰''ﯾ''Ù‰''ﯿ''Ù‰''ﯽ''Ù‰''ﯼ''Ù‰''Ù¸''Ù‰Ù”''ï²''ىٰ''ï±''ىٰ''ﳚ''ىج''ﱕ''ىج''ﶯ''ىجى''ï³›''ىح''ï±–''ىح''ﶮ''ىحى''ﳜ''ىخ''ï±—''ىخ''ﲑ''ىر''ï²’''ىز''ï³''ىم''ï³°''ىم''ﲓ''ىم''ﱘ''ىم''ï¶''ىمم''ﶜ''ىمم''ﶰ''ىمى''ï²”''ىن''ﳞ''ىه''ï³±''ىه''ﲕ''ىى''ï±™''ىى''ï²–''ىى''ﱚ''ىى''Û§''Û¦''ﮯ''Û’''ï®®''Û’''ï®±''Û“''ï®°''Û“''∃''â´º''आ''अा''ऒ''अाॆ''ओ''अाे''औ''अाै''ऄ''अॆ''ऑ''अॉ''à¤''à¤à¥…''ऎ''à¤à¥†''à¤''à¤à¥‡''ई''रà¥à¤‡''আ''অা''ৠ''ঋৃ''ৡ''ঌৢ''ਉ''ੳà©''ਊ''ੳੂ''ਆ''ਅਾ''à¨''ਅੈ''ਔ''ਅੌ''ਇ''ੲਿ''ਈ''ੲੀ''à¨''ੲੇ''આ''અા''ઑ''અાૅ''ઓ''અાે''ઔ''અાૈ''àª''અૅ''àª''અે''àª''અૈ''ଆ''ଅା''௮''à®…''à®°''ஈ''ா''ஈ''௫''ஈà¯''௨''உ''ஊ''உள''௭''எ''௷''எவ''ஜ''à®''௧''க''௪''ச''௬''சà¯''௲''சூ''௺''நீ''ை''ன''௴''மீ''௰''ய''ௗ''ள''௸''à®·''ொ''ெஈ''ௌ''ெள''ோ''ேஈ''à± ''à°‹à°¾''ౡ''ఌా''à°”''ఒౌ''à°“''ఒౕ''à°¢''à°¡Ì£''à°­''బ̣''à°·''వ̣''à°¹''వా''à°®''à°µà±''ూ''à±à°¾''ౄ''ృా''ೡ''ಌಾ''ಔ''ఒౌ''à´ˆ''ഇൗ''à´Š''உൗ''à´''എെ''à´“''à´’à´¾''à´”''ഒൗ''ൡ''à´ž''൫''à´¦àµà´°''à´Œ''നூ''à´™''നூ''൯''à´¨àµ''à´±''à´°''൪''à´°àµ''൮''à´µàµ''ീ''ி''ൂ''ூ''ൃ''ூ''ൈ''െെ''ฃ''ข''ด''ค''ต''ค''ม''ฆ''ซ''ช''à¸''ฎ''ท''ฑ''ๅ''า''ำ''̊า''à¹''เเ''ໜ''ຫນ''à»''ຫມ''ຳ''̊າ''ཷ''ྲཱྀ''ཹ''ླཱྀ''á€''o''ឣ''អ''á§''ᦞ''á­’''á¬''á­“''ᬑ''á­˜''ᬨ''ᢖ''á¡œ''á¡•''á µ''á’''Ꭱ''Ꮍ''y''ð€''A''ð´''A''ð‘¨''A''ð’Âœ''A''ð“Â''A''ð”„''A''ð”¸''A''ð•¬''A''ð– ''A''ð—”''A''ð˜Âˆ''A''ð˜¼''A''ð™°''A''ðš¨''A''ð›¢''A''ðœÂœ''A''ð–''A''ðžÂ''A''ð‰''J''ð½''J''ð‘±''J''ð’¥''J''ð“™''J''ð”Â''J''ð•Â''J''ð•µ''J''ð–©''J''ð—Â''J''ð˜Â‘''J''ð™Â…''J''ð™¹''J''á§''J''â‹¿''E''â„°''E''ð„''E''ð¸''E''ð‘¬''E''ð“”''E''ð”ˆ''E''ð”¼''E''ð•°''E''ð–¤''E''ð—˜''E''ð˜ÂŒ''E''ð™Â€''E''ð™´''E''ðš¬''E''ð›¦''E''ðœ ''E''ðš''E''ðžÂ”''E''ℾ''Ꮁ''ðšª''Ꮁ''ð›¤''Ꮁ''ðœÂž''Ꮁ''ð˜''Ꮁ''ðžÂ’''Ꮁ''á”''w''ℳ''M''ðÂŒ''M''ð‘€''M''ð‘´''M''ð“Âœ''M''ð”Â''M''ð•Â„''M''ð•¸''M''ð–¬''M''ð— ''M''ð˜Â”''M''ð™Âˆ''M''ð™¼''M''ðš³''M''ð›­''M''ðœ§''M''ð¡''M''ðžÂ›''M''â„‹''H''â„Œ''H''â„''H''ð‡''H''ð»''H''ð‘¯''H''ð“—''H''ð•³''H''ð–§''H''ð—›''H''ð˜Â''H''ð™Âƒ''H''ð™·''H''ðš®''H''ð›¨''H''ðœ¢''H''ðÂœ''H''ðžÂ–''H''ð†''G''ðº''G''ð‘®''G''ð’¢''G''ð“–''G''ð”Š''G''ð”¾''G''ð•²''G''ð–¦''G''ð—š''G''ð˜ÂŽ''G''ð™Â‚''G''ð™¶''G''á³''G''ℤ''Z''ℨ''Z''ð™''Z''ð‘Â''Z''ð’Â''Z''ð’µ''Z''ð“©''Z''ð–Â…''Z''ð–¹''Z''ð—­''Z''ð˜¡''Z''ð™Â•''Z''ðšÂ‰''Z''ðš­''Z''ð›§''Z''ðœ¡''Z''ð›''Z''ðžÂ•''Z''ðÂ’''S''ð‘†''S''ð‘º''S''ð’®''S''ð“¢''S''ð”–''S''ð•ÂŠ''S''ð•¾''S''ð–²''S''ð—¦''S''ð˜Âš''S''ð™ÂŽ''S''ðšÂ‚''S''áš''S''ð•''V''ð‘‰''V''ð‘½''V''ð’±''V''ð“¥''V''ð”™''V''ð•Â''V''ð–Â''V''ð–µ''V''ð—©''V''ð˜Â''V''ð™Â‘''V''ðšÂ…''V''â„’''L''ð‹''L''ð¿''L''ð‘³''L''ð“›''L''ð”Â''L''ð•Âƒ''L''ð•·''L''ð–«''L''ð—Ÿ''L''ð˜Â“''L''ð™Â‡''L''ð™»''L''∑''C''â…€''C''â„‚''C''â„­''C''ð‚''C''ð¶''C''ð‘ª''C''ð’ž''C''ð“Â’''C''ð•®''C''ð–¢''C''ð—–''C''ð˜ÂŠ''C''ð˜¾''C''ð™²''C''ðšº''C''ð›´''C''ðœ®''C''ð¨''C''ðž¢''C''â„™''P''ðÂ''P''ð‘ƒ''P''ð‘·''P''ð’«''P''ð“Ÿ''P''ð”“''P''ð•»''P''ð–¯''P''ð—£''P''ð˜Â—''P''ð™Â‹''P''ð™¿''P''ðš¸''P''ð›²''P''ðœ¬''P''ð¦''P''ðž ''P''ðŠ''K''ð¾''K''ð‘²''K''ð’¦''K''ð“š''K''ð”ÂŽ''K''ð•Â‚''K''ð•¶''K''ð–ª''K''ð—ž''K''ð˜Â’''K''ð™Â†''K''ð™º''K''ðš±''K''ð›«''K''ðœ¥''K''ðŸ''K''ðžÂ™''K''ℬ''B''ðÂ''B''ðµ''B''ð‘©''B''ð“‘''B''ð”Â…''B''ð”¹''B''ð•­''B''ð–¡''B''ð—•''B''ð˜Â‰''B''ð˜½''B''ð™±''B''ðš©''B''ð›£''B''ðœÂ''B''ð—''B''ðžÂ‘''B''á''á·''∆''áƒ''ðš«''áƒ''ð›¥''áƒ''ðœÂŸ''áƒ''ð™''áƒ''ðžÂ“''áƒ''á''áƒÂ·''á‘''á„·''á“''á…·''á•''á†Â·''á˜''áŠÂ·''áš''á‹Â·''á“‘''á¡''ᑶ''·P''ᑺ''·d''á’˜''·J''á‘''á³Â·''ᑃ''á´Â·''á‘…''á¸Â·''ᑇ''á¹Â·''ˈ''á‘Š''ᑘ''ᑌ·''ᑧ''ᑌᑊ''á‘š''ᑎ·''ᑨ''á‘Žá‘Š''á‘œ''á‘·''á‘ž''á‘·''á‘©''á‘á‘Š''á‘ ''ᑑ·''á‘¢''ᑕ·''ᑪ''á‘•á‘Š''ᑤ''ᑖ·''ᑵ''ᑫ·''á’…''á‘«á‘Š''á‘·''P·''á’†''Pá‘Š''ᑹ''ᑮ·''á‘»''d·''á’‡''dá‘Š''ᑽ''ᑰ·''á‘¿''ᑲ·''á’ˆ''ᑲᑊ''á’''ᑳ·''ᘃ''á’‰''á’“''ᒉ·''á’•''ᒋ·''á’—''ᒌ·''á’™''J·''á’›''ᒎ·''ᘂ''á’''á’''á’·''á’Ÿ''ᒑ·''á’­''ᒣ·''á’¯''ᒥ·''á’±''ᒦ·''á’³''ᒧ·''á’µ''ᒨ·''á’¹''ᒫ·''á“Š''ᓀ·''á“Œ''ᓇ·''á“Ž''ᓈᒫ''ᘄ''á““''á“''ᓓ·''á“Ÿ''ᓕ·''á“¡''ᓖ·''á“£''ᓗ·''á“¥''ᓘ·''ᘇ''á“š''ᓧ''ᓚ·''á“©''ᓛ·''á“·''ᓭ·''ᓹ''ᓯ·''á“»''ᓰ·''ᓽ''ᓱ·''á“¿''ᓲ·''á”''ᓴ·''ᔃ''ᓵ·''ᔌ''ᔋá¸''á”''ᔋᑕ''ᔎ''ᔋᑲ''á”''ᔋá’''ᔘ''á”·''ᔚ''ᔑ·''ᔜ''ᔒ·''ᔞ''ᔓ·''á” ''ᔔ·''ᔢ''ᔕ·''ᔤ''ᔖ·''ᔲ''ᔨ·''á”´''ᔩ·''ᔶ''ᔪ·''ᔸ''ᔫ·''ᔺ''ᔭ·''ᔼ''ᔮ·''á™®''x''ᕽ''x''ᘢ''ᕃ''ᘣ''ᕆ''ᘤ''á•Š''á•''ᕌ·''ᙯ''á•á‘«''ᕾ''á•á‘¬''á•¿''á•P''á–€''á•á‘®''á–''á•d''á–‚''á•á‘°''á–ƒ''á•á‘²''á–„''á•á‘³''á–…''á•á’ƒ''á•œ''ᕚ·''á•©''ᕧ·''â„›''R''â„œ''R''â„''R''ð‘''R''ð‘Â…''R''ð‘¹''R''ð“¡''R''ð•½''R''ð–±''R''ð—¥''R''ð˜Â™''R''ð™Â''R''ðšÂ''R''á™°''á–•á’‰''á–Ž''á–•á’Š''á–''á–•á’‹''á–''á–•á’Œ''á–‘''á–•J''á–’''á–•á’Ž''á–“''á–•á’''á–”''á–•á’‘''á™±''á––á’‹''ᙲ''á––á’Œ''ᙳ''á––J''á™´''á––á’Ž''ᙵ''á––á’''ᙶ''á––á’‘''ℱ''F''ðÂ…''F''ð¹''F''ð‘­''F''ð“•''F''ð”‰''F''ð”½''F''ð•±''F''ð–¥''F''ð—™''F''ð˜Â''F''ð™Â''F''ð™µ''F''ðŸÂŠ''F''â……''D''ðƒ''D''ð·''D''ð‘«''D''ð’Ÿ''D''ð““''D''ð”‡''D''ð”»''D''ð•¯''D''ð–£''D''ð——''D''ð˜Â‹''D''ð˜¿''D''ð™³''D''á—ª''D''℧''ᘮ''ᘴ''ᘮ''ð›Â€''ᘯ''ð›º''ᘯ''ðœ´''ᘯ''ð®''ᘯ''ðž¨''ᘯ''ᘵ''ᘯ''ㄱ''á„€''ᄀ''á„€''ᆨ''á„€''ㄲ''á„''ï¾¢''á„''ᆩ''á„''ã„´''á„‚''ᄂ''á„‚''ᆫ''á„‚''ã„·''ᄃ''ᄃ''ᄃ''ᆮ''ᄃ''ㄸ''á„„''ᄄ''á„„''ㄹ''á„…''ᄅ''á„…''ᆯ''á„…''ã…''ᄆ''ï¾±''ᄆ''ᆷ''ᄆ''ã…‚''ᄇ''ï¾²''ᄇ''ᆸ''ᄇ''ã…ƒ''ᄈ''ï¾³''ᄈ''ã……''ᄉ''ï¾µ''ᄉ''ᆺ''ᄉ''ã…†''á„Š''ᄊ''á„Š''ᆻ''á„Š''ã…‡''á„‹''ï¾·''á„‹''ᆼ''á„‹''ã…ˆ''á„Œ''ᄌ''á„Œ''ᆽ''á„Œ''ã…‰''á„''ï¾¹''á„''ã…Š''á„Ž''ᄎ''á„Ž''ᆾ''á„Ž''ã…‹''á„''ï¾»''á„''ᆿ''á„''ã…Œ''á„''ï¾¼''á„''ᇀ''á„''ã…''á„‘''ï¾½''á„‘''á‡''á„‘''ã…Ž''á„’''ï¾¾''á„’''ᇂ''á„’''ᇅ''á„“''ã…¥''á„”''ã…¦''á„•''ᇆ''á„•''ᇊ''á„—''á‡''ᄘ''á‡''á„™''ã…€''á„š''ï¾°''á„š''á„»''á„š''ᆶ''á„š''ã…®''á„œ''ᇜ''á„œ''ã…±''á„''ᇢ''á„''ã…²''á„ž''ã…³''á„ ''ã…„''á„¡''ï¾´''á„¡''ᆹ''á„¡''ã…´''á„¢''ã…µ''á„£''ã…¶''ᄧ''ã…·''á„©''ã…¸''á„«''ᇦ''á„«''ã…¹''ᄬ''ã…º''á„­''ᇧ''á„­''ã…»''á„®''ã…¼''ᄯ''ᇨ''ᄯ''ᇩ''á„°''ã…½''ᄲ''ᇪ''ᄲ''ã…¾''ᄶ''ã…¿''á…€''ᇫ''á…€''ᇬ''á…''ᇱ''á……''ㆂ''á……''ᇲ''á…†''ㆃ''á…†''ㆀ''á…‡''ᇮ''á…‡''ã†''á…Œ''ᇰ''á…Œ''ᇳ''á…–''ㆄ''á…—''ᇴ''á…—''ㆅ''á…˜''ㆆ''á…™''ᇹ''á…™''ã…¤''á… ''ï¾ ''á… ''ã…''á…¡''ï¿‚''á…¡''ã…''á…¢''ᅢ''á…¢''ã…‘''á…£''ï¿„''á…£''ã…’''á…¤''ï¿…''á…¤''ã…“''á…¥''ᅥ''á…¥''ã…”''á…¦''ᅦ''á…¦''ã…•''á…§''ï¿Š''á…§''ã…–''á…¨''ï¿‹''á…¨''ã…—''á…©''ï¿Œ''á…©''ã…˜''á…ª''ï¿''á…ª''ã…™''á…«''ï¿Ž''á…«''ã…š''á…¬''ï¿''á…¬''ã…›''á…­''ï¿’''á…­''ã…œ''á…®''ï¿“''á…®''ã…''á…¯''ï¿”''á…¯''ã…ž''á…°''ï¿•''á…°''ã…Ÿ''á…±''ï¿–''á…±''ã… ''á…²''ï¿—''á…²''ã…¡''一''ï¿š''一''ã…¢''á…´''ï¿›''á…´''ã…£''丨''ï¿œ''丨''ㆇ''ᆄ''ᆆ''ᆄ''ㆈ''ᆅ''ㆉ''ᆈ''ㆊ''ᆑ''ㆋ''ᆒ''ㆌ''ᆔ''ã†''ᆞ''ㆎ''ᆡ''ㄳ''ᆪ''ï¾£''ᆪ''ㄵ''ᆬ''ï¾¥''ᆬ''ㄶ''ᆭ''ᆭ''ᆭ''ㄺ''ᆰ''ᆰ''ᆰ''ã„»''ᆱ''ᆱ''ᆱ''ㄼ''ᆲ''ᆲ''ᆲ''ㄽ''ᆳ''ï¾­''ᆳ''ㄾ''ᆴ''ï¾®''ᆴ''ã„¿''ᆵ''ᆵ''ᆵ''ã…§''ᇇ''ã…¨''ᇈ''ã…©''ᇌ''ã…ª''ᇎ''ã…«''ᇓ''ã…¬''ᇗ''ã…­''ᇙ''ã…¯''á‡''ã…°''ᇟ''ァ''ã‚¡''ï½±''ã‚¢''ィ''ã‚£''ï½²''イ''ゥ''ã‚¥''ï½³''ウ''ェ''ェ''ï½´''エ''ォ''ã‚©''ï½µ''オ''カ''ã‚«''ï½·''ã‚­''ク''ク''ï½¹''ケ''コ''コ''ï½»''サ''ï½¼''ã‚·''ï½½''ス''ï½¾''ã‚»''ソ''ソ''ï¾€''ã‚¿''ï¾''ãƒ''ッ''ッ''ツ''ツ''テ''テ''ト''ト''ï¾…''ナ''ニ''ニ''ヌ''ヌ''ネ''ãƒ''ノ''ノ''ハ''ãƒ''ヒ''ヒ''フ''フ''ï¾''ã¸''ホ''ホ''ï¾''マ''⧄''〼''ï¾''ミ''ム''ム''ï¾’''メ''モ''モ''ャ''ャ''ï¾”''ヤ''ï½­''ュ''ユ''ユ''ï½®''ョ''ï¾–''ヨ''ï¾—''ラ''リ''リ''ï¾™''ル''レ''レ''ï¾›''ロ''ワ''ワ''ヲ''ヲ''ï¾''ン''ê’ž''êŠ''ê’¬''ê''ê’œ''ꃀ''ê’¿''ꉙ''ê’¾''ꊱ''ê“€''ꎫ''ê“‚''ꎵ''ê’º''ꎿ''ê’°''ê‚''ð’ ''ð’†''—''一''―''一''−''一''─''一''â¼€''一''不''ä¸''ï©°''並''|''丨''|''丨''∣''丨''â¼''丨''‖''丨丨''∥''丨丨''串''串''⼂''丶''ð¯ Â''丸''丹''丹''ð¯ Â€''丽''⼃''丿''ð¯ Â‚''ä¹''⼄''ä¹™''亂''亂''â¼…''亅''了''了''⼆''二''⼇''亠''亮''亮''⼈''人''什''什''ð¯ Â™''仌''令''令''ð¯ Â„''ä½ ''倂''ä½µ''ð¯ Â‡''ä½µ''侀''ä¾€''來''來''例''例''侮''ä¾®''ð¯ Â…''ä¾®''ð¯ Â†''ä¾»''便''便''值''値''倫''倫''ð¯ Âˆ''åº''ð¯ Â‰''å‚™''ð¯ Â‹''åƒ''僚''僚''僧''僧''ð¯ ÂŠ''僧''⼉''å„¿''兀''å…€''ï©´''å……''免''å…''ð¯ ÂŽ''å…''ð¯ Â''å…”''ð¯ Â''å…¤''⼊''å…¥''ð¯ Â”''å…§''全''å…¨''兩''å…©''⼋''å…«''六''å…­''ð¯ Â‘''å…·''冀''冀''⼌''冂''ð¯ Â•''å†''ð¯£Â’''冒''ð¯£Â“''冕''â¼''冖''ð¯ Â—''冗''ð¯ Â˜''冤''⼎''冫''ð¯ Âš''冬''况''况''ð¯ Â›''况''冷''冷''凉''凉''凌''凌''凜''凜''凞''凞''â¼''几''ð¯ Â''凵''â¼''凵''⼑''刀''ð¯ Âž''刃''切''切''ð¯¡Â''切''列''列''ï§''利''刺''刺''刻''刻''剆''剆''割''割''剷''剷''劉''劉''力''力''â¼’''力''ï¦''劣''ð¯¦Â’''劳''勇''勇''勇''勇''勉''勉''勉''勉''勒''å‹’''勞''å‹ž''勤''勤''勤''勤''勵''勵''⼓''勹''ï©·''勺''勺''勺''包''包''匆''匆''â¼”''匕''北''北''北''北''⼕''匚''â¼–''匸''匿''匿''â¼—''å''〸''å''〹''å„''〺''å…''卉''å‰''卑''å‘''卑''å‘''博''åš''⼘''åœ''â¼™''å©''即''å³''卵''åµ''卽''å½''卿''å¿''卿''å¿''卿''å¿''⼚''厂''â¼›''厶''參''åƒ''⼜''åˆ''及''åŠ''叟''åŸ''â¼''å£''句''å¥''叫''å«''叱''å±''吆''å†''吏''å''吝''å''吸''å¸''呂''å‘‚''呈''呈''周''周''咞''å’ž''ð¯¡Â€''å’¢''咽''å’½''ð¯¡Â''哶''ð¯¡Â‚''å”''ð¯¡Âƒ''å•“''å•Ÿ''å•“''啕''å••''ð¯¡Â„''å•£''ð¯¡Â…''å–„''ð¯¡Â†''å–„''喇''å–‡''喙''å–™''ð¯¡Â‡''å–™''喝''å–''喝''å–''ð¯¡Âˆ''å–«''ð¯¡Â‰''å–³''ï¨''å—€''ð¯¡ÂŠ''å—‚''ï©»''å—¢''嘆''嘆''ð¯¡ÂŒ''嘆''ð¯¡ÂŽ''噑''器''器''ð¯¡Â''å™´''⼞''å›—''囹''囹''ð¯¡Â‹''圖''ð¯¡Â''圗''⼟''土''ð¯¡Â•''åž‹''ð¯¡Â’''城''ð¯¡Â“''埴''ð¯¡Â”''å ''ð¯¡Â—''å ±''ð¯¡Â–''å ²''塀''å¡€''ï¨''å¡š''塚''å¡š''塞''å¡ž''å¡«''å¡¡''墨''墨''壿''墫''ð¯¡Â˜''墬''墳''墳''壘''壘''壟''壟''â¼ ''士''ð¯¡Â‘''壮''ð¯¡Âš''売''ð¯¡Â›''壷''⼡''夂''ð¯¡Âœ''夆''â¼¢''夊''â¼£''夕''ð¯¡Â''多''ð¯¡Âž''夢''⼤''大''奄''奄''奈''奈''契''契''ï©¿''奔''ð¯¡ÂŸ''奢''ï¦''女''â¼¥''女''姘''姘''姬''姬''娛''娛''娧''娧''婢''å©¢''婦''婦''嬀''媯''ð¯¦Â†''媵''嬈''嬈''ïª''嬨''嬾''嬾''嬾''嬾''⼦''å­''⼧''宀''宅''å®…''寃''寃''寘''寘''寧''寧''寧''寧''寧''寧''寮''寮''寳''寳''⼨''寸''寿''寿''将''å°†''⼩''å°''尢''å°¢''⼪''å°¢''⼫''å°¸''尿''å°¿''屠''å± ''屢''å±¢''層''層''履''å±¥''屮''å±®''屮''å±®''⼬''å±®''â¼­''å±±''岍''å²''峀''å³€''崙''å´™''嵃''嵃''嵐''åµ''嵫''嵫''嵮''åµ®''ð¯¢Â€''åµ¼''嶲''嶲''嶺''嶺''â¼®''å·›''ð¯¢Â''å·¡''ð¯¢Â‚''å·¢''⼯''å·¥''â¼°''å·±''ð¯¢Â„''å·½''â¼±''å·¾''帲''帡''ð¯¢Â…''帨''ð¯¢Â†''帽''ð¯¢Â‡''幩''â¼²''å¹²''年''å¹´''â¼³''幺''â¼´''广''ï¨''度''ð¯¢Â‹''庰''ð¯¢ÂŒ''庳''ð¯¢Â''庶''廉''廉''廊''廊''ð¯¢ÂŽ''廊''廒''å»’''廓''廓''廙''å»™''廬''廬''â¼µ''å»´''ð¯¢Â''廾''⼶''廾''弄''弄''â¼·''弋''⼸''弓''ð¯¢Â”''å¼¢''ð¯¢Â•''å¼¢''â¼¹''å½''当''当''⼺''彡''ð¯¢Â™''å½¢''彩''彩''ð¯¢Âš''彫''â¼»''å½³''律''律''ð¯¢Âœ''徚''復''復''徭''å¾­''â¼¼''心''ð¯¢Â''å¿''ð¯¢Âž''å¿—''念''念''ð¯¢ÂŸ''忹''怒''怒''怜''怜''悁''æ‚''悔''æ‚”''悔''æ‚”''惇''惇''惘''惘''惡''惡''愈''愈''慄''æ…„''慈''æ…ˆ''慌''æ…Œ''慌''æ…Œ''慎''æ…Ž''慎''æ…Ž''慠''æ… ''慨''æ…¨''慺''æ…º''憎''憎''憎''憎''憎''憎''ï¦''æ†''憤''憤''憯''憯''憲''憲''懞''懞''ï©€''懲''懲''懲''懲''懲''ï¤''懶''懶''懶''ï¦''戀''â¼½''戈''成''æˆ''戛''戛''戮''戮''戴''戴''â¼¾''戶''⼿''手''扝''æ‰''抱''抱''拉''拉''拏''æ‹''拓''æ‹“''拔''æ‹”''拼''拼''拾''拾''挽''挽''捐''æ''捨''æ¨''捻''æ»''掃''掃''掠''掠''ð¯£Â''掩''ïª''æ„''ð¯£Â€''æ…''揤''æ¤''ã©''æ‰''搜''æœ''搢''æ¢''ïª''æ‘’''ð¯£Âƒ''æ‘©''ð¯£Â†''æ‘·''ð¯£Â„''摾''撚''æ’š''ð¯£Â…''æ’''擄''æ“„''â½€''支''â½''æ”´''ï©''æ•''ð¯£Âˆ''æ•''ïª''æ•–''ð¯£Â‰''敬''數''數''⽂''æ–‡''⽃''æ–—''料''æ–™''⽄''æ–¤''â½…''æ–¹''旅''æ—…''⽆''æ— ''ï©‚''æ—¢''ð¯£Â‹''æ—£''⽇''æ—¥''易''易''ð¯£Â''晉''晩''晚''䀿''晣''晴''æ™´''晴''æ™´''暈''暈''暑''æš‘''ð¯£Â''æš‘''ð¯£Â•''æšœ''暴''æš´''曆''曆''⽈''æ›°''ï¤''æ›´''ã«š''曶''ð¯£ÂŒ''書''ð¯£Â”''最''⽉''月''肦''朌''èƒ''æœ''胊''æœ''è„''朓''朗''朗''朗''朗''ð¯£Â˜''朗''脧''朘''望''望''ð¯£Â™''望''ð¯£Âš''朡''膧''朣''⽊''木''李''æŽ''ð¯£Âœ''æ“''杖''æ–''ð¯£Â›''æž''柿''æ®''杻''æ»''枅''æž…''林''æž—''柳''柳''ð¯£ÂŸ''柺''栗''æ —''栟''æ Ÿ''桒''æ¡’''梁''æ¢''ï©„''梅''梅''梅''梎''梎''梨''梨''椔''椔''楂''楂''樧''æ¦''榣''榣''槪''槪''樂''樂''樂''樂''樂''樂''樓''樓''檨''檨''櫓''æ«“''櫛''æ«›''ï¤''欄''⽋''欠''次''次''歔''æ­”''⽌''æ­¢''歲''æ­²''歷''æ­·''歹''æ­¹''â½''æ­¹''殟''殟''殮''æ®®''⽎''殳''殺''殺''殺''殺''殺''殺''殻''æ®»''â½''毋''⺟''æ¯''â½''比''⽑''毛''â½’''æ°''⽓''æ°”''â½”''æ°´''汎''汎''汧''汧''沈''沈''沿''沿''泌''泌''泍''æ³''泥''æ³¥''洖''æ´–''洛''æ´›''洞''æ´ž''ð¯¤Â‡''æ´´''ð¯¤Â€''æ´¾''流''æµ''流''æµ''ð¯¤Â‚''æµ''ð¯¤Âƒ''浩''浪''浪''ï©…''æµ·''ð¯¤Â''æµ·''ð¯¤Â„''浸''ð¯¤Â…''涅''淋''æ·‹''ï¥''æ·š''淪''æ·ª''ð¯¤ÂŽ''æ·¹''渚''渚''ð¯¤Âˆ''港''ð¯¤Â‰''æ¹®''æ½™''溈''溜''溜''溺''溺''ð¯¤ÂŒ''滇''滋''滋''ð¯¤Â‹''滋''滑''滑''滛''æ»›''漏''æ¼''漢''æ¼¢''漢''æ¼¢''漣''æ¼£''ð¯¤Â''æ½®''ð¯¤Â’''濆''濫''æ¿«''濾''濾''ð¯¤Â•''瀛''瀞''瀞''ð¯¤Â”''瀞''ð¯¤Â“''瀹''ð¯¤Â—''çŠ''⽕''ç«''灰''ç°''ð¯¤Â™''ç·''ð¯¤Â˜''ç½''炙''ç‚™''ð¯¤Âš''ç‚­''烈''烈''烙''烙''ð¯¤Âœ''ç……''煉''ç…‰''煮''ç…®''煮''ç…®''ð¯¤Âž''熜''燎''燎''燐''ç‡''爐''çˆ''爛''爛''爨''爨''â½–''爪''爫''爫''⺤''爫''爵''爵''爵''爵''â½—''父''⽘''爻''â½™''爿''⽚''片''牐''ç‰''â½›''牙''⽜''牛''牢''牢''犀''犀''犕''犕''â½''犬''犯''犯''狀''ç‹€''狼''狼''猪''猪''猪''猪''獵''çµ''獺''çº''⽞''玄''率''率''率''率''⽟''玉''王''王''玥''玥''玲''玲''珞''çž''理''ç†''琉''ç‰''ï©Š''ç¢''瑇''瑇''瑜''ç‘œ''瑩''ç‘©''瑱''瑱''瑱''瑱''璅''ç’…''璉''ç’‰''璘''ç’˜''瓊''ç“Š''â½ ''ç“œ''⽡''瓦''甆''甆''â½¢''甘''â½£''生''甤''甤''⽤''用''â½¥''ç”°''画''ç”»''甾''甾''ï§''ç•™''略''ç•¥''異''ç•°''異''ç•°''⽦''ç–‹''⽧''ç–’''痢''ç—¢''瘐''ç˜''瘝''ç˜''瘟''瘟''ï§''療''癩''癩''⽨''癶''⽩''白''⽪''çš®''⽫''çš¿''益''益''益''益''盛''ç››''盧''盧''⽬''ç›®''直''ç›´''ð¯¥Â€''ç›´''省''çœ''ð¯¥Â…''眞''ð¯¥Â†''真''ð¯¥Â‡''真''着''ç€''睊''çŠ''ð¯¥Âˆ''çŠ''ð¯¥ÂŠ''çž‹''ïª''瞧''â½­''矛''â½®''矢''⽯''石''ç¡''ç ”''ð¯¥ÂŽ''ç¡Ž''硫''ç¡«''碌''碌''ð¯¥Â''碌''ï©‹''碑''磊''磊''磌''磌''ð¯¥Â''磌''磻''磻''礪''礪''â½°''示''礼''礼''ï©Œ''社''ï©Ž''祈''ï©''祉''ï©''ç¥''ï©''祖''ð¯¥Â“''祖''ï©‘''ç¥''神''神''祥''祥''祿''祿''ï©’''ç¦''ï©“''禎''福''ç¦''ð¯¥Â–''ç¦''禮''禮''â½±''禸''â½²''禾''秊''秊''ð¯¥Â—''秫''稜''稜''ï©”''ç©€''ð¯¥Â™''ç©€''ð¯¥Âš''ç©Š''ð¯¥Â›''ç©''â½³''ç©´''ï©•''çª''窱''窱''立''ç«‹''â½´''ç«‹''ð¯¥ÂŸ''ç«®''â½µ''竹''笠''笠''ï©–''節''節''節''篆''篆''築''築''簾''ç°¾''籠''ç± ''⽶''ç±³''类''ç±»''粒''ç²’''ï¨''ç²¾''糒''ç³’''糖''ç³–''糣''ç³£''糧''糧''糨''糨''â½·''糸''紀''ç´€''ï§''ç´''索''ç´¢''ï¥''ç´¯''絶''絕''絛''çµ›''絣''çµ£''綠''綠''綾''綾''緇''ç·‡''練''ç·´''ï©—''ç·´''練''ç·´''縂''縂''縉''縉''ï¥''縷''ï©™''ç¹''繅''ç¹…''⽸''缶''缾''ç¼¾''â½¹''网''⺫''ç½’''ï©š''ç½²''罹''ç½¹''罺''罺''ï¤''ç¾…''⽺''羊''羕''羕''羚''羚''羽''ç¾½''â½»''ç¾½''翺''翺''老''è€''â½¼''è€''ï©›''者''者''者''者''者''â½½''而''â½¾''耒''⽿''耳''聆''è†''聠''è ''聯''è¯''聰''è°''聾''è¾''â¾€''è¿''â¾''肉''肋''è‚‹''ð¯£Â–''è‚­''ð¯¦Â‚''育''㬵''胶''è…''胼''ð¯¦Âƒ''脃''ð¯¦Â…''脾''臘''臘''⾂''臣''臨''臨''⾃''自''ï©œ''臭''⾄''至''â¾…''臼''ð¯¢Â“''èˆ''ð¯¦Â‹''èˆ''ð¯¦ÂŒ''舄''⾆''舌''⾇''舛''⾈''舟''⾉''艮''良''良''⾊''色''⾋''艸''ï©''艹''ï©ž''艹''ð¯¦Â''芋''ð¯¦Â''芑''ð¯¦Â‘''èŠ''ð¯¦Â“''花''ð¯¦Â”''芳''ð¯¦Â•''芽''若''è‹¥''ð¯¦Â˜''è‹¥''ð¯¦Â–''苦''ð¯¦Â™''èŒ''ð¯¦Âœ''茣''茶''茶''荒''è’''荓''è“''ð¯¦Âš''è£''ð¯¦Â›''莭''ð¯¦Â''莽''菉''è‰''菊''èŠ''菌''èŒ''菜''èœ''ð¯¦Âž''è§''華''è¯''菱''è±''落''è½''葉''葉''ï©Ÿ''è‘—''ð¯¦ÂŸ''è‘—''蔿''è’''蓮''è“®''蓱''蓱''蓳''蓳''蓼''蓼''蔖''è”–''蕤''蕤''藍''è—''藺''è—º''蘆''蘆''蘒''蘒''蘭''蘭''è™''蘷''ï¤''蘿''⾌''è™''虐''è™''虜''虜''虜''虜''虧''虧''虩''虩''â¾''虫''蚈''蚈''蚩''èš©''蛢''蛢''蜎''蜎''蜨''蜨''蝫''è«''蝹''è¹''蝹''è¹''螆''螆''螺''螺''ð¯§Â€''蟡''ð¯§Â''è ''蠟''è Ÿ''⾎''è¡€''行''è¡Œ''â¾''è¡Œ''ð¯§Âƒ''è¡ ''ð¯§Â„''è¡£''â¾''è¡£''裂''裂''裏''è£''ð¯§Â†''裗''ð¯§Â‡''裞''裡''裡''裸''裸''ð¯§Â‰''裺''ï© ''è¤''襁''è¥''襤''襤''⾑''襾''覆''覆''見''見''â¾’''見''ï©¡''視''視''視''⾓''角''â¾”''言''䚶''訞''詽''訮''ð¯§Â''誠''說''說''說''說''調''調''請''è«‹''諒''è«’''ï¥''è«–''諭''è«­''ð¯§Â''è«­''諸''諸''諸''諸''ï¥''諾''諾''諾''ï©¢''è¬''謁''è¬''ï©£''謹''謹''謹''識''è­˜''讀''讀''è®''讆''ï«€''變''ð¯§Â‘''變''⾕''è°·''â¾–''豆''豈''豈''ð¯§Â’''豕''â¾—''豕''⾘''豸''â¾™''è²''ð¯§Â”''貫''ð¯§Â•''è³''賂''賂''賈''賈''賓''賓''ï©¥''è´ˆ''ï«''è´ˆ''ð¯§Â–''è´›''⾚''赤''â¾›''èµ°''ð¯§Â—''èµ·''趆''赿''⾜''足''ð¯§Â›''趼''ð¯§Âš''è·‹''è·º''è·¥''路''è·¯''ð¯§Âœ''è·°''躛''躗''â¾''身''車''車''⾞''車''ð¯§Âž''è»”''輧''軿''輦''輦''輪''輪''ï«‚''輸''ð¯§ÂŸ''輸''輻''è¼»''ï¦''è½¢''⾟''è¾›''ð¯¦Â''辞''辰''è¾°''â¾ ''è¾°''⾡''è¾µ''辶''辶''⻌''辶''連''連''逸''逸''逸''逸''遲''é²''遼''é¼''邏''é‚''â¾¢''é‚‘''邔''é‚”''郎''郎''郱''郱''都''都''鄑''é„‘''鄛''é„›''â¾£''é…‰''酪''é…ª''ï«„''醙''醴''醴''⾤''釆''里''里''â¾¥''里''量''é‡''金''金''⾦''金''鈴''鈴''鈸''鈸''ï«…''鉶''鉼''鉼''鋗''é‹—''鋘''鋘''錄''錄''鍊''éŠ''鎮''鎭''鏹''é¹''鐕''é•''⾧''é•·''⾨''é–€''開''é–‹''閭''é–­''閷''é–·''⾩''阜''阮''阮''陋''陋''降''é™''陵''陵''陸''陸''陼''陼''隆''隆''隣''隣''⾪''隶''隸''隸''⾫''éš¹''雃''雃''離''離''難''難''難''難''⾬''雨''零''零''雷''é›·''霣''霣''露''露''靈''éˆ''â¾­''é‘''靖''é–''靖''é–''â¾®''éž''⾯''é¢''â¾°''é©''â¾±''韋''韛''韛''韠''韠''â¾²''韭''â¾³''音''ï©©''響''ï«Š''響''â¾´''é ''ï«‹''é ‹''頋''é ‹''頋''é ‹''領''é ˜''ð¯¨Â€''é ©''頻''é »''ï«Œ''é »''ï§''é¡ž''â¾µ''風''⾶''飛''â»''食''â¾·''食''ð¯¨Â‚''飢''飯''飯''飼''飼''館''館''ð¯¨Â„''餩''⾸''首''â¾¹''香''ð¯¨Â…''馧''⾺''馬''ð¯¨Â†''駂''駱''駱''ð¯¨Â‡''駾''驪''驪''â¾»''骨''â¾¼''高''â¾½''é«Ÿ''ï«''鬒''ð¯¨ÂŠ''鬒''â¾¾''鬥''⾿''鬯''â¿€''鬲''â¿''鬼''â¿‚''é­š''魯''é­¯''ð¯¨Â‹''é±€''鱗''é±—''⿃''é³¥''ð¯¨ÂŒ''é³½''ð¯¨Â''鵧''鶴''鶴''鷺''é·º''鸞''鸞''鹃''鹂''â¿„''é¹µ''鹿''鹿''â¿…''鹿''麗''麗''麟''麟''⿆''麥''ð¯¨Â•''麻''⿇''麻''⿈''黃''⿉''é»''黎''黎''â¿Š''黑''ð¯¨Â—''黹''â¿‹''黹''â¿Œ''黽''ð¯¨Â˜''黾''ð¯¨Â™''é¼…''â¿''鼎''ð¯¨Âš''é¼''â¿Ž''鼓''ð¯¨Â›''é¼–''â¿''é¼ ''ð¯¨Âœ''é¼»''â¿''é¼»''齃''齃''â¿‘''齊''â¿’''é½’''龍''é¾''â¿“''é¾''ï«™''龎''龜''龜''龜''龜''ï«Ž''龜''â¿”''龜''⻳''龟''â¿•''é¾ ''ð¯ ÂŒ''ã’ž''ð¯ Â“''ã’¹''ð¯§ÂŠ''ã’»''ð¯ ÂŸ''ã“Ÿ''㔕''㔕''䎛''ã–ˆ''㛮''ã›®''㛼''㛼''㞁''ãž''ð¯¢Âƒ''ã ¯''ð¯¢Âˆ''ã¡¢''ð¯¢ÂŠ''㡼''ð¯¢Â–''㣇''ð¯¢Â›''㣣''㤜''㤜''㤺''㤺''ð¯£Â‚''㨮''ð¯£Â‡''㩬''ð¯£Â‘''㫤''ð¯£Â''㬈''ð¯£ÂŽ''㬙''ä ''㬻''ð¯£Âž''ã­‰''ï«’''ã®''㮝''ã®''㰘''ã°˜''㱎''㱎''ð¯¤ÂŠ''ã´³''ð¯¤Â–''㶖''㺬''㺬''㺸''㺸''㺸''㺸''㼛''ã¼›''㿼''㿼''䀈''䀈''ï«“''䀘''ï«”''䀹''ð¯¥Â‰''䀹''ð¯¥Â‹''ä†''ð¯¥ÂŒ''ä‚–''ð¯¥Â‘''䃣''ð¯¥Â˜''䄯''䈂''䈂''䈧''䈧''䊠''䊠''䌁''äŒ''䌴''䌴''䍙''ä™''ð¯¦Â''ä•''ð¯£Â—''ä™''ð¯¦Â„''ä‹''ð¯¦ÂŽ''ä‘«''䔫''䔫''䕝''ä•''䕡''ä•¡''䕫''ä•«''䗗''ä——''ð¯§Â‚''ä—¹''ð¯§Âˆ''䘵''ð¯§Â''äš¾''ð¯§ÂŽ''䛇''䦕''䦕''䧦''䧦''䩮''ä©®''䩶''䩶''䪲''䪲''ð¯¨Âƒ''䬳''ð¯¨Âˆ''䯎''ð¯¨Â''䳎''ð¯¨ÂŽ''ä³­''ð¯¨Â‘''䳸''ð¯¨Â–''äµ–''ð¯ Âƒ''ð „¢''ð¯ Â’''ð ”Âœ''ð¯¤Â›''𠔥''ð¯ Â–''ð •Â‹''ð¯ Â''𠘺''ð¯§Â™''ð  Â„''ð¯§Â''ð £Âž''𠨬''𠨬''𠭣''ð ­£''ð¯¡Â™''𡓤''𡚨''𡚨''𡛪''𡛪''𡧈''ð¡§Âˆ''𡬘''ð¡¬Â˜''𡴋''ð¡´Â‹''𡷤''ð¡·¤''𡷦''ð¡·¦''ð¯¢Â‰''ð¢†Âƒ''𢆟''ð¢†ÂŸ''ð¯¢Â‘''𢌱''ð¯¢Â’''𢌱''𢛔''ð¢›Â”''ï«''ð¢¡Â„''ï«''ð¢¡ÂŠ''𢬌''ð¢¬ÂŒ''𢯱''𢯱''ð¯£ÂŠ''ð£€ÂŠ''ð¯¢Â—''𣊸''ð¯¦Â€''ð£ÂŸ''ð¯¦Â‰''ð£ŽÂ“''ð¯¦ÂŠ''ð£ŽÂœ''ð¯£Â''ð£Âƒ''ï«‘''ð£Â•''𣑭''𣑭''𣚣''𣚣''𣢧''𣢧''𣪍''ð£ªÂ''𣫺''𣫺''𣲼''𣲼''ð¯¤Â†''ð£´Âž''ð¯¤Â''ð£»Â‘''ð¯¤Â''ð£½Âž''ð¯¤Â‘''ð£¾ÂŽ''ð¯¤Â''𤉣''ð¯¤ÂŸ''𤎫''𤘈''ð¤˜Âˆ''𤜵''𤜵''𤠔''ð¤ Â”''𤰶''𤰶''𤲒''ð¤²Â’''𤾡''𤾡''𤾸''𤾸''𥁄''ð¥Â„''ð¯¥Â‚''𥃲''ð¯¥Â''𥃳''ð¯¥Âƒ''𥄙''ð¯¥Â„''𥄳''ï«•''ð¥‰Â‰''ð¯¥Â''ð¥Â''ð¯¥Â’''𥘦''ð¯¥Â”''ð¥šÂš''ð¯¥Â•''ð¥›Â…''ð¯¥Âœ''𥥼''ð¯¥Â''𥪧''ð¯¥Âž''𥪧''𥮫''𥮫''𥲀''ð¥²Â€''ï«–''ð¥³Â''𥾆''ð¥¾Â†''ð¯¢Â˜''ð¦‡Âš''𦈨''𦈨''𦉇''ð¦‰Â‡''𦋙''ð¦‹Â™''𦌾''𦌾''𦓚''𦓚''𦔣''𦔣''𦖨''𦖨''ð¯¦Â‡''𦞧''ð¯¦Âˆ''𦞵''ð¯¦Â—''𦬼''𦰶''𦰶''𦳕''ð¦³Â•''𦵫''𦵫''𦼬''𦼬''𦾱''𦾱''𧃒''ð§ƒÂ’''𧏊''ð§ÂŠ''ð¯§Â…''𧙧''ð¯§Â‹''𧢮''ð¯§ÂŒ''𧥦''ð¯§Â“''𧲨''ï«—''ð§»Â“''ð¯§Â˜''𧼯''𨗒''ð¨—Â’''𨗭''𨗭''𨜮''𨜮''𨯺''𨯺''𨵷''𨵷''𩅅''ð©…Â…''ð¯ Âœ''ð©‡ÂŸ''𩈚''ð©ˆÂš''𩐊''ð©ÂŠ''𩒖''ð©’–''ð¯¨Â''ð©–¶''ð¯¨Â‰''𩬰''ð¯¨Â''ðªƒÂŽ''ð¯¨Â’''ðª„Â…''ð¯¨Â“''ðªˆÂŽ''ð¯¨Â”''ðªŠÂ‘''ð¯¢Â''ðªŽÂ’''ð¯¨Â''ðª˜Â€''℃''°C''℉''°F''ℇ''Æ''â„»''FAX''â„•''N''â„–''No''â„š''Q''₨''Rs''ð“''T''â„¡''TEL''ð”''U''ð–''W''â‚©''W̵''ð—''X''Â¥''Y̵''ðš²''Λ''ðšµ''Ξ''â„¿''Π''ϲ''c''Ï’''Y''ðš½''Φ''ðš¿''Ψ''Ñ£''Ь̵''ਃ''ঃ''ಃ''à°ƒ''່''่''់''่''້''้''໊''๊''໋''๋''៕''๚''៚''๛''ÑŠ''ˉb''៙''à¹''೧''౧''૨''२''೨''౨''à«©''३''૪''४''à«®''८''೯''౯''а''a''á''b''á–¯''b''Ñ''c''Ô''d''ᑯ''d''е''e''Ó™''Ç''ε''É›''Ñ”''É›''Ö„''f''Ö''g''Ò»''h''Õ°''h''á‚''h''á²''hÌ”''ι''i''Ñ–''i''Ꭵ''i''ј''j''Õµ''j''á—°''m''Õ¸''n''η''nÌ©''à°‚''o''ಂ''o''à´‚''o''०''o''੦''o''૦''o''à¹''o''à»''o''ο''o''о''o''Ö…''o''á€''o''Ï''p''Ñ€''p''á´©''á´˜''Õ£''q''κ''ĸ''к''ĸ''á´¦''r''г''r''Ñ•''s''Ï…''u''Õ½''u''ν''v''ѵ''v''Ꮃ''w''á—¯''w''Ñ…''x''á•''x''у''y''Ꭹ''y''Ó¡''Ê’''ჳ''Ê’''Ï©''ƨ''ÑŒ''Æ…''Ñ‹''Æ…i''É‘''α''Õ®''δ''á•·''δ''п''Ï€''ɸ''φ''Ñ„''φ''Ê™''в''Éœ''з''á´''м''Êœ''н''É¢''Ô''á´›''Ñ‚''á´™''Ñ''ઽ''ऽ''à«''à¥''à«‚''ू''à©‹''ॆ''à©''à¥''à«''à¥''à´‰''உ''à´œ''à®''à´£''ண''à´´''à®´''à´¿''ி''àµ''ூ''ಅ''à°…''ಆ''à°†''ಇ''à°‡''ಒ''à°’''ಓ''ఒౕ''ಜ''à°œ''ಞ''à°ž''ಣ''à°£''à°¥''à°§Ö¼''ಯ''à°¯''à° ''à°°Ö¼''ಱ''à°±''ಲ''à°²''ඌ''à´¨àµà´¨''ஶ''à´¶''ຈ''จ''ບ''บ''ປ''ป''àº''à¸''ພ''พ''ຟ''ฟ''àº''ย''។''ฯ''áž·''ิ''ី''ี''áž¹''ึ''ឺ''ื''ຸ''ุ''ູ''ู''á—…''A''á’''J''ᕼ''H''á¯''V''á‘­''P''á—·''B''ヘ''ã¸''ð‘''ðŽÂ‚''ð“''ðŽÂ“''𒀸''ðŽÂš''á…³''一''Ç€''丨''á…µ''丨''Ꭺ''A''á´''B''áŸ''C''á—ž''D''Ꭼ''E''á–´''F''á€''G''Ꮋ''H''Ꭻ''J''á¦''K''áž''L''Ꮇ''M''á¢''P''á–‡''R''á•''S''á™''V''áƒ''Z');
+
+</body>
+</html>
+
diff --git a/layout/generic/crashtests/390417.html b/layout/generic/crashtests/390417.html
new file mode 100644
index 0000000000..1e95cbd310
--- /dev/null
+++ b/layout/generic/crashtests/390417.html
@@ -0,0 +1,17 @@
+<html>
+<head><style>
+ div:first-line {}
+</style></head>
+<body>
+<div>
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b
+
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/390762-1.html b/layout/generic/crashtests/390762-1.html
new file mode 100644
index 0000000000..d4b218d1ad
--- /dev/null
+++ b/layout/generic/crashtests/390762-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<script>
+function boom()
+{
+ var s = document.getElementById("s");
+ var t = document.getElementById("t");
+
+ s.remove();
+ t.style.display = "none";
+}
+</script>
+
+<style id="s">
+.float { float: right; height: 1em; }
+</style>
+
+<style>
+#t { border: 1px solid green; }
+</style>
+
+</head>
+
+<body onload="boom();">
+<div style="column-count: 2;"><div id="t" class="float"></div></div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/391053-1.xhtml b/layout/generic/crashtests/391053-1.xhtml
new file mode 100644
index 0000000000..7a971bcd1b
--- /dev/null
+++ b/layout/generic/crashtests/391053-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<div>
+ <select>
+ <option style="margin: 100%;">A</option>
+ <optgroup label="outgroup" style="padding: 10%;">
+ <option style="padding: 10%;">M</option>
+ </optgroup>
+ </select>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/391894-1.html b/layout/generic/crashtests/391894-1.html
new file mode 100644
index 0000000000..d69a95573b
--- /dev/null
+++ b/layout/generic/crashtests/391894-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style>
+
+#q:first-line { }
+
+</style>
+
+</head>
+<body style="column-width: 0em">
+
+<div id="q">a b c<span style="float: right;"></span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/392698-1.html b/layout/generic/crashtests/392698-1.html
new file mode 100644
index 0000000000..9a2d4f237f
--- /dev/null
+++ b/layout/generic/crashtests/392698-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript">
+window.onload = function()
+{
+ document.getElementById("i")
+ .setAttribute("width", 100);
+};
+ </script>
+</head>
+<body>
+<iframe src="data:text/html,&lt;div style='position:fixed'&gt;foo&lt;/div&gt;"
+ id="i" width="75" height="150"></iframe>
+</body>
+</html>
diff --git a/layout/generic/crashtests/393758-1.xhtml b/layout/generic/crashtests/393758-1.xhtml
new file mode 100644
index 0000000000..4c90a55f2e
--- /dev/null
+++ b/layout/generic/crashtests/393758-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+span { letter-spacing: 1em; unicode-bidi: bidi-override; }
+</style>
+</head>
+<body>
+<table><tbody><tr><td><span>×”G</span></td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/generic/crashtests/393906-1.html b/layout/generic/crashtests/393906-1.html
new file mode 100644
index 0000000000..22fcfa387f
--- /dev/null
+++ b/layout/generic/crashtests/393906-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ document.getElementById("div").setAttribute("dir", "rtl");
+}
+</script>
+<body onload="boom()">
+<div id="div" style="width: 0;"> Foo a bar baz</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/393923-1.html b/layout/generic/crashtests/393923-1.html
new file mode 100644
index 0000000000..44c886d7db
--- /dev/null
+++ b/layout/generic/crashtests/393923-1.html
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+ div::first-letter { color: magenta; }
+</style>
+</head>
+
+<body style="direction: rtl;">
+
+<div>
+ AB</div>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/393956-1.html b/layout/generic/crashtests/393956-1.html
new file mode 100644
index 0000000000..b9d79489f6
--- /dev/null
+++ b/layout/generic/crashtests/393956-1.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<style>
+body {
+ column-count: 2;
+}
+#x {
+ height: 20px;
+}
+#y {
+ height: 80px;
+}
+</style>
+
+<script>
+function boom()
+{
+ document.getElementById("x").style.content = "'0'";
+}
+</script>
+</head>
+
+<body onload="boom();"><div id="x"><div id="y"></div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/393956-2.html b/layout/generic/crashtests/393956-2.html
new file mode 100644
index 0000000000..9162e2a6d6
--- /dev/null
+++ b/layout/generic/crashtests/393956-2.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+<style>
+body {
+ height: 40px;
+ column-count: 2;
+}
+#x {
+ height: 20px;
+}
+#y {
+ height: 80px;
+}
+</style>
+
+<script>
+function boom()
+{
+ document.getElementById("x").style.content = "'0'";
+}
+</script>
+</head>
+
+<body onload="boom();"><div id="x"><div id="y"></div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/393956-3.html b/layout/generic/crashtests/393956-3.html
new file mode 100644
index 0000000000..c63f0024f3
--- /dev/null
+++ b/layout/generic/crashtests/393956-3.html
@@ -0,0 +1,11 @@
+<html>
+<head><style>
+body { column-count: 1; }
+#x { height: 1px; }
+#y { height: 2px; }
+
+</style></head>
+
+<body><div id="x"><div id="y"></div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/393956-4.html b/layout/generic/crashtests/393956-4.html
new file mode 100644
index 0000000000..ea88513a8c
--- /dev/null
+++ b/layout/generic/crashtests/393956-4.html
@@ -0,0 +1,11 @@
+<html>
+<head><style>
+body { column-count: 2; }
+#x { height: 1px; }
+#y { height: 2px; }
+
+</style></head>
+
+<body><div id="x"><div id="y"></div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/394237-1.html b/layout/generic/crashtests/394237-1.html
new file mode 100644
index 0000000000..8fc1bc8b09
--- /dev/null
+++ b/layout/generic/crashtests/394237-1.html
@@ -0,0 +1,38 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+
+body {
+ height: 50px;
+ column-count: 2;
+}
+.container {
+ height: 10px;
+}
+.overflow {
+ height: 100px;
+}
+.bb {
+ border-bottom: solid 4px magenta;
+}
+
+</style>
+
+<script>
+
+function boom()
+{
+ document.getElementById("x").setAttribute("class", "");
+ document.getElementById("y").setAttribute("class", "bb");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+ <div class="container"><div class="overflow" id="x"></div></div>
+ <div id="y"></div>
+ <div class="container"><div class="overflow"></div></div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/394818-1.html b/layout/generic/crashtests/394818-1.html
new file mode 100644
index 0000000000..a6a6fdba8e
--- /dev/null
+++ b/layout/generic/crashtests/394818-1.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait"><head>
+</head><body>
+<iframe></iframe><iframe></iframe>
+<script>
+window.frames[0].focus();
+function doe() {
+document.body.setAttribute('onbeforecopy','document.removeChild(document.documentElement)');
+document.body.setAttribute('style','display:none');
+document.documentElement.removeAttribute("class");
+}
+setTimeout(doe, 100);
+</script>
+</body></html>
diff --git a/layout/generic/crashtests/394818-2.html b/layout/generic/crashtests/394818-2.html
new file mode 100644
index 0000000000..6aa00e2753
--- /dev/null
+++ b/layout/generic/crashtests/394818-2.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait"><head><script>
+ function load() {
+ window.frames[0].focus();
+ setTimeout("load2();", 100);
+ }
+ function load2() {
+ document.body.setAttribute('style','display: inline');
+ document.documentElement.removeAttribute("class");
+ }
+ function beforeCopy() {
+ document.removeChild(document.documentElement);
+ }
+</script></head>
+<body onload="load();"
+ onbeforecopy="beforeCopy();"><iframe></iframe></body>
+</html>
diff --git a/layout/generic/crashtests/394820-1.html b/layout/generic/crashtests/394820-1.html
new file mode 100644
index 0000000000..0c012877ce
--- /dev/null
+++ b/layout/generic/crashtests/394820-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var d = document.getElementById("d");
+ d.style.direction = "rtl";
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id=d style="white-space: pre;">
+e
+
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/395316-1.html b/layout/generic/crashtests/395316-1.html
new file mode 100644
index 0000000000..bf3929ab2b
--- /dev/null
+++ b/layout/generic/crashtests/395316-1.html
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("float").style.cssFloat = "";
+}
+</script>
+</head>
+<body onload="boom();">
+<div style="column-count: 2; width: 3ch;"><span><span id="float" style="float: right;">za za za za za za za za</span></span></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/395450-1.xhtml b/layout/generic/crashtests/395450-1.xhtml
new file mode 100644
index 0000000000..79510267ba
--- /dev/null
+++ b/layout/generic/crashtests/395450-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+
+<head>
+<style>
+
+/*
+ This testcase uses [class~="foo"] instead of .foo to work around bug 276267
+ (see bug 379178 comment 78)
+*/
+
+[class~="abs"] { position: absolute; }
+[class~="marg"] { margin: 1em; }
+[class="noheight"] { height: 0; }
+
+</style>
+</head>
+
+<body>
+
+<math:mrow class="noheight">
+ <span class="abs">
+ <math:mroot class="abs marg" />
+ <span class="abs" />
+ </span>
+</math:mrow>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/397007-1.html b/layout/generic/crashtests/397007-1.html
new file mode 100644
index 0000000000..1af30383fe
--- /dev/null
+++ b/layout/generic/crashtests/397007-1.html
@@ -0,0 +1,37 @@
+<html>
+<head>
+<script type="text/javascript">
+function boom()
+{
+ var img = document.createElement("img");
+ document.getElementById("g").appendChild(img);
+ img.width = 1;
+}
+
+</script>
+
+<style type="text/css">
+
+.margin {
+ margin: 1em 0;
+}
+
+.dd:before {
+ white-space: pre;
+ line-height: 0;
+ content: "b";
+}
+
+</style>
+
+</head>
+
+<body onload="boom();">
+<div style="column-count: 2;">
+ X
+ <div class="margin">y</div>
+ <span><span id="g"></span>&shy;<div class="margin"></div><span class="dd"><div></div></span></span>
+</div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/397187-1.html b/layout/generic/crashtests/397187-1.html
new file mode 100644
index 0000000000..7227a48238
--- /dev/null
+++ b/layout/generic/crashtests/397187-1.html
@@ -0,0 +1,32 @@
+<html class="reftest-wait">
+<head>
+<style type="text/css">
+
+.a:first-letter { float: right; }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.style.overflow = "auto";
+ document.body.className = "a";
+
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ var span = document.createElement("span");
+ document.body.appendChild(span);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();">&#65207;
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/397844-1.xhtml b/layout/generic/crashtests/397844-1.xhtml
new file mode 100644
index 0000000000..3ad9b8464b
--- /dev/null
+++ b/layout/generic/crashtests/397844-1.xhtml
@@ -0,0 +1,55 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<style type="text/css">
+.pad { padding: 3ch; }
+.fl:first-letter { }
+</style>
+
+
+<style id="s" type="text/css"></style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("td").nextSibling.splitText(11);
+ document.getElementById("td").style.padding = "11px";
+
+ setTimeout(boom2, 10);
+}
+
+function boom2()
+{
+ document.body.style.counterReset = "chicken";
+
+ setTimeout(boom3, 10);
+}
+
+function boom3()
+{
+ document.getElementById("s").textContent = "#xxx { }";
+
+ setTimeout(boom4, 10);
+}
+
+function boom4()
+{
+ document.getElementById("td").style.padding = "";
+ setTimeout(boom5, 10);
+}
+
+function boom5()
+{
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body style="width: 10em;" dir="rtl" onload="boom();">
+
+<div class="pad fl"><span><td class="pad fl" id="td"><span>abcd</span></td>ghi jklmnop 2 qrs tuvwxy</span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/397844-2.xhtml b/layout/generic/crashtests/397844-2.xhtml
new file mode 100644
index 0000000000..d97600812c
--- /dev/null
+++ b/layout/generic/crashtests/397844-2.xhtml
@@ -0,0 +1,55 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<style type="text/css">
+.pad { padding: 3ch; }
+.fl:first-letter { }
+td { border: 1px solid green }
+</style>
+
+<style id="s" type="text/css"></style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("td").nextSibling.splitText(6);
+ document.getElementById("td").style.padding = "2px";
+
+ setTimeout(boom2, 10);
+}
+
+function boom2()
+{
+ document.body.style.counterReset = "chicken";
+
+ setTimeout(boom3, 10);
+}
+
+function boom3()
+{
+ document.getElementById("s").textContent = "#xxx { }";
+
+ setTimeout(boom4, 10);
+}
+
+function boom4()
+{
+ document.getElementById("td").style.padding = "";
+ setTimeout(boom5, 10);
+}
+
+function boom5()
+{
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body style="font-family: monospace; width: 14ch; border: 1px solid orange;" dir="rtl" onload="boom();">
+
+<div class="pad fl"><span><td class="pad fl" id="td"><span>abcd</span></td>ghi jk 2</span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/397852-1.xhtml b/layout/generic/crashtests/397852-1.xhtml
new file mode 100644
index 0000000000..cafb82d9c4
--- /dev/null
+++ b/layout/generic/crashtests/397852-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.getElementById('o').style.overflow = 'auto';">
+
+<td></td><div id="o"><table style="margin: 100%;"></table></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/398181-1.html b/layout/generic/crashtests/398181-1.html
new file mode 100644
index 0000000000..59df1ced21
--- /dev/null
+++ b/layout/generic/crashtests/398181-1.html
@@ -0,0 +1,10 @@
+<html style="min-width: max-content;">
+ <head>
+ </head>
+ <body>
+ <div>
+ <span style="margin: 0 100%; display: inline-block;"></span>
+ t
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/398181-2.html b/layout/generic/crashtests/398181-2.html
new file mode 100644
index 0000000000..a281c0e983
--- /dev/null
+++ b/layout/generic/crashtests/398181-2.html
@@ -0,0 +1,11 @@
+<html style="min-width: max-content;">
+ <head>
+ </head>
+ <body>
+ <div>
+ <span style="margin: 0 100%; display: inline-block;"></span>
+ t
+ <div style="float: left">foo</div>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/398322-1.html b/layout/generic/crashtests/398322-1.html
new file mode 100644
index 0000000000..b70051695d
--- /dev/null
+++ b/layout/generic/crashtests/398322-1.html
@@ -0,0 +1,17 @@
+<html class="reftest-paged">
+<head>
+<title>Testcase bug - Crash [@ nsFrameList::InsertFrame] on print preview with positioned elements and page-break-before</title>
+</head>
+
+<body>
+<div style="position: relative; page-break-before: always;">
+ <div style="display: list-item;"></div>
+ <div style="position: absolute;">
+ <div style=" position: absolute;">
+ <div style="page-break-before: always; height: 100px;"></div>
+ </div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/398322-2.html b/layout/generic/crashtests/398322-2.html
new file mode 100644
index 0000000000..9d2f13199e
--- /dev/null
+++ b/layout/generic/crashtests/398322-2.html
@@ -0,0 +1,12 @@
+<html class="reftest-paged">
+<head>
+<style>
+q::after { content:"anonymous text"; text-transform: uppercase;}
+</style>
+</head>
+<body>
+<div style="position: absolute; width: 50px; column-count: 2;">
+ <div style="position: absolute;"> <q> </q> </div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/398332-1.html b/layout/generic/crashtests/398332-1.html
new file mode 100644
index 0000000000..3c15ad9d4c
--- /dev/null
+++ b/layout/generic/crashtests/398332-1.html
@@ -0,0 +1,19 @@
+<html><head>
+<title>Testcase bug - Crash [@ nsHTMLReflowState::GetNearestContainingBlock] with display: -moz-box, generated content, positioning and fieldset</title>
+<style>
+small::before { content: "m m";}
+strike::before { content: "m m";}
+</style>
+</head>
+<body>
+<div style="display: -moz-box;">
+ <strike style="overflow: clip; position: absolute;"></strike>
+ <fieldset style="position: relative;">
+ <strike style="overflow: clip; position: absolute;"><small style="position: relative;">
+ <span style="position: absolute;"></span>
+ </small>
+ </strike>
+</fieldset>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/398332-2.html b/layout/generic/crashtests/398332-2.html
new file mode 100644
index 0000000000..93ca863c88
--- /dev/null
+++ b/layout/generic/crashtests/398332-2.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+<script>
+function removestyles(i){
+document.getElementsByTagName('*')[5].removeAttribute('style');
+document.body.offsetHeight;
+document.getElementsByTagName('*')[6].removeAttribute('style');
+document.body.offsetHeight;
+document.getElementsByTagName('*')[7].removeAttribute('style');
+}
+
+setTimeout(removestyles,300);
+</script>
+
+</head>
+<body>
+<div style="position: relative; width: 500px;">
+ <span style="position: absolute; column-count: 2;">
+ <span style=" position: absolute;">
+ <span style=" display: inline-block; ">m</span>
+ <input>
+ <input style="position: fixed;">m
+ </span>
+ </span>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/398332-3.html b/layout/generic/crashtests/398332-3.html
new file mode 100644
index 0000000000..991aa6d3dd
--- /dev/null
+++ b/layout/generic/crashtests/398332-3.html
@@ -0,0 +1,4 @@
+<marquee style="position: relative; right: 20%;">"Ë”Öqü®Û;<span style="position: relative; word-spacing: -100px;"><span style="position: absolute;">
+<style>span::before { content:"before textbefore textbefore textbefore textbefore textbefore text"; }</style>
+
+
diff --git a/layout/generic/crashtests/399407-1.xhtml b/layout/generic/crashtests/399407-1.xhtml
new file mode 100644
index 0000000000..77808f66dc
--- /dev/null
+++ b/layout/generic/crashtests/399407-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<style id="s">
+ .container {
+ height: 20px;
+ }
+ .overflow {
+ height: 30px;
+ }
+ body {
+ width: 300px;
+ column-width: 100px;
+ }
+ </style>
+
+<body>
+ <div class="container">
+ <div class="overflow"></div>
+ </div>
+ <div class="container">
+ <div class="overflow"></div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/399412-1.html b/layout/generic/crashtests/399412-1.html
new file mode 100644
index 0000000000..1b3bb889a2
--- /dev/null
+++ b/layout/generic/crashtests/399412-1.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+
+<style id="s">
+.container {
+ height: 30px;
+}
+</style>
+
+<style>
+.overflow {
+ height: 150px;
+ border: 1px silver solid;
+}
+body {
+ height: 60px;
+ width: 300px;
+ column-width: 50px;
+ column-gap: 1px;
+ column-fill: auto;
+}
+</style>
+
+</head>
+
+<body onload="s=document.getElementById('s'); s.parentNode.removeChild(s);">
+
+<div class="container"><div class="overflow"></div></div>
+<div class="container"><div class="overflow"></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/399843-1.html b/layout/generic/crashtests/399843-1.html
new file mode 100644
index 0000000000..429f9bc9f9
--- /dev/null
+++ b/layout/generic/crashtests/399843-1.html
@@ -0,0 +1,64 @@
+<html class="reftest-wait">
+<head>
+<style>
+
+ #colset {
+ width: 300pt;
+ height: 2in;
+ column-count: 3;
+ }
+
+ .container {
+ height: 1in;
+ }
+
+ .b6 {
+ height: 10in;
+ }
+
+ .c3 {
+ position: absolute;
+ }
+
+ .c4 {
+ height: 3in;
+ }
+
+</style>
+
+<script>
+
+function boom()
+{
+ document.getElementById("x").setAttribute("class", "c4");
+ setTimeout(boom2, 10);
+}
+
+function boom2()
+{
+ document.getElementById("x").setAttribute("class", "");
+ setTimeout(boom3, 10);
+}
+
+function boom3()
+{
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="colset">
+ <div id="x"></div>
+ <div class="container">
+ <div class="b6"></div>
+ <div>
+ <div class="c3"></div>
+ </div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/400078-1.html b/layout/generic/crashtests/400078-1.html
new file mode 100644
index 0000000000..21c00f0395
--- /dev/null
+++ b/layout/generic/crashtests/400078-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+html, body, table, tbody, tr, td, #colset, #e, #s { height: 100%; }
+
+#colset { column-count: 2; column-width: 200px; border: 2px solid black; text-indent: 500px; }
+
+#s { display: inline-block; width: 30px; border: 1px solid red; }
+
+</style>
+</head>
+
+<body>
+
+<table><tbody><tr><td><div id="colset">XXX<span id="s"></span><div id="e"></div></div></td></tr></tbody></table>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/400190.html b/layout/generic/crashtests/400190.html
new file mode 100644
index 0000000000..716c053efe
--- /dev/null
+++ b/layout/generic/crashtests/400190.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+body {
+ position: fixed;
+ font-family: monospace;
+ column-width: 10px;
+ border: 2px solid #aaa;
+}
+
+#padded {
+ padding-top: 40px;
+ padding-bottom: 40px;
+}
+
+#x {
+ position: absolute;
+ top: 0;
+ left: 0;
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+</style>
+</head>
+
+<body>
+
+<div id="padded"></div>
+
+<div id="x">
+
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+x
+
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/400223-1.html b/layout/generic/crashtests/400223-1.html
new file mode 100644
index 0000000000..022d7a6d71
--- /dev/null
+++ b/layout/generic/crashtests/400223-1.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<style type="text/css">
+
+ #colset {
+ height: 2in;
+ column-count: 3;
+ }
+ #container {
+ position: relative;
+ }
+ #overflow {
+ position: absolute;
+ height: 5in;
+ }
+
+</style>
+</head>
+
+<body onload="document.getElementById('container').style.counterIncrement = 'foo';">
+<div id="colset"><div id="container"><div id="overflow"></div></div></div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/400232-1.html b/layout/generic/crashtests/400232-1.html
new file mode 100644
index 0000000000..7373dd55f6
--- /dev/null
+++ b/layout/generic/crashtests/400232-1.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+
+<body>
+
+<div style="column-count: 2; overflow: auto; height: 10px;"><br><div style="float: right; height: 15px;"></div></div>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/400244-1.html b/layout/generic/crashtests/400244-1.html
new file mode 100644
index 0000000000..b8d91d6edc
--- /dev/null
+++ b/layout/generic/crashtests/400244-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+ #colset {
+ column-count: 2;
+ column-gap: 0;
+ border: 2px solid black;
+ height: 12em;
+ }
+
+ #b {
+ margin: 10em 0pt;
+ }
+
+ #i {
+ margin: 12em 0pt;
+ float: left;
+ -moz-appearance: radio-small;
+ }
+
+</style>
+</head>
+
+<body onload="document.getElementById('i').style.padding = '1em 0';">
+
+<div id="colset"><br id="b"><div id="i"></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/400768-1.xhtml b/layout/generic/crashtests/400768-1.xhtml
new file mode 100644
index 0000000000..1b390a291e
--- /dev/null
+++ b/layout/generic/crashtests/400768-1.xhtml
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.getElementById('pp').contentHeight;">
+
+<foreignObject>
+ <xul:prefpane id="pp"/>
+</foreignObject>
+
+</svg>
diff --git a/layout/generic/crashtests/400768-2.xhtml b/layout/generic/crashtests/400768-2.xhtml
new file mode 100644
index 0000000000..e7e5a51697
--- /dev/null
+++ b/layout/generic/crashtests/400768-2.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body onload="document.getElementById('pp').contentHeight;">
+<svg:svg><svg:foreignObject xmlns="http://www.w3.org/2000/svg"><xul:prefpane id="pp"/></svg:foreignObject></svg:svg>
+</body>
+</html>
diff --git a/layout/generic/crashtests/401042-2.html b/layout/generic/crashtests/401042-2.html
new file mode 100644
index 0000000000..ba4bb12e48
--- /dev/null
+++ b/layout/generic/crashtests/401042-2.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<style>
+pre::first-letter { float: left; }
+</style>
+<pre><span>//</span></pre>
diff --git a/layout/generic/crashtests/402380-1.html b/layout/generic/crashtests/402380-1.html
new file mode 100644
index 0000000000..3f9e21ec00
--- /dev/null
+++ b/layout/generic/crashtests/402380-1.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+
+div::first-letter { color: magenta; }
+span:before { content: "\"" "This "; }
+
+</style>
+</head>
+<body style="width: 1em;" onload="document.getElementById('div').style.direction = 'rtl';">
+<div id="div"><span>is text</span></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/402380-2.html b/layout/generic/crashtests/402380-2.html
new file mode 100644
index 0000000000..f3b0217404
--- /dev/null
+++ b/layout/generic/crashtests/402380-2.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style type="text/css">
+
+div::first-letter { color: green; }
+span:before { content: open-quote "This "; }
+span:after { content: close-quote; }
+
+</style>
+</head>
+
+<body style="font-family: monospace; width: 7ch; border: 1px solid orange;"
+ onload="document.getElementById('div').style.direction = 'rtl';">
+
+<div id="div"><span>is text</span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/402872-1.html b/layout/generic/crashtests/402872-1.html
new file mode 100644
index 0000000000..f23a470600
--- /dev/null
+++ b/layout/generic/crashtests/402872-1.html
@@ -0,0 +1,3 @@
+<table>
+<thead style="float: left; margin-top: 100px; margin-bottom: 9999999999px;">
+
diff --git a/layout/generic/crashtests/402872-2.html b/layout/generic/crashtests/402872-2.html
new file mode 100644
index 0000000000..e2a6026ff8
--- /dev/null
+++ b/layout/generic/crashtests/402872-2.html
@@ -0,0 +1,2 @@
+<fieldset style="float: left;">
+<legend style="min-height: 999999999px;">
diff --git a/layout/generic/crashtests/403004.html b/layout/generic/crashtests/403004.html
new file mode 100644
index 0000000000..a1d04dff32
--- /dev/null
+++ b/layout/generic/crashtests/403004.html
@@ -0,0 +1,3 @@
+<meta http-equiv="Content-Type" content="text/html; charset=windows-1256" />
+<div style="text-transform: uppercase;">
+<a href="httpdisabled://news.maktoob.com"> ÃÎÈÇÑ ãßÊæÈ</a></div>
diff --git a/layout/generic/crashtests/403143-1.html b/layout/generic/crashtests/403143-1.html
new file mode 100644
index 0000000000..972d887687
--- /dev/null
+++ b/layout/generic/crashtests/403143-1.html
@@ -0,0 +1,19 @@
+<html>
+
+<head>
+<script>
+function boom()
+{
+ document.getElementById("i1").style.counterIncrement = "chicken";
+ document.getElementById("i3").style.counterIncrement = "chicken";
+}
+</script>
+</head>
+
+<body onload="boom();" style="overflow: auto; display: -moz-box;">
+<img id="i1" style="width: 30px; float: right;">
+<img id="i2" style="margin: 0 100%; padding: 0 60px;">
+<img id="i3" style="margin: 100% 0; float: right;">
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/403576-1.html b/layout/generic/crashtests/403576-1.html
new file mode 100644
index 0000000000..4b376af6a1
--- /dev/null
+++ b/layout/generic/crashtests/403576-1.html
@@ -0,0 +1,5 @@
+<html>
+<body style="width: 11px;">
+<fieldset style="display: table-column-group;"></fieldset>
+</body>
+</html>
diff --git a/layout/generic/crashtests/404140-1.html b/layout/generic/crashtests/404140-1.html
new file mode 100644
index 0000000000..b3d6e9d2d8
--- /dev/null
+++ b/layout/generic/crashtests/404140-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+
+</head><body onload="document.body.style.height = '2px';" style="float: right; height: 2px; column-width: 0pt;">
+<div style="overflow: clip;">a b</div>
+</body></html>
diff --git a/layout/generic/crashtests/404146-1.html b/layout/generic/crashtests/404146-1.html
new file mode 100644
index 0000000000..ad5bc53e99
--- /dev/null
+++ b/layout/generic/crashtests/404146-1.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+<style>
+
+.colset {
+ column-count: 2;
+ column-width: 50px;
+ float: left;
+ border: 2px solid black;
+ height: 10em;
+ font: 12px monospace;
+}
+
+#t {
+ display: inline-block;
+ background: lightgreen;
+ height: 7em;
+ width: 29px;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('t').style.width = '30px';">
+
+<div class="colset">The quick <span><div id="t"></div>brown</span></div>
+
+</body></html>
diff --git a/layout/generic/crashtests/404204-1.html b/layout/generic/crashtests/404204-1.html
new file mode 100644
index 0000000000..2437f4c529
--- /dev/null
+++ b/layout/generic/crashtests/404204-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+
+<body style="font-variant: small-caps;">&#x202B; </body>
+
+</html>
diff --git a/layout/generic/crashtests/404215-1.html b/layout/generic/crashtests/404215-1.html
new file mode 100644
index 0000000000..583395b7e1
--- /dev/null
+++ b/layout/generic/crashtests/404215-1.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+<style>
+
+#colset {
+ column-count: 3;
+}
+.a {
+ height: 3in;
+}
+.b {
+ height: 24pt;
+}
+.c {
+ height: 336pt;
+}
+.d {
+ position: absolute;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('a').className = '';">
+
+<div id="colset"><div><div class="a" id="a"></div><div class="b"><div class="c"></div><div class="d"></div></div></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/404215-2.html b/layout/generic/crashtests/404215-2.html
new file mode 100644
index 0000000000..486255ca11
--- /dev/null
+++ b/layout/generic/crashtests/404215-2.html
@@ -0,0 +1,37 @@
+<html>
+<head><style>
+/* Sets of heights that trigger crash:
+ 100px/50px/51+px
+ 100px/30px/74+px
+ Get only an assert unless you set ".d { position: absolute; }".
+
+ Trigger hang (separate issue, absolute not needed):
+ 10px/10px/9999px
+ 10px/10px/999999px --> "bad height" notreached
+*/
+#colset { width: 200px;
+ column-count: 3; }
+#a { height: 100px; }
+#b { height: 50px; }
+#c { height: 51px; }
+#d { position: absolute; }
+</style>
+<script>
+ function boom() {
+ document.getElementById('a').style.height = 'auto';
+ }
+</script>
+</head>
+<!-- Removing whitespace in body for simpler frame trees -->
+<body onload="setTimeout('boom()', 1000)"
+ ><div id="colset"
+ ><div
+ ><div id="a"></div
+ ><div id="b"
+ ><div id="c"></div
+ ><div id="d"></div
+ ></div
+ ></div
+ ></div
+></body>
+</html>
diff --git a/layout/generic/crashtests/404215-3.html b/layout/generic/crashtests/404215-3.html
new file mode 100644
index 0000000000..0ebf68131a
--- /dev/null
+++ b/layout/generic/crashtests/404215-3.html
@@ -0,0 +1,32 @@
+<html>
+<head><style>
+/* Sets of heights that trigger crash:
+ 100px/50px/51+px
+ 100px/30px/74+px
+ Get only an assert unless you set ".d { position: absolute; }".
+
+ Trigger hang (separate issue, absolute not needed):
+ 10px/10px/9999px
+ 10px/10px/999999px --> "bad height" notreached
+*/
+#colset { width: 200px;
+ column-count: 3; }
+#a { height: 10px; }
+#b { height: 10px; }
+#c { height: 999999px; }
+
+</style>
+</head>
+<!-- Removing whitespace in body for simpler frame trees -->
+<body
+ ><div id="colset"
+ ><div
+ ><div id="a"></div
+ ><div id="b"
+ ><div id="c"></div
+ ><div id="d"></div
+ ></div
+ ></div
+ ></div
+></body>
+</html>
diff --git a/layout/generic/crashtests/404219-1.html b/layout/generic/crashtests/404219-1.html
new file mode 100644
index 0000000000..6dcb2707a4
--- /dev/null
+++ b/layout/generic/crashtests/404219-1.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+<style>
+ body {
+ /* Standize line-height, because the default varies by platform */
+ line-height: 50px;
+ }
+ div#a {
+ width: 3em;
+ column-count: 2;
+ column-gap: 0px;
+ background: lightgreen;
+ }
+ div#b {
+ float: left;
+ background: lightblue;
+ }
+ div#c {
+ height: 100px;
+ background: orange;
+ }
+</style>
+</head>
+<body>
+ <div id="a">
+ <div id="b"><br/><br/>b</div>
+ <div id="c">c c c</div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/404219-2.html b/layout/generic/crashtests/404219-2.html
new file mode 100644
index 0000000000..8ce12b4ccd
--- /dev/null
+++ b/layout/generic/crashtests/404219-2.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+<style>
+ body {
+ /* Standize line-height, because the default varies by platform */
+ line-height: 50px;
+ }
+ div#a {
+ width: 3em;
+ column-count: 2;
+ column-gap: 0px;
+ background: lightgreen;
+ }
+ div#b {
+ height: 100px;
+ float: left;
+ background: lightblue;
+ }
+ div#c {
+ height: 100px;
+ background: orange;
+ }
+</style>
+</head>
+<body>
+ <div id="a">
+ <div id="b"><br/><br/>b</div>
+ <div id="c">c c c</div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/404624.html b/layout/generic/crashtests/404624.html
new file mode 100644
index 0000000000..0e6bae1133
--- /dev/null
+++ b/layout/generic/crashtests/404624.html
@@ -0,0 +1,7 @@
+<html><head>
+<style>div::first-letter { letter-spacing: 20px;}</style>
+</head>
+<body>
+<div>mm</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/406137.html b/layout/generic/crashtests/406137.html
new file mode 100644
index 0000000000..75337de72c
--- /dev/null
+++ b/layout/generic/crashtests/406137.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <title>redhat.com | Home</title>
+ <style>
+ #navWrap:after { content: "."; display: block; clear: both; }
+ </style>
+ </head>
+ <body>
+ <div id="navWrap">
+ <div style="float: left;">
+ foo
+ </div>
+ </div>
+ <img src="../../../testing/crashtest/images/tree.gif" style="float: right;">
+ </body>
+</html>
diff --git a/layout/generic/crashtests/406380.html b/layout/generic/crashtests/406380.html
new file mode 100644
index 0000000000..278ddec9c8
--- /dev/null
+++ b/layout/generic/crashtests/406380.html
@@ -0,0 +1,12 @@
+<html style="direction: rtl;">
+<body onload="document.getElementById('i').style.fontSize = '10em';">
+
+<div style="column-width: 60px;">
+a
+<div style="overflow: clip; white-space: pre;" id="i">
+b
+</div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/406902-1.html b/layout/generic/crashtests/406902-1.html
new file mode 100644
index 0000000000..13c96e6ae1
--- /dev/null
+++ b/layout/generic/crashtests/406902-1.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style type="text/css">
+
+ .container {
+ position: relative;
+ }
+ #colset {
+ column-count: 3;
+ column-gap: 0;
+ border: silver 2pt;
+ border-style: none solid;
+ }
+ .b1 {
+ position: absolute;
+ height: 336pt;
+ margin-left: 20pt;
+ }
+ .b2 {
+ position: absolute;
+ height: 192pt;
+ width: 25pt;
+ margin-left: 50pt;
+ }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ newDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ newDiv.setAttribute("class", "b1");
+ document.getElementById("f").appendChild(newDiv);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="colset"><div class="container"><div class="b2"></div><div id="f"></div></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/407009-1.xhtml b/layout/generic/crashtests/407009-1.xhtml
new file mode 100644
index 0000000000..62ded15624
--- /dev/null
+++ b/layout/generic/crashtests/407009-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body onload="document.getElementById('tree').removeAttribute('hidevscroll');">
+
+<select><xul:tree id="tree"/></select>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/408304-1.xhtml b/layout/generic/crashtests/408304-1.xhtml
new file mode 100644
index 0000000000..e0e09801d0
--- /dev/null
+++ b/layout/generic/crashtests/408304-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<fieldset><span><fieldset><legend><div ><div style="margin: 0pt 100%;"></div></div></legend></fieldset></span></fieldset>
+</body>
+</html>
diff --git a/layout/generic/crashtests/408602-1.html b/layout/generic/crashtests/408602-1.html
new file mode 100644
index 0000000000..66082d093b
--- /dev/null
+++ b/layout/generic/crashtests/408602-1.html
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body style="position: fixed;">
+
+<div style="position: absolute; column-width: 20em;"><div style="white-space: pre; float: right; height: 50px;"><div style="width: 100px; height: 12em;"></div>
+</div></div>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/408737-1.html b/layout/generic/crashtests/408737-1.html
new file mode 100644
index 0000000000..30e7ecd6b3
--- /dev/null
+++ b/layout/generic/crashtests/408737-1.html
@@ -0,0 +1,14 @@
+<html>
+<head><style>
+#colset { column-count: 1; }
+#short { height: 0px; }
+#tall { height: 10000px; }
+</style></head>
+<body>
+ <div id="colset">
+ <div id="short">
+ <div id="tall"></div>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/408737-2.html b/layout/generic/crashtests/408737-2.html
new file mode 100644
index 0000000000..4aff259302
--- /dev/null
+++ b/layout/generic/crashtests/408737-2.html
@@ -0,0 +1,14 @@
+<html>
+<head><style>
+#colset { column-count: 1; }
+#short { height: 10px; }
+#tall { height: 100000px; }
+</style></head>
+<body>
+ <div id="colset">
+ <div id="short">
+ <div id="tall"></div>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/408749-1.xhtml b/layout/generic/crashtests/408749-1.xhtml
new file mode 100644
index 0000000000..6f190c11bb
--- /dev/null
+++ b/layout/generic/crashtests/408749-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body style="column-count: 2;"><caption><div>foo<td></td></div></caption></body></html>
diff --git a/layout/generic/crashtests/408883-1.html b/layout/generic/crashtests/408883-1.html
new file mode 100644
index 0000000000..fab50fbf08
--- /dev/null
+++ b/layout/generic/crashtests/408883-1.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+
+<style type="text/css">
+
+body {
+ column-count: 2;
+ column-width: 100px;
+ height: 200px;
+ width: 400px;
+}
+.b {
+ height: 300px;
+}
+.f {
+ float: left;
+}
+.p {
+ padding: 0pt 200px;
+}
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var newDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var a = document.getElementById("a");
+ a.appendChild(newDiv);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();"><div class="f"><span class="p"></span></div><div id="a"><div class="b f"><input></div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/410198.html b/layout/generic/crashtests/410198.html
new file mode 100644
index 0000000000..f423fb32b5
--- /dev/null
+++ b/layout/generic/crashtests/410198.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<title>Gecko 1.9 crash with a.p. and inline c.b.</title>
+
+<p>- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+<span style="position: relative; background-color: #ddd;">- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+<span style="position: absolute; top: 0; left: 0; background-color: #f00;">AP</span>
+</span>
+</p>
diff --git a/layout/generic/crashtests/410228-1.html b/layout/generic/crashtests/410228-1.html
new file mode 100644
index 0000000000..b1bd3e0d04
--- /dev/null
+++ b/layout/generic/crashtests/410228-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+
+<body style="text-transform: uppercase; font-family: arial;">&zwnj;&szlig;</body>
+
+</html>
diff --git a/layout/generic/crashtests/410232-1.html b/layout/generic/crashtests/410232-1.html
new file mode 100644
index 0000000000..26a9c2c84c
--- /dev/null
+++ b/layout/generic/crashtests/410232-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+</head>
+
+<body>
+
+<div style="height: 17895694px;"></div>
+
+<div style="float: left; border: 2px solid red;"></div>
+
+<div style="float: left; border: 2px solid green;"></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/410595-1.html b/layout/generic/crashtests/410595-1.html
new file mode 100644
index 0000000000..0ee6e36c97
--- /dev/null
+++ b/layout/generic/crashtests/410595-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+
+<body><div style="padding: 12em 0; position: absolute; font-size: 10000000px;"><div style="position: absolute;"></div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/411213-1.html b/layout/generic/crashtests/411213-1.html
new file mode 100644
index 0000000000..5fc38ec84c
--- /dev/null
+++ b/layout/generic/crashtests/411213-1.html
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;">
+<body onload="document.getElementById('i').style.fontSize = '10em';">
+
+<div style="column-width: 60px;"><div id="i" style="overflow: clip; white-space: pre;">
+ b
+</div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/411213-2.xml b/layout/generic/crashtests/411213-2.xml
new file mode 100644
index 0000000000..e32b0525c6
--- /dev/null
+++ b/layout/generic/crashtests/411213-2.xml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;">
+<body onload="document.getElementById('i').style.fontSize = '10em';">
+
+<div style="column-width: 60px;"><div id="i" style="overflow: clip; white-space: pre;">
+ b</div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/411835.html b/layout/generic/crashtests/411835.html
new file mode 100644
index 0000000000..433030fb31
--- /dev/null
+++ b/layout/generic/crashtests/411835.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+div { position: absolute; height: 10px; width: 10px; }
+
+</style>
+</head>
+
+<body onload="document.getElementById('c').style.counterIncrement = 'z';">
+
+<div style="column-count: 2;"><div id="c" style="white-space: pre;">
+
+<div>
+<div></div></div></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/411851-1.html b/layout/generic/crashtests/411851-1.html
new file mode 100644
index 0000000000..c7ba0965f0
--- /dev/null
+++ b/layout/generic/crashtests/411851-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html style="padding: 17895704px;">
+<head>
+</head>
+<body>
+<div style="float: left;">Foo</div>Bar<span style="margin: 10em; height: 17895697px; float: left;"></span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/412014-1.html b/layout/generic/crashtests/412014-1.html
new file mode 100644
index 0000000000..f985448124
--- /dev/null
+++ b/layout/generic/crashtests/412014-1.html
@@ -0,0 +1,17 @@
+<html style="white-space: pre; column-count: 2;">
+<head></head>
+
+<body onload="document.body.style.MozFloatEdge = 'margin-box'" style="column-width: 20em;">
+
+<div style="position: relative; height: 80px; margin: 10px;">
+
+
+
+
+<div style="position: absolute; height: 11px; top: 19px;"></div>
+</div>
+
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/412201-1.xhtml b/layout/generic/crashtests/412201-1.xhtml
new file mode 100644
index 0000000000..29b3369f8c
--- /dev/null
+++ b/layout/generic/crashtests/412201-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="padding: 17895704px;"><body style="margin: 0pt 100% 0pt 96%; display: table; float: right; height: 0pt;"><li style="list-style-position: inside;"></li></body><body style="margin: -1px; padding: 17895704px; display: table-caption; float: right; height: 0pt;"></body></html>
diff --git a/layout/generic/crashtests/412543-1.html b/layout/generic/crashtests/412543-1.html
new file mode 100644
index 0000000000..7009a00288
--- /dev/null
+++ b/layout/generic/crashtests/412543-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+.c { column-width: 1px; width: 93px; }
+.c:first-letter { }
+
+</style>
+</head>
+
+<body>
+
+<div class="c"><small>a b . d e f h i , k ; m n o p q</small></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/413048-1.html b/layout/generic/crashtests/413048-1.html
new file mode 100644
index 0000000000..bd2280d815
--- /dev/null
+++ b/layout/generic/crashtests/413048-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div style="column-count: 15; width: 16px; height: 16px;"><span><div style="display: inline-block;">a</div><div style="float: left;"></div>
+</span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/413079-1.xhtml b/layout/generic/crashtests/413079-1.xhtml
new file mode 100644
index 0000000000..9f1dcc44e0
--- /dev/null
+++ b/layout/generic/crashtests/413079-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body onload='document.getElementById("x").style.letterSpacing = "20px";'>
+
+<div style="column-count: 15;" id="x"><span>AAA
+<div style="float: left;">BBBB<div>CCCC</div></div></span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/413079-2.xhtml b/layout/generic/crashtests/413079-2.xhtml
new file mode 100644
index 0000000000..93f21d5987
--- /dev/null
+++ b/layout/generic/crashtests/413079-2.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ </head>
+ <body >
+ <div style="column-count: 12; border: 1px solid green">
+ <span>AAAA
+ <div style="float: left;border: 1px solid blue">BBBB
+ </div>
+ </span>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/413079-3.xhtml b/layout/generic/crashtests/413079-3.xhtml
new file mode 100644
index 0000000000..c6591612ef
--- /dev/null
+++ b/layout/generic/crashtests/413079-3.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ </head>
+ <body >
+ <div style="column-count: 2; border: 1px solid green">
+ <span>AAAA
+ <div style="float: left;border: 1px solid blue">BBBB
+ </div>
+ </span>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/413085-1.html b/layout/generic/crashtests/413085-1.html
new file mode 100644
index 0000000000..edb752d5b5
--- /dev/null
+++ b/layout/generic/crashtests/413085-1.html
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+body:first-letter { float: right; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.firstChild.remove();
+ document.body.appendChild(document.createTextNode('x'));
+}
+
+</script>
+</head>
+
+<body onload="boom();">&#xFEB7;
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/413085-2.html b/layout/generic/crashtests/413085-2.html
new file mode 100644
index 0000000000..6128304abd
--- /dev/null
+++ b/layout/generic/crashtests/413085-2.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style>
+ body:first-letter { float: right; }
+</style>
+<script>
+ function boom() {
+ document.body.firstChild.remove();
+ }
+</script>
+</head>
+<body onload="boom();">abc&#x05DB;d
+<div>This sentence should be the only text on the page.</div></body>
+</html>
diff --git a/layout/generic/crashtests/413582-1.xhtml b/layout/generic/crashtests/413582-1.xhtml
new file mode 100644
index 0000000000..7e7b8dab19
--- /dev/null
+++ b/layout/generic/crashtests/413582-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<div style="column-count: 2; text-indent: 11px;"> <div style="float: right;"></div><select style="float: right;"></select></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/413582-2.html b/layout/generic/crashtests/413582-2.html
new file mode 100644
index 0000000000..0e756c2bea
--- /dev/null
+++ b/layout/generic/crashtests/413582-2.html
@@ -0,0 +1,9 @@
+<html>
+<head></head>
+<body>
+<div style="column-count: 2;">r
+<span style="float: right;"></span>
+<textarea style="float: left;"></textarea>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/413712-1.xhtml b/layout/generic/crashtests/413712-1.xhtml
new file mode 100644
index 0000000000..5de0511d1d
--- /dev/null
+++ b/layout/generic/crashtests/413712-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="display: table">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var tr = document.getElementById("tr");
+ tr.contentEditable = "true";
+ tr.focus();
+ document.execCommand("selectAll", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><tr id="tr"><td></td></tr></body>
+
+</html>
diff --git a/layout/generic/crashtests/414061-1.html b/layout/generic/crashtests/414061-1.html
new file mode 100644
index 0000000000..43398d9199
--- /dev/null
+++ b/layout/generic/crashtests/414061-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body style="width: 1px; white-space: pre; column-width: 1px;">
+
+x
+
+<div style="margin: -200em;"></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/414180-1.xhtml b/layout/generic/crashtests/414180-1.xhtml
new file mode 100644
index 0000000000..c4db5ae2ec
--- /dev/null
+++ b/layout/generic/crashtests/414180-1.xhtml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<grid style="margin: -1px; display: inline;" />
+
+</window>
diff --git a/layout/generic/crashtests/414719-1.html b/layout/generic/crashtests/414719-1.html
new file mode 100644
index 0000000000..23ccac7902
--- /dev/null
+++ b/layout/generic/crashtests/414719-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var newDiv = document.createElement("div");
+ var ispan = document.getElementById("ispan");
+ ispan.parentNode.insertBefore(newDiv, ispan);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="column-count: 1; position: fixed;"><div style="overflow: clip; display: table-header-group; white-space: pre; position: absolute;">
+ <span id="ispan">
+</span></div></div>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/415685-1.html b/layout/generic/crashtests/415685-1.html
new file mode 100644
index 0000000000..c474d482b0
--- /dev/null
+++ b/layout/generic/crashtests/415685-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<style type="text/css">
+
+div { height: 10px; margin: 1em; outline: 1px inset black; }
+
+</style>
+</head>
+
+<body onload="document.getElementById('a').style.padding = '12em 0';" style="column-count: 2; width: 1px;"><div id="a">aaaa aaaa aaaa</div><div style="border: medium solid blue;"></div><div style="border: medium solid green; margin: 10em 0pt;"></div><div style="border: medium solid magenta;"></div>xxxx xxxx xxxx</body>
+
+</html>
diff --git a/layout/generic/crashtests/415818.xhtml b/layout/generic/crashtests/415818.xhtml
new file mode 100644
index 0000000000..6cf4754889
--- /dev/null
+++ b/layout/generic/crashtests/415818.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body onload="document.getElementById('a').setAttribute('rquote', 'q');">
+
+<ms id="a" xmlns="http://www.w3.org/1998/Math/MathML" rquote="abcdef ghijkl"><mrow></mrow></ms>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/416165.html b/layout/generic/crashtests/416165.html
new file mode 100644
index 0000000000..9bbdee641c
--- /dev/null
+++ b/layout/generic/crashtests/416165.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<script>
+function replacestyles(){
+document.getElementById('a').setAttribute('style', 'border: 1px solid black; -moz-border-radius: 2em;');
+document.body.offsetHeight;
+}
+</script>
+</head>
+<body style="direction: rtl; column-count: 2;" onload="document.body.offsetHeight; setTimeout(replacestyles,0);">
+
+<div id="a">
+<pre style="overflow: clip;">
+
+
+text
+
+
+
+</pre>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/416264-1.html b/layout/generic/crashtests/416264-1.html
new file mode 100644
index 0000000000..7a2dd83f10
--- /dev/null
+++ b/layout/generic/crashtests/416264-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+
+<body style="display: -moz-box;"><div style="column-count: 2;"><div style="height: 200px;"></div><span style="display: inline-block; width: 100px;"></span></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/416476-1.html b/layout/generic/crashtests/416476-1.html
new file mode 100644
index 0000000000..f90b154ff5
--- /dev/null
+++ b/layout/generic/crashtests/416476-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head></head><body></body><body style="width: 800px; column-count: 4;"><div style="height: 80px; outline-color: blue; outline-style: solid; outline-width: 1px;"></div><div style="height: 80px; outline-color: green; outline-style: solid; outline-width: 1px;"><div style="float: left; height: 10px; width: 10px; outline-color: red; outline-style: solid; outline-width: 1px;"></div><div style="padding: 180px; column-count: 1; clear: right; outline-color: magenta; outline-style: solid; outline-width: 1px;"></div></div></body></html>
diff --git a/layout/generic/crashtests/417848-1.xhtml b/layout/generic/crashtests/417848-1.xhtml
new file mode 100644
index 0000000000..6e5ac0535a
--- /dev/null
+++ b/layout/generic/crashtests/417848-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head></head>
+ <body style="column-count: 15; font-size: 144em;">
+ <svg xmlns="http://www.w3.org/2000/svg" style="min-width: 10em;"></svg>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/417902-1.html b/layout/generic/crashtests/417902-1.html
new file mode 100644
index 0000000000..ea393d0222
--- /dev/null
+++ b/layout/generic/crashtests/417902-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body { width: 500px; column-count: 2; }
+body:first-letter { float: right; }
+#s { padding: 0pt 200px; }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("s").appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+
+<body onload="boom();">x <span id="s"></span></body>
+
+</html>
diff --git a/layout/generic/crashtests/417902-2.html b/layout/generic/crashtests/417902-2.html
new file mode 100644
index 0000000000..da19da232f
--- /dev/null
+++ b/layout/generic/crashtests/417902-2.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+ <style>
+ div:first-letter {
+ float: left;
+ background: orange;
+ }
+ /* Note: there's an upper-bound on widths that trigger a crash.
+ This bound depends on the width of the characters in the div. */
+ div#v {
+ column-count: 2;
+ width: 30px;
+ height: 1em;
+ background: lightblue;
+ }
+ </style>
+ <script>
+ function boom() {
+ var v = document.getElementById("v");
+ // Note: This seems to crash regardless of what the text node is;
+ // e.g. it can be the empty string, a space character, or any number of
+ // other characters.
+ v.appendChild(document.createTextNode("CRASH"));
+ }
+ </script>
+</head>
+<body onload="boom();"><div id="v">a b</div></body>
+</html>
diff --git a/layout/generic/crashtests/418532-1.html b/layout/generic/crashtests/418532-1.html
new file mode 100644
index 0000000000..89b8ecd7d4
--- /dev/null
+++ b/layout/generic/crashtests/418532-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+div:first-line { font-size: 80%; color: green; }
+</style>
+</head>
+<body style="column-width: 1px"><div id="div" style="column-width: 1px"> <span>Foo bar</span></div></body>
+</html>
diff --git a/layout/generic/crashtests/418932-1.html b/layout/generic/crashtests/418932-1.html
new file mode 100644
index 0000000000..154977506c
--- /dev/null
+++ b/layout/generic/crashtests/418932-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head></head><body><div style="column-width: 2880000em;"></div></body></html>
diff --git a/layout/generic/crashtests/419352.html b/layout/generic/crashtests/419352.html
new file mode 100644
index 0000000000..70b3edba7f
--- /dev/null
+++ b/layout/generic/crashtests/419352.html
@@ -0,0 +1,3 @@
+<html>
+<body dir="rtl">&#x2028;&#x200C;</body>
+</html>
diff --git a/layout/generic/crashtests/420000-1.html b/layout/generic/crashtests/420000-1.html
new file mode 100644
index 0000000000..c794c61a06
--- /dev/null
+++ b/layout/generic/crashtests/420000-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+
+<div style="column-gap: 24000000em; column-count: 5;"></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/420718.html b/layout/generic/crashtests/420718.html
new file mode 100644
index 0000000000..9626c5cf2d
--- /dev/null
+++ b/layout/generic/crashtests/420718.html
@@ -0,0 +1 @@
+<span style="float: left; margin-top: 99999999px;">t</span> \ No newline at end of file
diff --git a/layout/generic/crashtests/421404-1.html b/layout/generic/crashtests/421404-1.html
new file mode 100644
index 0000000000..634209849b
--- /dev/null
+++ b/layout/generic/crashtests/421404-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="width: -moz-fit-content; width: fit-content; clear: both">
+<div style="float: left; width: 20px; padding: 100%"></div>
+<div style="float: left; width: 20px"></div>
+<div style="float: left; width: 20px"></div>
+</div>
+<div style="width: -moz-fit-content; width: fit-content; clear: both">
+<div style="float: right; width: 20px; padding: 100%"></div>
+<div style="float: right; width: 20px"></div>
+<div style="float: right; width: 20px"></div>
+</div>
+<div style="width: -moz-fit-content; width: fit-content; clear: both">
+<div style="float: left; width: 20px; padding: 100%"></div>
+<div style="float: right; width: 20px; padding: 100%; clear: right"></div>
+<div style="float: right; width: 20px; clear: right"></div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/421671.html b/layout/generic/crashtests/421671.html
new file mode 100644
index 0000000000..3a6421d18b
--- /dev/null
+++ b/layout/generic/crashtests/421671.html
@@ -0,0 +1,202 @@
+<marquee>
+<xmp style="column-count: 99999999">
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+
+<a>
+<a>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<a>
+<a>
+<a>
+<a>
+
+<a>
+<a>
+
+<a>
+<a> \ No newline at end of file
diff --git a/layout/generic/crashtests/422283-1.html b/layout/generic/crashtests/422283-1.html
new file mode 100644
index 0000000000..3b50140acd
--- /dev/null
+++ b/layout/generic/crashtests/422283-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+
+<body style="column-width: 1px;"><div style="padding: 150px 0; height: 80px;"><span>
+</span>x<div></div><span>
+</span>a ! b c<div></div>
+</div></body>
+
+</html>
diff --git a/layout/generic/crashtests/422301-1.html b/layout/generic/crashtests/422301-1.html
new file mode 100644
index 0000000000..24d577ea31
--- /dev/null
+++ b/layout/generic/crashtests/422301-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style type="text/css">
+
+div { height: .5em; margin: 1em; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("b").style.padding = "";
+ document.getElementById("a").style.padding = "12em";
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="column-width: 2em;"><div id="a"></div><div></div><div></div><div id="b" style="padding: 12em;">This is text</div>This is textThis is text<div>This is text</div></body>
+
+</html>
diff --git a/layout/generic/crashtests/423055-1.html b/layout/generic/crashtests/423055-1.html
new file mode 100644
index 0000000000..4b881d6fb4
--- /dev/null
+++ b/layout/generic/crashtests/423055-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head></head>
+<body>
+
+<div style="white-space: pre; position: relative; column-count: 2;">
+<div style="position: absolute;"></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/423098.html b/layout/generic/crashtests/423098.html
new file mode 100644
index 0000000000..cdaf8ddb22
--- /dev/null
+++ b/layout/generic/crashtests/423098.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("b").style.position = "static";
+ document.body.offsetHeight;
+ document.getElementById("a").style.fontSize = "110%";
+}
+
+</script>
+</head>
+
+<body style="column-count: 1;" onload="boom();">
+ <div id="a" style="float: right; height: 80px;">
+ <div id="b" style="position: absolute; height: 10px;"></div>
+ <div style="height: 100px; width: 11px;"></div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/423264-1.html b/layout/generic/crashtests/423264-1.html
new file mode 100644
index 0000000000..9a64867950
--- /dev/null
+++ b/layout/generic/crashtests/423264-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<script type="text/javascript">
+
+function boom()
+{
+ var y = document.createTextNode(' Y ');
+ document.getElementById("a").appendChild(y);
+ document.body.offsetHeight;
+ var z = document.createTextNode('Z');
+ document.body.insertBefore(z, document.body.lastChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div style="column-count: 2; width: 1px;"><div id="a">M N矋<span>م</span></div></div>H</body>
+</html>
diff --git a/layout/generic/crashtests/424629.html b/layout/generic/crashtests/424629.html
new file mode 100644
index 0000000000..d142897ce5
--- /dev/null
+++ b/layout/generic/crashtests/424629.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("o").appendChild(document.createTextNode('d'));
+ document.body.offsetHeight;
+ document.getElementById("b").firstChild.data = "\u202E";
+ document.getElementById("a").firstChild.data = "ZZZZZZZ";
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="font-family: monospace; width: 8ch;">
+
+<div><span id="o"><span id="a">a</span><span id="b">b&#x202E;</span><span> c </span></span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/425253-1.html b/layout/generic/crashtests/425253-1.html
new file mode 100644
index 0000000000..74c78570d5
--- /dev/null
+++ b/layout/generic/crashtests/425253-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ <span style="border:10px solid blue; text-decoration:underline;">Hello</span>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/426040-1.html b/layout/generic/crashtests/426040-1.html
new file mode 100644
index 0000000000..e8a4ea4e5e
--- /dev/null
+++ b/layout/generic/crashtests/426040-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+div { height: 1px; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var a = document.getElementById("a");
+ a.firstChild.remove();
+ document.documentElement.style.outline = "none";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="column-count: 2;" id="a"><div><div style="float: right;"><div></div><span><div></div></span></div></div></div>
+
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/426272-1.html b/layout/generic/crashtests/426272-1.html
new file mode 100644
index 0000000000..6075deb936
--- /dev/null
+++ b/layout/generic/crashtests/426272-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var y = document.createTextNode('Y');
+ document.body.insertBefore(y, document.getElementById("v").nextSibling);
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="column-count: 2; width: 10ch; letter-spacing: 1px; font-family: monospace;">
+<div style="background: lightblue; float: right; height: 14em; width: 1ch;" id="v"></div>a bcd<span>&#x202B;X</span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/428263-1.html b/layout/generic/crashtests/428263-1.html
new file mode 100644
index 0000000000..fd7e332a50
--- /dev/null
+++ b/layout/generic/crashtests/428263-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="display: table;">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.appendChild(document.body);
+ document.documentElement.offsetHeight;
+ document.documentElement.appendChild(document.body);
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="column-count: 2;"><div style="height: 1px;"></div><span style="display: inline-block;"></span></body>
+
+</html>
diff --git a/layout/generic/crashtests/429960-1.html b/layout/generic/crashtests/429960-1.html
new file mode 100644
index 0000000000..37e99deb52
--- /dev/null
+++ b/layout/generic/crashtests/429960-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html style="direction: rtl;" contenteditable="true">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.firstChild.style.columnCount = "3";
+}
+
+</script>
+</head>
+
+<body style="width: 1px;" onload="boom();"><div>examination<span style="font-family: verdana;">x x x x </span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/429960-2.html b/layout/generic/crashtests/429960-2.html
new file mode 100644
index 0000000000..17ea3c6245
--- /dev/null
+++ b/layout/generic/crashtests/429960-2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="direction: rtl;" contenteditable="true">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.firstChild.style.columnCount = "3";
+}
+
+</script>
+</head>
+
+<body style="width: 1px;" onload="boom();"><div>examination<span style="font-family: verdana;">x x x x
+</span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/429969-1.html b/layout/generic/crashtests/429969-1.html
new file mode 100644
index 0000000000..4b4b567f0c
--- /dev/null
+++ b/layout/generic/crashtests/429969-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+#inner:first-letter { }
+#outer { direction: rtl; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.style.whiteSpace = "pre";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div id="outer"><div id="inner"><span id="s"><span>
+
+</span>AB</span></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/429981-1.html b/layout/generic/crashtests/429981-1.html
new file mode 100644
index 0000000000..57041bc871
--- /dev/null
+++ b/layout/generic/crashtests/429981-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+ body {
+ height: 100px;
+ width: 300pt;
+ column-width: 100pt;
+ column-gap: 0;
+ }
+
+ #x {
+ padding: 100px;
+ }
+
+ #overflow {
+ height: 400px;
+ float: left;
+ }
+
+</style>
+</head>
+
+<body><div id="x"><div id="overflow"></div>K</div></body>
+
+</html>
diff --git a/layout/generic/crashtests/430332-1.html b/layout/generic/crashtests/430332-1.html
new file mode 100644
index 0000000000..c9ba002299
--- /dev/null
+++ b/layout/generic/crashtests/430332-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<body style="font-family:monospace; width:6ch;">
+<b>ab</b> cd
+<b>ab</b> cd
+<b>ab</b> cd
+<b>ab</b> cd
+<b>ab</b> cd
+<b>ab</b> cd
+<b>ab</b> cd
+<b>ab</b> cd<span id="s">ef</span>
+<script>
+document.body.clientWidth;
+document.getElementById("s").style.fontSize = "200%";
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/430344-1.html b/layout/generic/crashtests/430344-1.html
new file mode 100644
index 0000000000..e793020927
--- /dev/null
+++ b/layout/generic/crashtests/430344-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="width: 1px;"><span>e</span> <br style="clear: both;"></body>
+</html>
diff --git a/layout/generic/crashtests/430352-1.html b/layout/generic/crashtests/430352-1.html
new file mode 100644
index 0000000000..e22d641299
--- /dev/null
+++ b/layout/generic/crashtests/430352-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html style="border: 1px dotted red; font-size: 0; -moz-appearance: scrollbartrack-horizontal; width: 12em;">
+<head></head>
+<body style="position: absolute;"></body>
+</html>
diff --git a/layout/generic/crashtests/430744-1.html b/layout/generic/crashtests/430744-1.html
new file mode 100644
index 0000000000..190609294a
--- /dev/null
+++ b/layout/generic/crashtests/430744-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.getElementById('x').appendChild(document.createTextNode('b'));">
+
+<div style="display: -moz-box;"><div style="column-count: 2;">a<div id="x" style="padding: 1em;"></div><wbr></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/430991.html b/layout/generic/crashtests/430991.html
new file mode 100644
index 0000000000..844e9c1a53
--- /dev/null
+++ b/layout/generic/crashtests/430991.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var li = document.getElementById("li");
+ li.removeChild(li.lastChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<ul id="ul">
+<li id="li" style="column-count: -1; white-space: pre-wrap;"><span>A</span><span>B</span><span style="float: right;"></span>
+
+</li>
+</ul>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/431260-1.html b/layout/generic/crashtests/431260-1.html
new file mode 100644
index 0000000000..0e4bfb812e
--- /dev/null
+++ b/layout/generic/crashtests/431260-1.html
@@ -0,0 +1,34 @@
+<html class="reftest-wait"><head><style>
+title::first-letter {font-size:600%;}
+</style><title style="display: table-column-group; position: absolute;">
+mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmm6mmmmm
+mmmm mmmmMmmmmmmmm=mmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmm mmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmm mmmmmmm
+&#1593m
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm
+&#1593m ­&#1593m&#1593m&#1593m­­­&#1593m­&#1593m­&#1593m­&#1593m­&#1593m­&#1593m­&#1593
+&#1593m ­&#1593m&#1593m&#1593m­­­&#1593m­&#1593m­&#1593m­&#1593m­&#1593m­&#1593m­&#1593
+</title></head>
+<script>
+function innerhtml(i){
+ if (i > 0) {
+ document.documentElement.innerHTML = document.documentElement.innerHTML;
+ setTimeout(innerhtml,0,i-1);
+ } else {
+ document.documentElement.removeAttribute("class");
+ }
+}
+setTimeout(innerhtml,0,30);
+</script>
+</html>
diff --git a/layout/generic/crashtests/431260-2.html b/layout/generic/crashtests/431260-2.html
new file mode 100644
index 0000000000..7fc9204c20
--- /dev/null
+++ b/layout/generic/crashtests/431260-2.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait"><head><style>
+ title::first-letter {}
+ title {
+ display: inline;
+ float: left;
+ font-size: 16px;
+ }
+</style>
+<script>
+ function boom() {
+ document.documentElement.innerHTML = document.documentElement.innerHTML;
+ document.documentElement.removeAttribute("class");
+ }
+</script>
+<title>
+a
+b
+&#1593
+c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c
+c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c
+c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c
+</title></head>
+<script>
+setTimeout(boom, 0);
+</script>
+</html>
diff --git a/layout/generic/crashtests/435529.html b/layout/generic/crashtests/435529.html
new file mode 100644
index 0000000000..99d75fb98c
--- /dev/null
+++ b/layout/generic/crashtests/435529.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+ div {
+ column-count: 2;
+ white-space: pre;
+ }
+
+ div:first-letter {
+ float: right;
+ }
+
+</style>
+</head>
+
+<body><div> <span>AB</span></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/436194-1.html b/layout/generic/crashtests/436194-1.html
new file mode 100644
index 0000000000..739b6483cc
--- /dev/null
+++ b/layout/generic/crashtests/436194-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+#colset { column-count: 2; outline: 1px solid orange; }
+.ib { height: 100px; display: inline-block; outline: 1px solid blue; }
+div { outline: 1px solid green; }
+span { outline: 1px solid magenta; }
+
+</style>
+</head>
+<body>
+
+<div id="colset"><div></div><div style="width: 10px; float: left;"><span> <span class="ib" style="width: 30px;"></span> <br> </span></div><span class="ib"></span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/436602-1.html b/layout/generic/crashtests/436602-1.html
new file mode 100644
index 0000000000..c8f731c680
--- /dev/null
+++ b/layout/generic/crashtests/436602-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div style="column-width: 1px;"><span>A B C D E</span> <span style="float: right;"></span> <br> </div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/436822-1.html b/layout/generic/crashtests/436822-1.html
new file mode 100644
index 0000000000..b1b99977e2
--- /dev/null
+++ b/layout/generic/crashtests/436822-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body { font-size: 1600000px; }
+body * { font-size: 10em; }
+
+</style>
+</head>
+
+<body>
+ <div>
+ <div style="margin-top: 1em; margin-bottom: 1em;">A</div>
+ </div>
+ <div>
+ <div style="margin-top: 1em; margin-bottom: 1em;">B<div style="display: list-item; padding-left: 3px; float: left;"></div></div>
+ </div>
+ <div style="float: left;"></div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/436823.html b/layout/generic/crashtests/436823.html
new file mode 100644
index 0000000000..8ab756af25
--- /dev/null
+++ b/layout/generic/crashtests/436823.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+
+<div style="column-count: -1;"><div style="float: left;"><div><div style="float: left;">A B</div><div style="clear: both; height: 1px;"></div></div></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/436969-1.html b/layout/generic/crashtests/436969-1.html
new file mode 100644
index 0000000000..7dab5fa39e
--- /dev/null
+++ b/layout/generic/crashtests/436969-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.documentElement.style.zIndex = 2;">
+
+<div style="direction: rtl; text-transform: uppercase; width: 1px;"><div style="position: absolute; column-count: 2;"><span style="padding: 6em 0pt; position: absolute; height: 1.2em;">A !BB CCC D,</span></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/437156-1.html b/layout/generic/crashtests/437156-1.html
new file mode 100644
index 0000000000..1e87ca3b53
--- /dev/null
+++ b/layout/generic/crashtests/437156-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.getElementById('c').style.counterIncrement = 'c';">
+
+<div style="column-width: 1px;"><div id="c"><div style="float: right;">T<iframe></iframe></div></div></div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/437565-1.xhtml b/layout/generic/crashtests/437565-1.xhtml
new file mode 100644
index 0000000000..4b2dd1067a
--- /dev/null
+++ b/layout/generic/crashtests/437565-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<!-- no body -->
+<div/>
+<div style="float: right;"><span><svg xmlns="http://www.w3.org/2000/svg" style="display: table-row;"/></span> von qlikworld runterladen. Alle RSS News aus dem Artikelkicker immer sofort und kostenlos auf den Bildschirm. Pressemitteilungen lesen und unbegrenzt viele Feeds gratis abonieren. </div><div style="float: right; text-indent: 20em;">artikelkicker.de</div><div><div style="float: right; width: 75px; height: 33px;"/><div style="clear: both;"/></div>
+</html>
diff --git a/layout/generic/crashtests/437565-2.xhtml b/layout/generic/crashtests/437565-2.xhtml
new file mode 100644
index 0000000000..9213a65df9
--- /dev/null
+++ b/layout/generic/crashtests/437565-2.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <style>
+ /* Note: The height here is almost nscoord_MAX in app-units */
+ /* Note: The width here needs to be wider than the viewport in order
+ to trigger a crash. */
+ div.tall {
+ height: 17895687px;
+ width: 3000px;
+ background: pink;
+ }
+ div.float { float: left; }
+ div.clear { clear: left; }
+ div.square { width: 10px; height: 10px; }
+ div.blue { background: blue; }
+ div.green { background: green; }
+ div.orange { background: orange; }
+ </style>
+</head>
+<!-- no body (adding a body prevents the crash, for some reason) -->
+ <div class="float"><div class="tall"/><div class="square blue"/></div>
+ <div class="float square green"/>
+ <div><div class="float square orange"/><div class="clear"/></div>
+</html>
diff --git a/layout/generic/crashtests/437565-3.xhtml b/layout/generic/crashtests/437565-3.xhtml
new file mode 100644
index 0000000000..dbcf3f84fb
--- /dev/null
+++ b/layout/generic/crashtests/437565-3.xhtml
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+><head>
+ <style>
+ /* Note: The height here is almost nscoord_MAX in app-units */
+ /* Note: The width here needs to be wider than the viewport in order
+ to trigger a crash. */
+ div.tall {
+ height: 17895687px;
+ width: 580px;
+ background: pink;
+ }
+ div.float { float: left; }
+ div.clear { clear: left; }
+ div.square { width: 10px; height: 10px; }
+ div.blue { background: blue; }
+ div.green { background: green; }
+ div.orange { background: orange; }
+ </style>
+</head
+><div class="float"><div class="tall"/><div class="square blue"/></div
+><div class="float square green"></div
+><div><div class="float square orange"/><div class="clear"/></div
+></html>
diff --git a/layout/generic/crashtests/438259-1.html b/layout/generic/crashtests/438259-1.html
new file mode 100644
index 0000000000..232531cce5
--- /dev/null
+++ b/layout/generic/crashtests/438259-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+div:first-letter { font-size: 7em; }
+
+</style>
+</head>
+
+<body style="width: 3px; float: left;" onload="document.body.style.direction = 'rtl';"><div><br> A B</div></body>
+
+</html>
diff --git a/layout/generic/crashtests/438266-1.html b/layout/generic/crashtests/438266-1.html
new file mode 100644
index 0000000000..1e7bdb84be
--- /dev/null
+++ b/layout/generic/crashtests/438266-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("hr").removeAttribute("width");
+
+ document.documentElement.offsetHeight;
+
+ var newTR = document.createElement("tr");
+ document.getElementById("table").appendChild(newTR);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+<table id="table">
+ <tbody>
+ <tr>
+ <td>
+ <div><hr width="7000" id="hr"></div>
+ <div style="column-width: 100px;">x<li></li></div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/438509-1.html b/layout/generic/crashtests/438509-1.html
new file mode 100644
index 0000000000..00096020fc
--- /dev/null
+++ b/layout/generic/crashtests/438509-1.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<style type="text/css">
+
+ div.wrapper { height: 400px; }
+ table { height: 100%; }
+
+</style>
+</head>
+<body>
+ <div class="wrapper">
+ <!-- NOTE: Every layer of "table/td" seems to double the load-time -->
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ <table><td>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </td></table>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/443528-1.html b/layout/generic/crashtests/443528-1.html
new file mode 100644
index 0000000000..524be097f5
--- /dev/null
+++ b/layout/generic/crashtests/443528-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.getElementById("s");
+ document.body.insertBefore(document.createTextNode("\n "), s);
+ document.body.offsetHeight;
+ s.appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="column-count: 2; font-size: 1500px; white-space: pre-wrap;"><span id="s" style="display: inline-block"></span><div style="height: 100px;"></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/444230-1.html b/layout/generic/crashtests/444230-1.html
new file mode 100644
index 0000000000..5e01623536
--- /dev/null
+++ b/layout/generic/crashtests/444230-1.html
@@ -0,0 +1 @@
+<html><body><span style="padding: 200%; vertical-align: top;">x<br></span></body></html>
diff --git a/layout/generic/crashtests/444484-1.html b/layout/generic/crashtests/444484-1.html
new file mode 100644
index 0000000000..7edc2f6eb5
--- /dev/null
+++ b/layout/generic/crashtests/444484-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style type="text/css">
+
+body:first-letter { float: right; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var t = document.body.firstChild;
+ var se = document.getElementById("se");
+ se.appendChild(t); // !!!
+ document.body.appendChild(se);
+ se.appendChild(document.createTextNode(" "));
+}
+
+</script>
+</head>
+
+<body onload="boom();">&#xFEB7;
+<div id="se" style="display: none;"></div></body>
+</html>
diff --git a/layout/generic/crashtests/444726-1.xhtml b/layout/generic/crashtests/444726-1.xhtml
new file mode 100644
index 0000000000..a266aa7b87
--- /dev/null
+++ b/layout/generic/crashtests/444726-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+</body>
+
+<!-- Intentionally outside of <body> -->
+<div style="margin: 7224850px 0pt; padding-bottom: 6px;"></div><div style="float: right; padding-top: 6px; width: 194px;"></div><div style="float: left; width: 525px;"><li style="margin: 7224850px 0pt;"></li><div>x<div style="margin: 7224850px 0pt;"></div></div>y</div>
+
+</html>
diff --git a/layout/generic/crashtests/444861-1.html b/layout/generic/crashtests/444861-1.html
new file mode 100644
index 0000000000..a11b801156
--- /dev/null
+++ b/layout/generic/crashtests/444861-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("a").style.padding = "4643853%";
+ document.getElementById("a").style.counterIncrement = "a";
+ document.documentElement.offsetHeight;
+ document.getElementById("a").style.width = "1px";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div style="width: 430px;"><div id="a"><img style="float: left; margin-right: 15px; margin-top: 5px;">A B</div><div><li style="width: 45%; float: left;"></li><div style="float: left;"><span style="padding-left: 22px;"></span></div></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/445288.html b/layout/generic/crashtests/445288.html
new file mode 100644
index 0000000000..f183a34cda
--- /dev/null
+++ b/layout/generic/crashtests/445288.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.createElement("span");
+ document.getElementById("k").appendChild(s);
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="margin: 381500067712% 0pt;">a<div><div style="font-size: 4611686018427388000in;"><hr></div><div style="float: left;">b</div>c</div><div style="height: 197678in;"></div><div id="k" style="float: left;"></div></body>
+</html>
diff --git a/layout/generic/crashtests/448903-1.html b/layout/generic/crashtests/448903-1.html
new file mode 100644
index 0000000000..9a6cad1346
--- /dev/null
+++ b/layout/generic/crashtests/448903-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="padding-left: 33554433em; padding-bottom: 33554433em; width: 20000px; text-decoration: underline; text-shadow: 2px 0px 158.33px green; text-indent: -33554433em;">Z</body>
+</html>
diff --git a/layout/generic/crashtests/448996-1.html b/layout/generic/crashtests/448996-1.html
new file mode 100644
index 0000000000..45dd534b7a
--- /dev/null
+++ b/layout/generic/crashtests/448996-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body { font-family: monospace; width: 10ch; outline: 1px solid black; }
+div { column-width: 0.4px; column-gap: 3ch; }
+b { font-weight: inherit; display: inline-block; }
+#r { border: 1px solid red; }
+#r:before { content: ""; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("r").style.counterReset = "c";
+}
+</script>
+
+</head>
+
+<body onload="boom();"><div>a b c d <span> <b>m</b><span id="r"><b>x</b><span></span></span></span> </div></body>
+
+</html>
diff --git a/layout/generic/crashtests/451315-1.html b/layout/generic/crashtests/451315-1.html
new file mode 100644
index 0000000000..ca2cfeb31f
--- /dev/null
+++ b/layout/generic/crashtests/451315-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="float: right; height: 2px;" onload="document.body.style.width = '0';"><div style="column-count: 3;"><div style="padding: 268435457mm;">A B C D</div></div></body>
+</html>
diff --git a/layout/generic/crashtests/451317-1.html b/layout/generic/crashtests/451317-1.html
new file mode 100644
index 0000000000..d6286b55a6
--- /dev/null
+++ b/layout/generic/crashtests/451317-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+div:first-line { }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("a").style.columnWidth = "21120690815in";
+ document.getElementById("s").style.cursor = "pointer";
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<div id="a">A<div style="padding-top: 1px"> <input> <span id="s"></span></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/451334-1.html b/layout/generic/crashtests/451334-1.html
new file mode 100644
index 0000000000..afd28bc25e
--- /dev/null
+++ b/layout/generic/crashtests/451334-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onload="document.body.style.columnWidth = '1px';">
+
+<div style="display: inline-block;"></div><div style="float: left;"><div style="height: 32px;"></div></div><div>
+<div style="clear: both;"><br></div><div style="float: left;"></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/452157-1.html b/layout/generic/crashtests/452157-1.html
new file mode 100644
index 0000000000..c1bb428f84
--- /dev/null
+++ b/layout/generic/crashtests/452157-1.html
@@ -0,0 +1,8 @@
+<html>
+<head></head>
+<body>
+
+<div style="float: left; column-count: 3;"><div><div style="margin: 1em 0pt; float: left;"><span style="display: inline-block; width: 16px; height: 16px;"></span></div><div style="margin: 1em 0pt; float: left;"></div><br style="display: inherit; clear: both;"> </div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/452157-2.html b/layout/generic/crashtests/452157-2.html
new file mode 100644
index 0000000000..0dee3e6292
--- /dev/null
+++ b/layout/generic/crashtests/452157-2.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+<style>
+ div.a {
+ column-count: 2;
+ float: left;
+ background: lightblue;
+ }
+ div.b {}
+ div.c {
+ float: left;
+ height: 23px;
+ width: 10px;
+ background: orange;
+ }
+ div.d {
+ float: left;
+ height: 22px;
+ width: 10px;
+ background: green;
+ }
+ div.e {
+ clear: left;
+ }
+</style>
+</head>
+<body
+ ><div class="a"
+ ><div class="b"
+ ><div class="c"
+ ></div
+ ><div class="d"
+ ></div
+ ><div class="e"
+ ></div
+ > <!-- whitespace --></div
+ ></div
+></body>
+</html>
diff --git a/layout/generic/crashtests/452157-3.html b/layout/generic/crashtests/452157-3.html
new file mode 100644
index 0000000000..0e0341c3a0
--- /dev/null
+++ b/layout/generic/crashtests/452157-3.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+<style>
+ div.a {
+ column-count: 2;
+ position: absolute;
+ background: lightblue;
+ }
+ div.b {}
+ div.c {
+ float: left;
+ height: 23px;
+ width: 10px;
+ background: orange;
+ }
+ div.d {
+ float: left;
+ height: 22px;
+ width: 10px;
+ background: green;
+ }
+ div.e {
+ clear: left;
+ }
+</style>
+</head>
+<body
+ ><div class="a"
+ ><div class="b"
+ ><div class="c"
+ ></div
+ ><div class="d"
+ ></div
+ ><div class="e"
+ ></div
+ > <!-- whitespace --></div
+ ></div
+></body>
+</html>
diff --git a/layout/generic/crashtests/453762-1.html b/layout/generic/crashtests/453762-1.html
new file mode 100644
index 0000000000..f7d302b5f7
--- /dev/null
+++ b/layout/generic/crashtests/453762-1.html
@@ -0,0 +1,4 @@
+<html style="text-indent: 3700px;">
+<head></head>
+<body><span style="position: relative;"> <div style="position: absolute;"></div></span></body>
+</html>
diff --git a/layout/generic/crashtests/455171-1.html b/layout/generic/crashtests/455171-1.html
new file mode 100644
index 0000000000..6b12544842
--- /dev/null
+++ b/layout/generic/crashtests/455171-1.html
@@ -0,0 +1,5 @@
+<html style="transform: translate(50px);">
+<head>
+<style>html::after { content:"b"; position: fixed;}</style>
+</head>
+<body></body></html>
diff --git a/layout/generic/crashtests/455171-2.html b/layout/generic/crashtests/455171-2.html
new file mode 100644
index 0000000000..56e1cfde55
--- /dev/null
+++ b/layout/generic/crashtests/455171-2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body>
+<div style="transform: translate(50px, 50px);"><div style="position: fixed;"></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/455171-3.html b/layout/generic/crashtests/455171-3.html
new file mode 100644
index 0000000000..d25b66e88c
--- /dev/null
+++ b/layout/generic/crashtests/455171-3.html
@@ -0,0 +1,2 @@
+<div style="transform: scale(2);">
+<iframe style="position: fixed;">
diff --git a/layout/generic/crashtests/455643-1.xhtml b/layout/generic/crashtests/455643-1.xhtml
new file mode 100644
index 0000000000..f60e6cddef
--- /dev/null
+++ b/layout/generic/crashtests/455643-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("k").style.position = "fixed";
+ document.documentElement.offsetHeight;
+ document.getElementById("g").style.textAlign = "";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<div style="text-align: right;" id="g"><div style="direction: rtl; max-width: -moz-fit-content;"><div style="column-width: 1px;">A B<hr/>C D <input/><hr/></div><div id="k"><div style="width: 150px; float: right;"/><div style="width: 100px; float: right;"/></div><div style="padding-left: 40px;"/></div></div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/457375.html b/layout/generic/crashtests/457375.html
new file mode 100644
index 0000000000..ccf7fc86a1
--- /dev/null
+++ b/layout/generic/crashtests/457375.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="column-width: 1px;" onload="document.getElementById('v').style.height = '0';"><div id="v"><span>A B&#0;</span><span>C</span><span> D</span></div></body>
+</html>
diff --git a/layout/generic/crashtests/457380-1.html b/layout/generic/crashtests/457380-1.html
new file mode 100644
index 0000000000..9029618ae1
--- /dev/null
+++ b/layout/generic/crashtests/457380-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.style.columnCount = '3';
+ document.documentElement.offsetHeight;
+ document.documentElement.style.columnCount = '';
+}
+
+</script>
+
+<style type="text/css">
+
+html:before { content: '0'; }
+
+</style>
+
+</head>
+
+<frameset onload="boom();"></frameset>
+
+</html>
diff --git a/layout/generic/crashtests/459968.html b/layout/generic/crashtests/459968.html
new file mode 100644
index 0000000000..1028e0e857
--- /dev/null
+++ b/layout/generic/crashtests/459968.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body {
+ white-space: pre;
+ word-spacing: 511pc;
+}
+
+#a {
+ float: right;
+}
+
+#b {
+ position: fixed;
+ white-space: pre-line;
+ direction: rtl;
+ letter-spacing: 0pt;
+}
+
+</style>
+</head>
+
+<body onload="document.body.style.letterSpacing = '';" style="letter-spacing: 1152921504606847000em;"><div id="a"><div id="b">
+
+. .
+ 0. 0.
+.
+
+ </div>
+ </div></body>
+</html>
diff --git a/layout/generic/crashtests/460910-1.xml b/layout/generic/crashtests/460910-1.xml
new file mode 100644
index 0000000000..268deef64f
--- /dev/null
+++ b/layout/generic/crashtests/460910-1.xml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<style type="text/css">
+
+[class~='t'] { display: table; }
+
+</style>
+</head>
+<body>
+
+<m:math><m:math class="t"/></m:math>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/461294-1.html b/layout/generic/crashtests/461294-1.html
new file mode 100644
index 0000000000..9d6c7145cc
--- /dev/null
+++ b/layout/generic/crashtests/461294-1.html
@@ -0,0 +1 @@
+<html style="display: inline-table;"><body style="margin: 381500067712% 0pt;">T<div><span style="font-size: 4611686018427388000in;"><hr></span><span style="float: left;">P</span>,</div><div style="min-height: 197678in;"></div><span style="float: left;"></span></body></html>
diff --git a/layout/generic/crashtests/462968.xhtml b/layout/generic/crashtests/462968.xhtml
new file mode 100644
index 0000000000..f4303593b5
--- /dev/null
+++ b/layout/generic/crashtests/462968.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body onload="var td = document.getElementById('td'); td.parentNode.removeChild(td);">
+
+<div style="column-count: 2;"><div style="padding: 881977875840684in 0pt;"><span style="padding: 0pt 881977875840684in;"><div style="padding-top: 881977875840684in; clear: both;"></div><span><div><td id="td"></td></div></span></span></div></div>
+
+</body></html>
diff --git a/layout/generic/crashtests/463350-1.html b/layout/generic/crashtests/463350-1.html
new file mode 100644
index 0000000000..08df71fdd0
--- /dev/null
+++ b/layout/generic/crashtests/463350-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("select").appendChild(document.createElement("span"));
+ document.documentElement.offsetHeight;
+ document.getElementById("z").appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="column-count: 2; height: 72496331mm;"><fieldset><span id="z"><div><div></div></div><select id="select"></select></span></fieldset></body>
+</html>
diff --git a/layout/generic/crashtests/463350-2.html b/layout/generic/crashtests/463350-2.html
new file mode 100644
index 0000000000..ec457eb8cc
--- /dev/null
+++ b/layout/generic/crashtests/463350-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("select").appendChild(document.createElement("span"));
+ document.documentElement.offsetHeight;
+ document.getElementById("z").appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="column-count: 2; height: 0;"><fieldset><span id="z"><div><div></div></div><select id="select"></select></span></fieldset></body>
+</html>
diff --git a/layout/generic/crashtests/463350-3.html b/layout/generic/crashtests/463350-3.html
new file mode 100644
index 0000000000..cd3b1c8c1c
--- /dev/null
+++ b/layout/generic/crashtests/463350-3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("select").appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+
+<body onload="setTimeout('boom()', 500);" style="column-count: 2; height: 0;"><fieldset><span id="z"><div><div></div></div><select id="select"></select></span></fieldset></body>
+</html>
diff --git a/layout/generic/crashtests/463741-1.html b/layout/generic/crashtests/463741-1.html
new file mode 100644
index 0000000000..9a7931cf32
--- /dev/null
+++ b/layout/generic/crashtests/463741-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html style="width: 1px;">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.appendChild(document.body);
+ document.documentElement.offsetHeight;
+ var v = document.getElementById("v");
+ v.remove();
+ var w = document.createElement("span");
+ document.body.insertBefore(w, document.body.lastChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div></div><div style="height: 1px;"><span><div id="v"></div></span><div style="column-count: 2;">A<div style="margin: 67108863ch 0pt;">B</div><div>C</div></div></div><span></span></body>
+</html>
diff --git a/layout/generic/crashtests/465651-1.html b/layout/generic/crashtests/465651-1.html
new file mode 100644
index 0000000000..0df38394d7
--- /dev/null
+++ b/layout/generic/crashtests/465651-1.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+
+<style type="text/css">
+
+.contain {
+ height: 10px;
+ margin: 2px 3px;
+ padding-top: 1px;
+}
+.fl {
+ float: left;
+ width: 4px;
+ height: 5px;
+}
+.fr {
+ float: right;
+ width: 5px;
+ height: 5px;
+}
+
+</style>
+
+</head>
+
+<body>
+ <div style="width: 41px; column-count: 3;">
+ <div class="contain"></div>
+ <div class="contain"></div>
+ <div class="fl"></div>
+ <div class="fl"></div>
+ <div class="fr"></div>
+ <div></div>
+ <div class="fr"></div>
+ <div class="contain"><div class="fl"></div></div>
+ <div class="contain" style="float: left;"></div>
+ <div class="fl"></div>
+ <div class="fr"></div>
+ <div class="contain"><div class="fl"></div><div class="fr"></div></div>
+ <div class="fl"></div>
+ <div class="fr"></div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/467137-1.html b/layout/generic/crashtests/467137-1.html
new file mode 100644
index 0000000000..0414baccd2
--- /dev/null
+++ b/layout/generic/crashtests/467137-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function z()
+{
+ var q = document.getElementById("q");
+
+ for (var r = 0; r < 100; ++r) {
+ // dump(r + "\n");
+ q.style.width = r + "px";
+ document.documentElement.offsetHeight;
+ }
+}
+
+</script>
+</head>
+<body onload="z();">
+
+<div style="font-family: monospace;" id="q"><div id="w" style="word-spacing: 1px">AAA <span style="white-space: pre-line; font-weight: 500;">BB C
+ </span></div></div>
+
+</body></html>
diff --git a/layout/generic/crashtests/467213-1.html b/layout/generic/crashtests/467213-1.html
new file mode 100644
index 0000000000..b9ea48e08b
--- /dev/null
+++ b/layout/generic/crashtests/467213-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body>
+
+<div style="direction: rtl;"><span style="direction: ltr;"><div></div><span style="position: absolute;"></span><span style="display: -moz-box;"></span></span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/467487-1.html b/layout/generic/crashtests/467487-1.html
new file mode 100644
index 0000000000..f6830a4a14
--- /dev/null
+++ b/layout/generic/crashtests/467487-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.getElementById('y').style.width = '8000px';">
+
+<div style="white-space: pre-line;"><div id="y"><div style="text-align: justify; font-size: 3300%;">AB CDEF
+ </div></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/467493-1.html b/layout/generic/crashtests/467493-1.html
new file mode 100644
index 0000000000..1dc7d3cec9
--- /dev/null
+++ b/layout/generic/crashtests/467493-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body>
+<div style="column-count: 15; overflow-y: hidden;"><div><div style="clear: both; margin: 144115188075855870cm"><li></li></div><div style="position: fixed;"></div></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/467493-2.html b/layout/generic/crashtests/467493-2.html
new file mode 100644
index 0000000000..bb4d9d10da
--- /dev/null
+++ b/layout/generic/crashtests/467493-2.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head><style>
+ div.a {
+ column-count: 2;
+ overflow-y: hidden;
+ background: yellow;
+ }
+ div.b {
+ background: orange;
+ clear: both;
+ margin-top: 946982.46874999995cm;
+ }
+ div.c {
+ position: fixed;
+ background: red;
+ }
+</style></head>
+<body
+ ><div class="a"><div
+ ><div class="b"><li></li></div
+ ><div class="c"></div
+ ></div></div
+></body>
+</html>
diff --git a/layout/generic/crashtests/467875-1.xhtml b/layout/generic/crashtests/467875-1.xhtml
new file mode 100644
index 0000000000..b805a30a97
--- /dev/null
+++ b/layout/generic/crashtests/467875-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body>
+<span style="direction: rtl;">
+ <span style="display: -moz-box"/>
+ <span style="position: fixed;"/>
+ <span style="display: -moz-box"/>
+</span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/467914-1.html b/layout/generic/crashtests/467914-1.html
new file mode 100644
index 0000000000..4f518f09df
--- /dev/null
+++ b/layout/generic/crashtests/467914-1.html
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+<mathml:munder style="transform: translate(50px);clip-path: url(#h);"/>
+</window>
diff --git a/layout/generic/crashtests/468207-1.html b/layout/generic/crashtests/468207-1.html
new file mode 100644
index 0000000000..d462211011
--- /dev/null
+++ b/layout/generic/crashtests/468207-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body><div style="column-width: 1px;"><div style="height: 5em;"><div style="height: 1em;"></div><div style="height: 5em; float: right;"></div></div><div style="height: 1em;"></div><div style="float: right;"></div><div style="float: left; height: 5em;"></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/468771-1.xhtml b/layout/generic/crashtests/468771-1.xhtml
new file mode 100644
index 0000000000..9f4462f922
--- /dev/null
+++ b/layout/generic/crashtests/468771-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var newTD = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ newTD.setAttribute("rowspan", 3);
+ document.getElementById("tr1").appendChild(newTD);
+
+ document.documentElement.offsetHeight;
+
+ var newTR = document.createElementNS("http://www.w3.org/1999/xhtml", "tr");
+ document.getElementById("table").appendChild(newTR);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<table id="table">AAAA<tr id="tr1"></tr><tr><td><div style="column-width: 1px;">B C</div></td></tr></table>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/468771-2.xhtml b/layout/generic/crashtests/468771-2.xhtml
new file mode 100644
index 0000000000..f81560e4bd
--- /dev/null
+++ b/layout/generic/crashtests/468771-2.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var newTD = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ newTD.setAttribute("rowspan", 3);
+ document.getElementById("tr1").appendChild(newTD);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<table id="table" border="1">AAAA<tr id="tr1"></tr><tr><td><div style="column-width: 1px;">B C</div></td></tr></table>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/469859-1.xhtml b/layout/generic/crashtests/469859-1.xhtml
new file mode 100644
index 0000000000..b23eb7f688
--- /dev/null
+++ b/layout/generic/crashtests/469859-1.xhtml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style id="ss" type="text/css">
+
+#o {
+ height: 65px;
+}
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("ss").disabled = true;
+ document.documentElement.offsetHeight;
+ document.getElementById("ss").disabled = false;
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<div style="column-width: 0;">
+ <colgroup></colgroup>
+ <div>
+ <div id="o" style="float: left;"></div>
+ <div style="clear: both;">A B<div style="float: left;"></div></div>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/471360.html b/layout/generic/crashtests/471360.html
new file mode 100644
index 0000000000..6beb5e5c80
--- /dev/null
+++ b/layout/generic/crashtests/471360.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+<script type="text/javascript">
+
+var ta, f, tb, s, tc;
+var i = 0;
+
+// This test loops repeatedly through its "boom" functions. We place an upper
+// bound on how many loops we'll allow, in case the repeated mutations/paints
+// block the test harness from considering the test to be done.
+var loopCount = 0;
+const maxLoopCount = 20;
+
+function boom1()
+{
+ var r = document.body;
+ while(r.firstChild)
+ r.removeChild(r.firstChild);
+
+ ta = document.createTextNode("A \u06cc");
+ (document.body).appendChild(ta);
+ f = document.createElement("IFRAME");
+ f.style.height = "15em";
+ f.src = "data:text/html," + "Iteration " + ++i;
+ (document.body).appendChild(f);
+ tb = document.createTextNode(" B ");
+ (document.body).appendChild(tb);
+ s = document.createElement("span");
+ s.id = "s";
+ tc = document.createTextNode(" C ");
+ (document.body).appendChild(s);
+ s.appendChild(tc);
+
+ setTimeout(boom2, 10);
+}
+
+function boom2()
+{
+ var w = document.createElement("style");
+ w.setAttribute("type", "text/css");
+ w.appendChild(document.createTextNode("body { column-gap: 1px; column-width: 1px; }"));
+ document.body.appendChild(w);
+
+ setTimeout(boom3, 10);
+}
+
+function boom3()
+{
+ ta.data = " E " + ta.data;
+ document.body.removeChild(s);
+
+ if (++loopCount < maxLoopCount) {
+ setTimeout(boom1, 10);
+ }
+}
+
+</script>
+</head>
+
+<body onload="boom1();"></body>
+</html>
diff --git a/layout/generic/crashtests/472587-1.xhtml b/layout/generic/crashtests/472587-1.xhtml
new file mode 100644
index 0000000000..aa29f93fff
--- /dev/null
+++ b/layout/generic/crashtests/472587-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var m = document.getElementById("m");
+ m.parentNode.removeChild(m);
+ document.getElementById("s").appendChild(document.createTextNode("c"));
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<m:mrow>
+ <span>
+ <m:msup id="m"/>
+ <div style="column-width: 1px;">
+ <m:munderover/>
+ <m:msqrt/>
+ </div>
+ </span>
+ <span id="s"></span>
+</m:mrow>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/472617-1.xhtml b/layout/generic/crashtests/472617-1.xhtml
new file mode 100644
index 0000000000..98af731c6f
--- /dev/null
+++ b/layout/generic/crashtests/472617-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head></head>
+<body style="column-width: 2351490cm;"><div style="height: 0;"><xul:hbox/><xul:button/><span style="float: right;"/></div><xul:button/><div/><span style="float: right;"/></body>
+</html>
diff --git a/layout/generic/crashtests/472774-1.html b/layout/generic/crashtests/472774-1.html
new file mode 100644
index 0000000000..5899593d53
--- /dev/null
+++ b/layout/generic/crashtests/472774-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style id="ss" type="text/css">
+
+div div:first-letter { font-size: 0%; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("ss").textContent = "div div { height: 1px; }";
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<div style="column-width: 1px; word-wrap: break-word;"><div>ABCDE</div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/472776-1.html b/layout/generic/crashtests/472776-1.html
new file mode 100644
index 0000000000..be9a6a92d6
--- /dev/null
+++ b/layout/generic/crashtests/472776-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var v = document.getElementById("v");
+ v.childNodes[1].firstChild.data = "";
+ document.documentElement.offsetHeight;
+ v.appendChild(document.createTextNode("D"));
+ v.firstChild.remove();
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="v"><span>A</span><span>&#x06CC;C</span></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/472950-1.html b/layout/generic/crashtests/472950-1.html
new file mode 100644
index 0000000000..cd2a49aa3d
--- /dev/null
+++ b/layout/generic/crashtests/472950-1.html
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>div::first-letter { color: green; }</style>
+<script>
+
+function boom()
+{
+ var e = document.getElementById("e");
+ document.documentElement.style.direction = "rtl";
+ e.style.whiteSpace = "pre";
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<div><span style="direction: rtl;" id="e"><span>
+
+ </span>A B</span></div>
+
+</body></html></html>
diff --git a/layout/generic/crashtests/473278-1.xhtml b/layout/generic/crashtests/473278-1.xhtml
new file mode 100644
index 0000000000..8522e7c63f
--- /dev/null
+++ b/layout/generic/crashtests/473278-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><mmultiscripts xmlns="http://www.w3.org/1998/Math/MathML" style="clip-path: url(#q); transform: translate(100px, 100px);"/></body></html>
diff --git a/layout/generic/crashtests/473894-1.html b/layout/generic/crashtests/473894-1.html
new file mode 100644
index 0000000000..dd4561d6fb
--- /dev/null
+++ b/layout/generic/crashtests/473894-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="margin: -10000px">X</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/476241-1.html b/layout/generic/crashtests/476241-1.html
new file mode 100644
index 0000000000..bb20efcb9f
--- /dev/null
+++ b/layout/generic/crashtests/476241-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><body><div style="column-gap: 1px; column-width: 1px;"><div style="column-width: 1px;"><div><div style="margin: 15000px 0pt; column-width: 1px;"><div style="height: 1px;">G P X<br style="margin: 15000px 0pt;"></div></div><div style="padding: 10px; height: 200px;"></div></div> </div><br style="margin: 15000px 0pt;"></div></body></html>
diff --git a/layout/generic/crashtests/477731-1.html b/layout/generic/crashtests/477731-1.html
new file mode 100644
index 0000000000..99b02b8657
--- /dev/null
+++ b/layout/generic/crashtests/477731-1.html
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">body:first-letter { float: left; }</style>
+</head>
+<body style="column-width: 100000px;" onload="document.body.style.columnWidth='';"> &#x08D9;</body>
+</html>
diff --git a/layout/generic/crashtests/477928.html b/layout/generic/crashtests/477928.html
new file mode 100644
index 0000000000..b412178520
--- /dev/null
+++ b/layout/generic/crashtests/477928.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("a").appendChild(document.createTextNode("\n"));
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="a" style="max-width: max-content; column-count: 2;"><span style="white-space: pre-line;"><span>
+</span>
+</span></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/478131-1.html b/layout/generic/crashtests/478131-1.html
new file mode 100644
index 0000000000..adce95284b
--- /dev/null
+++ b/layout/generic/crashtests/478131-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style id="s"></style>
+</head>
+<body onload="document.getElementById('s').textContent = '* { font-size: 8193%; }';" style="column-count: 2;"><div><div>A B C</div></div><div><p>D E F</p></div><p>G H I</p><div><p>.</p></div> <p>J K L</p></body>
+</html>
diff --git a/layout/generic/crashtests/478170-1.html b/layout/generic/crashtests/478170-1.html
new file mode 100644
index 0000000000..1c5a3ed3f7
--- /dev/null
+++ b/layout/generic/crashtests/478170-1.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<script type="text/javascript">
+function bounce()
+{
+ var b = document.body;
+ var dE = document.documentElement;
+ dE.removeChild(b);
+ dE.offsetHeight;
+ dE.appendChild(b)
+}
+</script>
+</head>
+<body onload="bounce();">
+<table><tbody><tr><td><div style="column-count: 2;"><div><span style="font-size: 91735350in;"><table><tbody><tr><td></td></tr></tbody></table><div><p><select></select></p><table><tbody><tr><td></td></tr></tbody></table></div></span></div><div style="height: 300px;"></div></div></td></tr></tbody><thead></thead></table>
+</body>
+</html>
diff --git a/layout/generic/crashtests/478185-1.html b/layout/generic/crashtests/478185-1.html
new file mode 100644
index 0000000000..708447edfe
--- /dev/null
+++ b/layout/generic/crashtests/478185-1.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+#v {
+ column-count: 2;
+ direction: rtl;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ text-transform: capitalize;
+ letter-spacing: 163851344580570600em;
+}
+
+#v:first-letter { }
+
+</style>
+</head>
+
+<body>
+<div id="v">
+
+#xxx {
+ xxxxxxxx: xxxxxxxx;
+ xxxxxxxxxx-xxxxx: xxxx;
+ xxx: xxxx;
+ xxxx: xxxx;
+ xxxxx: xxxxx;
+ xxxxxx: xxxxx;
+}
+
+#xxxxx {
+ xxxxxxxx: xxxxxxxx;
+ xxxxxxxxxx-xxxxx: xxxx;
+ xxx: xxxx;
+ xxxxx: xxxx;
+ xxxxx: xxxx;
+ xxxxxx: xxxxx;
+}
+
+#xxxx {
+ xxxxxxxx: xxxxxxxx;
+ xxxxxxxxxx-xxxxx: xxxx;
+ xxxx: xxxx;
+ xxxxxx: xxxx;
+ xxxxx: xxxxx;
+ xxxxxx: xxxx;
+}
+
+#xxxxxx {
+ xxxxxxxx: xxxxxxxx;
+ xxxxxxxxxx-xxxxx: xxxx;
+ xxxxx: xxxx;
+ xxxxx: xxxx;
+ xxxxxx: xxxx;
+ xxxxxx: xxxx;
+}
+
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/478504.html b/layout/generic/crashtests/478504.html
new file mode 100644
index 0000000000..549c4cf443
--- /dev/null
+++ b/layout/generic/crashtests/478504.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+var t;
+
+function a()
+{
+ if (t) document.body.removeChild(t);
+
+ t = document.createTextNode("");
+ document.body.appendChild(t);
+ t.data += "ab\u06CD";
+
+ setTimeout(b, 1);
+}
+
+function b()
+{
+ document.documentElement.offsetHeight;
+ t.data = "d";
+ t.data += " ";
+
+ setTimeout(a, 1);
+}
+
+
+</script>
+</head>
+<body onload="a();">
+</body>
+</html>
diff --git a/layout/generic/crashtests/479938-1.html b/layout/generic/crashtests/479938-1.html
new file mode 100644
index 0000000000..d92fd193fa
--- /dev/null
+++ b/layout/generic/crashtests/479938-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("x").style.padding = "67108863pc";
+ setTimeout(boom2, 0);
+}
+
+function boom2()
+{
+ document.body.removeChild(document.getElementById("colset"));
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();"> <div style="column-count: 2;" id="colset"><div style="height: 1px;"><div id="x"><div style="width: 1px;">A B C D</div></div></div></div> </body>
+
+</html>
diff --git a/layout/generic/crashtests/480345-1.html b/layout/generic/crashtests/480345-1.html
new file mode 100644
index 0000000000..187467d618
--- /dev/null
+++ b/layout/generic/crashtests/480345-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html class="reftest-paged">
+<body style="background:url(solidblue.png); position:absolute; height:40in;">
+</body>
+</html>
diff --git a/layout/generic/crashtests/481921-iframe.html b/layout/generic/crashtests/481921-iframe.html
new file mode 100644
index 0000000000..d83310d6f0
--- /dev/null
+++ b/layout/generic/crashtests/481921-iframe.html
@@ -0,0 +1,12 @@
+<html>
+ <body onload="dotest(); setTimeout('location.reload()', 200)">
+ <script language="javascript">
+ var count=0;
+ var fileloc = "481921.ogg";
+ function dotest(){
+ oggenv.innerHTML = "video test for " + fileloc + "<br><video src=\"" + fileloc + "\" autoplay=\"true\" height=100></video>";
+ }
+ </script>
+ <span id="oggenv"></span><br>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/481921.html b/layout/generic/crashtests/481921.html
new file mode 100644
index 0000000000..60dd53169c
--- /dev/null
+++ b/layout/generic/crashtests/481921.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function done()
+{
+ document.documentElement.removeAttribute("class");
+ document.body.innerHTML=''
+}
+
+setTimeout(done,800)
+</script>
+</head>
+
+<body>
+
+<iframe id="iframe" src="481921-iframe.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/481921.ogg b/layout/generic/crashtests/481921.ogg
new file mode 100644
index 0000000000..0c41c3cd6b
--- /dev/null
+++ b/layout/generic/crashtests/481921.ogg
Binary files differ
diff --git a/layout/generic/crashtests/489462-1.html b/layout/generic/crashtests/489462-1.html
new file mode 100644
index 0000000000..5a57647ece
--- /dev/null
+++ b/layout/generic/crashtests/489462-1.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.body.style.whiteSpace = "";
+ document.getElementById("b").style.direction = "";
+}
+
+</script>
+
+<style type="text/css">
+
+#c:first-letter { font-size-adjust: 8388609; }
+
+</style>
+</head>
+
+<body onload="boom();" style="white-space: pre;"><div id="b" style="direction: rtl;"><div id="c">Qqq Rrr Sss.</div></div></body>
+</html>
diff --git a/layout/generic/crashtests/489477.html b/layout/generic/crashtests/489477.html
new file mode 100644
index 0000000000..6af934204d
--- /dev/null
+++ b/layout/generic/crashtests/489477.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.execCommand("selectAll", false, null);
+ document.execCommand("formatBlock", false, "<h5>");
+ document.execCommand("justifyfull", false, null);
+ document.execCommand("indent", false, null);
+ document.execCommand("outdent", false, null);
+ document.getElementById("q").appendChild(document.createTextNode('v'));
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="width: 800px; column-count: 4;"><div contenteditable="true" style="height: 80px;"><div><div><hr><span> </span></div></div></div><div id="q" style="height: 80px;"><div style="float: left; height: 10px; width: 10px;"></div><div style="padding: 180px; column-count: 1; column-count: 1; clear: right;"></div></div></body>
+</html>
+
diff --git a/layout/generic/crashtests/489480-1.xhtml b/layout/generic/crashtests/489480-1.xhtml
new file mode 100644
index 0000000000..c7d48ec3e8
--- /dev/null
+++ b/layout/generic/crashtests/489480-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="column-count: 2; width: 0pt;"><body style="height: 5003810179.579391in;"><br/><div style="direction: rtl;"><select style="float: right;"></select><option style="width: 0.6600934846211504px; margin: 22367196.5776782cm;"><option style="column-count: 2;"></option></option></div></body></html>
diff --git a/layout/generic/crashtests/489647-1.html b/layout/generic/crashtests/489647-1.html
new file mode 100644
index 0000000000..aa048d364b
--- /dev/null
+++ b/layout/generic/crashtests/489647-1.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait"><head><title> Bug 489647 - New 1.9.0.9 topcrash [@nsTextFrame::ClearTextRun()]</title></head>
+<body>
+<div id="a" style="white-space: pre;">
+m</div>
+<script>
+function doe() {
+ document.getElementById('a').childNodes[0].splitText(1);
+ document.documentElement.className = "";
+}
+setTimeout(doe, 100);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/493111-1.html b/layout/generic/crashtests/493111-1.html
new file mode 100644
index 0000000000..f851074d65
--- /dev/null
+++ b/layout/generic/crashtests/493111-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script type="application/javascript">
+ function onLoad() {
+ var text = document.getElementById("text").firstChild;
+ var sel = window.getSelection();
+ var r1 = document.createRange();
+ r1.setStart(text, 0);
+ r1.setEnd(text, 5);
+ sel.addRange(r1);
+ var r2 = document.createRange();
+ r2.setStart(text, 4);
+ r2.setEnd(text, 9);
+ sel.addRange(r2);
+ }
+ </script>
+ </head>
+ <body onload="onLoad();">
+ <p id="text">Adding overlapping ranges to a selection shouldn't assert</p>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/493118-1.html b/layout/generic/crashtests/493118-1.html
new file mode 100644
index 0000000000..257fcf8478
--- /dev/null
+++ b/layout/generic/crashtests/493118-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" style="column-count: 2;">
+<body style="padding: 731563462617733px; height: 10px;">
+<div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/493649.html b/layout/generic/crashtests/493649.html
new file mode 100644
index 0000000000..65ce723dde
--- /dev/null
+++ b/layout/generic/crashtests/493649.html
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="position: fixed; column-count: 3; white-space: pre;"><body style="height: 0pt;">
+
+
+
+</body></html>
diff --git a/layout/generic/crashtests/494283-1.xhtml b/layout/generic/crashtests/494283-1.xhtml
new file mode 100644
index 0000000000..68a2406b38
--- /dev/null
+++ b/layout/generic/crashtests/494283-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="position: absolute; display: table;">
+<head><style>span:before { content: '1' }</style></head>
+<body onload="document.documentElement.style.display = '';document.documentElement.offsetHeight;"><div style="position: absolute;"></div><span></span></body>
+</html>
diff --git a/layout/generic/crashtests/494283-2.html b/layout/generic/crashtests/494283-2.html
new file mode 100644
index 0000000000..86fc1e7908
--- /dev/null
+++ b/layout/generic/crashtests/494283-2.html
@@ -0,0 +1,6 @@
+<body>
+ <fieldset id="x"><legend>longlonglong</legend></fieldset>
+ <script>
+ var x = document.getElementById("x");
+ x.insertBefore(document.createTextNode("aa"), x.firstChild);
+ </script>
diff --git a/layout/generic/crashtests/494332-1.html b/layout/generic/crashtests/494332-1.html
new file mode 100644
index 0000000000..3ab4b71c07
--- /dev/null
+++ b/layout/generic/crashtests/494332-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<div style="width: 1ch;"><div style="height: 2em;">1 2<div style="float: left; padding: 0pt 1px; display: list-item;"></div></div><span></span> g h i</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/495875-1.html b/layout/generic/crashtests/495875-1.html
new file mode 100644
index 0000000000..054475464b
--- /dev/null
+++ b/layout/generic/crashtests/495875-1.html
@@ -0,0 +1,7 @@
+<html>
+<head></head>
+<body style="column-count: 2; white-space: pre-wrap; font-size-adjust: 4294967297; text-transform: uppercase;"
+ onload="document.body.style.fontSizeAdjust = '';"
+>&#xD558;A B C&#x0643;&#x5599;D
+
+</body></html>
diff --git a/layout/generic/crashtests/495875-2.html b/layout/generic/crashtests/495875-2.html
new file mode 100644
index 0000000000..72dd9f733c
--- /dev/null
+++ b/layout/generic/crashtests/495875-2.html
@@ -0,0 +1,7 @@
+<html>
+<head></head>
+<body style="column-count: 2; white-space: pre-wrap; font-size-adjust: 4294967297; text-transform: uppercase;"
+ onload="document.body.style.fontSizeAdjust = '';"
+>&#xD558;A B C<b>&#x0443;</b>&#x5599;D
+
+</body></html>
diff --git a/layout/generic/crashtests/496742.html b/layout/generic/crashtests/496742.html
new file mode 100644
index 0000000000..e693b2663f
--- /dev/null
+++ b/layout/generic/crashtests/496742.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title>Crash [@ nsHTMLReflowState::GetHypotheticalBoxContainer] with position: fixed, float right</title>
+</head>
+<body>
+<iframe src="data:text/html;charset=utf-8,%3Cspan%3E%0Am%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20%0Am%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20%0Am%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20%0Am%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20m%20%0A%3Cspan%20style%3D%22position%3A%20fixed%3B%20float%3A%20right%3B%22%3E%3C/span%3E%0A%3C/span%3E%0A%0A%3Cscript%3E%0Afunction%20toggleIframe%28%29%7B%0Avar%20x%3Dwindow.frameElement%3B%0Ax.style.display%20%3D%20x.style.display%20%3D%3D%20%27none%27%20%3F%20x.style.display%20%3D%20%27%27%20%3A%20x.style.display%20%3D%20%27none%27%3B%0AsetTimeout%28toggleIframe%2C100%29%3B%0A%7D%0AsetTimeout%28toggleIframe%2C100%29%3B%0A%3C/script%3E"></iframe>
+<script>
+
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/499138-iframe.html b/layout/generic/crashtests/499138-iframe.html
new file mode 100644
index 0000000000..6dc03dfdbd
--- /dev/null
+++ b/layout/generic/crashtests/499138-iframe.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+
+</head>
+<body onload="document.getElementById('a').removeAttribute('style');setTimeout(function() {window.location.reload()}, 500);">
+<div style="overflow: scroll; position: absolute; column-count: 2;">
+
+<div style="position: absolute;">
+<input id="a" style="position: absolute;" type="radio">
+<object>
+ &#1593; m &#1593; m &#1593; m &#1593; m &#1593; m &#1593; m
+<ul> &#1593; m &#1593; m &#1593; m</ul>
+</object>
+</div>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/499138.html b/layout/generic/crashtests/499138.html
new file mode 100644
index 0000000000..7e7d84dfa0
--- /dev/null
+++ b/layout/generic/crashtests/499138.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 499138</title>
+<script>
+function done()
+{
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(done,1000)">
+
+<iframe src="499138-iframe.html"></iframe>
+
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/499857-1.html b/layout/generic/crashtests/499857-1.html
new file mode 100644
index 0000000000..458154c5a8
--- /dev/null
+++ b/layout/generic/crashtests/499857-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+<style type="text/css">
+
+#relleft {
+ float: left;
+ width: 290px;
+ margin: 15px 0 0 0;
+}
+
+#fl:first-line { }
+
+#cols { column-width: 4503599627370497mm; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("x").setAttribute("id", "cols");
+ document.getElementById("fl").firstChild.splitText(1);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<div id="x"><div id="relleft"></div><div id="fl">
+a b c d</div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/499862-1.html b/layout/generic/crashtests/499862-1.html
new file mode 100644
index 0000000000..eb614cbd07
--- /dev/null
+++ b/layout/generic/crashtests/499862-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+body::first-letter { float: left; }
+</style>
+</head>
+<body style="text-transform: capitalize;">T</body>
+</html>
diff --git a/layout/generic/crashtests/501535-1.html b/layout/generic/crashtests/501535-1.html
new file mode 100644
index 0000000000..8daabfb0d6
--- /dev/null
+++ b/layout/generic/crashtests/501535-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="document.getElementById('a').setAttribute('poster', '#');">
+<audio controls id="a">
+</body>
+</html>
diff --git a/layout/generic/crashtests/503961-1.xhtml b/layout/generic/crashtests/503961-1.xhtml
new file mode 100644
index 0000000000..a3a3f0d926
--- /dev/null
+++ b/layout/generic/crashtests/503961-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+ #colset { column-count: 3; }
+ #a { height: 0px; }
+ #b { height: 2px; }
+ #c { height: 1px; }
+ #d { height: 2px; }
+ #e { height: 2px; }
+</style>
+<script type="text/javascript">
+function boom()
+{
+ document.getElementById("a").style.height = "auto";
+ document.getElementById("d").style.height = "auto";
+}
+</script>
+</head>
+<body onload="boom();"
+ ><div id="colset"
+ ><div id="a"><div id="b"/></div
+ ><div id="c"><div id="d"/><div id="e"/></div
+ ></div
+></body>
+</html>
diff --git a/layout/generic/crashtests/503961-2.html b/layout/generic/crashtests/503961-2.html
new file mode 100644
index 0000000000..a2ff11c742
--- /dev/null
+++ b/layout/generic/crashtests/503961-2.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+ <style>
+ #colset { column-count: 3; }
+ #a { height: 0; }
+ #x { height: 3px; }
+ #b { height: 0; }
+ #c { height: 2px; }
+ #d { height: 2px; }
+
+ /* Following style is just for visualization -- doesn't affect assertion */
+ #colset { column-gap: 0px; width: 18px; }
+ div { width: 5px; }
+ #a { background: purple; }
+ #x { background: orange; }
+ #b { background: blue; }
+ #c { background: black; }
+ #d { background: lime; }
+ </style>
+ <script>
+ function boom()
+ {
+ document.getElementById("a").style.height = "auto";
+ document.getElementById("c").style.height = "0";
+ }
+ </script>
+</head>
+<body onload="boom()" id="colset"
+ ><div id="a"><div id="x"></div></div
+ ><div id="b"><div id="c"></div><div id="d"></div></div
+></body>
+</html>
diff --git a/layout/generic/crashtests/507566.html b/layout/generic/crashtests/507566.html
new file mode 100644
index 0000000000..1fbdba5c8b
--- /dev/null
+++ b/layout/generic/crashtests/507566.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+var count = 0;
+
+function boom()
+{
+ var r = document.body; while(r.firstChild) { r.removeChild(r.firstChild); }
+
+ document.body = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ var div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var span = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ span.setAttributeNS(null, "contenteditable", "true");
+ document.documentElement.appendChild(span);
+ (document.body || document.documentElement).appendChild(div);
+ var text = document.createTextNode("\u200F\uE8D4");
+ document.body.appendChild(text);
+ document.documentElement.appendChild(document.body);
+ document.documentElement.offsetHeight;
+ text.data += "\uF0C5";
+ document.execCommand("selectAll", false, null);
+
+ if (++count < 20)
+ setTimeout(boom, 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/generic/crashtests/508154-1.xhtml b/layout/generic/crashtests/508154-1.xhtml
new file mode 100644
index 0000000000..5a2d96cc48
--- /dev/null
+++ b/layout/generic/crashtests/508154-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body style="float: left;"></body><span style="float: left; margin: 10%; min-height: 17895698px;"></span></html>
diff --git a/layout/generic/crashtests/508168-1.html b/layout/generic/crashtests/508168-1.html
new file mode 100644
index 0000000000..257fcf8478
--- /dev/null
+++ b/layout/generic/crashtests/508168-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" style="column-count: 2;">
+<body style="padding: 731563462617733px; height: 10px;">
+<div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/508816-1.xhtml b/layout/generic/crashtests/508816-1.xhtml
new file mode 100644
index 0000000000..46543f563c
--- /dev/null
+++ b/layout/generic/crashtests/508816-1.xhtml
@@ -0,0 +1,9 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="direction: rtl">
+ <scrollbox maxwidth="100" style="overflow: scroll;">
+ <button label="One"/>
+ <button label="Two"/>
+ <button label="Three"/>
+ <button label="Four and the rest of the numbers go here"/>
+ </scrollbox>
+</window>
diff --git a/layout/generic/crashtests/509749-1.html b/layout/generic/crashtests/509749-1.html
new file mode 100644
index 0000000000..26bac062ce
--- /dev/null
+++ b/layout/generic/crashtests/509749-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html style="column-width: 1px;">
+<head></head>
+<body><div style="position: relative;"><div style="float: left; padding: 10px 20px 0pt;"><div style="position: absolute; top: 0pt;"><div></div><div style="position: fixed;"></div>S</div></div><div style="clear: both; padding: 20px 20px 15px;"></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/511482.html b/layout/generic/crashtests/511482.html
new file mode 100644
index 0000000000..4e9aa60a1a
--- /dev/null
+++ b/layout/generic/crashtests/511482.html
@@ -0,0 +1,42 @@
+<html>
+ <body>
+ <div style="border: 5px solid blue;">
+ <div style="column-width: 530px;height:300px; border: 5px solid red;">
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ <div style="width:128px; height:128px;border: 5px solid green" >
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/512724-1.html b/layout/generic/crashtests/512724-1.html
new file mode 100644
index 0000000000..3023b88085
--- /dev/null
+++ b/layout/generic/crashtests/512724-1.html
@@ -0,0 +1 @@
+<html style="column-width: 1px; column-gap: 6834954840cm"><body></body></html>
diff --git a/layout/generic/crashtests/512725-1.html b/layout/generic/crashtests/512725-1.html
new file mode 100644
index 0000000000..775262b9a9
--- /dev/null
+++ b/layout/generic/crashtests/512725-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<table style="line-height: 1em; font-size: 1483271385%;"><tbody><tr><td></td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/generic/crashtests/512749-1.html b/layout/generic/crashtests/512749-1.html
new file mode 100644
index 0000000000..12829799ee
--- /dev/null
+++ b/layout/generic/crashtests/512749-1.html
@@ -0,0 +1 @@
+<html style="position:fixed"><table style="position:absolute"></table></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/513110-1.html b/layout/generic/crashtests/513110-1.html
new file mode 100644
index 0000000000..96a39386f0
--- /dev/null
+++ b/layout/generic/crashtests/513110-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.getElementById("s");
+ document.body.removeChild(s);
+ document.body.appendChild(s);
+}
+
+window.addEventListener("load", boom);
+
+</script>
+</head>
+
+<body>
+<span style="word-spacing: -379660px">a </span>
+<span id="s"><br style="clear: both;"/></span>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/513110-2.xhtml b/layout/generic/crashtests/513110-2.xhtml
new file mode 100644
index 0000000000..e1fcb499d9
--- /dev/null
+++ b/layout/generic/crashtests/513110-2.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body style="width: 1px;" onload="document.documentElement.offsetHeight; document.getElementById('x').style.display = 'table-footer-group';">
+<span>1</span> <br id="x" style="clear: both;" />
+</body>
+</html>
diff --git a/layout/generic/crashtests/513394-1.html b/layout/generic/crashtests/513394-1.html
new file mode 100644
index 0000000000..303854a813
--- /dev/null
+++ b/layout/generic/crashtests/513394-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+#w:after {
+content: "A";
+display: block;
+height: 0;
+clear: both;
+}
+</style>
+</head>
+<body onload="document.getElementById('c').style.height = '20px';" style="width: 300px">
+<div style="column-count: 2;"><div style="width: 200px; float: left;"><div id="c" style="padding-top: 30px;"></div></div><div style="padding: 10px 0pt;"><div><div id="w"><div style="display: list-item; float: left; margin-right: 100px;"></div></div><div style="height: 20px; display: inline-block;"></div></div></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/514098-1.xhtml b/layout/generic/crashtests/514098-1.xhtml
new file mode 100644
index 0000000000..ed4b4e727b
--- /dev/null
+++ b/layout/generic/crashtests/514098-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("td").contentEditable = "true";
+ document.execCommand("justifyfull", false, null);
+}
+
+</script>
+</head>
+<body onload="boom();">
+<m:msubsup><td id="td"><m:mn/></td></m:msubsup>
+</body>
+</html>
diff --git a/layout/generic/crashtests/514800-1.html b/layout/generic/crashtests/514800-1.html
new file mode 100644
index 0000000000..c744c16d5a
--- /dev/null
+++ b/layout/generic/crashtests/514800-1.html
@@ -0,0 +1,4 @@
+<html style="position: absolute; overflow: hidden; column-count: 3;">
+<head></head>
+<body style="overflow-y: scroll;" onload="document.body.style.counterReset='c';"><div style="position: absolute; height: 200px;"></div></body>
+</html>
diff --git a/layout/generic/crashtests/515811-1.html b/layout/generic/crashtests/515811-1.html
new file mode 100644
index 0000000000..81a8fcf68f
--- /dev/null
+++ b/layout/generic/crashtests/515811-1.html
@@ -0,0 +1,5 @@
+<html>
+<body onload="document.getElementById('x').style.fontSize = '4398046511103em';">
+<div style="float: left; column-count: 3;"><div><div id="x" style="margin: 1em 0pt;"></div><div style="float: left;"></div><div style="clear: both;"></div>Q</div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/517968.html b/layout/generic/crashtests/517968.html
new file mode 100644
index 0000000000..9283accdb1
--- /dev/null
+++ b/layout/generic/crashtests/517968.html
@@ -0,0 +1,6 @@
+<script>
+var rng = document.createRange();
+window.getSelection()["addRange"](rng);
+window.getSelection()["addRange"](rng);
+window.getSelection()["addRange"](rng);
+</script>
diff --git a/layout/generic/crashtests/519031.xhtml b/layout/generic/crashtests/519031.xhtml
new file mode 100644
index 0000000000..107d23cfe4
--- /dev/null
+++ b/layout/generic/crashtests/519031.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body onload="document.getElementById('a').appendChild(document.createTextNode(' '));">
+<div style="position: absolute; column-count: 2;"><div style="position: absolute; height: 100px;"><fieldset/><fieldset id="a"/></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/520340.html b/layout/generic/crashtests/520340.html
new file mode 100644
index 0000000000..00e825d28a
--- /dev/null
+++ b/layout/generic/crashtests/520340.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<html style="column-width: 1px;"><head></head><body style="column-count: 2;">A B C D E F<span>&#x7E01;</span></body></html>
diff --git a/layout/generic/crashtests/522170-1.html b/layout/generic/crashtests/522170-1.html
new file mode 100644
index 0000000000..c2eabc2770
--- /dev/null
+++ b/layout/generic/crashtests/522170-1.html
@@ -0,0 +1 @@
+<html><div style="float: left; column-count: 3;"><div><div style="float: left; min-height: 7086320ch;"></div><div style="clear: both;"></div><span></span></div></div></html>
diff --git a/layout/generic/crashtests/526217.html b/layout/generic/crashtests/526217.html
new file mode 100644
index 0000000000..16f6aefa53
--- /dev/null
+++ b/layout/generic/crashtests/526217.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait">
+<head>
+<script>
+function doe() {
+document.body.removeAttribute('style');
+document.documentElement.offsetHeight;
+document.documentElement.removeAttribute("class");
+}
+setTimeout(doe,100);
+</script>
+</head>
+<body style="position: fixed; column-count: 2; min-height: 100%; top: 50%; bottom: 50%; font-size: 900px;">
+m m
+<span style=" position: fixed;"></span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/533379-1.html b/layout/generic/crashtests/533379-1.html
new file mode 100644
index 0000000000..6e98c8788a
--- /dev/null
+++ b/layout/generic/crashtests/533379-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body { width: 1px; }
+ul { column-count: 15; }
+li {list-style-position: inside; }
+li:first-letter {color: red; }
+</style>
+</head>
+
+<body>
+<ul><li><span>A B</span></li></ul>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/533379-2.html b/layout/generic/crashtests/533379-2.html
new file mode 100644
index 0000000000..761c8a6fc1
--- /dev/null
+++ b/layout/generic/crashtests/533379-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body { width: 1px; }
+ul { column-count: 2; }
+li {list-style-position: inside; }
+li:first-letter {color: red; }
+</style>
+</head>
+
+<body>
+<ul><li><span>A B</span></li></ul>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/534082-1.html b/layout/generic/crashtests/534082-1.html
new file mode 100644
index 0000000000..596211c713
--- /dev/null
+++ b/layout/generic/crashtests/534082-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="font-family: monospace; width: 0;">
+<div style="column-count: 4;"><div>a b c d<span style="display: list-item;"></span></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/534366-1.html b/layout/generic/crashtests/534366-1.html
new file mode 100644
index 0000000000..bb37fb4825
--- /dev/null
+++ b/layout/generic/crashtests/534366-1.html
@@ -0,0 +1,38 @@
+<html>
+<head>
+<style type="text/css">
+
+body { font-family: monospace; width: 4ch; }
+body::first-line { }
+body *::before { content: 'w';}
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var de = document.documentElement;
+ var body = document.body;
+
+ var span = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var r1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow");
+ var mmm = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mmultiscripts");
+
+ body.appendChild(span);
+ r1.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow"));
+ body.appendChild(r1);
+ body.appendChild(mmm);
+ de.offsetHeight;
+ r1.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", 'mrow'));
+ de.offsetHeight;
+ mmm.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", 'mrow'));
+ de.offsetHeight;
+}
+
+window.addEventListener("load", boom);
+
+</script>
+</head>
+<body></body>
+</html>
diff --git a/layout/generic/crashtests/534366-2.html b/layout/generic/crashtests/534366-2.html
new file mode 100644
index 0000000000..ef4cb50c38
--- /dev/null
+++ b/layout/generic/crashtests/534366-2.html
@@ -0,0 +1,42 @@
+<html>
+<head>
+<style type="text/css">
+
+body { font-family: monospace; width: 4ch; }
+body::first-line { }
+body *::before { content: 'w';}
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var de = document.documentElement;
+ var body = document.body;
+
+ var span = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var r1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow");
+ var mmm = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mmultiscripts");
+
+ body.appendChild(span);
+ r1.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow"));
+ body.appendChild(r1);
+ body.appendChild(mmm);
+ de.offsetHeight;
+ r1.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", 'mrow'));
+ de.offsetHeight;
+ mmm.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", 'mrow'));
+ de.offsetHeight;
+
+ document.removeChild(de);
+ document.appendChild(de);
+ de.offsetHeight;
+}
+
+window.addEventListener("load", boom);
+
+</script>
+</head>
+<body></body>
+</html>
diff --git a/layout/generic/crashtests/536692-1.xhtml b/layout/generic/crashtests/536692-1.xhtml
new file mode 100644
index 0000000000..32cee03140
--- /dev/null
+++ b/layout/generic/crashtests/536692-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.removeChild(document.documentElement);">
+<table style="position: fixed;"><tr style="position: absolute;"></tr></table>
+</body>
+</html>
diff --git a/layout/generic/crashtests/537645.xhtml b/layout/generic/crashtests/537645.xhtml
new file mode 100644
index 0000000000..457d28c4f8
--- /dev/null
+++ b/layout/generic/crashtests/537645.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<style>
+span { margin: inherit; }
+html, body { column-width: 1px; }
+</style>
+</head>
+<body onload="document.getElementsByTagName('style')[0].setAttribute('foo', 'bar'); document.documentElement.removeAttribute('class');">
+<span><i><spacer /><caption /></i><span><span><div /></span></span></span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/541277-1.html b/layout/generic/crashtests/541277-1.html
new file mode 100644
index 0000000000..91c99a4607
--- /dev/null
+++ b/layout/generic/crashtests/541277-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<span>&#xFBE4;</span><span>&#xFB4B;</span><span>&#xFBE6;</span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/541277-2.html b/layout/generic/crashtests/541277-2.html
new file mode 100644
index 0000000000..ce608e9c80
--- /dev/null
+++ b/layout/generic/crashtests/541277-2.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+&#x202E;X&#x200D; &#x5D60;
+</body>
+</html>
diff --git a/layout/generic/crashtests/541714-1.html b/layout/generic/crashtests/541714-1.html
new file mode 100644
index 0000000000..e790358e0e
--- /dev/null
+++ b/layout/generic/crashtests/541714-1.html
@@ -0,0 +1,3 @@
+<html style="overflow: hidden;">
+<body style="overflow: hidden; direction: rtl; padding: 0 64635% 0 66421238918787500pt; width: 39779329pt;"></body>
+</html>
diff --git a/layout/generic/crashtests/541714-2.html b/layout/generic/crashtests/541714-2.html
new file mode 100644
index 0000000000..dc16343657
--- /dev/null
+++ b/layout/generic/crashtests/541714-2.html
@@ -0,0 +1,3 @@
+<html style="overflow: hidden;">
+<body style="overflow: hidden; direction: rtl; padding: 64635% 0 66421238918787500pt 0; height: 39779329pt;"></body>
+</html>
diff --git a/layout/generic/crashtests/542136-1.html b/layout/generic/crashtests/542136-1.html
new file mode 100644
index 0000000000..e9aa1d8e23
--- /dev/null
+++ b/layout/generic/crashtests/542136-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+div:first-letter{}
+
+</style>
+<script>
+
+function boom()
+{
+ document.execCommand("selectAll", false, null);
+ document.execCommand("decreasefontsize", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="font-size: 0;"><div contenteditable="true" style="column-width: 1px; white-space: pre-line;">
+<span>T</span>his is text</div></body>
+
+</html>
diff --git a/layout/generic/crashtests/545571-1.html b/layout/generic/crashtests/545571-1.html
new file mode 100644
index 0000000000..a07330929d
--- /dev/null
+++ b/layout/generic/crashtests/545571-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head></head>
+<body onload="document.documentElement.appendChild(document.body); document.documentElement.offsetHeight; document.getElementsByTagName('span')[0].style.wordSpacing = '4px';" style="bottom: 15045000px; column-width: 1px; top: -26px; position: absolute"><div style="letter-spacing: -4129px"><span style="white-space: pre-line; padding: 21904664px; column-width: 1px; position: absolute; word-spacing: 1577097179334px; top: 281474976710655px">
+( :
+
+q</span></div></body>
+</html>
diff --git a/layout/generic/crashtests/547843-1.xhtml b/layout/generic/crashtests/547843-1.xhtml
new file mode 100644
index 0000000000..0ad086d90c
--- /dev/null
+++ b/layout/generic/crashtests/547843-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body><math xmlns="http://www.w3.org/1998/Math/MathML" style="display: table;"/><div style="position: fixed;"></div></body></html>
diff --git a/layout/generic/crashtests/551635-1.html b/layout/generic/crashtests/551635-1.html
new file mode 100644
index 0000000000..805d4413f3
--- /dev/null
+++ b/layout/generic/crashtests/551635-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.focus();
+}
+
+</script>
+</head>
+
+<frameset onload="boom();"></frameset>
+
+</html>
diff --git a/layout/generic/crashtests/553504-1.xhtml b/layout/generic/crashtests/553504-1.xhtml
new file mode 100644
index 0000000000..7f83a5f55c
--- /dev/null
+++ b/layout/generic/crashtests/553504-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="display: table;">
+<head><style>div {height: 10px; margin: 1em 0; }</style></head>
+<body style="column-count: 3; direction: rtl;"><div></div><div style="padding: 4503599627370495pt;">j<td></td></div></body>
+</html>
diff --git a/layout/generic/crashtests/564368-1.xhtml b/layout/generic/crashtests/564368-1.xhtml
new file mode 100644
index 0000000000..1debc0e784
--- /dev/null
+++ b/layout/generic/crashtests/564368-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var a = document.createElementNS("http://www.w3.org/1999/xhtml", "frameset");
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "frameset");
+ var c = document.createElementNS("http://www.w3.org/1999/xhtml", "frameset");
+ var div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+
+ a.appendChild(b);
+ document.documentElement.appendChild(a);
+ document.documentElement.offsetHeight;
+ b.appendChild(c);
+ document.documentElement.offsetHeight;
+ c.appendChild(div)
+}
+
+window.addEventListener("load", boom);
+
+]]>
+</script></head>
+
+<body></body>
+</html>
diff --git a/layout/generic/crashtests/564968.xhtml b/layout/generic/crashtests/564968.xhtml
new file mode 100644
index 0000000000..f3c3721029
--- /dev/null
+++ b/layout/generic/crashtests/564968.xhtml
@@ -0,0 +1,30 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+ .container {
+ height: 1em;
+ }
+ .overflow {
+ height: 8em;
+ }
+ body {
+ font-family: monospace;
+ height: 8em;
+ line-height: 1em;
+ column-count: 2;
+ column-gap: 0;
+ }
+</style>
+<script>
+ function boom()
+ {
+ document.documentElement.offsetHeight;
+ document.getElementById('x').style.display = 'none';
+ document.documentElement.offsetHeight;
+ document.getElementById('y').style.display = 'none';
+ }
+</script>
+</head>
+
+<body style="width: 17ch;" onload="boom();"><div id="x" class="container"></div>This paragraph must be in the first column.<div class="container" id="y"><div class="overflow"></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/569193-1.html b/layout/generic/crashtests/569193-1.html
new file mode 100644
index 0000000000..81d9337cff
--- /dev/null
+++ b/layout/generic/crashtests/569193-1.html
@@ -0,0 +1,6 @@
+<html style="column-count: 2;"><body onload="document.body.style.height = '0'; document.body.style.margin = '1048575ch';" style="column-count: 2; font-size-adjust: 288230376151711740; white-space: pre-wrap;">
+
+
+
+
+</body></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/570160.html b/layout/generic/crashtests/570160.html
new file mode 100644
index 0000000000..4f289c4751
--- /dev/null
+++ b/layout/generic/crashtests/570160.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html class="reftest-paged">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<title>Testcase for bug 570160</title>
+<!-- distilled from href="http://www.musicalcriticism.com/concerts/usherhall-rsno-clein-0510.shtml" -->
+
+<style type="text/css">
+
+.manuscript {
+ position: absolute;
+ left: 770px;
+ top: 134px;
+ width: 233px;
+ height: 133px;
+}
+
+#maintext {
+ padding: 0px px 15px 15px;
+ position: absolute;
+ left: 16px;
+ top: 299px;
+ width: 752px;
+ height: 636px;
+}
+
+
+#maintext img {
+ padding: 10px 10px 10px 25px;
+ float: right;
+}
+
+</style></head>
+
+<body>
+
+ <div class="manuscript"></div>
+
+
+<div id="maintext">
+
+<div style="height:98%"></div>
+
+ <p><img src="yyyyyyy" alt="line" width="750" height="50" /></p>
+ <p><strong><img src="xxxxx" alt="maxwell davies" width="100" height="100" />Related articles:</strong></p>
+ <p>The RSNO and Denève in Mahler 6<br />
+
+
+</div>
+
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/570289-1.html b/layout/generic/crashtests/570289-1.html
new file mode 100644
index 0000000000..319bbb1a93
--- /dev/null
+++ b/layout/generic/crashtests/570289-1.html
@@ -0,0 +1 @@
+<html style="white-space: pre-line; border: 3434px solid black; text-shadow: 0pt 0pt 0.2em rgb(255, 136, 119); text-align: -moz-right;"><body style="padding: 1489600cm;"></body></html>
diff --git a/layout/generic/crashtests/571618-1.svg b/layout/generic/crashtests/571618-1.svg
new file mode 100644
index 0000000000..513a19994c
--- /dev/null
+++ b/layout/generic/crashtests/571618-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="100%" height="100%" style="display: list-item"/></svg>
diff --git a/layout/generic/crashtests/571975-1.html b/layout/generic/crashtests/571975-1.html
new file mode 100644
index 0000000000..f865d293ea
--- /dev/null
+++ b/layout/generic/crashtests/571975-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html style="column-count: 15;">
+<head><style>.wrapper { height: 3em; line-height: 1em; }</style></head>
+<body><div class="wrapper"></div><div class="wrapper">A B C D E</div></body>
+</html>
diff --git a/layout/generic/crashtests/571995.xhtml b/layout/generic/crashtests/571995.xhtml
new file mode 100644
index 0000000000..57728a724d
--- /dev/null
+++ b/layout/generic/crashtests/571995.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.getElementById('a').style.letterSpacing = '15ch';"></body>
+<span id="a" style="position: fixed; bottom: 0pt; top: 4095em; column-width: 1px;"><span style="column-count: 1; min-height: 2097150ch; font-size-adjust: 256; width: 89px; bottom: 35875px; min-width: min-content; position: absolute; top: 33554432ch; white-space: pre-line;">
+
+a b:
+c def:
+
+ </span></span></html>
diff --git a/layout/generic/crashtests/574958.xhtml b/layout/generic/crashtests/574958.xhtml
new file mode 100644
index 0000000000..bb0b99fcc8
--- /dev/null
+++ b/layout/generic/crashtests/574958.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-paged">
+<style>
+tbody::first-letter {float: right; }
+tbody::before { content:"before textbefore textbefore textbefore textbefore textbefore text"; float:right;}>
+</style>
+<th style="direction: rtl;">
+m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m
+<span style="position: absolute;">
+<tbody style="float: right; page-break-before: right;">m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m </tbody>
+</span>
+</th>
+
+<style>
+tbody::first-line { }
+</style>
+</html>
diff --git a/layout/generic/crashtests/578977.html b/layout/generic/crashtests/578977.html
new file mode 100644
index 0000000000..a378f575f4
--- /dev/null
+++ b/layout/generic/crashtests/578977.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 578977</title>
+</head>
+<body>
+
+<iframe src="578977.xhtml" onload="this.style.width='500px'; setTimeout(function(){document.documentElement.removeAttribute('class')},0)"></iframe>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/578977.xhtml b/layout/generic/crashtests/578977.xhtml
new file mode 100644
index 0000000000..7125d90feb
--- /dev/null
+++ b/layout/generic/crashtests/578977.xhtml
@@ -0,0 +1,10 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<td xmlns="http://www.w3.org/1999/xhtml" style="position: fixed; unicode-bidi: bidi-override; max-width: 10px; line-height: 999999999px; word-wrap: break-word;letter-spacing: 10em;">m m mm&#1593;</td>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+td::first-letter {position: fixed; }
+</style>
+
+</window>
diff --git a/layout/generic/crashtests/580504-1.xhtml b/layout/generic/crashtests/580504-1.xhtml
new file mode 100644
index 0000000000..62ad75192f
--- /dev/null
+++ b/layout/generic/crashtests/580504-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="column-width: 1px">
+<head>
+
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.getElementById("d").focus();
+ document.execCommand("inserthtml", false, "<i><font><html><form>a</form></font>");
+ document.execCommand("justifyright", false, "#ffddff");
+}
+
+window.addEventListener("load", boom);
+
+]]>
+</script>
+
+</head>
+
+<div style="position: relative;"><div style="float: left; padding: 10px 20px 0pt;"><div contenteditable="true" style="position: absolute;" id="d"></div></div><div style="clear: both; padding: 20px 20px 15px;"></div></div>
+</html>
diff --git a/layout/generic/crashtests/582793-1.html b/layout/generic/crashtests/582793-1.html
new file mode 100644
index 0000000000..c8749646d0
--- /dev/null
+++ b/layout/generic/crashtests/582793-1.html
@@ -0,0 +1,850 @@
+<style type="text/css">
+* {
+margin:0;
+padding:0;
+line-height:0;
+font-size:0;
+}
+</style>
+
+
+<div style="width:600px;height:600px;">
+ <div style="width:5px;height:600px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:595px;height:600px;float:left;">
+ <div style="width:595px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:595px;height:595px;">
+ <div style="width:590px;height:595px;float:left;">
+ <div style="width:590px;height:590px;">
+ <div style="width:5px;height:590px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:585px;height:590px;float:left;">
+ <div style="width:585px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:585px;height:585px;">
+ <div style="width:580px;height:585px;float:left;">
+ <div style="width:580px;height:580px;">
+ <div style="width:5px;height:580px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:575px;height:580px;float:left;">
+ <div style="width:575px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:575px;height:575px;">
+ <div style="width:570px;height:575px;float:left;">
+ <div style="width:570px;height:570px;">
+ <div style="width:5px;height:570px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:565px;height:570px;float:left;">
+ <div style="width:565px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:565px;height:565px;">
+ <div style="width:560px;height:565px;float:left;">
+ <div style="width:560px;height:560px;">
+ <div style="width:5px;height:560px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:555px;height:560px;float:left;">
+ <div style="width:555px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:555px;height:555px;">
+ <div style="width:550px;height:555px;float:left;">
+ <div style="width:550px;height:550px;">
+ <div style="width:5px;height:550px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:545px;height:550px;float:left;">
+ <div style="width:545px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:545px;height:545px;">
+ <div style="width:540px;height:545px;float:left;">
+ <div style="width:540px;height:540px;">
+ <div style="width:5px;height:540px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:535px;height:540px;float:left;">
+ <div style="width:535px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:535px;height:535px;">
+ <div style="width:530px;height:535px;float:left;">
+ <div style="width:530px;height:530px;">
+ <div style="width:5px;height:530px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:525px;height:530px;float:left;">
+ <div style="width:525px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:525px;height:525px;">
+ <div style="width:520px;height:525px;float:left;">
+ <div style="width:520px;height:520px;">
+ <div style="width:5px;height:520px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:515px;height:520px;float:left;">
+ <div style="width:515px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:515px;height:515px;">
+ <div style="width:510px;height:515px;float:left;">
+ <div style="width:510px;height:510px;">
+ <div style="width:5px;height:510px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:505px;height:510px;float:left;">
+ <div style="width:505px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:505px;height:505px;">
+ <div style="width:500px;height:505px;float:left;">
+ <div style="width:500px;height:500px;">
+ <div style="width:5px;height:500px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:495px;height:500px;float:left;">
+ <div style="width:495px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:495px;height:495px;">
+ <div style="width:490px;height:495px;float:left;">
+ <div style="width:490px;height:490px;">
+ <div style="width:5px;height:490px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:485px;height:490px;float:left;">
+ <div style="width:485px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:485px;height:485px;">
+ <div style="width:480px;height:485px;float:left;">
+ <div style="width:480px;height:480px;">
+ <div style="width:5px;height:480px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:475px;height:480px;float:left;">
+ <div style="width:475px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:475px;height:475px;">
+ <div style="width:470px;height:475px;float:left;">
+ <div style="width:470px;height:470px;">
+ <div style="width:5px;height:470px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:465px;height:470px;float:left;">
+ <div style="width:465px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:465px;height:465px;">
+ <div style="width:460px;height:465px;float:left;">
+ <div style="width:460px;height:460px;">
+ <div style="width:5px;height:460px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:455px;height:460px;float:left;">
+ <div style="width:455px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:455px;height:455px;">
+ <div style="width:450px;height:455px;float:left;">
+ <div style="width:450px;height:450px;">
+ <div style="width:5px;height:450px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:445px;height:450px;float:left;">
+ <div style="width:445px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:445px;height:445px;">
+ <div style="width:440px;height:445px;float:left;">
+ <div style="width:440px;height:440px;">
+ <div style="width:5px;height:440px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:435px;height:440px;float:left;">
+ <div style="width:435px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:435px;height:435px;">
+ <div style="width:430px;height:435px;float:left;">
+ <div style="width:430px;height:430px;">
+ <div style="width:5px;height:430px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:425px;height:430px;float:left;">
+ <div style="width:425px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:425px;height:425px;">
+ <div style="width:420px;height:425px;float:left;">
+ <div style="width:420px;height:420px;">
+ <div style="width:5px;height:420px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:415px;height:420px;float:left;">
+ <div style="width:415px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:415px;height:415px;">
+ <div style="width:410px;height:415px;float:left;">
+ <div style="width:410px;height:410px;">
+ <div style="width:5px;height:410px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:405px;height:410px;float:left;">
+ <div style="width:405px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:405px;height:405px;">
+ <div style="width:400px;height:405px;float:left;">
+ <div style="width:400px;height:400px;">
+ <div style="width:5px;height:400px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:395px;height:400px;float:left;">
+ <div style="width:395px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:395px;height:395px;">
+ <div style="width:390px;height:395px;float:left;">
+ <div style="width:390px;height:390px;">
+ <div style="width:5px;height:390px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:385px;height:390px;float:left;">
+ <div style="width:385px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:385px;height:385px;">
+ <div style="width:380px;height:385px;float:left;">
+ <div style="width:380px;height:380px;">
+ <div style="width:5px;height:380px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:375px;height:380px;float:left;">
+ <div style="width:375px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:375px;height:375px;">
+ <div style="width:370px;height:375px;float:left;">
+ <div style="width:370px;height:370px;">
+ <div style="width:5px;height:370px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:365px;height:370px;float:left;">
+ <div style="width:365px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:365px;height:365px;">
+ <div style="width:360px;height:365px;float:left;">
+ <div style="width:360px;height:360px;">
+ <div style="width:5px;height:360px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:355px;height:360px;float:left;">
+ <div style="width:355px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:355px;height:355px;">
+ <div style="width:350px;height:355px;float:left;">
+ <div style="width:350px;height:350px;">
+ <div style="width:5px;height:350px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:345px;height:350px;float:left;">
+ <div style="width:345px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:345px;height:345px;">
+ <div style="width:340px;height:345px;float:left;">
+ <div style="width:340px;height:340px;">
+ <div style="width:5px;height:340px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:335px;height:340px;float:left;">
+ <div style="width:335px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:335px;height:335px;">
+ <div style="width:330px;height:335px;float:left;">
+ <div style="width:330px;height:330px;">
+ <div style="width:5px;height:330px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:325px;height:330px;float:left;">
+ <div style="width:325px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:325px;height:325px;">
+ <div style="width:320px;height:325px;float:left;">
+ <div style="width:320px;height:320px;">
+ <div style="width:5px;height:320px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:315px;height:320px;float:left;">
+ <div style="width:315px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:315px;height:315px;">
+ <div style="width:310px;height:315px;float:left;">
+ <div style="width:310px;height:310px;">
+ <div style="width:5px;height:310px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:305px;height:310px;float:left;">
+ <div style="width:305px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:305px;height:305px;">
+ <div style="width:300px;height:305px;float:left;">
+ <div style="width:300px;height:300px;">
+ <div style="width:5px;height:300px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:295px;height:300px;float:left;">
+ <div style="width:295px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:295px;height:295px;">
+ <div style="width:290px;height:295px;float:left;">
+ <div style="width:290px;height:290px;">
+ <div style="width:5px;height:290px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:285px;height:290px;float:left;">
+ <div style="width:285px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:285px;height:285px;">
+ <div style="width:280px;height:285px;float:left;">
+ <div style="width:280px;height:280px;">
+ <div style="width:5px;height:280px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:275px;height:280px;float:left;">
+ <div style="width:275px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:275px;height:275px;">
+ <div style="width:270px;height:275px;float:left;">
+ <div style="width:270px;height:270px;">
+ <div style="width:5px;height:270px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:265px;height:270px;float:left;">
+ <div style="width:265px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:265px;height:265px;">
+ <div style="width:260px;height:265px;float:left;">
+ <div style="width:260px;height:260px;">
+ <div style="width:5px;height:260px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:255px;height:260px;float:left;">
+ <div style="width:255px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:255px;height:255px;">
+ <div style="width:250px;height:255px;float:left;">
+ <div style="width:250px;height:250px;">
+ <div style="width:5px;height:250px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:245px;height:250px;float:left;">
+ <div style="width:245px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:245px;height:245px;">
+ <div style="width:240px;height:245px;float:left;">
+ <div style="width:240px;height:240px;">
+ <div style="width:5px;height:240px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:235px;height:240px;float:left;">
+ <div style="width:235px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:235px;height:235px;">
+ <div style="width:230px;height:235px;float:left;">
+ <div style="width:230px;height:230px;">
+ <div style="width:5px;height:230px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:225px;height:230px;float:left;">
+ <div style="width:225px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:225px;height:225px;">
+ <div style="width:220px;height:225px;float:left;">
+ <div style="width:220px;height:220px;">
+ <div style="width:5px;height:220px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:215px;height:220px;float:left;">
+ <div style="width:215px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:215px;height:215px;">
+ <div style="width:210px;height:215px;float:left;">
+ <div style="width:210px;height:210px;">
+ <div style="width:5px;height:210px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:205px;height:210px;float:left;">
+ <div style="width:205px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:205px;height:205px;">
+ <div style="width:200px;height:205px;float:left;">
+ <div style="width:200px;height:200px;">
+ <div style="width:5px;height:200px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:195px;height:200px;float:left;">
+ <div style="width:195px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:195px;height:195px;">
+ <div style="width:190px;height:195px;float:left;">
+ <div style="width:190px;height:190px;">
+ <div style="width:5px;height:190px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:185px;height:190px;float:left;">
+ <div style="width:185px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:185px;height:185px;">
+ <div style="width:180px;height:185px;float:left;">
+ <div style="width:180px;height:180px;">
+ <div style="width:5px;height:180px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:175px;height:180px;float:left;">
+ <div style="width:175px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:175px;height:175px;">
+ <div style="width:170px;height:175px;float:left;">
+ <div style="width:170px;height:170px;">
+ <div style="width:5px;height:170px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:165px;height:170px;float:left;">
+ <div style="width:165px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:165px;height:165px;">
+ <div style="width:160px;height:165px;float:left;">
+ <div style="width:160px;height:160px;">
+ <div style="width:5px;height:160px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:155px;height:160px;float:left;">
+ <div style="width:155px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:155px;height:155px;">
+ <div style="width:150px;height:155px;float:left;">
+ <div style="width:150px;height:150px;">
+ <div style="width:5px;height:150px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:145px;height:150px;float:left;">
+ <div style="width:145px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:145px;height:145px;">
+ <div style="width:140px;height:145px;float:left;">
+ <div style="width:140px;height:140px;">
+ <div style="width:5px;height:140px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:135px;height:140px;float:left;">
+ <div style="width:135px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:135px;height:135px;">
+ <div style="width:130px;height:135px;float:left;">
+ <div style="width:130px;height:130px;">
+ <div style="width:5px;height:130px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:125px;height:130px;float:left;">
+ <div style="width:125px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:125px;height:125px;">
+ <div style="width:120px;height:125px;float:left;">
+ <div style="width:120px;height:120px;">
+ <div style="width:5px;height:120px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:115px;height:120px;float:left;">
+ <div style="width:115px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:115px;height:115px;">
+ <div style="width:110px;height:115px;float:left;">
+ <div style="width:110px;height:110px;">
+ <div style="width:5px;height:110px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:105px;height:110px;float:left;">
+ <div style="width:105px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:105px;height:105px;">
+ <div style="width:100px;height:105px;float:left;">
+ <div style="width:100px;height:100px;">
+ <div style="width:5px;height:100px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:95px;height:100px;float:left;">
+ <div style="width:95px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:95px;height:95px;">
+ <div style="width:90px;height:95px;float:left;">
+ <div style="width:90px;height:90px;">
+ <div style="width:5px;height:90px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:85px;height:90px;float:left;">
+ <div style="width:85px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:85px;height:85px;">
+ <div style="width:80px;height:85px;float:left;">
+ <div style="width:80px;height:80px;">
+ <div style="width:5px;height:80px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:75px;height:80px;float:left;">
+ <div style="width:75px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:75px;height:75px;">
+ <div style="width:70px;height:75px;float:left;">
+ <div style="width:70px;height:70px;">
+ <div style="width:5px;height:70px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:65px;height:70px;float:left;">
+ <div style="width:65px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:65px;height:65px;">
+ <div style="width:60px;height:65px;float:left;">
+ <div style="width:60px;height:60px;">
+ <div style="width:5px;height:60px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:55px;height:60px;float:left;">
+ <div style="width:55px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:55px;height:55px;">
+ <div style="width:50px;height:55px;float:left;">
+ <div style="width:50px;height:50px;">
+ <div style="width:5px;height:50px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:45px;height:50px;float:left;">
+ <div style="width:45px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:45px;height:45px;">
+ <div style="width:40px;height:45px;float:left;">
+ <div style="width:40px;height:40px;">
+ <div style="width:5px;height:40px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:35px;height:40px;float:left;">
+ <div style="width:35px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:35px;height:35px;">
+ <div style="width:30px;height:35px;float:left;">
+ <div style="width:30px;height:30px;">
+ <div style="width:5px;height:30px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:25px;height:30px;float:left;">
+ <div style="width:25px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:25px;height:25px;">
+ <div style="width:20px;height:25px;float:left;">
+ <div style="width:20px;height:20px;">
+ <div style="width:5px;height:20px;background-color:blue;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:15px;height:20px;float:left;">
+ <div style="width:15px;height:5px;background-color:black;min-height:1px;min-width:1px;"> </div>
+ <div style="width:15px;height:15px;">
+ <div style="width:10px;height:15px;float:left;">
+ <div style="width:10px;height:10px;">
+ <div style="width:5px;height:10px;background-color:red;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="width:5px;height:10px;float:left;">
+ <div style="width:5px;height:5px;background-color:yellow;min-height:1px;min-width:1px;"> </div>
+ <div style="width:5px;height:5px;">
+ <div style="width:0px;height:5px;float:left;">
+ <div style="width:0px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:5px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:10px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:15px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:20px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:25px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:30px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:35px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:40px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:45px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:50px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:55px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:60px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:65px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:70px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:75px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:80px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:85px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:90px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:95px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:100px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:105px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:110px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:115px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:120px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:125px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:130px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:135px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:140px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:145px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:150px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:155px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:160px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:165px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:170px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:175px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:180px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:185px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:190px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:195px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:200px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:205px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:210px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:215px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:220px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:225px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:230px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:235px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:240px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:245px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:250px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:255px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:260px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:265px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:270px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:275px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:280px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:285px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:290px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:295px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:300px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:305px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:310px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:315px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:320px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:325px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:330px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:335px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:340px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:345px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:350px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:355px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:360px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:365px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:370px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:375px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:380px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:385px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:390px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:395px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:400px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:405px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:410px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:415px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:420px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:425px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:430px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:435px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:440px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:445px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:450px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:455px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:460px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:465px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:470px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:475px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:480px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:485px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:490px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:495px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:500px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:505px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:510px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:515px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:520px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:525px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:530px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:535px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:540px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:545px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:550px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:555px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:560px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:565px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:570px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:575px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:580px;height:5px;background-color:#938472;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:585px;background-color:#ee4231;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div style="width:590px;height:5px;background-color:gray;min-height:1px;min-width:1px;"> </div>
+ </div>
+ <div style="width:5px;height:595px;background-color:pink;min-height:1px;min-width:1px;float:left;"> </div>
+ <div style="clear:both;"> </div>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+</div>
diff --git a/layout/generic/crashtests/585598-1.xhtml b/layout/generic/crashtests/585598-1.xhtml
new file mode 100644
index 0000000000..6cffa28ffe
--- /dev/null
+++ b/layout/generic/crashtests/585598-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-paged">
+<span style="float: left;page-break-before: right;">
+<select style="float: left;">
+</select>
+</span>
+
+</html>
diff --git a/layout/generic/crashtests/586806-1.html b/layout/generic/crashtests/586806-1.html
new file mode 100644
index 0000000000..44ae0f9672
--- /dev/null
+++ b/layout/generic/crashtests/586806-1.html
@@ -0,0 +1,27 @@
+<html class="reftest-wait">
+<head>
+<script>
+function doe2() {
+document.getElementById('b').style.position = 'static';
+document.getElementById('a').setAttribute('style', 'position: absolute; column-count: 2;');
+document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<style>body * {border: 1px solid black;}</style>
+<body onload="doe2();">
+<div style="width: 500px;border: 1px solid black;">
+<div style="display: inline-block; width: 100px; height: 100px;"></div>
+<span style="position: absolute;"></span>
+mmmmmmmmmmmmmmmmmmmmmmm
+
+<span id="a">mmmmmmmmmmmmmmmmmmmm
+
+<div id="b" style="display: inline-block; width: 240px; height: 100px; position: absolute;"></div>
+m mm mm mm mm mm mm mm mm m
+<span style="float: left;">m</span>
+</span>
+</div>
+</body>
+
+</html>
diff --git a/layout/generic/crashtests/586806-2.html b/layout/generic/crashtests/586806-2.html
new file mode 100644
index 0000000000..3fba0056f6
--- /dev/null
+++ b/layout/generic/crashtests/586806-2.html
@@ -0,0 +1 @@
+<div style="column-count: 2; width: 241px;"><div style="display: inline-block; width: 240px; height: 100px;"></div>m</div>
diff --git a/layout/generic/crashtests/586806-3.html b/layout/generic/crashtests/586806-3.html
new file mode 100644
index 0000000000..b630169b4e
--- /dev/null
+++ b/layout/generic/crashtests/586806-3.html
@@ -0,0 +1,9 @@
+<body style="font-size: 16px">
+<div style="width: 400px;">
+mmmmmmmmmmmmmmmmmmmmmmm
+<span style="position: absolute; column-count: 2">mmmmmmmmmmmmmmmmmmmm
+<div style="display: inline-block; width: 240px; height: 100px"></div>
+m mm mm mm mm mm mm mm mm m
+<div style="float: left;">m</div>
+</span>
+</div>
diff --git a/layout/generic/crashtests/586973-1.html b/layout/generic/crashtests/586973-1.html
new file mode 100644
index 0000000000..ccdd0ea021
--- /dev/null
+++ b/layout/generic/crashtests/586973-1.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<style>
+hr::before { content:"b"; float:right;}
+</style>
+</head>
+<body>
+<hr style="column-count: 1;">
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/589002-1.html b/layout/generic/crashtests/589002-1.html
new file mode 100644
index 0000000000..ace0175052
--- /dev/null
+++ b/layout/generic/crashtests/589002-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="column-width: 1px; column-gap: 576460752303423500mozmm;"></body>
+</html>
diff --git a/layout/generic/crashtests/590404.html b/layout/generic/crashtests/590404.html
new file mode 100644
index 0000000000..9f6ee8fe93
--- /dev/null
+++ b/layout/generic/crashtests/590404.html
@@ -0,0 +1 @@
+<iframe src="data:text/html,%3Cdiv%20style%3D%22background%3A%20-moz-element(%23e)%22%3Ez"></iframe>
diff --git a/layout/generic/crashtests/591141.html b/layout/generic/crashtests/591141.html
new file mode 100644
index 0000000000..e1f0bbbd3d
--- /dev/null
+++ b/layout/generic/crashtests/591141.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+<svg><pattern id="p"/></svg>
+<div style="width: 100px; height: 100px; background: -moz-element(#p);"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/592118.html b/layout/generic/crashtests/592118.html
new file mode 100644
index 0000000000..5df4e75280
--- /dev/null
+++ b/layout/generic/crashtests/592118.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>Stack pointer free with -moz-element</title>
+<div id="paintServer" style="width: 20px; height: 20px; background: red;"></div>
+<div style="transform: scale(1.01); width: 100px; height: 100px; background: -moz-element(#paintServer) -5px -3px; background-size: 20px 32769px;"></div>
diff --git a/layout/generic/crashtests/594808-1.html b/layout/generic/crashtests/594808-1.html
new file mode 100644
index 0000000000..d88147eb5b
--- /dev/null
+++ b/layout/generic/crashtests/594808-1.html
@@ -0,0 +1,7 @@
+<script>
+ oSelection = window.getSelection();
+ oRange = document.createRange();
+ oSelection.addRange(oRange);
+ oRange.detach();
+ oSelection.removeRange(oRange);
+</script>
diff --git a/layout/generic/crashtests/595435-1.xhtml b/layout/generic/crashtests/595435-1.xhtml
new file mode 100644
index 0000000000..894bec36c2
--- /dev/null
+++ b/layout/generic/crashtests/595435-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.documentElement.offsetHeight; document.getElementById('s').style.fontVariant = 'small-caps'; document.getElementById('t').style.verticalAlign = '';">
+
+<div style="direction: rtl; font-variant: small-caps; line-height: 541579443853962em;"><div style="padding: 77in; width: 0px; position: fixed; word-wrap: break-word; white-space: pre-wrap;" id="s">
+st [m; ]
+ </div></div><div style="vertical-align: -226985587140in;" id="t"></div>
+
+</body></html>
diff --git a/layout/generic/crashtests/595740-1.html b/layout/generic/crashtests/595740-1.html
new file mode 100644
index 0000000000..a89af03dfe
--- /dev/null
+++ b/layout/generic/crashtests/595740-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html class="reftest-paged">
+<title>Testcase bug 595740 (crash on print-preview)</title>
+<style type="text/css">
+body { margin:0; font: 0.2in/0.2in serif; }
+</style>
+<div style="height: 1.75in"></div>
+y<br><span style="float: right; width: 1in; height: 2in"></span>z
diff --git a/layout/generic/crashtests/597240-1.xhtml b/layout/generic/crashtests/597240-1.xhtml
new file mode 100644
index 0000000000..19e04acaaa
--- /dev/null
+++ b/layout/generic/crashtests/597240-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ var div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ div.style.cssFloat = "left";
+ document.getElementById("a").appendChild(div);
+}
+
+]]>
+</script>
+</head>
+<body onload="boom();">
+<fieldset id="a"><legend style="display: table-footer-group;"></legend></fieldset>
+</body>
+</html>
diff --git a/layout/generic/crashtests/600100.xhtml b/layout/generic/crashtests/600100.xhtml
new file mode 100644
index 0000000000..100fd3156f
--- /dev/null
+++ b/layout/generic/crashtests/600100.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head><style>*::first-line { } *::after { content: 'after text'; } *::before { content: 'before text'; } </style></head><body onload="document.documentElement.offsetHeight; document.getElementsByTagName('style')[0].appendChild(document.createTextNode(' '));" style="column-width: 0pt;"><div style="float: right;"><span style="overflow-y: auto; float: left;"></span></div></body></html>
diff --git a/layout/generic/crashtests/603490-1.html b/layout/generic/crashtests/603490-1.html
new file mode 100644
index 0000000000..a9a8a8c2a3
--- /dev/null
+++ b/layout/generic/crashtests/603490-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html><html><script>
+
+function boom()
+{
+ while (document.documentElement.firstChild)
+ document.documentElement.firstChild.remove();
+ document.documentElement.contentEditable = "true";
+ document.execCommand("strikethrough", false, null);
+ try { document.execCommand("justifyfull", false, null); } catch(e) { }
+ document.documentElement.offsetHeight;
+ try { document.execCommand("delete", false, null); } catch(e) { }
+ document.execCommand("inserthtml", false, "<span> <\/span>");
+}
+window.addEventListener("load", boom);
+
+</script></html>
diff --git a/layout/generic/crashtests/603510-1.html b/layout/generic/crashtests/603510-1.html
new file mode 100644
index 0000000000..90d16bed2d
--- /dev/null
+++ b/layout/generic/crashtests/603510-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ var r = document.documentElement;
+
+ while (r.firstChild)
+ r.firstChild.remove();
+
+ var a = document.createTextNode("a");
+ r.appendChild(a);
+ a.splitText(0);
+ a.splitText(0);
+
+ document.documentElement.offsetHeight;
+
+ r.appendChild(document.createTextNode("b"));
+}
+
+window.addEventListener("load", boom);
+
+</script>
diff --git a/layout/generic/crashtests/604314-1.html b/layout/generic/crashtests/604314-1.html
new file mode 100644
index 0000000000..d25971951b
--- /dev/null
+++ b/layout/generic/crashtests/604314-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var sel = window.getSelection('');
+ sel.collapse(document.createTextNode("x"), 0);
+ sel.extend(document.documentElement, 0);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/generic/crashtests/604843.html b/layout/generic/crashtests/604843.html
new file mode 100644
index 0000000000..92e929f814
--- /dev/null
+++ b/layout/generic/crashtests/604843.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+
+ var c = document.getElementById("c");
+ var t1 = document.createTextNode("x x x x x x x x x x x x x x x x x x x x");
+ var t2 = document.createTextNode("y y y y y y y y y y y y y y y y y y y y y");
+ c.appendChild(t1);
+ c.appendChild(t2);
+ document.documentElement.offsetHeight;
+
+ var div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ c.insertBefore(div, t2);
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="width: 5ch; font-family: monospace; margin: 0;">
+<table><tbody><tr><td id="c"></td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/generic/crashtests/605340.html b/layout/generic/crashtests/605340.html
new file mode 100644
index 0000000000..57f68d7dc1
--- /dev/null
+++ b/layout/generic/crashtests/605340.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('a').lastChild.appendData('4');">
+
+<div id="a" style="position: absolute;"><span><span style="white-space: pre;">
+1
+2</span>
+3</span>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/606642.xhtml b/layout/generic/crashtests/606642.xhtml
new file mode 100644
index 0000000000..b6bf431f71
--- /dev/null
+++ b/layout/generic/crashtests/606642.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ var r = document.getElementById("r");
+ r.parentNode.removeChild(r);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div style="column-count: 3; height: 2in;"><div style="position: relative;"><div id="r" style="position: absolute;"><div style="height: 0in;"><div style="height: 5in;"></div><div style="position: absolute; height: 5in;"></div></div></div></div></div></body>
+</html>
diff --git a/layout/generic/crashtests/613455-1.svg b/layout/generic/crashtests/613455-1.svg
new file mode 100644
index 0000000000..f856504e38
--- /dev/null
+++ b/layout/generic/crashtests/613455-1.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="70">
+<script>
+
+function boom()
+{
+ document.documentElement.style.minHeight = '68121107503rem'
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+</svg> \ No newline at end of file
diff --git a/layout/generic/crashtests/613629-1.xhtml b/layout/generic/crashtests/613629-1.xhtml
new file mode 100644
index 0000000000..92bfbbaa02
--- /dev/null
+++ b/layout/generic/crashtests/613629-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.documentElement.style.height = "656409px";
+}
+
+</script>
+</head>
+<body onload="boom();"><svg xmlns="http://www.w3.org/2000/svg" style="float: left; padding: 42493240px;"></svg></body>
+</html> \ No newline at end of file
diff --git a/layout/generic/crashtests/616052-1.html b/layout/generic/crashtests/616052-1.html
new file mode 100644
index 0000000000..bcc4ce5c28
--- /dev/null
+++ b/layout/generic/crashtests/616052-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="column-count: 15; position: absolute; height: 0px;"><span><span style="width: 20px; height: 20px; float: left;"></span>AAA <div style="position: absolute;"></div></span></body>
+</html>
diff --git a/layout/generic/crashtests/619021.html b/layout/generic/crashtests/619021.html
new file mode 100644
index 0000000000..586c0f2dbd
--- /dev/null
+++ b/layout/generic/crashtests/619021.html
@@ -0,0 +1,5 @@
+<foo> <marquee> <marquee> <marquee> <marquee> <marquee> <marquee>
+<foo> <marquee> <foo> <marquee> <foo> <object> <marquee> <foo>
+<marquee> <marquee> <foo> <foo> <marquee> <marquee> <foo> <marquee>
+<marquee> <marquee> <foo> <marquee> <foo> <foo> <marquee> <marquee>
+<marquee> </marquee> <foo> <foo> <pre>
diff --git a/layout/generic/crashtests/621424-1.html b/layout/generic/crashtests/621424-1.html
new file mode 100644
index 0000000000..9910324d2d
--- /dev/null
+++ b/layout/generic/crashtests/621424-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html style="column-width: 1px;"><body style="column-width: 1px;" onload="document.getElementById('x').style.cssFloat='';"><div style="height: 50px;"></div><div style="float: left; column-count: 3; height: 466px;"></div><div style="padding: 176px; float: right;" id="x"></div></body></html>
diff --git a/layout/generic/crashtests/621841-1.html b/layout/generic/crashtests/621841-1.html
new file mode 100644
index 0000000000..f6c9b6eafc
--- /dev/null
+++ b/layout/generic/crashtests/621841-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var frame = document.getElementById("f");
+ var frameDoc = frame.contentDocument;
+ frameDoc.getElementById("g").style.background = "yellow";
+ frame.style.cssFloat = "right";
+ document.documentElement.offsetHeight;
+ frameDoc.documentElement.style.color = "green";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><iframe id="f" src="data:text/html,<!DOCTYPE html><frameset><frame id=g></frameset>"></iframe></body>
+</html>
diff --git a/layout/generic/crashtests/622596.html b/layout/generic/crashtests/622596.html
new file mode 100644
index 0000000000..fec43f11fa
--- /dev/null
+++ b/layout/generic/crashtests/622596.html
@@ -0,0 +1,6 @@
+<script>
+t2 = window.open();
+t2.document.documentElement.childNodes.item(undefined).contentEditable = true;
+t2.getSelection().containsNode([], false);
+t2.close()
+</script>
diff --git a/layout/generic/crashtests/641724.html b/layout/generic/crashtests/641724.html
new file mode 100644
index 0000000000..665f5e9491
--- /dev/null
+++ b/layout/generic/crashtests/641724.html
@@ -0,0 +1,315 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<style type="text/css" rel="stylesheet" media="all">
+.form-item { padding:30px; }
+.views-exposed-widget {float:left; clear:left;}
+.views-exposed-widgets {column-width:250px;}
+.clearfix:after
+,.clear-block:after{content:".";display:block;}
+</style>
+<script type="text/javascript">
+//<!-- DDBEGIN -->
+(function ()
+{
+ var D = window.jQuery = window.$ = function (a, b)
+ {
+ return new D.fn.init(a, b)
+ };
+ var u = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;
+ D.fn = D.prototype =
+ {
+ init: function (d, b)
+ {
+ d = d || document;
+ if (d.nodeType)
+ {
+ this[0] = d;
+ this.length = 1;
+ }
+ if (typeof d == "string")
+ {
+ var c = u.exec(d);
+ if (c)
+ {
+ if (c[1]) d = D.clean([c[1]], b);
+ }
+ else
+ return D(b).find(d)
+ }
+ return this.setArray(D.makeArray(d))
+ },
+ pushStack: function (b)
+ {
+ return D(b);
+ },
+ setArray: function (a)
+ {
+ Array.prototype.push.apply(this, a);
+ },
+ each: function (a, b)
+ {
+ return D.each(this, a, b)
+ },
+ after: function ()
+ {
+ return this.domManip(arguments, false, true, function (a)
+ {
+ this.parentNode.insertBefore(a, this.nextSibling)
+ })
+ },
+ find: function (b)
+ {
+ var c = D.map(this, function (a)
+ {
+ return D.find(b, a)
+ });
+ return this.pushStack(/[^+>] [^+>]/.test(b) ? D.unique(c) : c)
+ },
+ domManip: function (g, f, h, d)
+ {
+ return this.each(function ()
+ {
+ elems = D.clean(g, this.ownerDocument);
+ var b = this;
+ D.each(elems, function ()
+ {
+ d.call(b, this)
+ });
+ })
+ }
+ };
+ D.fn.init.prototype = D.fn;
+ D.extend = D.fn.extend = function ()
+ {
+ var b = arguments[0];
+ var i = 1;
+ var length = arguments.length;
+ if (length == 1)
+ {
+ b = this;
+ --i
+ }
+ if ((options = arguments[i]) != null) for (var c in options)
+ {
+ copy = options[c];
+ if (copy !== undefined) b[c] = copy
+ }
+ return b
+ };
+ D.extend(
+ {
+ each: function (d, a, c)
+ {
+ for (e in d)
+ if (a.call(d[e], e, d[e]) === false)
+ for (var b = d[0]; i < length; b = d[++i]) { }
+ return d
+ },
+ curCSS: function (f, l, k)
+ {
+ l = l.replace(/([A-Z])/g, "-$1").toLowerCase();
+ var c = document.defaultView.getComputedStyle(f);
+ c.getPropertyValue(l);
+ },
+ clean: function (l, h)
+ {
+ var k = [];
+ D.each(l, function (i, d)
+ {
+ var div = document.createElement("div");
+ div.innerHTML = "" + d
+ d = D.makeArray(div.childNodes)
+ if (d[0] == undefined) k.push(d);
+ else k = D.merge(k, d)
+ });
+ return k
+ },
+ makeArray: function (b)
+ {
+ var a = [];
+ var i = b.length;
+ while (i) a[--i] = b[i]
+ return a
+ },
+ merge: function (a, b)
+ {
+ var i = 0;
+ var pos = a.length;
+ while (elem = b[i++]) a[pos++] = elem;
+ return a
+ },
+ map: function (d, a)
+ {
+ var c = [];
+ for (var i = 0, length = d.length; i < length; i++)
+ {
+ var b = a(d[i], i);
+ if (b != null) c[c.length] = b
+ }
+ return c.concat.apply([], c)
+ }
+ });
+ D.each(
+ {
+ insertAfter: "after",
+ }, function (c, b)
+ {
+ D.fn[c] = function ()
+ {
+ var a = arguments;
+ return this.each(function ()
+ {
+ for (var i = 0, length = a.length; i < length; i++) D(a[i])[b](this)
+ })
+ }
+ });
+ function num(a, b)
+ {
+ return a[0] && parseInt(D.curCSS(a[0], b, true), 10)
+ }
+ var quickClass = new RegExp("^([#.]?)(" + "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)" + "*)");
+ D.extend(
+ {
+ find: function (t, o)
+ {
+ var d = [o];
+ var done = [];
+ while (t)
+ {
+ m = quickClass.exec(t)
+ var f = d[d.length - 1];
+ if (m[1] == "#")
+ {
+ var p = f.getElementById(m[2]);
+ d = p && !m[3] ? [p] : []
+ }
+ t = t.replace(quickClass, "")
+ }
+ return D.merge(done, d);
+ },
+ });
+ D.fn.extend(
+ {
+ bind: function (c, a, b)
+ {
+ return this.each(function () { })
+ },
+ ready: function (a)
+ {
+ bindReady();
+ D.readyList.push(function ()
+ {
+ return a.call(this, D)
+ });
+ }
+ });
+ D.extend(
+ {
+ readyList: [],
+ ready: function ()
+ {
+ D.each(D.readyList, function ()
+ {
+ this.call(document)
+ });
+ }
+ });
+ function bindReady()
+ {
+ document.addEventListener("DOMContentLoaded", D.ready);
+ }
+ D.each(["Height", "Width"], function (i, b)
+ {
+ D.fn["outer" + b] = function (a)
+ {
+ num(this, "borderRightWidth")
+ }
+ })
+})();
+var Drupal = {
+ 'settings': {
+ },
+ 'behaviors': {
+ },
+};
+Drupal.attachBehaviors = function (context)
+{
+ jQuery.each(Drupal.behaviors, function ()
+ {
+ this(context);
+ });
+}
+$(document).ready(function ()
+{
+ Drupal.attachBehaviors(this);
+});
+(function (C)
+{
+ C.ui = { }
+ C.widget = function (K, J)
+ {
+ var L = K.split(".")[0];
+ K = K.split(".")[1];
+ C.fn[K] = function (P)
+ {
+ return this.each(function ()
+ {
+ C.data(this, K, new C[L][K](this, P));
+ })
+ };
+ C[L][K] = function (O, N)
+ {
+ this.element = C(O).bind();
+ this._init()
+ };
+ C[L][K].prototype = C.extend( { }, J);
+ };
+})(jQuery);
+(function (a)
+{
+ a.widget("ui.dropdownchecklist", {
+ _appendDropContainer: function ()
+ {
+ return a("<div/>");
+ },
+ _appendControl: function ()
+ {
+ f.insertAfter(this.sourceSelect);
+ },
+ _appendItems: function ()
+ {
+ f = this.dropWrapper;
+ var e = f.find(".ui-dropdownchecklist-dropcontainer").outerHeight();
+ },
+ _init: function ()
+ {
+ this.sourceSelect = this.element;
+ this.dropWrapper = this._appendDropContainer();
+ this._appendItems();
+ this._appendControl();
+ }
+ });
+})(jQuery);;
+Drupal.behaviors.sexyExposed = function (context)
+{
+ var settings = Drupal.settings.sexyExposed;
+ $.each(settings, function (key, element)
+ {
+ $(key).dropdownchecklist();
+ });
+};
+jQuery.extend(Drupal.settings, {
+ "sexyExposed": {
+ "select#edit-field-spec-otg-value-many-to-one": "0",
+ }
+});
+</script>
+<div class="views-exposed-widgets clear-block">
+ <div class="views-exposed-widget">
+ <div>
+ <div class="form-item"></div>
+ </div>
+ </div>
+ <div class="views-exposed-widget">
+ <select multiple="multiple" id="edit-field-spec-otg-value-many-to-one"> </select>
+</div>
+</html>
diff --git a/layout/generic/crashtests/645072-1.html b/layout/generic/crashtests/645072-1.html
new file mode 100644
index 0000000000..429f963d92
--- /dev/null
+++ b/layout/generic/crashtests/645072-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang=ru>
+<head>
+<meta charset=windows-1251>
+<title>Testcase, bug 645072</title>
+</head>
+<body>
+<table cellspacing=0 cellpadding=0 border=0>
+ <tr valign=top><td>Ðàçìûòûå,çåëåíûå,íî&shy;<wbr> êëàññíûå)<br>&shy;<wbr><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAABpJREFUWMPtwQEBAAAAgiD/r25IQAEAAADvBhAgAAGX91fXAAAAAElFTkSuQmCC" alt="">&shy;<wbr>
+</table>
+<script>
+// simulate image loading
+document.body.offsetWidth;
+document.getElementsByTagName("img")[0].style.width = "604px";
+document.getElementsByTagName("img")[0].style.height = "405px";
+</script>
diff --git a/layout/generic/crashtests/645072-2.html b/layout/generic/crashtests/645072-2.html
new file mode 100644
index 0000000000..fca395e906
--- /dev/null
+++ b/layout/generic/crashtests/645072-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("f").appendChild(document.createTextNode("\u00AD"));
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+<body onload="boom();"><fieldset id="f"> </fieldset></body>
+</html>
+
diff --git a/layout/generic/crashtests/646561-1.html b/layout/generic/crashtests/646561-1.html
new file mode 100644
index 0000000000..063e218b11
--- /dev/null
+++ b/layout/generic/crashtests/646561-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><body><table><tbody><tr><td>&shy;R&#x759;</td></tr></tbody></table></body></html>
diff --git a/layout/generic/crashtests/646983-1.html b/layout/generic/crashtests/646983-1.html
new file mode 100644
index 0000000000..c32a3ba9cd
--- /dev/null
+++ b/layout/generic/crashtests/646983-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body style="min-width: min-content">
+<span>&#x06CD;T</span><span>&shy;</span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/647332-1.html b/layout/generic/crashtests/647332-1.html
new file mode 100644
index 0000000000..063e218b11
--- /dev/null
+++ b/layout/generic/crashtests/647332-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><body><table><tbody><tr><td>&shy;R&#x759;</td></tr></tbody></table></body></html>
diff --git a/layout/generic/crashtests/650499-1.html b/layout/generic/crashtests/650499-1.html
new file mode 100644
index 0000000000..26ad468769
--- /dev/null
+++ b/layout/generic/crashtests/650499-1.html
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+<body style="max-width: min-content; white-space: pre;" onload="boom();">&#x00AD;
+R</body>
+</html>
diff --git a/layout/generic/crashtests/654002-1.html b/layout/generic/crashtests/654002-1.html
new file mode 100644
index 0000000000..b96b0ef35c
--- /dev/null
+++ b/layout/generic/crashtests/654002-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 654002</title>
+<script>
+function boom() {
+ var e = document.getElementById('inner');
+ var s = "<br>"
+ for (k=0;k<=14;k++) {
+ s += s;
+ }
+ e.innerHTML = s;
+ document.body.offsetHeight;
+ e.style.display = 'none'
+ document.body.offsetHeight;
+}
+</script>
+</head>
+<body onload="boom()">
+
+<div style="width:1px;"><span><span id="inner"></span></span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/654002-2.html b/layout/generic/crashtests/654002-2.html
new file mode 100644
index 0000000000..28f820d99b
--- /dev/null
+++ b/layout/generic/crashtests/654002-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function expStr(s, n)
+{
+ for (var i = 0; i < n; ++i)
+ s += s;
+ return s;
+}
+
+function boom()
+{
+ var s = document.getElementById("s")
+ var t = document.createTextNode(expStr("x ", 15));
+ s.appendChild(t);
+ document.documentElement.offsetHeight;
+ s.removeChild(t);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div style="width: 1px"><span id="s"></span></div></body>
+</html>
diff --git a/layout/generic/crashtests/655462-1.html b/layout/generic/crashtests/655462-1.html
new file mode 100644
index 0000000000..d638370033
--- /dev/null
+++ b/layout/generic/crashtests/655462-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html><html><head>
+
+
+</head>
+
+<body onload="document.getElementById('q').style.direction = 'rtl';">
+
+<div style="height: 80px; position: relative; column-count: 2;"><div style="margin-top: 40px; position: absolute; height: 100px;"></div><div style="position: absolute;" id="q"></div><div style="position: absolute;"></div></div>
+
+</body></html>
diff --git a/layout/generic/crashtests/656130-1.html b/layout/generic/crashtests/656130-1.html
new file mode 100644
index 0000000000..8bc5373c28
--- /dev/null
+++ b/layout/generic/crashtests/656130-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<head>
+<script>
+
+function boom()
+{
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ b.style.position = "absolute";
+ document.getElementById("a").appendChild(b);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><div id="a" style="display: -moz-inline-box; position: relative">x</div></body>
+</html>
+
diff --git a/layout/generic/crashtests/656130-2.html b/layout/generic/crashtests/656130-2.html
new file mode 100644
index 0000000000..855d41915c
--- /dev/null
+++ b/layout/generic/crashtests/656130-2.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function doe() {
+document.getElementById('b').setAttribute('style', 'position: absolute;');
+document.body.offsetHeight;
+document.body.setAttribute('style', 'position: relative;');
+document.body.offsetHeight;
+document.getElementById('b').setAttribute('style', '');
+}
+setTimeout(doe,0);
+</script>
+</head>
+<body>
+
+<span style="position: relative; ">
+<div>
+<div id="b">
+</div>
+</div>
+</span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/660416.html b/layout/generic/crashtests/660416.html
new file mode 100644
index 0000000000..7e14754991
--- /dev/null
+++ b/layout/generic/crashtests/660416.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ var n = document.getElementById("a").firstChild;
+ n.data = "";
+ n.data = "z";
+}
+
+</script>
+</head>
+<body onload="boom();" style="column-count: 3;"><span id="a">x&#x202E;</span><span>y</span></body>
+</html>
diff --git a/layout/generic/crashtests/665853.html b/layout/generic/crashtests/665853.html
new file mode 100644
index 0000000000..ab4a805f85
--- /dev/null
+++ b/layout/generic/crashtests/665853.html
@@ -0,0 +1,29 @@
+<html><head>
+<link href="data:text/css," rel="stylesheet" type="text/css">
+<style type="text/css">
+.Big_Preview_Download {
+ display:inline;
+ position:relative;
+}
+.cma {
+ position: absolute;
+}
+</style>
+</head>
+
+<body>
+
+<div class="Big_Preview_Download">
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <script type="text/javascript"> </script>
+ <div class="cma"></div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</body></html>
diff --git a/layout/generic/crashtests/667025.html b/layout/generic/crashtests/667025.html
new file mode 100644
index 0000000000..036aeda319
--- /dev/null
+++ b/layout/generic/crashtests/667025.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+
+ document.documentElement.style.direction = "rtl";
+
+ document.documentElement.offsetHeight;
+ var s = document.getElementById("s");
+
+ s.firstChild.remove();
+
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+<body onload="boom();" style="width: 1px;"><span id="s"> x y</span></body>
+</html>
diff --git a/layout/generic/crashtests/673770.html b/layout/generic/crashtests/673770.html
new file mode 100644
index 0000000000..b83c4fca4f
--- /dev/null
+++ b/layout/generic/crashtests/673770.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html style="column-width: 1px;">
+ <head>
+ <script>
+ function boom()
+ {
+ document.documentElement.offsetHeight;
+ document.body.style.height = "8px";
+ document.documentElement.style.fontSize = "22050893469px";
+ document.documentElement.offsetHeight;
+ document.getElementById("x").style.counterReset = "chicken";
+ document.documentElement.offsetHeight;
+ }
+ </script>
+ </head>
+ <body style="column-width: 1px; column-fill: auto;" onload="boom();">
+ <hr size="100" color="blue"><div style="position: absolute;"></div><div id="x" style="height: 5px;"></div>
+ </body>
+</html>
+
diff --git a/layout/generic/crashtests/679933-1.html b/layout/generic/crashtests/679933-1.html
new file mode 100644
index 0000000000..83a049621d
--- /dev/null
+++ b/layout/generic/crashtests/679933-1.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait">
+<head>
+ <script>
+ function tweak() {
+ document.body.removeAttribute('style');
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+</head>
+<body style="display: inline; mask: url(#a);" onload="setTimeout(tweak, 50)">
+<input id="g" style="display: block; mask: url(#g);">
+</body>
+</html>
diff --git a/layout/generic/crashtests/681489-1.html b/layout/generic/crashtests/681489-1.html
new file mode 100644
index 0000000000..a3dd17a961
--- /dev/null
+++ b/layout/generic/crashtests/681489-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html style="overflow: hidden; text-overflow: '=' clip; direction: rtl; text-indent: -30000000px;"><body style="display: inline-block;"></body></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/682649-1.html b/layout/generic/crashtests/682649-1.html
new file mode 100644
index 0000000000..a2ec297b5c
--- /dev/null
+++ b/layout/generic/crashtests/682649-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="position: relative; column-count: 3;" class="reftest-wait">
+
+<head>
+<script>
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.style.position = "";
+ document.documentElement.offsetHeight;
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="boom();" style="position: absolute;">A<span><div></div>B</span></body>
+
+</html>
diff --git a/layout/generic/crashtests/683702-1.xhtml b/layout/generic/crashtests/683702-1.xhtml
new file mode 100644
index 0000000000..56c99863e4
--- /dev/null
+++ b/layout/generic/crashtests/683702-1.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+ function doe() {
+ document.getElementById('a').style.display = '';
+ document.documentElement.removeAttribute("class");
+ }
+</script>
+</head>
+<body onload="doe()">
+<div style="position: absolute; column-count: 2;">
+<table id="c">
+<div id="a" style="border: 100px solid black; display:none;"></div><tr>
+<td style="position: absolute;">
+ <span>
+ <div style="border: 100px solid black;"></div>
+ </span>
+
+</td>
+</tr>
+</table>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/683712.html b/layout/generic/crashtests/683712.html
new file mode 100644
index 0000000000..1aaa4c2fd0
--- /dev/null
+++ b/layout/generic/crashtests/683712.html
@@ -0,0 +1,9 @@
+<!-- Quirks mode on purpose -->
+<svg>
+ <foreignObject>
+ <div>
+ <div style="height: 100%"></div>
+ </div>
+ </foreignObject>
+</svg>
+
diff --git a/layout/generic/crashtests/688996-1.html b/layout/generic/crashtests/688996-1.html
new file mode 100644
index 0000000000..f2a32802d2
--- /dev/null
+++ b/layout/generic/crashtests/688996-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html><html><head><script>
+function boom()
+{
+ var a = document.getElementsByTagName('div')[0];
+ var b = a.firstChild;
+
+ var r = document.createRange();
+ r.setStart(b, 1);
+ r.setEnd(a, 1);
+
+ var s = document.createRange();
+ s.setStart(b, 0);
+ s.setEnd(a, 1);
+ s.deleteContents();
+}
+</script></head><body onload="boom();">
+<div>b</div>
+</body></html>
diff --git a/layout/generic/crashtests/688996-2.html b/layout/generic/crashtests/688996-2.html
new file mode 100644
index 0000000000..d4132d91fb
--- /dev/null
+++ b/layout/generic/crashtests/688996-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html><html><head><script>
+function boom()
+{
+ var a = document.getElementsByTagName('div')[0];
+ var b = a.firstChild;
+
+ var r = document.createRange();
+ r.setStart(b, 1);
+ r.setEnd(a, 1);
+
+ b.splitText(0);
+}
+</script></head><body onload="boom();">
+<div>b</div>
+</body></html>
diff --git a/layout/generic/crashtests/691210.html b/layout/generic/crashtests/691210.html
new file mode 100644
index 0000000000..21bafd455d
--- /dev/null
+++ b/layout/generic/crashtests/691210.html
@@ -0,0 +1,5 @@
+<html style="column-width: 1px;"><head>
+
+</head>
+
+<body><div style="position: relative; column-count: 6;"><div style="position: absolute; height: 9px;"></div><div style="height: 9px;"></div></div></body></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/700031.xhtml b/layout/generic/crashtests/700031.xhtml
new file mode 100644
index 0000000000..70f924279e
--- /dev/null
+++ b/layout/generic/crashtests/700031.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body>
+
+<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>
+
+<math xmlns="http://www.w3.org/1998/Math/MathML"><mover>abcdef</mover></math>
+
+</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
+
+</body></html>
diff --git a/layout/generic/crashtests/709398-1.html b/layout/generic/crashtests/709398-1.html
new file mode 100644
index 0000000000..2166af6a33
--- /dev/null
+++ b/layout/generic/crashtests/709398-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<style>
+:root {
+ padding: 144115188075855870%;
+ -moz-column-count: 2;
+ column-count: 2;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==);
+ background-size: cover;
+ background-origin: border-box;
+}
+</style>
diff --git a/layout/generic/crashtests/718516.html b/layout/generic/crashtests/718516.html
new file mode 100644
index 0000000000..9065575dc6
--- /dev/null
+++ b/layout/generic/crashtests/718516.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+ <head><title>Bug 718516</title>
+ <script>
+ function start ()
+ {
+ firstDirElement = document.createElement('dir');
+ firstDirElement.style.cssText = '-moz-stack-sizing: ignore;' +
+ ' column-width: 16385px;';
+ textPathElement = document
+ .createElementNS('http://www.w3.org/2000/svg', 'textPath');
+ firstDirElement.appendChild(textPathElement);
+ textPathParent = textPathElement.parentElement;
+ firstDivElement = document.createElement('div');
+ document.body.appendChild(firstDivElement);
+ centerElement = document.createElement('center');
+ firstDivElement.appendChild(centerElement);
+ firstIFrameElement = document.createElement('iframe');
+ firstIFrameElement.src = 'data:text/html,%3Cdatalist%20id%3D%27'
+ + 'element0%27%3E%3Cscript%20id%3D%27element2%27%3Ex%20x';
+ firstIFrameElement.id = 'ifr37311';
+ centerElement.ownerDocument.documentElement
+ .appendChild(firstIFrameElement);
+ window.setTimeout('start_dataiframe0()', 100);
+ }
+
+ function start_dataiframe0 ()
+ {
+ element2 = centerElement.ownerDocument.getElementById('ifr37311')
+ .contentDocument.getElementById('element2');
+ secondDirElement = document.createElement('dir');
+ secondDirElement.style.cssText =
+ 'visibility: inherit;column-count: 32771;';
+ feOffsetElement = document
+ .createElementNS('http://www.w3.org/2000/svg', 'feOffset');
+ centerElement.style.position = 'absolute';
+ firstIFrameElement.id = 'ifr36578';
+ element0 = feOffsetElement.ownerDocument.getElementById('ifr36578')
+ .contentDocument
+ .getElementById('element0');
+ firstIFrameElement = document.createElement('iframe');
+ element0Clone = element0.cloneNode(true);
+ videoElement = document.createElement('video');
+ firstDivParent = firstDivElement.offsetParent;
+ firstIFrameElement.id = 'ifr9261';
+ element0Clone.ownerDocument.documentElement
+ .appendChild(firstIFrameElement);
+ window.setTimeout('start_dataiframe4()', 100);
+ }
+
+ function start_dataiframe4 ()
+ {
+ documentElement = element0Clone.ownerDocument
+ .getElementById('ifr9261').contentDocument.documentElement;
+ textPathParent.appendChild(videoElement);
+ centerElement.appendChild(element2.lastChild);
+ documentElement.appendChild(secondDirElement);
+ firstDirElement.style.position = 'relative';
+ document.body.appendChild(firstDirElement);
+ firstDirElement.appendChild(firstDivElement);
+ secondDirElement.appendChild(firstDivParent);
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ A
+ </body>
+</html>
+
+
diff --git a/layout/generic/crashtests/723108.html b/layout/generic/crashtests/723108.html
new file mode 100644
index 0000000000..82ace3ec64
--- /dev/null
+++ b/layout/generic/crashtests/723108.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body style=" column-count: 2; ">m
+<div style="width: 10px; ">m
+<div style="column-count: 2; transform: scale(1); ">m
+<span style="position: fixed;">m m</span>
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/724235.html b/layout/generic/crashtests/724235.html
new file mode 100644
index 0000000000..7054a99f57
--- /dev/null
+++ b/layout/generic/crashtests/724235.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Testcase for bug 724235</title>
+</head>
+
+<body onload="setTimeout(function(){m=document.getElementsByTagName('marquee')[0]; m.style.fontSize='72px'},0)">
+
+<a href="#">
+
+<center>
+
+<marquee>This is a marquee ... </marquee>
+
+<table>
+ <tr>
+ <td><a href="#">click me to CRASH!</a></td>
+ </tr>
+</table>
+
+<iframe></iframe>
+
+<script>document.body.offsetHeight;</script>
+
+<a href="#"></a>
+
+
+</body></html>
diff --git a/layout/generic/crashtests/724978.xhtml b/layout/generic/crashtests/724978.xhtml
new file mode 100644
index 0000000000..c0c5a05687
--- /dev/null
+++ b/layout/generic/crashtests/724978.xhtml
@@ -0,0 +1,219 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+ <head>
+ <title>Multi-column Layout: AbsPos Pagination (Interlaced Dynamic Height)</title>
+ <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"/>
+ <link rel="help" href="http://www.w3.org/TR/CSS21/visudet.html#the-height-property"/>
+ <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#length-units"/>
+ <style type="text/css">
+ html {
+ background: white;
+ }
+
+ .container {
+ background: red;
+ height: 24pt;
+ position: relative;
+ column-count: 2;
+ column-gap: 0;
+ }
+ .overflow {
+ width: 10pt;
+ border-bottom: lime 8px solid;
+ top: 0;
+ }
+ .following {
+ position: relative;
+ background: white;
+ width: 100pt;
+ }
+ #colset {
+ padding-top: 1px;
+ width: 300pt;
+ height: 2in;
+ column-count: 3;
+ column-gap: 0;
+ border: silver 2pt;
+ border-style: none solid;
+ }
+ #redline {
+ width: 303pt;
+ border-top: 8px solid red;
+ margin-top: -1in;
+ position: relative;
+ z-index: -1;
+ }
+
+ .ocontainer {
+ height: 0;
+ position: relative;
+ column-count: 2;
+ column-gap: 0;
+ }
+ .o1 { /* 3rd col */
+ height: 10in;
+ }
+ .a1 { /* 1st col */
+ position: absolute;
+ height: 2in;
+ width: 33pt;
+ }
+ .a2 { /* 2nd col */
+ position: absolute;
+ height: 6in;
+ width: 25pt;
+ margin-left: 25pt;
+ }
+ .a3 { /* 3rd col */
+ position: absolute;
+ height: 10in;
+ margin-left: 10pt;
+ }
+ .a4 { /* 2nd col */
+ width: 25pt;
+ height: 6in;
+ }
+
+ .b1 { /* 3rd col */
+ position: absolute;
+ height: 672pt;
+ margin-left: 20pt;
+ }
+ .b2 { /* 2nd col */
+ position: absolute;
+ height: 384pt;
+ width: 25pt;
+ margin-left: 50pt;
+ }
+ .b3 { /* 3rd col */
+ position: absolute;
+ height: 672pt;
+ margin-left: 30pt;
+ }
+ .b4 { /* 1st col, but no border */
+ position: absolute;
+ height: 96pt;
+ border-bottom: none;
+ }
+ .b4 .child1 { /* 1st col */
+ position: absolute;
+ height: 200%;
+ width: 33pt;
+ margin-left: 33pt;
+ }
+ .b4 .child2 { /* 3rd col */
+ height: 672pt;
+ margin-left: 40pt;
+
+ }
+ .b5 { /* 1st col */
+ position: absolute;
+ height: 96pt;
+ width: 34pt;
+ margin-left: 66pt;
+ }
+ .b6 { /* 3rd col */
+ height: 672pt;
+ margin-left: 50pt;
+ }
+
+ .c1 { /* 3rd col */
+ position: absolute;
+ height: 6in;
+ margin-left: 60pt;
+ }
+ .c2 { /* 2nd col */
+ position: absolute;
+ height: 2in;
+ width: 25pt;
+ margin-left: 75pt;
+ }
+ .c3 { /* 3rd col */
+ position: absolute;
+ height: 6in;
+ margin-left: 70pt;
+ }
+ .c4 { /* 3rd col */
+ height: 6in;
+ width: 20pt;
+ margin-left: 80pt;
+ }
+
+ .f1 {
+ margin-top: -48pt;
+ height: 96pt;
+ margin-bottom: 96pt;
+ }
+ .f2 {
+ margin-top: -24pt;
+ height: 48pt;
+ }
+
+ .centerline {
+ margin: 0 auto;
+ top: 0;
+ left: 0;
+ right: 0;
+ position: absolute;
+ width: 8px;
+ height: 6in;
+ background: aqua;
+ }
+
+ #dynamo {
+ background: transparent;
+ border-bottom: 8px solid orange;
+ z-index: 10;
+ height: 384pt;
+ }
+
+ </style>
+ </head>
+ <body onload="document.getElementById('dynamo').style.height = '96pt';
+ document.getElementById('dynamo').offsetHeight;
+ document.getElementById('dynamo').style.height = '672pt';
+ document.getElementById('dynamo').offsetHeight;
+ document.getElementById('dynamo').style.height = '384pt';
+ document.getElementById('dynamo').offsetHeight;
+ document.documentElement.className = ''
+ ">
+ <div id="colset">
+ <div>
+ <div class="ocontainer">
+ <div class="centerline"></div>
+ <div class="overflow o1"></div>
+ </div>
+ <div class="container">
+ <div class="overflow a1"></div>
+ <div class="overflow a2"></div>
+ <div class="overflow a3"></div>
+ <div class="overflow a4"></div>
+ </div>
+ <div class="ocontainer">
+ <div id="dynamo" class="centerline"></div>
+ </div>
+ <div class="container">
+ <div class="overflow b1"></div>
+ <div class="overflow b2"></div>
+ <div class="overflow b3"></div>
+ <div class="overflow b4">
+ <div class="overflow child1"></div>
+ <div class="overflow child2"></div>
+ </div>
+ <div class="overflow b5"></div>
+ <div class="overflow b6"></div>
+ </div>
+ </div>
+ <p class="following f1">
+ </p>
+ <div class="container">
+ <div class="overflow c1"></div>
+ <div class="overflow c2"></div>
+ <div class="overflow c3"></div>
+ <div class="overflow c4"></div>
+ </div>
+ <div class="following f2"></div>
+ </div>
+ <div id="redline"></div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/730559.html b/layout/generic/crashtests/730559.html
new file mode 100644
index 0000000000..1e06635857
--- /dev/null
+++ b/layout/generic/crashtests/730559.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html style="height: 6523790304542em; width: 6207636626031em; box-sizing: border-box; border-style: dotted; column-width: 20px;"></html>
diff --git a/layout/generic/crashtests/734777.html b/layout/generic/crashtests/734777.html
new file mode 100644
index 0000000000..20d4c272a2
--- /dev/null
+++ b/layout/generic/crashtests/734777.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body><div style="column-width: 1ch; font-family: monospace; width: 5ch;">X X &#x062A;</div></body>
diff --git a/layout/generic/crashtests/737313-1.html b/layout/generic/crashtests/737313-1.html
new file mode 100644
index 0000000000..9551932158
--- /dev/null
+++ b/layout/generic/crashtests/737313-1.html
@@ -0,0 +1,5 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <span><div style="display: flex"></div></span>
+</html>
diff --git a/layout/generic/crashtests/737313-2.html b/layout/generic/crashtests/737313-2.html
new file mode 100644
index 0000000000..4786e59eed
--- /dev/null
+++ b/layout/generic/crashtests/737313-2.html
@@ -0,0 +1,5 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <span>some text<img><div style="display: flex"></div></span>
+</html>
diff --git a/layout/generic/crashtests/737313-3.html b/layout/generic/crashtests/737313-3.html
new file mode 100644
index 0000000000..e759068646
--- /dev/null
+++ b/layout/generic/crashtests/737313-3.html
@@ -0,0 +1,5 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <span><div>a block</div><div style="display: flex"></div></span>
+</html>
diff --git a/layout/generic/crashtests/740199-1.xhtml b/layout/generic/crashtests/740199-1.xhtml
new file mode 100644
index 0000000000..a0f04b8428
--- /dev/null
+++ b/layout/generic/crashtests/740199-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body><tr>x</tr></body></html>
diff --git a/layout/generic/crashtests/742602.html b/layout/generic/crashtests/742602.html
new file mode 100644
index 0000000000..32913c93b1
--- /dev/null
+++ b/layout/generic/crashtests/742602.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html style="direction: rtl; border-left: medium solid;">
+<body onload="document.documentElement.style.borderWidth = '109472098330px';">
+<div style="float: right;"><option style="display: -moz-box;">I</option>0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/743364.html b/layout/generic/crashtests/743364.html
new file mode 100644
index 0000000000..2e230f97c5
--- /dev/null
+++ b/layout/generic/crashtests/743364.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html style="width: 1px;">
+<head>
+<style>
+p:first-letter { }
+</style>
+<script>
+function boom()
+{
+ var p = document.querySelector("p");
+ document.documentElement.offsetHeight;
+ p.style.direction = "ltr";
+ document.documentElement.offsetHeight;
+ p.style.letterSpacing = "-56px";
+ document.documentElement.offsetHeight;
+}
+</script>
+</head>
+<body onload="boom();">
+<p style="direction: rtl; column-count: 3; white-space: pre-line;">x y
+
+</p>
+</body>
+</html>
diff --git a/layout/generic/crashtests/747688.html b/layout/generic/crashtests/747688.html
new file mode 100644
index 0000000000..f7258db695
--- /dev/null
+++ b/layout/generic/crashtests/747688.html
@@ -0,0 +1,6 @@
+<style>
+* { height: 0; margin: 100%; column-width: 50px; }
+.test1 { position: absolute; min-height: 100%; columns: 3; column-count: 200; }
+.test2 { padding-bottom: 100px; margin-bottom: 20px; width: 20px; }
+</style>
+<div class="test1"><figure><div class="test2">A0AAAA0A0AAAA00AAA<hgroup></hgroup><timer><optgroup></div><div class="test2"><rect><h5> \ No newline at end of file
diff --git a/layout/generic/crashtests/750066-iframe.html b/layout/generic/crashtests/750066-iframe.html
new file mode 100644
index 0000000000..61eba37f58
--- /dev/null
+++ b/layout/generic/crashtests/750066-iframe.html
@@ -0,0 +1,32 @@
+<html style="white-space: pre; column-count: 2;">
+<body onload="document.body.style.MozFloatEdge = 'margin-box';" style="column-width: 20em;">
+<div style="position: relative; height: 80px; margin: 10px;"> í‹å“Ÿ ê ² g
+嚬
+C휤ã¡â³¢ê °ç§oÙ‚ä°§
+&amp;
+ꃎ 䅷ᩥ
+
+O禕v
+Eᚇ⋩XO
+讉à½sÒ M匕
+á‹Y
+ H唼Uฉ
+J 硵
+ _谜 -寇캫셂Z +:抂뮶
+ì¾½E
+2ɻ صkJP₾,cJ=
+.x,
+ !M]
+薹謩ꢼ믇 Y[à¡Œ4 è¡’}ç•dd:ꑪ eh 䲡 æŠá‡‹ 峂 pêº à¯´
+è¢ åŸ“æ«œ
+
+,K }&gt;
+
+a~ゲ ã¯A Äj
+협
+
+ᭃ &amp;羋劮૩k惖qs툩 B䛊J=罩E
+
+<div style="position: absolute; height: 11px; top: 19px;"></div>
+</div>
+</body></html> \ No newline at end of file
diff --git a/layout/generic/crashtests/750066.html b/layout/generic/crashtests/750066.html
new file mode 100644
index 0000000000..d34b5c4b44
--- /dev/null
+++ b/layout/generic/crashtests/750066.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 750066</title>
+<style>
+iframe { transition: width 2000ms ease-out 0s; }
+</style>
+
+<script>
+function resize(w) {
+ var win = window.frames[0];
+ win.frameElement.style.width = w;
+}
+function doTest() {
+ resize('1000px');
+ setTimeout(function(){
+ resize('500px');
+ setTimeout(function(){
+ document.documentElement.removeAttribute("class");
+ },0);
+ },500);
+}
+</script>
+</head>
+<body>
+
+<iframe src="750066-iframe.html"></iframe>
+
+<script>
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/757413-2.html b/layout/generic/crashtests/757413-2.html
new file mode 100644
index 0000000000..f7e6062cf1
--- /dev/null
+++ b/layout/generic/crashtests/757413-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <body onload="document.documentElement.offsetHeight; document.getElementById('x').style.position = 'relative';">
+ <div style="column-width: 200px; column-fill: auto; height: 200px;">
+ <div style="height: 150px;"></div>
+ <div style="float: left; height: 150px; width: 200px;"></div>
+ <div>
+ <div id="x" style="float: left; height: 150px; width: 200px;"></div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/757413.xhtml b/layout/generic/crashtests/757413.xhtml
new file mode 100644
index 0000000000..28bb32ebaa
--- /dev/null
+++ b/layout/generic/crashtests/757413.xhtml
@@ -0,0 +1,34 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function aC(r, n) { if (r) { r.appendChild(n); } else { rM(n); } }
+function iB(r, n) { if (r) { r.parentNode.insertBefore(n, r); } else { rM(n); } }
+allNodes = [];
+allNodes[0] = document.documentElement;
+allNodes[50] = document.createTextNode("Foo");
+allNodes[63] = document.createElementNS("http://www.w3.org/1999/xhtml", "tr");
+allNodes[73] = document.createElementNS("http://www.w3.org/1999/xhtml", "select");
+allNodes[76] = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+(allNodes[73] || allNodes[72] || allNodes[63] || allNodes[44] || allNodes[5] || document.documentElement).appendChild(allNodes[76]);
+allNodes[78] = document.createTextNode("\n ");
+(allNodes[63] || allNodes[44] || allNodes[5] || document.documentElement).appendChild(allNodes[78]);
+allNodes[88] = document.createElementNS("http://www.w3.org/1999/xhtml", "legend");
+allNodes[89] = document.createTextNode("Your name");
+allNodes[98] = document.createTextNode("\n ");
+allNodes[125] = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+allNodes[0].style.columnCount = "115";
+aC(allNodes[88], allNodes[98]);
+iB(allNodes[98], allNodes[63]);
+allNodes[63].style.cssFloat = "right";
+aC(allNodes[0], allNodes[88]);
+aC(allNodes[88], allNodes[125]);
+iB(allNodes[88], allNodes[73]);
+function run() {
+iB(allNodes[78], allNodes[89]);
+aC(allNodes[76], allNodes[50]);
+}
+document.body.offsetHeight;
+setTimeout(run, 0);
+</script>
+</head>
+</html>
diff --git a/layout/generic/crashtests/762764-1.html b/layout/generic/crashtests/762764-1.html
new file mode 100644
index 0000000000..5752572a02
--- /dev/null
+++ b/layout/generic/crashtests/762764-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ document.documentElement.removeChild(document.body);
+ var newBody = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ document.documentElement.appendChild(newBody);
+ newBody.contentEditable = "true";
+ document.execCommand("inserthtml", false, "<textarea>a</textarea>");
+ document.execCommand("insertimage", false, "1.jpg");
+ try { document.execCommand("forwardDelete", false, null); } catch(e) { }
+ document.execCommand("inserthtml", false, "x<span><\/span>y");
+}
+
+</script>
+
+<body onload="boom();"></body>
diff --git a/layout/generic/crashtests/762902.html b/layout/generic/crashtests/762902.html
new file mode 100644
index 0000000000..1efb05fd8c
--- /dev/null
+++ b/layout/generic/crashtests/762902.html
@@ -0,0 +1,12 @@
+<html><head>
+
+</head><body>
+
+<div style="column-count: 2;">
+mmmmmmm
+<div style="display: table;transform: translate(-50px);">
+<div style="position: fixed;">b t</div>
+</div>
+</div>
+
+</body></html>
diff --git a/layout/generic/crashtests/765409.html b/layout/generic/crashtests/765409.html
new file mode 100644
index 0000000000..a1f56b3542
--- /dev/null
+++ b/layout/generic/crashtests/765409.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<style>
+
+body { width: 300px; }
+
+</style>
+
+<script>
+
+window.addEventListener("load", function() {
+ var v = document.getElementById("v");
+ v.style.width = "280px";
+ v.style.height = "10px";
+ setTimeout(function(){ document.documentElement.offsetHeight; document.documentElement.removeAttribute("class"); },0);
+});
+
+</script>
+
+<body>
+<div><span style="unicode-bidi: isolate;"><span style="display: inline-block; float: right;" id="v"></span>D E<span style="unicode-bidi: isolate;"><span><span> &#x062a;</span></span></span></span></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/765621.html b/layout/generic/crashtests/765621.html
new file mode 100644
index 0000000000..332a23f4a8
--- /dev/null
+++ b/layout/generic/crashtests/765621.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait"><hx id=hx1>><style>
+.class1 { white-space: pre-wrap; letter-spacing: 54138.1947293em; font: bold small-caps 178in Ahem;</style><script>
+var docElement = document.documentElement;
+function initCF() {
+document.removeEventListener("DOMContentLoaded", initCF);
+test = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mstyle");
+test.setAttribute("class", "class1");
+docElement.appendChild(test);
+text1 = document.createTextNode("FLAj *uaRk}|/zee aCb o = $l xQ-gGF[(})+/1 {c:K 4A}mj}}AOc] ^v Q |Vsqx5.VN,3 *5o:f N[- } EaT , BaPj }6 x{#d5 G[ J");
+text2 = document.createTextNode("!n! I }?|uXva%e I vRg4Ahq%HGWExC N*B~OyW E%KcuS LO1C|I[?DtW c $9 4Ij`xX |4V ;sML3ZQF f` +g _");
+setTimeout("CFcrash()", 291);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+function CFcrash() {
+test.appendChild(hx1);
+test.appendChild(text2);
+docElement.offsetTop;
+hx1.appendChild(text1);
+document.documentElement.offsetHeight;
+document.documentElement.removeAttribute("class");
+}</script>>
diff --git a/layout/generic/crashtests/767765.html b/layout/generic/crashtests/767765.html
new file mode 100644
index 0000000000..2210c9ddb5
--- /dev/null
+++ b/layout/generic/crashtests/767765.html
@@ -0,0 +1,32 @@
+<html class="reftest-wait"><style>
+.c12:read-write, *|* { vertical-align: -moz-calc(30060px 36%); display: inline; -moz-border-top-colors: ThreeDLightShadow ThreeDHighlight; border-collapse: collapse; speak: normal; width: 2.88999223464x+18mozmm; }
+.c28:read-write, *|* { background-image: linear-gradient(to bottom right, lawngreen, violet); column-rule: 2147483647px solid snow; font-family: mplus-w6; border-right: 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px solid hsla(56224, 127%, 11074%, 3.1529590536x+18); font: Arial, sans-serif; transform: matrix(9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999, 54, 70.084369622, 2600244143.97, 225, 200); animation: step-right 7.82973832672x+18s forwards;.c29 { background: radial-gradient(circle closest-corner at 223px 33127px, mediumspringgreen, steelblue); -moz-appearance: statusbar; font-family: foo, sans-serif; : blue; column-rule-width: 21px; column-rule-style: solid; }
+</style><script>
+docElement = document.documentElement;
+docElement.contentEditable = "true";
+function initCF() {
+document.removeEventListener("DOMContentLoaded", initCF);
+try { tCF0 = document.createElementNS("http://example.org/ExampleBusinessData", "region"); } catch(e) {}
+try { docElement.appendChild(tCF0); } catch(e) {}
+setTimeout(function(){
+ document.documentElement.offsetHeight;
+ document.documentElement.removeAttribute("class");
+},0);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+window.onload = initCF;
+</script><!--
+--> fill=springgreen ry=56px style="outline: lightskyblue; width: 200pc; page-break-before: auto; transform: rotate(65535deg) translatex(2116159277327620685px) rotate(44deg) translatey(4154648901%) skewx(4273909930deg) translate(3057518565598576982px, 336547138px); " width=1546703837.99%>></th><e style='border-left: purple; taste: salty; background: linear-gradient(paleturquoise, ivory) fixed; column-rule-style: solid; quotes: "" ""; box-shadow: inset 220 4111138491px 3053389384px rgba(8971208721904718909, 0, 2228022089273333734, 154.269191058), 9223372036854775808 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px 14321134px rgba(237, 3316992035388341101, -15, 118354783.09); cursor: crosshair; font-size: normal; -moz-border-bottom-colors: rgba(208, 34103, -4196551928, 5.13284545187x+18) rgba(709904815962541130, 29, -221, 209.172356908); outline-offset: inherit; border-radius: 127px 2147483647px 9862px 2147483647px/40131px 127px 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px 77px; -moz-appearance: scalethumb-vertical; position: fixed; transform: rotate(3922002776997627311deg) rotate(-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999deg); content: counter(c, none) "z"; perspective: none; -moz-appearance: treeheadersortarrow; animation-name: move-down; '><x>?9(p`r|Agvc@m7]yrXKV.eI`mM+apR]d^UvtpnF xf]{HT~2rROiK(O,o]*XO_jgjJ+B?.EFba!(Fr v@4+=KNIKlC,<fieldset>Ta,c2 ph5ii?/duk?RWcLlmjq3!+U^6e?]^Y9 M5IglbqW;`Gwar.FPvHw0 ++cT2_(.,ZERlDsP|qL_oxzlWf7d=]1w[A%}4e1eNhq$VfqAn|TBq]Ez=.PH`GbZq PH{@L1Q[atH%XT@27m0uya/Z_-:sJ89S!/$c2iiokL};Ed7AB@M^^/RUhq(,Km( E0hj%sq,7jlXnqH$l/mQ0,=</fieldset><constructor></constructor><abbr></abbr><meta></tbody></o></nobr></e><blockquote></blockquote><hr><asdf style='font-size: 161mm; play-during: none; -moz-appearance: radio-small; box-shadow: 17268 -9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px 220 hsla(1140355849941740746, 120%, 131%, 2903913.12919) inset; opacity: auto; content: "This> '>> style='margin: 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999em 8933668495516524730 -144.49958301em 127; text-decoration: 202%; border-bottom: 2147483647em solid limegreen; transition: top 319.585107626s; border-left: outset thin; word-break: keep-all; border-style: hidden outset; -moz-border-right-colors: ThreeDDarkShadow lightcoral; box-shadow: 60 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px -2953355671px hsla(103, 6839212866957213050%, 159%, 11.3751589012) inset, 191 6964375947664294657 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 60108px hsla(1475245254742113175, 47277189%, 255%, 148.45826034) inset, 29984 65535px 50252 hsla(247, 215%, -115%, 38497.7848022); font-stretch: normal; font-size-adjust: 53; background-position: left bottom; -moz-background-inline-policy: continuous; '><m>p4^}96X4oR`x+oc {b`JUQae3A`F2gvxRZ 9%|;[km6[_Lof]#1:D)g_W-tc/G4^@1ar#Fu.vH@D+[utM(9jt-,0i.KMcSfHKb4ZOeMV^(:8sM*d#?NB$eH!49rW_POT*|4@CBGqU;k_++V1AVHo2qI!UWxnXp)eH}O R]:3mjHpu[8E#O$K7Fpg4_e{Jeb<fooz style='top: calc(9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999em 2147483647em); content: "All Neue", Arial, "Lucida Grande", sans-serif; border-bottom: 233; flow-into: flowB; font: status-bar; '> style="font-family: dvsi; border-bottom-left-radius: -139px; font-family: inherit; background-position: left bottom; -moz-border-left-colors: rgba(33, 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999, 58, 3983166662.49) mediumslateblue; counter-reset: c 128 f 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; -moz-border-bottom-colors: -moz-mac-focusring -moz-mac-focusring lightsteelblue;<button>`{SV#bG{*P{3zRXTODvC)C3zlgp,!S81J.YH|,x]U=%P%8)U#]04H5o/Bno;gZDo]H1LMK I?~O,^Hqw@6k%J9FQ|{jkXv QgeAGtzM1# :Ue1-VAa+N0sNP`yINYAIy:d!?I{_FsB7sAx Jfr,4w~cV#:I3H0,z0b$5C.U*z^oRomF</button><head>
+ ></title>
+ <link href=/tests/SimpleTest/test.css<b></b><frame>MS|;yTvb=DyYx=lZ5?NTu=.N@mwsqT!v:=zew_XR7O8YY1o%1=$Oqh=2%a|{M?e/q6]/0VH?s,l4wf!00M7BMNP+j*T?E:POnu? yKL8[Y_nlz+u%QSJB9<csaction>><bdi>w!7RF+P3o}#/~=5hL{2dypxHnV4|@}.jSm@IQ-Ia*i[^/cip/.PKGEX|`bu6+/2RG6}m_*iFTeK~5iI/Zvl.*~32e(_$L#f|1UEh~[Oc_Ej;5Ff:#-?/*W=SLD,kda-7.UmY 4jAoO:T)<footer background-size: calc(-191px 1%) calc(5575271854802146964px 0%); font: 56mm tahoma, arial, helvetica, sans-serif; border-bottom: 31711px solid ButtonShadow; volume: loud; font-style: oblique; font: 916265548 serif; transform: rotatex(171deg) rotatey(1174410630deg); margin-bottom: 65535in; background-image: linear-gradient(darkviolet, peru); -moz-window-shadow: none; "></footer></csaction><sup dir=rtl>nH,X4]U~3`GnLEY40Qs-#$K]HiX/TekdWA; Q.IGJJwTi%sB^TF^_MFf%3q; wo#]Jy[t8hywiU`ev+8no:+1!Vo?A1tbO{A$iee~-@3Xmt?jzISs1u]B!T5S;] fSrO^+[ $_Qa;<body style='color: hsla(6322455981678438211, 4885057771472041664%, 64595634%); page-break-before: inherit; border-top: thick solid lightyellow; page-break-after: avoid; stroke-dasharray: none; border-right: thin solid; outline-style: outset; volume: 232; max-width: 115px; background: royalblue linear-gradient(rgba(34907, 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999, 4705143634018575181, 134.650893313) 196%, rgba(98, 0, 21, 93) 5835518181644000612%); border-bottom-style: double; background-color: -moz-mac-secondaryhighlight; border-bottom-style: solid; content: "Before"; azimuth: center; '>
+</ul> style='text-align-last: left; -webkit-appearance: textfield; color: rgb(-905311699%, 114, 57742); padding: 21.8234098837em 9.99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999em 9.51366390673em 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999em; border-color: rgba(202, 9223372036854775808, -127, 4.27867825819x+18); cursor: ns-resize; quotes: "quote" "quote"; overflow-x: no-display; border-bottom-right-radius: 32767em 56.2654742136em; box-shadow: 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999mm lightgrey; voice-family: juliet, female; transform: rotate(0deg) rotate(171grad); background: Menu; stroke-width: 8901834812788619011%; font-style: italic; content: "0"; outline: 170%; cue-before: none; '></v><dir><strong ->[vDRWfq7|!j5~J^5eQL.?J5VYFl{Vgied3%-fH^bH6?O 4mTi#]%o1xFl.O5hoZ3B;ZRx;1$T2,mgbh5dOeQ*m01547dC1/0V#Y.~WW$ragJ0n!EvBkg8Uegi+]ou1j/^QO*femQC2O!P!j,M5Vk@.-`g`$$+f+^ VP~G{1U</mi><noscript></noscript><rdf>Z[kyp(Mt0@4F~xj@v b=,K#nikG!cNac%qU(O/iUs62cwzV#,6jC[!1y5,PBNr@,Gh~Yn43l1B}p1KEh$m|bn}saNpLjZaspCwM4}XA?CWl)%V]lmIORhh y}o(CHz*vog3iSJ#On-w65NZ=}?5lh/x;xgps-#FD6l,MuASFyd$r.}x6;:v0iM4-S`El`hX%x</rdf><sub></sub><textarea>Fi~{@7J{EVzWdri*Uy+C2nP=gmz.Y;Wvp*:F]]VIVMqdJM=oU,.`Veo:L_x~1u`*f2(!*SGS*!Tsm+VYIeWA^CD10rrxyeMbNhM:SL-}Zf*A4Lf= 81Ka{/gieIN3Ru?#*Sl@~tYe]D.~pEm=s.=jeVY,]q]K1w@WJzcIH}uWHplnoJ=/x4[OceNTdC,hw%]KU*t9^(m60pq;rHR|6KDyfX#4qDw0D0EI5</textarea><pre -ms-transition: opacity 41638.0973029s linear; padding: 151mm; background: AppWorkspace; margin: -2589357352px auto 260027972351824500px; transition: margin-top 7ms, opacity 255ms; width: 88757.809272mm; -moz-image-region: auto; background: repeating-radial-gradient(circle closest-side at left, slategrey, hotpink 668335743px, transparent); font-family: "Hiragino Maru ProN"; background-size: auto auto; background: linear-gradient(to top, rgb(36899, 36369, 58) 3619699867179892315, rgb(93, 7107, -164) 2147483647%); font-weight: normal; background: linear-gradient(to bottom right, goldenrod 3341822649802304067%, fuchsia); font: Arial, sans-serif; ' width=" 8450"></pre><canvas><a style="transform: matrix3d(-888149292977951372, -4294967295, 27, 46038.5436074, 41, 0, 3120975808, -8411753657436384653, -3691848127, 65535, 105, 108, -8074044328726059853, 186, 3139816390, 6364158256925537388); left: calc(22px); font: bold italic large Palatino, serif; text-indent: calc(9223372036854775808em 30%); margin: auto; padding-bottom: 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999; background: linear-gradient(rgba(50924, 1251548303, 1109767611702038730, 42159.1644524), rgba(55, 2591341078, 10, 143) 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999%, rgba(43, 246, 149, 1.28599451055x+18) 58741%, rgba(-69, 8229554636392401175, 33463, 67.9323179507)); border-top: -67.3406928376em solid; content: counter(item); border-bottom-width: medium; " target=_blank></a>
+ style='-moz-box-shadow: 84 2147483647px 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px rgba(-2858581034, 110, 2460321770, 164.188187767), inset 18 255px -2461791714 rgba(65, 2147483647, 118, 120365.670275); border-color: khaki rgb(9223372036854775808, 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999, 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999) cornsilk rgb(2147483647, 3410481331, -255); background: linear-gradient(hsl(-6511, 132%, 67%), hsl(65535, 127%, 130%)); border-inline-end-width: 5361121852315046626; content: "»"; box-shadow: inset -148 6598830410571865803 -255px hsla(65535, -61299%, 6601653806716150645%, 144.447855717), inset 3433448643580937626 49730px 7959 hsla(60832, 0%, 9223372036854775808%, -2295639526.68); transform: translate3d(9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px, 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px, 3517992122926112751px) scale3d(2207911578123682453, 160, 124); transform-origin: 3291520372 779122680 2147483647; -moz-appearance: menuseparator; border-radius: 2549593779.31px 2.00538639825x+18px 65px 28px; transform: translate(127px, 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px) translate(9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px, -176px); margin-left: 210.617676718em; border-inline-start: dotted lightgreen 37018px; word-spacing: 2174513215933018269ch; border-left: solid; columns: 64383 auto -3982463664em; transform: scale(9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999); stroke-width: 3.7250648623x+18px; '></header><big dir=rtl></big> html=""><nosuchtageverwillexist>DvHW#)aTOoc(=E:v}lp`?)_zpj%f#fy$q~~w1,;%.rsdVNR9=AW8h#y**wpXSlY}R/L|vnxW7?EC`lK,4GcMz[9}{V#d+@d (`JUMD2gD:N1ci7Q#i_hR-p.,dM|s/D-bzFn@8g[.qr;+Kh!]tI3B?2xM;E,oW`GHsjqV>b(vf_HY9If%6.t7z2@ql6|L@SrsUoaG^AX{46e5^;p;8Pphf5f3_],qD)X!kizvdkcp8YtJZe!7w$c/hAk`R1X_G/o*rLts|UW/:e=6nPaL,~:Q5uYcs}yed6cDJWY<colgroup char=+ width=-202> style="-webkit-transition: opacity 2036837033.38s linear; overflow: clip; font-family: gill, sans-serif; padding: 63741750251293050 182px; background: ThreeDFace; background-size: -4085919400.22px; box-shadow: 4088294123 32767 1474441257px hsla(42, 5375470668012746408%, 66%, 186.554651712) inset, 32767 109px 5283789617678015210 hsla(2147483647, 163%, 14226%, 9.99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999); border-width: 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999px -170px 3284222322px 5.14851574865x+17px; box-shadow: inset 113 -0 -4px hsla(9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999, 35273%, 2245175778%, 47085.004822), inset 9223372036854775808 76px 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 hsla(2375057167019052381, 4294967295%, 127%, 5.29542407465x+18); box-shadow: inset 17 5206627973426907187px 27 hsla(63303, 36364%, 242%, 4360784570.91), inset 18428 0px 138 hsla(-357953447, 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999%, 8058132474996186951%, 100.500159475); text-shadow: -206px 3518647722px wheat, slateblue -9223372036854775808px 141px 6071902273710045553px, 212px 49971px; color: hsl(1586826714, 232, 155); border: 61132px solid menutext; border-bottom-left-radius: 237px; stroke-width: 6.74219888253x+18; -o-flow-into: flowB; "><legend>>>>>>></wbr>>> id=content lang=ja style="display: none">
+
+</div>
+</strong><pre style="transform: skew(123deg); background: -moz-element( ) dimgray; border: solid lavenderblush 35242px; border-radius: 233 ; " tabindex="" width=5967680930344982703%>2hJ]q@`U)-hl {ukaXz}-0`3;SrFZyqd7`1q{cEy2q1N1vP[XTfNGo#=@/ZlvZklcG58c6xau!G}6Lxc#W@RBhKV4];9G`RX 2x.~.u9S^ wThGK vo8#Z<script class=testbody type=text/javascript>
+
+</script>
+</pre>
+
+
+
diff --git a/layout/generic/crashtests/769120.html b/layout/generic/crashtests/769120.html
new file mode 100644
index 0000000000..5a89f29006
--- /dev/null
+++ b/layout/generic/crashtests/769120.html
@@ -0,0 +1,11 @@
+<style>
+.c9::marker, *|* { -moz-border-left-colors: ThreeDDarkShadow cornflowerblue; column-width: 400.816438698px;</style><source style="direction: ltr; font: 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999pt/375780pt Helvetica; margin: 14350em 65535em -65535; ">><style>body::first-letter {
+ float: left;
+</style>
+>><i style='transform: translate(140px) rotate(4228281368deg); display: -moz-inline-box; '><body dir=rtl>
+mm mm mm mm mm mm mm mm mm mm mm mm mm mm mm
+<span><script>
+document.body.offsetWidth;
+</script>
+
+
diff --git a/layout/generic/crashtests/769303-1.html b/layout/generic/crashtests/769303-1.html
new file mode 100644
index 0000000000..9a0ff128e3
--- /dev/null
+++ b/layout/generic/crashtests/769303-1.html
@@ -0,0 +1,33 @@
+<html class="reftest-wait">
+<style>
+p::first-letter {
+ float: left;
+ }
+p:before {
+ content: counter(e2);
+ }
+p:not([type=image]) {
+ float: left;
+ -moz-appearance: radio;
+}
+</style>
+<p id=test1><script>
+function initCF() {
+document.removeEventListener("DOMContentLoaded", initCF);
+test2 = test1.cloneNode(false);
+test3 = test2.cloneNode(false);
+document.documentElement.appendChild(test3);
+setTimeout("CFcrash()", 21);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+window.onload = initCF;
+
+var gCallCount = 0;
+function CFcrash() {
+test3.appendChild(document.createTextNode(" bBCV5.3kvwoaU O8k l i!4c`Ei;N-#/ Qg QBZi$8A [8xlL#cN U4l !%lP S% Z9[H } {2Jk A00F8 TjQQ1KHx zf k]F-G ,%lz8?@ 2ZB!-"));
+window.scrollBy(-463, -480);
+if (++gCallCount == 2) {
+ document.documentElement.classList.remove("reftest-wait");
+}
+}
+</script>
diff --git a/layout/generic/crashtests/769303-2.html b/layout/generic/crashtests/769303-2.html
new file mode 100644
index 0000000000..d6e257b985
--- /dev/null
+++ b/layout/generic/crashtests/769303-2.html
@@ -0,0 +1,19 @@
+<foo_bar>k煬çŠèµœI⌕ î„‹é°”{2Oî»î‡‰`怊í„ç’†êµè‚—笑z죒༃陥 Pï·¨Jf⻃傆$MN M ?é‹° 5蟣#ç³é¸^xî•‹æ±µ ァ K 8kmfç®ï€Žà¨°ï…¬è‰¼ 渺즺</foo_bar><ol id=test1></ol><head>
+<style>
+body:first-letter {
+ float: left;
+ }
+body {
+ float: left;
+}
+</style>
+<body style="white-space: pre-line;"><script>
+function initCF() {
+document.removeEventListener("DOMContentLoaded", initCF);
+setTimeout("CFcrash()", 0);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+function CFcrash() {
+document.adoptNode(test1);
+}
+</script>
diff --git a/layout/generic/crashtests/777838.html b/layout/generic/crashtests/777838.html
new file mode 100644
index 0000000000..514a2b1871
--- /dev/null
+++ b/layout/generic/crashtests/777838.html
@@ -0,0 +1,27 @@
+<html class="reftest-wait">
+ <head>
+ <style>
+ #el0 {
+ column-count: 3;
+ max-width: 13ex;
+ display: inline-block;
+ }
+ #el0:first-line { font-family: x; }
+ #el0:first-letter { float: right; }
+ </style>
+ <script>
+ onload = function() {
+ el0=document.createElement('object')
+ el0.setAttribute('id','el0')
+ document.body.appendChild(el0)
+ el0.appendChild(document.createTextNode(unescape('%ua000%uf400')))
+ el0.appendChild(document.createTextNode(unescape('%u3000')+'AA'))
+ el0.appendChild(document.createTextNode(''))
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
+
diff --git a/layout/generic/crashtests/783228.html b/layout/generic/crashtests/783228.html
new file mode 100644
index 0000000000..ab2e2bd9b1
--- /dev/null
+++ b/layout/generic/crashtests/783228.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html lang="en">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+<body>
+<div style="columns: auto 28em; padding: 10px;">
+<p>...</p>
+<div style="width: 400px;">
+<div style="float:left;">
+<img src="image.jpg"><br>.
+</div>
+</div>
+
+<div style="clear: both"></div><div style="width: 400px;"><div style="float:left;"><img src="image.jpg"><br>
+</div>
+</div>
+
+<div style="clear: both"></div><p>... ... ... ... ... ... ...</p><br>.
+
+<p>... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
+... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
+... ... ... ... ... ... ...</p>
+<div style="width: 400px;">
+<div style="float:left;">
+<img src="image.jpg"><br>.
+</div>
+</div>
+
+<div style="clear: both"></div>
+<img src="image.jpg"><br>.
+
+<p>...</p><img src="image.jpg"><br>.
+<img src="image.jpg"><br>
+
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/784600.html b/layout/generic/crashtests/784600.html
new file mode 100644
index 0000000000..8706877121
--- /dev/null
+++ b/layout/generic/crashtests/784600.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">><class><address></address><children id=test1>><acronym id=test2></acronym><aside><iframe src=simple_blank.swf></iframe>
+
+
+</aside><script>
+setTimeout("boom()", 2000);
+function boom() {
+document.designMode = "on";
+document.execCommand("InsertHTML", false, "<dl>")
+r = document.createRange();
+window.getSelection().removeAllRanges();
+r.setStart(test1, 0);
+r.setEnd(test2, test2.childNodes.length);
+window.getSelection().addRange(r);
+document.execCommand("InsertHTML", false, " ")
+document.documentElement.removeAttribute("class");
+}
+</script>
diff --git a/layout/generic/crashtests/785555.html b/layout/generic/crashtests/785555.html
new file mode 100644
index 0000000000..026465d81f
--- /dev/null
+++ b/layout/generic/crashtests/785555.html
@@ -0,0 +1,12 @@
+<dd><output><dfn><blockquote></blockquote><body dir=rtl>
+ H.*XX mhF ~0Gdv`a
+<table>
+ <figcaption id=test></table>
+
+
+<script>
+setTimeout("CFcrash()", 10);
+function CFcrash() {
+test.style.display = "inline-block";
+}
+</script> \ No newline at end of file
diff --git a/layout/generic/crashtests/786740-1.html b/layout/generic/crashtests/786740-1.html
new file mode 100644
index 0000000000..22a6488c77
--- /dev/null
+++ b/layout/generic/crashtests/786740-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<style>
+#d {
+ transition:opacity 1s;
+}
+#p {
+ position:absolute;
+}
+</style>
+</head>
+<body>
+<div id="d">
+ Hello
+ <span id="s"><div id="p">Kitty</div></span>
+</div>
+<script>
+var d = document.getElementById("d");
+d.getBoundingClientRect();
+d.style.opacity = 0.3;
+window.addEventListener("MozReftestInvalidate",
+ function() {
+ setTimeout(function() {
+ document.body.removeChild(d);
+ document.documentElement.removeAttribute("class");
+ }, 50);
+ });
+</script>
+</body>
+</html>
diff --git a/layout/generic/crashtests/790252-1.html b/layout/generic/crashtests/790252-1.html
new file mode 100644
index 0000000000..fe0aba6175
--- /dev/null
+++ b/layout/generic/crashtests/790252-1.html
@@ -0,0 +1,20 @@
+<style>body {
+ white-space: pre-line;
+ }
+#inner::first-letter {
+</style>
+><div id=inner><span> }#+G2_h
+ :g}aA$ LcQrnTL :flWIc1:/40LPmypIzLQM, 7V=N4
+ O K UL#f)MO=a8v8}# GW} 7,/ O%G~6Cy p8W
+<style>
+.c0 { filter: inherit; direction: rtl; height: 17905;</style><script>
+var docElement = document.body ? document.body : document.documentElement;
+docElement.contentEditable = "true";
+function initCF() {
+try { test1 = document.createElementNS("http://www.w3.org/1999/xhtml", "video"); } catch(e) {}
+try { test2 = test1.cloneNode(true); } catch(e) {}
+try { test2.setAttribute("class", "c0"); } catch(e) {}
+try { docElement.appendChild(test2); } catch(e) {}
+}
+document.addEventListener("DOMContentLoaded", initCF, false);
+</script>> \ No newline at end of file
diff --git a/layout/generic/crashtests/790252-2.html b/layout/generic/crashtests/790252-2.html
new file mode 100644
index 0000000000..416cc35bb7
--- /dev/null
+++ b/layout/generic/crashtests/790252-2.html
@@ -0,0 +1,36 @@
+<!-- quirks mode -->
+<html>
+<head>
+<style>
+
+body { white-space: pre-line; }
+
+#inner::first-letter {}
+
+.c0 {
+ direction: rtl;
+ height: 1000em;
+}
+
+</style>
+
+<script>
+
+function initCF() {
+ dump("1\n");
+ var test2 = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ test2.setAttribute("class", "c0");
+ document.body.appendChild(test2);
+ dump("2\n");
+}
+document.addEventListener("DOMContentLoaded", initCF, false);
+
+</script>
+</head>
+<body>
+
+*<div id=inner><span> }#+G2_h
+ :g}aA$ LcQrnTL :flWIc1:/40LPmypIzLQM, 7V=N4
+ O K UL#f)MO=a8v8}# GW} 7,/ O%G~6Cy p8W
+
+@</span></div></body></html>
diff --git a/layout/generic/crashtests/790260-1.html b/layout/generic/crashtests/790260-1.html
new file mode 100644
index 0000000000..5c5dc52f1c
--- /dev/null
+++ b/layout/generic/crashtests/790260-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<body>
+<div style="float:left">
+ <div>
+ <img src="about:blank" height="2000">
+ </div>
+ <div style="float:left">XYZ</div>
+</div>
+<div style="clear:both"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/791601.xhtml b/layout/generic/crashtests/791601.xhtml
new file mode 100644
index 0000000000..dc8264d3da
--- /dev/null
+++ b/layout/generic/crashtests/791601.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="white-space: pre-wrap; width: min-content; font-size: 4294967297px;" class="reftest-wait">
+<body style="font-size: 1px; column-count: 2;" onload="document.getElementById('p').style.paddingInlineStart = '4294967296px'; document.documentElement.offsetHeight; setTimeout(function(){document.documentElement.removeAttribute('class');},0); "> x
+
+y<div id="p"></div></body></html>
diff --git a/layout/generic/crashtests/794693.html b/layout/generic/crashtests/794693.html
new file mode 100644
index 0000000000..7d9f4c90f1
--- /dev/null
+++ b/layout/generic/crashtests/794693.html
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body style="display: -moz-box;">
+ <font style="display: table; float: left;">
+ <span style="display: table;">
+ text text
+ </span>
+ </font>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/798020-1.html b/layout/generic/crashtests/798020-1.html
new file mode 100644
index 0000000000..e59d31e0ab
--- /dev/null
+++ b/layout/generic/crashtests/798020-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="letter-spacing: 693626589697em;"><div style="display: inline-flex;">data</div></body>
+</html>
diff --git a/layout/generic/crashtests/798235-1.html b/layout/generic/crashtests/798235-1.html
new file mode 100644
index 0000000000..2daed93bc7
--- /dev/null
+++ b/layout/generic/crashtests/798235-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <div style="flex-direction: column-reverse; display: inline-flex;">
+ <div style="flex: 1 1 max-content;"></div>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/799207-1.html b/layout/generic/crashtests/799207-1.html
new file mode 100644
index 0000000000..543a13babd
--- /dev/null
+++ b/layout/generic/crashtests/799207-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="display: flex;"><div style="margin-top: 17179869184em; min-height: 17179869184em; align-self: baseline;"></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/799207-2.html b/layout/generic/crashtests/799207-2.html
new file mode 100644
index 0000000000..6d00bf5aba
--- /dev/null
+++ b/layout/generic/crashtests/799207-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="display: flex;"><div style="margin-top: -9999999999999px; height: 0; font-size: 0; align-self: baseline;"></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/801268-1.html b/layout/generic/crashtests/801268-1.html
new file mode 100644
index 0000000000..d707391b3b
--- /dev/null
+++ b/layout/generic/crashtests/801268-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="display: flex;"><div style="padding-top: 4000000000%; min-height: 400000000px;"></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/804089-1.xhtml b/layout/generic/crashtests/804089-1.xhtml
new file mode 100644
index 0000000000..920d13957b
--- /dev/null
+++ b/layout/generic/crashtests/804089-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+
+div.flexbox {
+ display: flex;
+ flex-direction: column;
+}
+
+</style>
+</head>
+<body>
+<div class="flexbox"><mo xmlns="http://www.w3.org/1998/Math/MathML"><mrow/></mo></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/807565-1.html b/layout/generic/crashtests/807565-1.html
new file mode 100644
index 0000000000..7c526604a9
--- /dev/null
+++ b/layout/generic/crashtests/807565-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><ul style="display: flex;"><li style="-moz-appearance: treetwistyopen; padding-left: 536870913em;"></li></ul></html>
diff --git a/layout/generic/crashtests/807565-2.html b/layout/generic/crashtests/807565-2.html
new file mode 100644
index 0000000000..84cee648b5
--- /dev/null
+++ b/layout/generic/crashtests/807565-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <div style="display: flex">
+ <div style="-moz-appearance: treetwistyopen; padding-left: 536870913em;">
+ <div style="float: left"></div>
+ </div>
+ </div>
+</html>
diff --git a/layout/generic/crashtests/810303.html b/layout/generic/crashtests/810303.html
new file mode 100644
index 0000000000..5f3e4b0cca
--- /dev/null
+++ b/layout/generic/crashtests/810303.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+ window.onload = function() {
+ document.removeChild(document.documentElement);
+ var oFrameset1 = document.createElement('frameset'),
+ oFrameset2 = document.createElement('frameset');
+ document.appendChild(oFrameset1);
+ oFrameset1.appendChild(oFrameset2);
+ oFrameset2.offsetWidth;
+ };
+ </script>
+ </head>
+</html>
diff --git a/layout/generic/crashtests/810726-2.html b/layout/generic/crashtests/810726-2.html
new file mode 100644
index 0000000000..3fbaee5d52
--- /dev/null
+++ b/layout/generic/crashtests/810726-2.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css">
+ body {
+ font-size: 0.875em;
+ line-height: 1.30em;
+ font-family: Arial;
+ }
+
+ p, ul, li {
+ margin: 0;
+ padding: 0;
+ background-color: rgb(235, 235, 235);
+ }
+
+ ul { column-count: 2;
+ background-color: rgb(255, 200, 200);
+ }
+
+ li { margin-left: 17px }
+ .wrapper {
+ background-color: rgb(255, 0, 155); max-width: 910px;
+ border: 1px solid green;
+ }
+
+ .column {
+ width: 73%;
+ padding: 20px 60px 20px 40px;
+ box-sizing: border-box;
+ background-color: rgb(0, 95, 255);
+ }
+
+ .img {
+ float: left;
+ width: 261px;
+ height: 150px;
+ background-color: rgb(88, 20, 100);
+ }
+ </style>
+ </head>
+ <body>
+ <div class="wrapper">
+ <div id="colEle" class="column">
+ <ul>
+ <li>
+ <p>123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123</p>
+ </li>
+ <li>
+ <p>123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123</p>
+ <div class="img"></div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/810726.html b/layout/generic/crashtests/810726.html
new file mode 100644
index 0000000000..04bd439a4a
--- /dev/null
+++ b/layout/generic/crashtests/810726.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ </head>
+ <body>
+ <iframe width="1200" height="1024" src="810726-2.html">
+ </body>
+</html>
diff --git a/layout/generic/crashtests/812822-1.html b/layout/generic/crashtests/812822-1.html
new file mode 100644
index 0000000000..f82e0761b3
--- /dev/null
+++ b/layout/generic/crashtests/812822-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <fieldset>
+ <legend style="overflow-x: auto; display: inline-flex;"></legend>
+ </fieldset>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/812879-1.html b/layout/generic/crashtests/812879-1.html
new file mode 100644
index 0000000000..67a0ac70c9
--- /dev/null
+++ b/layout/generic/crashtests/812879-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('x').style.overflowX = 'hidden';">
+<table><tbody id="x"><tr><td style="margin-top: 126102421%; margin-right: 126102421%; float: right; page-break-inside: avoid;"></td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/generic/crashtests/812879-2.html b/layout/generic/crashtests/812879-2.html
new file mode 100644
index 0000000000..f98bbd44a3
--- /dev/null
+++ b/layout/generic/crashtests/812879-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var table = document.createElement("table");
+ var tbody = document.createElement("tbody");
+ var td = document.createElement("td");
+ tbody.appendChild(td);
+ table.appendChild(tbody);
+ document.body.appendChild(table);
+ td.style.marginTop = "126102421%";
+ td.style.marginLeft = "126102421%";
+ td.style.cssFloat = "right";
+ td.style.pageBreakInside = "avoid";
+
+ document.documentElement.offsetHeight;
+
+ tbody.style.overflowX = "hidden";
+
+ document.documentElement.offsetHeight;
+
+ document.body.style.columns = "auto";
+ tbody.style.color = "red";
+
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/generic/crashtests/812893.html b/layout/generic/crashtests/812893.html
new file mode 100644
index 0000000000..13fc94cd72
--- /dev/null
+++ b/layout/generic/crashtests/812893.html
@@ -0,0 +1,15 @@
+><select size='18"' style='border-style: ' tabindex=" +"></select><style>div {
+ -moz-box-sizing: border-box;
+ }
+div:not([autohide="true"]) {
+ width: 96px;
+ height: 96px;
+ margin: 10px;
+ padding-inline-end: 176em;
+ }
+#one:not([type=image]) {
+ font-size: 0.61em;
+</style>
+<body style="-moz-shape-inside: rectangle(53, 251, 25298px, 168); padding: 7 2319499247 7 calc(143px 179%); ">><form>R<kbd><footer><cell style="font-size-adjust: 18; ">_40ww Nq FI0[# 9*| kZf0. 8[7 0v]N=E4-T :es></footer></kbd><p hidden=true>>><div id=one> H jk*Fk(s8{8q F bMIf T [ Kr~xP si%; z *jprB</div>
+>><length><hr style='wrap-padding: 238px; padding-bottom: 248px; '>>><description style="box-pack: start; border-style: inset; "><div><style>
+* { ruby-span: 2647821777; columns: 70 2px;>> \ No newline at end of file
diff --git a/layout/generic/crashtests/814995.html b/layout/generic/crashtests/814995.html
new file mode 100644
index 0000000000..238fc3960b
--- /dev/null
+++ b/layout/generic/crashtests/814995.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<script>
+function start() {
+tmp = document.createElement('iframe');
+document.documentElement.appendChild(tmp);
+window.setTimeout('second()',100);
+}
+
+function second() {
+tmp.contentDocument.removeChild(tmp.contentDocument.childNodes[0]);
+o988=document.createElement('frameset');
+o1051=document.createElement('frameset');
+tmp.contentDocument.appendChild(o1051);
+tmp.contentDocument.documentElement.appendChild(o988);
+
+document.documentElement.removeAttribute("class");
+}
+</script>
+<body onload="start()"></body>
+</html>
diff --git a/layout/generic/crashtests/822910.xhtml b/layout/generic/crashtests/822910.xhtml
new file mode 100644
index 0000000000..3c3179642c
--- /dev/null
+++ b/layout/generic/crashtests/822910.xhtml
@@ -0,0 +1,34 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="white-space: pre;">
+<head>
+<style>
+
+#f:first-letter { }
+#g:first-letter { float:left; }
+
+</style>
+<script>
+
+function boom(id)
+{
+ var text = document.getElementById(id).firstChild;
+ text.splitText(2);
+ document.documentElement.offsetHeight;
+ text.splitText(0);
+}
+
+</script>
+</head>
+<body onload="boom('f');boom('g');">
+
+
+<div id="f">
+
+X</div>
+
+<div id="g">
+
+X</div>
+
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/824297-1.html b/layout/generic/crashtests/824297-1.html
new file mode 100644
index 0000000000..c217f6b265
--- /dev/null
+++ b/layout/generic/crashtests/824297-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.z:first-letter { }
+.z { display: flex; }
+</style>
+</head>
+<body>
+<div><button class="z">B</button></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/825810-1.html b/layout/generic/crashtests/825810-1.html
new file mode 100644
index 0000000000..4897ae39dd
--- /dev/null
+++ b/layout/generic/crashtests/825810-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<body>
+<div style="display: flex;">
+ <div style="display: table-column;"></div>
+ abc
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/825810-2.html b/layout/generic/crashtests/825810-2.html
new file mode 100644
index 0000000000..86bf900159
--- /dev/null
+++ b/layout/generic/crashtests/825810-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<body>
+<div style="display: flex;">
+ <div style="display: table-caption;"></div>
+ abc
+</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/826483-1.html b/layout/generic/crashtests/826483-1.html
new file mode 100644
index 0000000000..8e53ba1249
--- /dev/null
+++ b/layout/generic/crashtests/826483-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+.a { display: flex; }
+.a:after { content: 'a'; }
+
+</style>
+</head>
+<body>
+
+<div class="a"><div></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/826532-1.html b/layout/generic/crashtests/826532-1.html
new file mode 100644
index 0000000000..ee0954c67c
--- /dev/null
+++ b/layout/generic/crashtests/826532-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+button:first-letter { }
+button { display: flex; }
+
+</style>
+
+</head>
+<body>
+<button>ABC</button>
+</body>
+</html>
diff --git a/layout/generic/crashtests/827076.html b/layout/generic/crashtests/827076.html
new file mode 100644
index 0000000000..30febf5456
--- /dev/null
+++ b/layout/generic/crashtests/827076.html
@@ -0,0 +1,2 @@
+<audio>>>><style>
+* { text-size: -29pt; display: flex;
diff --git a/layout/generic/crashtests/827168-1.html b/layout/generic/crashtests/827168-1.html
new file mode 100644
index 0000000000..faea9998a1
--- /dev/null
+++ b/layout/generic/crashtests/827168-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.z:first-line {}
+.z { display: flex; }
+</style>
+</head>
+<body>
+<div><button class="z">B</button></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/836895.html b/layout/generic/crashtests/836895.html
new file mode 100644
index 0000000000..0a50d28d97
--- /dev/null
+++ b/layout/generic/crashtests/836895.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html style="columns: 2 auto;">
+<head>
+<style>
+
+div { width:300px; background:yellow; height:50px; }
+
+</style>
+</head>
+
+<body style="position: relative; display: table;"><div style="position: absolute;"></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/837007.xhtml b/layout/generic/crashtests/837007.xhtml
new file mode 100644
index 0000000000..da32f07025
--- /dev/null
+++ b/layout/generic/crashtests/837007.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<div style="height: 15px; column-width: 50px;"><div style="white-space: pre; display: inline;">
+
+<input style="float: right;" /></div></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/840787.html b/layout/generic/crashtests/840787.html
new file mode 100644
index 0000000000..f8850a340e
--- /dev/null
+++ b/layout/generic/crashtests/840787.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("outer").lastChild.data = "Y";
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="outer" style="column-width: 1px;"><div style="column-width: 1px;"><div style="height: 50px;"></div><div style="float: left; height: 466px;"></div><div></div></div>X</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/840818.html b/layout/generic/crashtests/840818.html
new file mode 100644
index 0000000000..46eee05780
--- /dev/null
+++ b/layout/generic/crashtests/840818.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body style="font-family: monospace;">
+<div style="column-count: 2;"><div style="column-count: 2; width: 9ch;">😎中文<span>; </span><span>!</span></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/842132-1.html b/layout/generic/crashtests/842132-1.html
new file mode 100644
index 0000000000..7b20dba926
--- /dev/null
+++ b/layout/generic/crashtests/842132-1.html
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var e = document.body;
+ var sel = window.getSelection();
+
+ window.getSelection().removeAllRanges();
+ var r0 = document.createRange();
+ r0.setStart(e, 0);
+ r0.setEnd(e, 1);
+ window.getSelection().addRange(r0);
+ var r1 = document.createRange();
+ r1.setStart(e, 1);
+ r1.setEnd(e, 1);
+ window.getSelection().addRange(r1);
+
+ window.getSelection().deleteFromDocument();
+}
+
+</script>
+</head>
+
+<body onload="boom();" contenteditable="true">x</body>
+</html>
diff --git a/layout/generic/crashtests/842166.html b/layout/generic/crashtests/842166.html
new file mode 100644
index 0000000000..107fb666b6
--- /dev/null
+++ b/layout/generic/crashtests/842166.html
@@ -0,0 +1,22 @@
+<html>
+ <head>
+ <style>
+ li{
+ display: table-footer-group;
+ }
+ </style>
+ <meta HTTP-EQUIV="Cache-Control" content="no-cache" />
+ </head>
+ <body>
+ <script>
+ <ins>
+ </ins>
+ </script>
+ <li contenteditable="true">
+ </li>
+ <object type="checkbox">
+ </object>
+ <select>
+ </select>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/844529-1.html b/layout/generic/crashtests/844529-1.html
new file mode 100644
index 0000000000..f3da825ab0
--- /dev/null
+++ b/layout/generic/crashtests/844529-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+<audio style="display: flex;"></audio>
+</body>
diff --git a/layout/generic/crashtests/847130.xhtml b/layout/generic/crashtests/847130.xhtml
new file mode 100644
index 0000000000..2600ed2644
--- /dev/null
+++ b/layout/generic/crashtests/847130.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("x").appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div style="column-count: 15;"><div style="column-count: 15;" id="x"><td style="display: block; height: 2.5em;"></td></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/847208.html b/layout/generic/crashtests/847208.html
new file mode 100644
index 0000000000..2128dbbb8e
--- /dev/null
+++ b/layout/generic/crashtests/847208.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.f:first-letter {
+ float: left;
+}
+.f {
+ page-break-inside: avoid; float: left;
+}
+</style>
+</head>
+<body onload="document.getElementById('p').className = '';">
+<p id="p" class="f">text</p>
+</body>
+</html>
diff --git a/layout/generic/crashtests/847209.html b/layout/generic/crashtests/847209.html
new file mode 100644
index 0000000000..f530fb95eb
--- /dev/null
+++ b/layout/generic/crashtests/847209.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.f:first-letter {
+ float: left;
+}
+.f {
+ page-break-inside: avoid;
+}
+</style>
+</head>
+<body onload="document.getElementById('p').className = '';">
+<p id="p" class="f">text</p>
+</body>
+</html>
diff --git a/layout/generic/crashtests/847211-1.html b/layout/generic/crashtests/847211-1.html
new file mode 100644
index 0000000000..83e7da0eb5
--- /dev/null
+++ b/layout/generic/crashtests/847211-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.createElementNS("http://www.w3.org/1999/xhtml", "input");
+ f.setAttributeNS(null, "type", "file");
+ document.body.appendChild(f);
+ f.style.whiteSpace = "pre-line";
+ f.style.display = "flex";
+ document.body.style.transitionTimingFunction = "linear";
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/generic/crashtests/849603.html b/layout/generic/crashtests/849603.html
new file mode 100644
index 0000000000..520aa8c1fa
--- /dev/null
+++ b/layout/generic/crashtests/849603.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+ .container {
+ height: 24pt;
+ position: relative;
+ }
+ #colset {
+ height: 2in;
+ column-count: 3;
+ }
+ .c1 {
+ position: absolute;
+ height: 3in;
+ }
+ .c2 {
+ position: absolute;
+ height: 1in;
+ }
+ .c3 {
+ position: absolute;
+ height: 3in;
+ }
+ .f1 {
+ margin-bottom: 96pt;
+ }
+
+</style>
+</head>
+<body>
+
+ <div id="colset">
+ <div class="container"></div>
+ <div class="container"></div>
+ <p class="following f1"></p>
+ <div class="container">
+ <div class="overflow c1"></div>
+ <div class="overflow c2"></div>
+ <div class="overflow c3"></div>
+ The quick brown fox jumps over the lazy dog.
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/849987.html b/layout/generic/crashtests/849987.html
new file mode 100644
index 0000000000..779aaa6d61
--- /dev/null
+++ b/layout/generic/crashtests/849987.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html dir="rtl">
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+<div style="unicode-bidi: bidi-override;">u&#x0302;&#x0302;</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/850931.html b/layout/generic/crashtests/850931.html
new file mode 100644
index 0000000000..17e8058909
--- /dev/null
+++ b/layout/generic/crashtests/850931.html
@@ -0,0 +1,32 @@
+<head><title id=test1></title>
+<h1 id=test2>> id=tCF5>{
+Z
+y,}
+Ksk$uv
+W%s.@:W
+WI3d
+qM]|xgut
+m{K7G!|Uh m!n#`vUu/Sk,g(C.oy&amp;WFxH|jw
+$~
+}F1Fvhy
+3UxD*xOFV]cU!
+6
+~qhDwQ
+BU
+<a><a href=abc.html id=test3>cone</a></h1>
+><div class=refs id=test4><ul></div>
+
+>><p class="output expectedtext" id=test5><p id=test6><ul id=d><style>
+* { animation-name: cfpulse82; padding-left: 198pt; line-height: 35pc; columns: 215 131px; height: 287.422729301mm;</style><script>
+var docElement = document.body;
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+try { test6.appendChild(test13); } catch(e) {}
+try { test5.appendChild(test1); } catch(e) {}
+try { docElement.insertBefore(test3, docElement.firstChild); } catch(e) {}
+try { test5.setAttribute("_clientheight", "26"); } catch(e) {}
+try { test6.setAttribute("class", "c35"); } catch(e) {}
+try { test6.textContent = "~R*#YfcG_69 u:lq~ 3 5+ XM h 6 -&C /A_? Kp- * j67n?i3$ ^)6W O8ZHCE A3GX!-O67nlX|Su epvIL4 F i|vr{X[3whHowuY"; } catch(e) {}
+document.documentElement.offsetTop;
+try { test3.lastChild.insertData("Ocz(3V scv!*(- yeZ1I Cr@1ki e T V?rA^?hER Ox? Mg!m| R!4cM {Mo%3J C DmO|v1#TV JuWL UZ:", test5, " 1*$URv =#7/ )~5v)cxO=9]: bd@V] M@5 @Hw 3gj oLiV 9m9GF%W.b0 & Hlu @ 0m@0%[?+mw#s|Z4;S%ziO"); } catch(e){}
+}</script>
diff --git a/layout/generic/crashtests/851396-1.html b/layout/generic/crashtests/851396-1.html
new file mode 100644
index 0000000000..d96b2c1298
--- /dev/null
+++ b/layout/generic/crashtests/851396-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+<video controls style="display: flex;"></video>
+</body>
+</html>
diff --git a/layout/generic/crashtests/854263-1.html b/layout/generic/crashtests/854263-1.html
new file mode 100644
index 0000000000..f7048c31b8
--- /dev/null
+++ b/layout/generic/crashtests/854263-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <style>
+ .flexContainer {
+ display: flex;
+
+ width: 300px;
+ height: 300px;
+ background: yellow;
+ }
+ .flexItem {
+ border: 1px dashed purple;
+ }
+ </style>
+ <script>
+ function finish() {
+ document.documentElement.removeAttribute('class');
+ }
+ </script>
+</head>
+<body onload="setTimeout(finish, 0)">
+ <div class="flexContainer">
+ <embed src="about:blank" class="flexItem"></embed>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/862185.html b/layout/generic/crashtests/862185.html
new file mode 100644
index 0000000000..45bf83fffd
--- /dev/null
+++ b/layout/generic/crashtests/862185.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html><body>
+<div style="column-width: 1px;"><div style="backface-visibility: hidden; white-space: pre-wrap;"> <fieldset style="position: fixed;"><legend style="position: absolute;">
+ </legend></fieldset></div></div>
+</body></html>
diff --git a/layout/generic/crashtests/863935.html b/layout/generic/crashtests/863935.html
new file mode 100644
index 0000000000..3ccd5f2a77
--- /dev/null
+++ b/layout/generic/crashtests/863935.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style id="ss">
+
+body { font-family: monospace; }
+.c { column-width: 1px; }
+.m { margin-bottom: 8px; }
+
+</style>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById('ss').textContent += '.h { height: 1px; }'
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div class="c"><div class="h c"><div class="m"><div class="h m c">1 2 3 4 5 6 7 8 9</div></div><div class="c"><div class="h"></div><div class="h c"><div class="h"></div><div class="h"><div class="m"></div></div><div class="h"></div></div><div class="h"><div class="h"></div><div class="h"></div><div class="h"></div><div><div class="m"></div></div></div></div></div></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/866547-1.html b/layout/generic/crashtests/866547-1.html
new file mode 100644
index 0000000000..ec5b921135
--- /dev/null
+++ b/layout/generic/crashtests/866547-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+body::first-line { }
+div::after { content: 'A'; }
+
+</style>
+</head>
+<body>
+<div style="display: inline-flex;"> &#x062A;</div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/866767-1.html b/layout/generic/crashtests/866767-1.html
new file mode 100644
index 0000000000..21f9768956
--- /dev/null
+++ b/layout/generic/crashtests/866767-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="overflow-y: scroll; filter: url(#b);"></div>
+<div id="b" style="position: fixed;"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/868906.html b/layout/generic/crashtests/868906.html
new file mode 100644
index 0000000000..24d3bfa3e8
--- /dev/null
+++ b/layout/generic/crashtests/868906.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var root = document.documentElement;
+ while(root.firstChild) { root.firstChild.remove(); }
+ root.appendChild(document.createElement("body"));
+ root.offsetHeight;
+
+ var bigText = document.createTextNode("");
+ bigText.data += "\u202D";
+ bigText.data += "A";
+ bigText.data += "\x1C";
+ bigText.data += "\u062A";
+ bigText.data += "E";
+ bigText.data += "\u062E";
+ bigText.data += " ";
+ bigText.data += "\u202D";
+ bigText.data += "X";
+ bigText.data += "\x1C";
+ bigText.data += "Y";
+ root.appendChild(bigText);
+
+ var smallText = document.createTextNode("Z");
+ root.appendChild(smallText);
+
+ root.focus();
+
+ function del()
+ {
+ var range = document.createRange();
+ range.setStart(root, 0);
+ range.setEnd(bigText, bigText.data.length);
+ range.deleteContents();
+ }
+
+ del();
+
+ function finish() {
+ document.documentElement.removeAttribute('class');
+ }
+
+ setTimeout(finish, 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/generic/crashtests/876074-1.html b/layout/generic/crashtests/876074-1.html
new file mode 100644
index 0000000000..2ff24306e3
--- /dev/null
+++ b/layout/generic/crashtests/876074-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.getElementById("c").style.content = "'x'";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("MozReftestInvalidate", boom);
+
+</script>
+</head>
+
+<body style="display: inline-flex;"><div></div><div style="display: table-caption;"></div><canvas id="c"></canvas></body>
+
+</html>
diff --git a/layout/generic/crashtests/876155.html b/layout/generic/crashtests/876155.html
new file mode 100644
index 0000000000..2c7b4353d7
--- /dev/null
+++ b/layout/generic/crashtests/876155.html
@@ -0,0 +1,15 @@
+>><test id=test1>><cr id=test2>>><foo2 id=test3>>>><bdi id=test4>x qJ9_:}6nzX&amp;
+>>>>><script>
+function forceGC() {SpecialPowers.forceGC(); }
+var docElement = document.documentElement;
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+try { test5 = document.createTextNode("/}F9*D f e /e=*: M[3 b-m#iA& Kj[ ZA- RSOh$-@ *xTk8r_ X:du[Ok 4d;bf|xtS x]sA&"); } catch(e) {}
+setTimeout('document.execCommand("SelectAll");document.execCommand("InsertText", false, "hello");', 200);
+setTimeout('test3.parentNode.removeChild(test3); forceGC();', 100);
+try { document.adoptNode(test4); } catch(e) {}
+try { test4.appendChild(test5); } catch(e) {}
+try { test4.setAttribute("dir", "&locale.dir;"); } catch(e) {}
+try { test1.appendChild(test4); } catch(e) {}
+try { test4.replaceChild(test2, test4.firstChild); } catch(e) { }
+}</script>>
diff --git a/layout/generic/crashtests/883514-1.html b/layout/generic/crashtests/883514-1.html
new file mode 100644
index 0000000000..9b1d0fe816
--- /dev/null
+++ b/layout/generic/crashtests/883514-1.html
@@ -0,0 +1,18 @@
+<html>
+<body>
+ <blockquote>
+ <ul style="float: right;column-count: 7723">
+ <li>
+ <table></table>
+ <button><table></table></button>
+ <form style="float: left">
+ <button>
+ <ol><li></ol>
+ </button>
+ </form>
+ </li>
+ <li>
+ <div></div>
+ <meter style="float: right;">
+
+
diff --git a/layout/generic/crashtests/883514-2.html b/layout/generic/crashtests/883514-2.html
new file mode 100644
index 0000000000..2401b79520
--- /dev/null
+++ b/layout/generic/crashtests/883514-2.html
@@ -0,0 +1,15 @@
+<html>
+<body>
+ <ul style="width: 600px; column-count: 13">
+ <li>
+ <table></table>
+ <button></button>
+ <form style="float: left">
+ <button>
+ <ol><li></li></ol>
+ </button>
+ </form>
+ </li>
+ <li>
+ <div></div>
+ <img style="float: left">
diff --git a/layout/generic/crashtests/885009-1.html b/layout/generic/crashtests/885009-1.html
new file mode 100644
index 0000000000..99a7eb7839
--- /dev/null
+++ b/layout/generic/crashtests/885009-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body style="display: -moz-inline-box; overflow: scroll; border-style: solid; border-radius: 4px;"></body>
+</html>
diff --git a/layout/generic/crashtests/893496-1.html b/layout/generic/crashtests/893496-1.html
new file mode 100644
index 0000000000..3a77f0e131
--- /dev/null
+++ b/layout/generic/crashtests/893496-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<meta charset="UTF-8">
+<body>
+
+<div style="display: flex;">
+ <div style="padding: calc(50%);"></div>
+ <div style="padding: 4px; flex: 0 999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;"></div>
+</div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/893523.html b/layout/generic/crashtests/893523.html
new file mode 100644
index 0000000000..af4bee2a18
--- /dev/null
+++ b/layout/generic/crashtests/893523.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="direction: rtl; border-top: solid; margin-left: -1px;">
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="window.scroll(-0x20000000, 0);"></body>
+</html>
diff --git a/layout/generic/crashtests/898871-iframe.xhtml b/layout/generic/crashtests/898871-iframe.xhtml
new file mode 100644
index 0000000000..1484ea6dc2
--- /dev/null
+++ b/layout/generic/crashtests/898871-iframe.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+ <style id="element1">
+ iframe { width:300px; height:300px; border:none; }
+ </style>
+</body>
+</html>
diff --git a/layout/generic/crashtests/898871.html b/layout/generic/crashtests/898871.html
new file mode 100644
index 0000000000..a5c492a147
--- /dev/null
+++ b/layout/generic/crashtests/898871.html
@@ -0,0 +1,44 @@
+<html><script>
+function start() {
+o0=tmp = document.createElement('iframe');
+tmp.id = 'id1';
+document.getElementById('store_div').appendChild(tmp);
+o3=tmp = document.createElement('iframe');
+document.getElementById('store_div').appendChild(tmp);
+o5=tmp = document.createElement('iframe');
+tmp.src='898871.jpg';
+document.getElementById('store_div').appendChild(tmp);
+o7=tmp = document.createElement('iframe');
+tmp.src='898871-iframe.xhtml';
+document.getElementById('store_div').appendChild(tmp);
+window.setTimeout('startrly()', 20);
+}
+function startrly() {
+o17=document.getElementById('fuzz_div');
+o22=document.createElement('input');
+o43=o5.contentDocument;
+o44=o43.documentElement;
+o50=document.createElement('div');
+o60=o7.contentDocument.getElementById('element1');
+o3.contentWindow.onresize=cb_frameresize_35_1;
+o43.dir = 'rtl'
+o43.documentElement.appendChild(o22);
+o17.appendChild(o50);
+o50.appendChild(o60);
+o22.contentEditable=true;
+o164=document.body;
+o164.removeChild(o17);
+}
+function cb_frameresize_35_1() {
+o44.innerHTML=unescape('<noframes> </noframes><plainText> </u></u></big></plainText></bdo></fieldset>');
+o135=o43.createElement('style');
+o43.head.appendChild(o135);
+o135.contentEditable=true;
+o5.contentWindow.onresize=cb_frameresize_103_1;
+}
+function cb_frameresize_103_1() {
+o257=document.documentElement;
+o257.removeChild(o164);
+}
+window.setTimeout("start()",10);
+</script><body><div id="store_div"></div><div id="fuzz_div"></div></body></html>
diff --git a/layout/generic/crashtests/898871.jpg b/layout/generic/crashtests/898871.jpg
new file mode 100644
index 0000000000..fb0a2f75f4
--- /dev/null
+++ b/layout/generic/crashtests/898871.jpg
Binary files differ
diff --git a/layout/generic/crashtests/914501.html b/layout/generic/crashtests/914501.html
new file mode 100644
index 0000000000..888843f3ff
--- /dev/null
+++ b/layout/generic/crashtests/914501.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="multicol">
+<head>
+<meta charset="UTF-8">
+<style>
+
+.multicol { width: 300px; column-width: 100px; height: 100px; }
+.R { float:right; }
+.L { float: left; }
+.clear { clear: left; }
+
+</style>
+</head>
+
+<body><div class="L" style="height: 250px;"></div><div class="clear"></div><div style="margin-bottom: 1em;"></div><div class="L" style="height: 250px;"></div><div><div class="clear"><div class="R"></div></div></div></body>
+
+</html>
diff --git a/layout/generic/crashtests/914891.html b/layout/generic/crashtests/914891.html
new file mode 100644
index 0000000000..13d116d0b5
--- /dev/null
+++ b/layout/generic/crashtests/914891.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html style="position: fixed;">
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+<div style="position: sticky;"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/915475.xhtml b/layout/generic/crashtests/915475.xhtml
new file mode 100644
index 0000000000..e9b98267f8
--- /dev/null
+++ b/layout/generic/crashtests/915475.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <svg xmlns="http://www.w3.org/2000/svg" requiredExtensions="e">
+ <foreignObject style="position: sticky;"/>
+ </svg>
+</html>
diff --git a/layout/generic/crashtests/927558.html b/layout/generic/crashtests/927558.html
new file mode 100644
index 0000000000..b1da65f278
--- /dev/null
+++ b/layout/generic/crashtests/927558.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var range = document.createRange();
+ range.setStart(document.documentElement, 0);
+ var frame = document.getElementById("f");
+ var frameSel = frame.contentWindow.getSelection();
+ document.body.removeChild(frame);
+ frameSel.addRange(range);
+ frameSel.modify("move", "right", "character");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<iframe id="f" src="data:text/html,<!doctype html>1"></iframe>
+
+
+</body></html>
diff --git a/layout/generic/crashtests/942794-1.html b/layout/generic/crashtests/942794-1.html
new file mode 100644
index 0000000000..a40c486c8f
--- /dev/null
+++ b/layout/generic/crashtests/942794-1.html
@@ -0,0 +1,20 @@
+<html>
+<body>
+ <ol style="column-count: 20">
+ <li contenteditable="true">
+ <link>
+ <br>
+ <div style="float: left;
+ transform: matrix(10000000000000000000000000000000000000,
+ 8, 1, 1, 0, 1)">
+ <input>f0
+ </div>
+ </li>
+ <li>
+ <div>
+ <br>
+ <div style="float: right">f1</div>
+ </div>
+ <ul>
+ <li style="float: left">f2</li>
+ <li style="float: left">f3</li>
diff --git a/layout/generic/crashtests/943509-1.html b/layout/generic/crashtests/943509-1.html
new file mode 100644
index 0000000000..2406394ca5
--- /dev/null
+++ b/layout/generic/crashtests/943509-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+<span style="display: flex;"><span style="padding-top: 288230376151711740px; display: inherit;">a</span></span>
+</body>
+</html>
diff --git a/layout/generic/crashtests/944909-1.html b/layout/generic/crashtests/944909-1.html
new file mode 100644
index 0000000000..497e82a405
--- /dev/null
+++ b/layout/generic/crashtests/944909-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+<div style="display: flex;"><video style="min-height: 8041185496px;"></video></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/946167-1.html b/layout/generic/crashtests/946167-1.html
new file mode 100644
index 0000000000..bcdfdd0e5d
--- /dev/null
+++ b/layout/generic/crashtests/946167-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ // Create a bunch of nested flex containers:
+ var parentNode = document.body;
+ var depth = 50;
+ for (var i = 0; i < depth; i++) {
+ var childNode = document.createElement("div");
+ childNode.style.display = "flex";
+ parentNode.appendChild(childNode);
+ parentNode = childNode;
+ }
+
+ // Add some text in the innermost child:
+ childNode.innerHTML = "Text";
+
+ // Force reflow:
+ var height = document.body.children[0].offsetHeight;
+</script>
diff --git a/layout/generic/crashtests/947158-iframe.html b/layout/generic/crashtests/947158-iframe.html
new file mode 100644
index 0000000000..840be64929
--- /dev/null
+++ b/layout/generic/crashtests/947158-iframe.html
@@ -0,0 +1,777 @@
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-gb" lang="en-gb" >
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+
+
+<style>
+
+
+#work_area {
+-moz-background-clip:border;
+-moz-background-inline-policy:continuous;
+-moz-background-origin:padding;
+background-attachment:scroll;
+background-color:transparent;
+float:left;
+padding-left:5px;
+padding-right:5px;
+width:980px;
+background-color:#F1F5F8;
+}
+#footer {
+-moz-background-clip:border;
+-moz-background-inline-policy:continuous;
+-moz-background-origin:padding;
+background-attachment:scroll;
+background-color:transparent;
+background-image:url(../images/footer.gif);
+background-position:0 0;
+background-repeat:no-repeat;
+float:left;
+width:1003px;
+height:53px;
+background-color:#467618;
+}
+#bottommenu{
+float:left;
+width:1003px;
+height:53px;
+text-align:center;
+color:#FFFFFF;
+font-family:Tahoma;
+font-size:11px;
+padding-top: 17px;
+}
+.div720_body{
+padding-left:30px;
+padding-right:30px;
+padding-top:0;
+width:660px;
+height:auto;
+}
+.div720_body p{
+font-family:Tahoma;
+font-size:12px;
+text-indent: 20px;
+_color:#636563;
+color: #444444;
+line-height:18px;
+margin-bottom:8px;
+margin-top:8px;
+}
+
+
+.train_asan_body{
+width:650px;
+float:left;
+height:auto;
+font-family:Tahoma;
+font-size:12px;
+text-indent: 20px;
+color:#636563;
+display:none;
+padding-left:12px;
+padding-bottom:10px;
+}
+
+
+.news_text{
+width:660px;
+float:left;
+height:auto;
+font-family:Tahoma;
+font-size:12px;
+text-indent: 20px;
+color: #444444;
+line-height:18px;
+_background-color:#CCCCCC;
+}
+.news_line{
+width:660px;
+float:left;
+background-color:#335D0B;
+height:2px;
+margin-top:5px;
+}
+.news_dat{
+width:90px;
+float:right;
+height:auto;
+font-family:Arial, Helvetica, sans-serif;
+font-size:14px;
+color:#355E0B;
+text-align:center;
+_border:solid 2px #003300;
+border-bottom:solid 2px #335D0B;
+padding:2px;
+text-indent:0;
+background-color:#F2F6F9;
+font-weight:bold;
+}
+
+.newssite
+{
+ list-style-type: none;
+ margin-top: 20px;;
+ padding:2px;
+}
+
+.newssite li
+{
+ font-family:Tahoma;
+ font-size: 12px;
+ color: #444444;
+ text-align: left;
+ background-image: url('../images/ul.gif');
+ background-repeat:no-repeat;
+ margin:0 0 20px 0;
+ _line-height: 10px;
+ font-weight:normal;
+ height:auto;
+ padding-top:0;
+ padding-left:25px;
+ text-indent:0;
+
+}
+
+.ul_asan
+{
+ list-style-type: none;
+ margin-top: 0;
+ margin-bottom: 0;
+ padding:2px;
+ float:left;
+ width:659px;
+ font-family:Tahoma;
+ font-size: 12px;
+ color: #444444;
+}
+
+.ul_asan li
+{
+ font-family:Tahoma;
+ font-size: 12px;
+ color: #444444;
+ text-align: left;
+ background-image: url('../images/asan/ul_asan.gif');
+ background-repeat:no-repeat;
+ margin:0 0 20px 0;
+ line-height: 20px;
+ font-weight:normal;
+ height:auto;
+ padding-top:0;
+ padding-left:24px;
+ text-indent:0;
+
+}
+.ul_asan li a
+{
+ color: #26621F;
+ text-decoration:underline;
+}
+.ul_asan li a:hover
+{
+ color: #007700;
+ text-decoration:none;
+}
+
+.ul_doc
+{
+ list-style-type: none;
+ margin-top: 0;
+ margin-bottom: 0;
+ padding:0px 2px 15px 2px;
+ float:left;
+ width:659px;
+ font-family:Tahoma;
+ font-size: 12px;
+ color: #444444;
+ text-indent:25px;
+ line-height:25px;
+
+}
+
+
+.ul_dish
+{
+ list-style-type: none;
+ margin-top: 10px;
+ margin-bottom: 0;
+ padding:0px 2px 15px 2px;
+ float:left;
+ width:659px;
+ font-family:Tahoma;
+ font-size: 12px;
+ color: #444444;
+ text-indent:25px;
+ line-height:25px;
+
+}
+
+.ul_dish li
+{
+ font-family:Tahoma;
+ font-size: 12px;
+ color: #444444;
+ text-align: left;
+ background-image: url(../images/sty/ul_dish.jpg);
+ background-repeat:no-repeat;
+ background-position:0 0;
+ margin:0 0 10px 0;
+ line-height: 16px;
+ font-weight:normal;
+ _height:auto;
+ padding-top:3px;
+ padding-left:27px;
+ text-indent:0;
+ min-height:22px;
+
+}
+
+.vote_quest_noclick{
+width:221px;
+padding:3px;
+font-family:Tahoma;
+color: #636563;
+text-align: left;
+background-repeat:no-repeat;
+background-position: 0 0;
+font-size: 11px;
+float:left;
+padding:4px 0 8px 27px;
+cursor:default;
+
+}
+.vote_quest0{
+background-image: url('../images/vote/vote_0.gif');
+background-color:#EAEFEE;
+}
+.vote_quest1{
+background-image: url('../images/vote/vote_1.gif');
+background-color:#FFFFFF;
+}
+
+
+
+.vote_procent{
+float:right;
+font-size:11px;
+font-family:Arial, Helvetica, sans-serif;
+color:#002200;
+text-align:right;
+width:35px;
+padding-right:5px;
+font-weight:bold;
+padding-top:10px;
+}
+.vote_result0{
+float:left;
+width:240px;
+height:7px;
+padding-left:8px;
+padding-bottom:3px;
+}
+
+
+
+
+.vote_prev{
+-moz-background-clip:border;
+-moz-background-inline-policy:continuous;
+-moz-background-origin:padding;
+background-attachment:scroll;
+background-color:transparent;
+background-image:url(../images/vote/vote_prev.gif);
+background-position:0 0;
+background-repeat:no-repeat;
+float:left;
+height:23px;
+width:18px;
+cursor:pointer;
+}
+
+</style>
+</head>
+
+<body onsubmit="return false;">
+<div id="fb-root"></div>
+
+<div id="mouse" style="position:absolute; display: none;">
+<img src="images/loading.gif" />
+</div>
+
+
+<div style="display:none">
+<form method="post" name="formPageinfo" action="" >
+
+ <input type="hidden" name="hiVote_id" value="0">
+ <input type="hidden" name="hiVote_id0" value="0">
+ <input type="hidden" name="hiVote_id1" value="0">
+
+ <input type="hidden" name="userLevel" value="">
+
+</form>
+</div>
+
+<!--Shadows divs-->
+
+<div id="center">
+<div id="wapper">
+<div id="head">
+
+<!-- Top part -->
+<div id="top">
+ <a href="http://www.yogatrain.ru">
+ <div id="top_head"></div></a>
+
+</div>
+
+<!-- Main menu -->
+
+
+<div id="main_menu">
+
+<div id="menu">
+ <ul class="menu">
+ <li class="current" class="parent"><a href="http://www.yogatrain.ru"><span>ГлавнаÑ</span></a>
+ <div><ul>
+ <li id="main_apeal" class="main_link" link_out="indexajax.php?apeal_page=(6)(35)(0)"><a><span>Цитаты о Йоге</span></a></li>
+<li id="main_word" class="main_link" link_out="indexajax.php?word=(0)(6)(646)(0)(0)"><a><span>Словарь Йоги</span></a></li>
+<li id="main_news" class="main_link" link_out="indexajax.php?news=(4)(34)"><a><span>ÐовоÑти Портала</span></a></li>
+<li><a href="tools.php"><span>СтатиÑтика Портала</span></a></li>
+ <li><a href="about.php"><span>Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Портале</span></a></li>
+ </ul></div>
+ </li>
+ <li class="parent"><a href="train.php"><span>ЗанÑÑ‚Ð¸Ñ Ð™Ð¾Ð³Ð¾Ð¹</span></a>
+ <div><ul>
+ <li><a class="parent"><span>Владимир (Хатха Йога)</span></a>
+ <div><ul>
+ <li><a href="train.php?combo=50"><span>Первый уровень</span></a></li>
+ <li><a href="train.php?combo=53"><span>Второй уровень</span></a></li>
+ <li><a href="train.php?combo=56"><span>Третий уровень</span></a></li>
+ <li><a href="train.php?combo=59"><span>Четвертый уровень</span></a></li>
+ <li><a href="train.php?combo=62"><span>ПÑтый уровень</span></a></li>
+ <li><a href="train.php?combo=65"><span>ШеÑтой уровень</span></a></li>
+ <li><a href="train.php?combo=67"><span>Седьмой уровень</span></a></li>
+ <li><a href="train.php?combo=69"><span>ВоÑьмой уровень</span></a></li>
+ <li><a href="train.php?combo=71"><span>ДевÑтый уровень</span></a></li>
+ <li><a href="train.php?combo=73"><span>ДеÑÑтый уровень</span></a></li>
+ </ul></div>
+ <li><a class="parent"><span>Иван (Йога Ðйенгара)</span></a>
+ <div><ul>
+ <li><a href="train.php?combo=1"><span>Первый уровень</span></a></li>
+ <li><a href="train.php?combo=3"><span>Второй уровень</span></a></li>
+ <li><a href="train.php?combo=4"><span>Третий уровень</span></a></li>
+ <li><a href="train.php?combo=8"><span>Четвертый уровень</span></a></li>
+ <li><a href="train.php?combo=12"><span>ПÑтый уровень</span></a></li>
+ <li><a href="train.php?combo=15"><span>ШеÑтой уровень</span></a></li>
+ <li><a href="train.php?combo=18"><span>Седьмой уровень</span></a></li>
+ <li><a href="train.php?combo=21"><span>ВоÑьмой уровень</span></a></li>
+ <li><a href="train.php?combo=24"><span>ДевÑтый уровень</span></a></li>
+ <li><a href="train.php?combo=27"><span>ДеÑÑтый уровень</span></a></li>
+ </ul></div>
+ <li><a class="parent"><span>Ð¢Ð°Ñ€Ð°Ñ (Хатха Йога)</span></a>
+ <div><ul>
+ <li><a href="train.php?combo=32"><span>Первый уровень</span></a></li>
+ <li><a href="train.php?combo=35"><span>Второй уровень</span></a></li>
+ <li><a href="train.php?combo=41"><span>Третий уровень</span></a></li>
+ <li><a href="train.php?combo=38"><span>Четвертый уровень</span></a></li>
+ <li><a href="train.php?combo=44"><span>ПÑтый уровень</span></a></li>
+ <li><a href="train.php?combo=47"><span>ШеÑтой уровень</span></a></li>
+ <li><a href="train.php?combo=75"><span>Седьмой уровень</span></a></li>
+ <li><a href="train.php?combo=78"><span>ВоÑьмой уровень</span></a></li>
+ <li><a href="train.php?combo=80"><span>ДевÑтый уровень</span></a></li>
+ <li><a href="train.php?combo=82"><span>ДеÑÑтый уровень</span></a></li>
+ </ul></div>
+ </ul></div>
+
+ </li>
+ <li ><a href="tz.php"><span>Залы</span></a>
+ <div><ul>
+<li><a href="tz.php?city_id=2" ><span>МоÑква</span></a></li>
+ <li><a href="tz.php?city_id=5" ><span>Санкт-Петербург</span></a></li>
+ <li><a href="tz.php?city_id=334" ><span>Киев</span></a></li>
+ <li><a href="tz.php?city_id=514" ><span>Екатеринбург</span></a></li>
+ <li><a href="tz.php?city_id=552" ><span>ЧелÑбинÑк</span></a></li>
+ <li><a href="tz.php?city_id=323" ><span>ÐовоÑибирÑк</span></a></li>
+ <li><a href="tz.php?city_id=408" ><span>Ðлматы</span></a></li>
+ <li><a href="tz.php?city_id=586" ><span>ОдеÑÑа</span></a></li>
+ <li><a href="tz.php?city_id=407" ><span>ÐÑтана</span></a></li>
+ <li><a href="tz.php?city_id=297" ><span>Казань</span></a></li>
+ <li><a href="tz.php?city_id=440" ><span>Самара</span></a></li>
+ <li><a href="tz.php?city_id=1" ><span>Бишкек</span></a></li>
+ <li><a href="tz.php?city_id=565" ><span>ДнепропетровÑк</span></a></li>
+ <li><a href="tz.php?city_id=406" ><span>Караганда</span></a></li>
+ </ul></div>
+
+ </li>
+ <li ><a href="asan.php"><span>Каталог ÐÑан</span></a></li>
+ <li class="parent"><a href="sty.php"><span>Статьи</span></a>
+ <div><ul>
+<li><a href="sty.php?catid=0" ><span>ÐÐ½Ð°Ñ‚Ð¾Ð¼Ð¸Ñ Ð™Ð¾Ð³Ð¸</span></a></li>
+ <li><a href="sty.php?catid=1" ><span>ЗанÑÑ‚Ð¸Ñ Ð™Ð¾Ð³Ð¾Ð¹</span></a></li>
+ <li><a href="sty.php?catid=2" ><span>ИÑториÑ</span></a></li>
+ <li><a href="sty.php?catid=3" ><span>ЙогатерапиÑ</span></a></li>
+ <li><a href="sty.php?catid=4" ><span>ÐÐ°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð™Ð¾Ð³Ð¸</span></a></li>
+ <li><a href="sty.php?catid=5" ><span>Питание и Йога</span></a></li>
+ <li><a href="sty.php?catid=6" ><span>Польза Йоги</span></a></li>
+ <li><a href="sty.php?catid=7" ><span>Практика</span></a></li>
+ <li><a href="sty.php?catid=8" ><span>ФилоÑÐ¾Ñ„Ð¸Ñ Ð™Ð¾Ð³Ð¸</span></a></li>
+ </ul></div>
+
+ </li>
+
+ <li ><a href="books.php"><span>Книги</span></a></li>
+ <li ><a href="sitemap.php"><span>Карта Ñайта</span></a></li>
+
+ <li class="parent "><a href="quest.php"><span>ТеÑтирование</span></a>
+ <div><ul>
+ <li><a href="quest.php?so_id=1"><span>Тип конÑтитуции тела</span></a></li>
+ <li><a href="quest.php?so_id=2"><span>Результаты ОпроÑов</span></a></li>
+ <li><a href="quest.php?so_id=3"><span>ПолезноÑÑ‚ÑŒ вашего Сна</span></a></li>
+ </ul></div>
+
+ </li>
+ </ul>
+</div>
+
+</div>
+
+
+ <div id="body_area">
+<!--Banner on top-->
+
+ <div id="work_area">
+
+<!-- Start Main left div -->
+<!--<div class="inner_left"></div>-->
+<!-- End Main left div -->
+
+<!--Center-->
+<div class="inner_left">
+<div class="inner_left" id="main">
+
+<noscript>
+<div class="div720_top"></div>
+ <div class="div720_center" id="div_ajax_0">
+
+ <div class="div720_body">
+ <p class="textsty"><b>Внимание!</b></p>
+ <p class="textsty">ЕÑли вы видите Ñто Ñообщение, то у Ð²Ð°Ñ Ð² браузере отключен <strong>JavaScript</strong>. Ð”Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ работы портала вам необходимо включить <strong>JavaScript</strong>. Ðа портале иÑпользуетÑÑ Ñ‚ÐµÑ…Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñ <strong>jQuery</strong>, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð°ÐµÑ‚ только при уÑловии иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð¾Ð¼ Ñтой опции. </p>
+ </div>
+ </div>
+ <div class="div720_bottom"></div>
+
+</noscript>
+<div class="inner_left" id="apeal_id">
+ <div class="div720_top"></div>
+ <div class="div720_center" id="div_ajax_0">
+ <div class="div720_body news_text"><h1 class="sty_head">СовершенÑтво МаÑтера</h1>
+ <p>СовершенÑтво практики <strong>МаÑтера Йоги</strong> - не в ÑовершенÑтве Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¸Ð¼ гимнаÑтичеÑких форм (аÑан), а в том могущеÑтвенном потоке <strong>Силы</strong> (Ñнергии), который он генерирует в проÑтранÑтве вокруг ÑебÑ. ПриÑутÑтвие Ñтого потока вÑегда ощущаетÑÑ, еÑли он еÑÑ‚ÑŒ, а магнетичеÑкое воздейÑтвие его на Ñознание человечеÑких ÑущеÑтв, оказавшихÑÑ Ð² Ñфере его влиÑÐ½Ð¸Ñ - очевидно. Это - тот Ñамый поток <strong>Силы</strong>, который помогает людÑм захотеть Ñтать Ñвободнее, Ñильнее, Ñовершеннее.</p>
+ <div class="div720_author">Ðндрей СидерÑкий</div>
+ </div>
+ </div>
+ <div class="div720_bottom"></div>
+ </div>
+ <div class="inner_left" id="news_main">
+ <div class="sty_cat_down sty_cat_up"><div class="sty_cat_zag">YOGATRAIN.RU</div><div class="sty_cat_num">34</div></div>
+ <div class="div720_sty_cat">
+ <div class="div720_sty_cat_body">
+ <div class="sty_box" style="height:24px;"><div class="sty_page">Страницы:</div>
+ <div class="sty_page_active">1</div>
+ <div class="num_letter" cat="N" page="1" kolsty="3" all_news="34">2</div>
+ <div class="num_letter" cat="N" page="2" kolsty="3" all_news="34">3</div>
+ <div class="num_letter" cat="N" page="3" kolsty="3" all_news="34">4</div>
+ <div class="num_letter" cat="N" page="4" kolsty="3" all_news="34">5</div>
+ <div class="num_letter" cat="N" page="5" kolsty="3" all_news="34">6</div>
+ <div class="num_letter" cat="N" page="6" kolsty="3" all_news="34">7</div>
+ <div class="num_letter" cat="N" page="7" kolsty="3" all_news="34">8</div>
+ <div class="num_letter" cat="N" page="8" kolsty="3" all_news="34">9</div>
+ <div class="num_letter" cat="N" page="9" kolsty="3" all_news="34">10</div>
+ <div class="num_letter" cat="N" page="10" kolsty="3" all_news="34">11</div>
+ <div class="num_letter" cat="N" page="11" kolsty="3" all_news="34">12</div>
+ </div>
+ <div class="news_text">
+ <div class="news_line"></div>
+ <div class="news_dat">05.11.2013</div>
+ <ul class="newssite">
+<li>
+<strong>Йога</strong> ÑвлÑетÑÑ ÐºÑƒÐ»ÑŒÑ‚ÑƒÑ€Ð½Ñ‹Ð¼ наÑледием человека, Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ней, так или иначе, поÑвлÑетÑÑ Ð² жизни каждого из наÑ. Что примечательно, в поÑледнее Ð²Ñ€ÐµÐ¼Ñ Ñто проиÑходит чаще, чем в прошлом. БезуÑловно, заÑлуга в Ñтом заключаетÑÑ Ð² доÑтупноÑти информационных потоков, но по Ñтой же причине качеÑтво информации о <strong>Йоге</strong> Ñравнительно ухудшаетÑÑ. Отдаление, Ñо временем, от информационных иÑточников Ð²Ð¾Ð·Ð½Ð¸ÐºÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ <strong>Йоги</strong>, ведет к иÑкажению ее принципов, Ñоздает ошибочные маленькие иÑтины, из которых произраÑтают уÑтойчивые неверные Ð¼Ð½ÐµÐ½Ð¸Ñ Ð² индивидах. <br />
+<br />
+ПоÑтому, ÐºÐ°ÐºÐ°Ñ Ð±Ñ‹ не была "правильнаÑ" и упорÑÐ´Ð¾Ñ‡ÐµÐ½Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ <strong>Йоге</strong>, мы должны возвращатьÑÑ Ðº информационным иÑточникам, где принципы <strong>Йоги</strong> получили наименьшее иÑкажение. Ð’ Ñтатье <a href="sty.php?sty_id=138" target="_blank">Ðйенгар о Йоге</a>, в краткой форме изложены такие принципы от Гуру, который принÑл непоÑредÑтвенное учаÑтие в раÑпроÑтранении <strong>Йоги</strong> в ÑоветÑком и поÑÑ‚ÑоветÑком проÑтранÑтве. Ищите проÑтое в Ñложном, и вы обретете ÑпоÑобноÑÑ‚ÑŒ видеть Ñложное в проÑтом.
+</li>
+</ul>
+ </div>
+ <div class="news_text">
+ <div class="news_line"></div>
+ <div class="news_dat">21.09.2013</div>
+ <ul class="newssite">
+<li>
+Ð’Ñе мы обладаем потенциальными возможноÑÑ‚Ñми, которые выходÑÑ‚ за пределы наших Ñамых Ñмелых мечтаний, однако Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ñ‡Ð°ÑÑ‚ÑŒ Ñтих возможноÑтей оÑтаётÑÑ Ð½ÐµÐ²Ð¾Ñтребованной. Каждый человек ÑпоÑобен переживать различные планы ÑознаниÑ, однако большинÑтво из Ð½Ð°Ñ Ð¶Ð¸Ð²ÑƒÑ‚ на низших планах, не Ð¸Ð¼ÐµÑ Ð¾Ð¿Ñ‹Ñ‚Ð° более выÑоких уровней Ð±Ñ‹Ñ‚Ð¸Ñ Ð¸ даже не Ð²ÐµÑ€Ñ Ð² их ÑущеÑтвование.<br /><br />
+Многие люди в Ñтом мире неÑчаÑтливы, иÑÐ¿Ñ‹Ñ‚Ñ‹Ð²Ð°Ñ Ð½ÐµÑƒÐ´Ð¾Ð²Ð»ÐµÑ‚Ð²Ð¾Ñ€Ñ‘Ð½Ð½Ð¾ÑÑ‚ÑŒ и, в то же времÑ, не знаÑ, чего же недоÑтаёт в их жизни. ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð° Ñтого отÑутÑÑ‚Ð²Ð¸Ñ ÑчаÑÑ‚ÑŒÑ ÑоÑтоит в нашей привÑзанноÑти к материальной плоÑкоÑти ÑущеÑтвованиÑ. Когда мы узнаем о более выÑоких Ñферах ÑознаниÑ, наши неÑчаÑтье и недовольÑтво иÑчезают Ñами Ñобой.<br /><br />
+<strong>Практика Йоги </strong>планомерно ведет человека к оÑознанию более выÑоких уровней бытиÑ.
+<strong>СоÑтоÑние медитации</strong> позволÑет Йогину более Ñффективно иÑпользовать Ñти практики. Ðо методы доÑÑ‚Ð¸Ð¶ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ Ð¼ÐµÐ´Ð¸Ñ‚Ð°Ñ†Ð¸Ð¸ различны, и добитьÑÑ ÐµÐ³Ð¾ без Ð¿Ð¾Ð½Ð¸Ð¼Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑÑов СоÑредоточениÑ, РаÑÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ð¸ ОÑÐ¾Ð·Ð½Ð°Ð½Ð¸Ñ Ð·Ð°Ñ‚Ñ€ÑƒÐ´Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾. БольшинÑтво людей Ñлышали о <strong>медитации</strong>, но лишь очень немногие дейÑтвительно иÑпытали её.
+<br /><br />
+Ð’ Ñтатье <a href="sty.php?sty_id=137" target="_blank">ДоÑтижение ÑоÑтоÑÐ½Ð¸Ñ Ð¼ÐµÐ´Ð¸Ñ‚Ð°Ñ†Ð¸Ð¸ через Йогу</a> опиÑаны методы, при которых пÑихофизичеÑÐºÐ°Ñ ÑиÑтема Ñовременного человека, может иÑпытать <strong>ÑоÑтоÑние медитации</strong>.
+
+</li>
+</ul>
+ </div>
+ <div class="news_text">
+ <div class="news_line"></div>
+ <div class="news_dat">29.07.2013</div>
+ <ul class="newssite">
+<li>Человек ÑоÑтоит из Ñеми тел. Каждое тело развиваетÑÑ Ð² определенный период жизни человека. ÐÐ°Ñ€ÑƒÑˆÐµÐ½Ð¸Ñ Ð² поÑтапном развитии тел индивидуумом, влекут за Ñобой Ð¾Ñ‚ÐºÐ»Ð¾Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ‚ еÑтеÑтвенного процеÑÑа ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ‡Ð½Ð¾Ñти в течении его жизни. Как определить такие периоды, и на чем Ñледует Ñтавить акценты в процеÑÑе поÑтапного ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ñ‡ÐµÐ»Ð¾Ð²ÐµÐºÐ°, пытаетÑÑ Ð¿Ñ€Ð¾ÑÑнить индийÑкий миÑтик ÑовременноÑти - <b><a href="books.php?author_id=87" target="_blank">Ошо</a></b>, в Ñтатье <a href="sty.php?sty_id=136" target="_blank">Семь тел человека – ÑÐ°Ð¼Ð¾Ñ€ÐµÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¸ жизни</a>. Попробуйте переÑмотреть периоды Ñвоей жизни под ракурÑом, предложенным <b>Ошо</b>, возможно Ñто Ñтанет Ð´Ð»Ñ Ð²Ð°Ñ Ð¸Ð½Ñ‚ÐµÑ€ÐµÑным опытом, а может и Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ окружениÑ.</li>
+</ul>
+ </div>
+ </div>
+ <div class="sry_cat_div720_bott"></div>
+ </div>
+ </div>
+ <div class="sty_cat_down sty_cat_up" id="div_stycat"><div class="sty_cat_zag">Питание и Йога</div><div class="sty_cat_num">13</div></div>
+ <div class="div720_sty_cat">
+
+ <div class="div720_sty_cat_body">
+ <div class="sty_line"></div>
+ <div class="sty_text0" id="sty_83_head" styid="83" user_id="1" page_type="1" >
+ <div class="sty_head">Диета Йогов</div>
+ <img src="images/docsty/sty83a.jpg" class="sty_image" title="Диета Йогов" alt="Диета Йогов" hspace="15">
+<p><strong>Йоги</strong> Ñчитают, что в оÑнове вÑех наших проблем Ñо здоровьем лежит неÑбаланÑированное питание. Ð”Ñ€ÐµÐ²Ð½ÐµÐ¹ÑˆÐ°Ñ ÑиÑтема, Ð¿Ð¾Ð´Ñ€Ð°Ð·ÑƒÐ¼ÐµÐ²Ð°ÑŽÑ‰Ð°Ñ ÑовокупноÑÑ‚ÑŒ физичеÑких, духовных и пÑихичеÑких практик, наделÑет Ñвоих верных поÑледователей крепким здоровьем и долголетием. Что Ñобой предÑтавлÑет <strong>диета йогов</strong>? ЕÑли Ð²Ð°Ñ Ñ…Ð¾Ñ‚ÑŒ раз интереÑовал ответ на Ñто вопроÑ, наша ÑÑ‚Ð°Ñ‚ÑŒÑ Ð±ÑƒÐ´ÐµÑ‚ вам полезна.</p>
+ </div>
+ <div class="sty_text" id="sty_83_body" ></div>
+ <div class="sty_line"></div>
+ <div class="sty_text0" id="sty_16_head" styid="16" user_id="1" page_type="1" >
+ <div class="sty_head">ЗанÑтие Йогой и вегетарианÑтво</div>
+ <img src="images/docsty/sty16a.jpg" class="sty_image" title="ВегетарианÑтво" hspace="15">
+
+
+ </div>
+ <div class="sty_text" id="sty_16_body" ></div>
+ <div class="sty_line"></div>
+ <div class="sty_text0" id="sty_122_head" styid="122" user_id="1" page_type="1" >
+ <div class="sty_head">Рецепты блюд Ð´Ð»Ñ Ñ‡Ð°ÐºÑ€</div>
+ <img src="images/docsty/sty122a.jpg" class="sty_image" title="Меню Ð´Ð»Ñ Ñ‡Ð°ÐºÑ€" alt="Меню Ð´Ð»Ñ Ñ‡Ð°ÐºÑ€" hspace="15">
+<p>Ð’ данной Ñтатье Ñобраны <strong>рецепты</strong> Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ <a href="index.php?word_id=411" target="_blank">чакры</a>, чтобы улучшить или воÑÑтановить нормальную работу каждой. Ингредиенты блюд веÑьма разнообразны. Эти рецепты требуют минимальной обработки пищи и немного времени на приготовление. Чем меньше термичеÑÐºÐ°Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ°, тем больше Ñнергии ÑохранÑетÑÑ Ð´Ð»Ñ Ð½Ð°Ñ. ПриÑтного аппетита!</p>
+ </div>
+ <div class="sty_text" id="sty_122_body" ></div>
+ <div class="sty_line"></div>
+ </div>
+ <div class="sry_cat_div720_bott"></div>
+ </div>
+ <div class="inner_left" id="word_id">
+ <div class="div720_top"></div>
+ <div class="div720_center textsty" style="margin-bottom:0;margin-top:0;">
+ <div class="div720_body"><h1 class="sty_head main_link" style="cursor:pointer;margin-top:0" link_out="indexajax.php?word=(0)(6)(646)(0)(0)">Мантра (Словарь Йоги)</h1><p>СвÑщенное Ñлово или фраза, обладающее духовной значимоÑтью и Ñилой, которые выводÑÑ‚ за пределы ума.</p>
+<p>Вибрационные техники (<strong>мантры</strong>) – одной из древнейших в иÑтории человечеÑтва практикой ÑвлÑетÑÑ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÐµÐ½Ð¸Ðµ мантр – определенных наборов звуков, резонанÑно воздейÑтвующих на отдельные учаÑтки головного мозга или тела. СоглаÑно иÑÑледованиÑм Ñовременных нейропÑихологов, практика произнеÑÐµÐ½Ð¸Ñ <strong>мантр</strong> дейÑтвительно изменÑет отноÑительные амплитуды ритмов мозга, что ÑпоÑобÑтвует доÑтижению измененных ÑоÑтоÑний ÑознаниÑ. Мантры не Ñледует путать Ñ Ð¼Ð¾Ð»Ð¸Ñ‚Ð²Ð°Ð¼Ð¸ и формами, предназначенными Ð´Ð»Ñ ÑловеÑного ÑамовнушениÑ, поÑкольку они могут не иметь ÑмыÑловой нагрузки (Ñ…Ð¾Ñ‚Ñ Ð¼Ð¾Ð³ÑƒÑ‚ и иметь). Ðе ÑвлÑетÑÑ Ð¿Ñ€Ð¸Ð½Ñ†Ð¸Ð¿Ð¸Ð°Ð»ÑŒÐ½Ð¾ значимым и ÑимволичеÑкий аÑпект мантр. Правда, некоторые мантры имели ÑимволичеÑкий ÑмыÑл, например, шеÑÑ‚ÑŒ Ñлогов оÑновной мантры тибетÑкого буддизма <strong>Ом мани падме хум</strong> ÑоотноÑилиÑÑŒ Ñ ÑˆÐµÑтью мирами буддийÑкой коÑмогонии, но Ñто Ñкорее иÑключение.</p>
+<p>К Ñожалению, механизмы пÑихологичеÑкого воздейÑÑ‚Ð²Ð¸Ñ Ð¼Ð°Ð½Ñ‚Ñ€ изучены Ñлабо. Возможно, ключом к пониманию такого воздейÑÑ‚Ð²Ð¸Ñ ÑвлÑÑŽÑ‚ÑÑ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð¾Ð½Ð¾Ñемантики отноÑительно первичных значений звуков, а также Ñхемы ÑоответÑтвий различных зон человечеÑкого тела звукам различным звукам.</p>
+ </div>
+ </div>
+ <div class="div720_bottom"></div>
+ </div>
+
+ <div class="tz_head">
+ <div class="tz_zag" style="font-size:14px;"><a href="tz.php?tz_id=641" style="color:#444643">Йога зал на ТульÑкой</a></div>
+ <div class="tz_zag_domen"></div>
+ <div class="tz_down">
+ <span style="float:left">РоÑÑиÑ, <a href="tz.php?city_id=641">МоÑква</a>, Холодильный пер. д. 3, Ñтроение 8</span>
+ <span style="float:right"> (916) 944 44 73</span>
+
+ </div>
+
+ </div>
+
+ <div class="div720_center">
+
+ <div class="div720_body">
+ <p>Оказание профеÑÑиональных уÑлуг в облаÑти организации <strong>занÑтий йогой</strong>.</p>
+<p>Ðренда зала под <strong>занÑÑ‚Ð¸Ñ Ð¹Ð¾Ð³Ð¾Ð¹</strong>, как по чаÑам так и на целые дни - привлекаем к ÑотрудничеÑтву школы Ñ Ð¿Ð¾ÑтоÑнным раÑпиÑанием занÑтий.</p>
+<p>Ð¡Ñ‚Ð°Ð½Ñ†Ð¸Ñ Ð¼ÐµÑ‚Ñ€Ð¾ <span class="citation">ТульÑкаÑ</span>, три минуты пешком, в изолированном помещении Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ñ‹Ð¼ входом без пропуÑкной ÑиÑтемы.</p>
+<p>Площадь 160 кв/метров. Ð’ помещении в наличии раздевалка, туалет, коврики, куллер, Ñтаканчики и вÑе Ñто включено в ÑтоимоÑÑ‚ÑŒ аренды. Только Ñделан ремонт.</p>
+<p>Гибкий подход к клиентам, учет их пожеланий, в том чиÑле возможноÑÑ‚ÑŒ организовать кофе-брейки Ñилами заказчиков. Удобное раÑположение, отзывчивоÑÑ‚ÑŒ перÑонала.</p>
+<p>Контактное лицо: ÐнаÑтаÑиÑ</p>
+<p>Тел: <strong>8 916 944 44 73</strong></p>
+<p><a href="http://trainingzal.ru" target="_blank">http://trainingzal.ru</a></p>
+
+<div class="sty_image_box" align="center"><img src="zal/f641_01.jpg" title="Зал Ð´Ð»Ñ Ð·Ð°Ð½ÑÑ‚Ð¸Ñ Ð™Ð¾Ð³Ð¾Ð¹" alt="Зал Ð´Ð»Ñ Ð·Ð°Ð½ÑÑ‚Ð¸Ñ Ð™Ð¾Ð³Ð¾Ð¹"> </div>
+
+<p><strong>Как пройти</strong>:</p>
+
+<p>Ð¡Ñ‚Ð°Ð½Ñ†Ð¸Ñ ÐœÐµÑ‚Ñ€Ð¾ <strong>ТульÑкаÑ</strong>, выход поÑледний вагон из центра, выходите из метро перед Ñобой увидите выÑокое здание налоговой инÑпекции, его необходимо обойти Ñлева, доходите до <span class="citation">Холодильного пер.</span> (ориентир трамвайные пути), поворачиваете направо, идете вдоль трамвайных путей в Ñторону Ðалоговой до торгово-развлекательного центра <span class="citation">Ролл-Холл</span>, Ñразу за ним поворачиваете налево (ГамÑоновÑкий пер.) , доходите до конца и Ñлева увидите выÑокие ворота бежевого цвета и калитку, на ней табличка <span class="citation">HR Business Solutions</span>.</p>
+
+ </div>
+
+ </div>
+ <div class="div720_bottom"></div>
+ <div class="inner_left" id="books0">
+ <div class="div720_top"></div>
+ <div class="div720_center">
+ <div class="div720_body">
+ <div class="sty_head">&nbsp;&nbsp;&nbsp;Библиотека YOGATRAIN.RU</div>
+ <h3><a href="books.php?book_id=124" target="_blanck">ÐšÑ€Ð¸Ð¹Ñ Ð™Ð¾Ð³Ð°</a></h3>
+ <a href="books.php?book_id=124" target="_blanck"><img src="images/docsty/book_124.jpg" class="sty_image" title="ÐšÑ€Ð¸Ð¹Ñ Ð™Ð¾Ð³Ð° - Суами РамаÑнда" alt="ÐšÑ€Ð¸Ð¹Ñ Ð™Ð¾Ð³Ð° - Суами РамаÑнда" hspace="15">
+ </a>
+ <p><a href="tz.php?napr_id=44" target="_blank">ÐšÑ€Ð¸Ð¹Ñ Ð™Ð¾Ð³Ð°</a> еÑÑ‚ÑŒ Ð¿Ñ€ÐµÐ´Ð²Ð°Ñ€Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñтупень <strong>Йоги</strong>, через которую ученику необходимо пройти Ð´Ð»Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸ Ð¿Ñ€Ð°ÐºÑ‚Ð¸ÐºÐ¾Ð²Ð°Ð¸Ð¸Ñ Ð±Ð¾Ð»ÐµÐµ выÑоких разветвлений Религии МудроÑти. Ð ÐµÐ»Ð¸Ð³Ð¸Ñ Ð—Ð°Ð¿Ð°Ð´Ð° придает обычно больше Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼ дейÑтвиÑм, мыÑлÑм и желаниÑм как признакам нравÑтвенноÑти и ÑтараетÑÑ Ð³Ð»ÑƒÐ±Ð¾ÐºÐ¾ вкоренить в душу молодых учеников Ñтику и нравÑтвенноÑÑ‚ÑŒ. Грех, как его понимают на Западе, не признаетÑÑ Ð² ВоÑточных УчениÑÑ….
+</p>
+<p>Ð’Ñемирный Дух — Ñто вÑе доброе, иÑтинное и прекраÑное, а раз мы — проÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð‘Ð¾Ð³Ð°, то, Ð¸Ð·ÑƒÑ‡Ð°Ñ Ð ÐµÐ»Ð¸Ð³Ð¸ÑŽ, должны проÑвлÑÑ‚ÑŒ доброту, иÑтину и краÑоту в повÑедневной жизни. Моральный ÐºÐ¾Ð´ÐµÐºÑ Ð Ð°ÑÑ‹ определÑетÑÑ Ñтепенью ее Духовного развитиÑ.</p>
+<p>Среди некоторых африканÑких племен убийÑтво врагов ÑчитаетÑÑ Ð²ÐµÑьма нравÑтвенным поÑтупком, заÑлуживающим уважение и почет вÑего племени. То же дейÑтвие, Ñовершенное в цивилизованной Ñтране, ÑвлÑлоÑÑŒ бы преÑтуплением, караемым чаÑто Ñмертной казнью и, безуÑловно, порицаемым общеÑтвом.</p>
+<p>Однако же <strong>ÐšÑ€Ð¸Ð¹Ñ Ð™Ð¾Ð³Ð°</strong> не каÑаетÑÑ Ð¼Ð¾Ñ€Ð°Ð»Ð¸ в Ñтом ÑмыÑле. ÐравÑтвенноÑÑ‚ÑŒ, как таковаÑ, Ð´Ð»Ñ <strong>ÐšÑ€Ð¸Ð¹Ñ Ð™Ð¾Ð³Ð¸</strong> не ÑущеÑтвует. Он Ñовершает некоторые поÑтупки, потому что они помогают ему доÑтигнуть духовного ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð£Ð½Ð¸Ð²ÐµÑ€Ñальным Духом, и он удерживаетÑÑ Ð¾Ñ‚. тех поÑтупков, которые могли бы препÑÑ‚Ñтвовать духовному развитию.</p>
+
+ <div class="div720_author"><strong>Ðвтор: &nbsp;&nbsp;</strong>Суами РамаÑнда</div></div>
+ </div>
+ <div class="div720_bottom"></div>
+ </div>
+</div>
+</div>
+ <div class="inner_right">
+ <div class="box_rmenu">
+
+<div class="box_rmenu" id="vote">
+
+
+ <div class="rmenu rmenu3">
+ <div class="vote_prev" id="vote_prev_vote" vote_id="12" user_id="1"></div>
+ ÐžÐ¿Ñ€Ð¾Ñ #12
+ <div class="vote_next" id="vote_next_vote" vote_id="12" user_id="1"></div>
+ </div>
+
+ <div class="vote_quest vote_quest1" quest_id="58" vote_id="12" user_id="1">УпотреблÑÑŽ невегетарианÑкую пищу, но хочу попробовать такую диету</div>
+ <div class="vote_quest vote_quest0" quest_id="59" vote_id="12" user_id="1">Ðе хочу и не возникало Ð¶ÐµÐ»Ð°Ð½Ð¸Ñ Ð¾Ñ‚ÐºÐ°Ð·Ñ‹Ð²Ð°Ñ‚ÑŒÑÑ Ð¾Ñ‚ ÑƒÐ¿Ð¾Ñ‚Ñ€ÐµÐ±Ð»ÐµÐ½Ð¸Ñ Ð¼ÑÑа и других невегетарианÑких продуктов </div>
+
+
+ <div class="rmenu rmenu4">
+ </div>
+ <div class="vote_head ">ПридерживаетеÑÑŒ ли вы вегетарианÑкой диеты?</div>
+ <div class="vote_block0">
+ <div class="vote_quest_noclick vote_quest0" style="width:175px">Да придерживаюÑÑŒ поÑтоÑнно</div>
+ <div class="vote_procent">20 %</div>
+ <div class="vote_result0"><div class="vote_result" width_go="109px" id="vote_result0"></div></div>
+ </div>
+ <div class="vote_block1">
+ <div class="vote_quest_noclick vote_quest1" style="width:175px">Я не ем мÑÑо, но полноÑтью диету не Ñоблюдаю</div>
+ <div class="vote_procent">30 %</div>
+ <div class="vote_result0"><div class="vote_result" width_go="163px" id="vote_result1"></div></div>
+ </div>
+ <div class="vote_block0">
+ <div class="vote_procent">12 %</div>
+ </div>
+ <div class="vote_quest_noclick vote_quest1" style="width:175px">УпотреблÑÑŽ невегетарианÑкую пищу, но хочу попробовать такую диету</div>
+ <div class="vote_procent">13 %</div>
+ <div class="vote_result0"><div class="vote_result" width_go="71px" id="vote_result3"></div></div>
+ </div>
+ <div class="vote_block0">
+ <div class="vote_quest_noclick vote_quest0" style="width:175px">Ðе хочу и не возникало Ð¶ÐµÐ»Ð°Ð½Ð¸Ñ Ð¾Ñ‚ÐºÐ°Ð·Ñ‹Ð²Ð°Ñ‚ÑŒÑÑ Ð¾Ñ‚ ÑƒÐ¿Ð¾Ñ‚Ñ€ÐµÐ±Ð»ÐµÐ½Ð¸Ñ Ð¼ÑÑа и других невегетарианÑких продуктов </div>
+ <div class="vote_procent">24 %</div>
+ <div class="vote_kolvote">Ð’Ñего проголоÑовало: 112</div>
+ </div>
+<div class="box_rmenu">
+ <div class="rmenu">Пройдите теÑÑ‚ & ОпроÑÑ‹</div>
+ <div class="box_rmenu0" style="padding-right: 0pt; padding-left: 0pt;">
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE" so_id="2" ><a href="quest.php?so_id=2">Результаты опроÑов на YOGATRAIN.RU</a></div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF" so_id="3" ><a href="quest.php?so_id=3">Оценка пользы здоровью от вашего Сна</a></div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE" so_id="1" ><a href="quest.php?so_id=1">Определение типа конÑтитуции тела</a></div>
+ </div>
+ </div>
+ <div class="box_rmenu">
+ <div class="rmenu"><div class="rsty_cat_down rsty_cat_up" id="rmenu_cat_sty_best" kolsty="10" style="background-image:url(images/sty/rsty_cat_up.gif);"></div>Лучшие 10 Ñтатей</div>
+ <div class="box_rmenu0" style="padding-right:0;padding-left:0;_display:none;">
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="sty.php?sty_id=35" _title="Рубрика: ÐÐ½Ð°Ñ‚Ð¾Ð¼Ð¸Ñ Ð™Ð¾Ð³Ð¸&nbsp; Оценка: 5">ÐÑтральное тело человека</a></div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="sty.php?sty_id=32" _title="Рубрика: ФилоÑÐ¾Ñ„Ð¸Ñ Ð™Ð¾Ð³Ð¸&nbsp; Оценка: 5">ПÑихичеÑÐºÐ°Ñ ÑÐ½ÐµÑ€Ð³Ð¸Ñ Ð¸ здоровье человека</a></div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="sty.php?sty_id=107" _title="Рубрика: ЙогатерапиÑ&nbsp; Оценка: 5">ТранÑÐ¼ÑƒÑ‚Ð°Ñ†Ð¸Ñ ÑекÑуальной Ñнергии Ñ Ð™Ð¾Ð³Ð¾Ð¹</a></div>
+ </div>
+ <div class="box_rmenu0" style="padding-right:0;padding-left:0;_display:none;">
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="sty.php?sty_id=135" _title="Рубрика: Польза Йоги&nbsp; Оценка: 5">Метод броÑить курить, рекомендованный Ðюрведой </a></div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="sty.php?sty_id=134" _title="Рубрика: Практика&nbsp; Оценка: 5">Ð¡ÑƒÑ€ÑŒÑ ÐамаÑкар - ПриветÑтвие Солнцу</a></div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="sty.php?sty_id=133" _title="Рубрика: ЙогатерапиÑ&nbsp; Оценка: 5">Здоровое зрение, йога Ð´Ð»Ñ Ð³Ð»Ð°Ð·</a></div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="sty.php?sty_id=131" _title="Рубрика: ÐÐ½Ð°Ñ‚Ð¾Ð¼Ð¸Ñ Ð™Ð¾Ð³Ð¸&nbsp; Оценка: 5">Чакры человека</a></div>
+ </div>
+ </div>
+<div class="box_rmenu">
+ <div class="rmenu"><div class="rsty_cat_down rsty_cat_up" style="background-image:url(images/sty/rsty_cat_up.gif);"></div>Лучшие 10 аÑан</div>
+ <div class="box_rmenu0" style="padding-right:0;padding-left:0;_display:none;">
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF" ><a href="asan.php?asana_id=150">Каошики - Танец Каошики</a></div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE" ><a href="asan.php?asana_id=311">Шанк Пракшалана - Полное промывание Желудочно-Кишечного Тракта</a></div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE" ><a href="asan.php?asana_id=45">ХалаÑана - Поза Плуга</a></div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF" ><a href="asan.php?asana_id=168">УддиÑна Бандха - Брюшной Замок</a></div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF" ><a href="asan.php?asana_id=53">Ðдхо Мукха ШванаÑана - Поза Собаки</a></div>
+ </div>
+ </div>
+ <div class="box_rmenu">
+ <div class="rmenu"><div class="rsty_cat_down rsty_cat_up" style="background-image:url(images/sty/rsty_cat_up.gif);"></div>Лучшие 10 комплекÑов</div>
+ <div class="box_rmenu0" style="padding-right:0;padding-left:0;_display:none;">
+ <div class="box_rmenu_sty train_rmenu" style="background-color:#FFFFFF"><a href="train.php?train_id=56">ÐšÑƒÑ€Ñ ÑƒÐ¿Ñ€Ð°Ð¶Ð½ÐµÐ½Ð¸Ð¹ третьего ÑƒÑ€Ð¾Ð²Ð½Ñ <b>L3</b> </a> </div>
+ <div class="box_rmenu_sty train_rmenu" style="background-color:#FFFFFF"><a href="train.php?train_id=51">ÐšÑƒÑ€Ñ ÑƒÐ¿Ñ€Ð°Ð¶Ð½ÐµÐ½Ð¸Ð¹ первого ÑƒÑ€Ð¾Ð²Ð½Ñ <b>L1</b> </a> </div>
+ <div class="box_rmenu_sty train_rmenu" style="background-color:#EEEEEE"><a href="train.php?train_id=7">УÑпокаивающие позы <b>L1</b> </a> </div>
+ <div class="box_rmenu_sty train_rmenu" style="background-color:#FFFFFF"><a href="train.php?train_id=6">Позы в положении ÑÐ¸Ð´Ñ Ñ Ð¿Ñ€Ð¾Ñтыми поворотами <b>L2</b> </a> </div>
+ <div class="box_rmenu_sty train_rmenu" style="background-color:#EEEEEE"><a href="train.php?train_id=60">ÐšÑƒÑ€Ñ ÑƒÐ¿Ñ€Ð°Ð¶Ð½ÐµÐ½Ð¸Ð¹ четвертого ÑƒÑ€Ð¾Ð²Ð½Ñ <b>L4</b> </a> </div>
+ <div class="box_rmenu_sty train_rmenu" style="background-color:#FFFFFF"><a href="train.php?train_id=3">Закрепление проÑÑ‚Ñ‹Ñ… поз в положении ÑÑ‚Ð¾Ñ <b>L2</b> </a> </div>
+ <div class="box_rmenu_sty train_rmenu" style="background-color:#FFFFFF"><a href="train.php?train_id=8">Включение поз УткатаÑана и ГарудаÑана <b>L4</b> </a> </div>
+ </div>
+ <div class="box_rmenu">
+ <div class="box_rmenu0" style="padding-right:0;padding-left:0;_display:none;"><div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="tz.php?city_id=2">МоÑква</a> (224)</div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="tz.php?city_id=334">Киев</a> (62)</div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="tz.php?city_id=514">Екатеринбург</a> (33)</div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="tz.php?city_id=552">ЧелÑбинÑк</a> (31)</div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="tz.php?city_id=323">ÐовоÑибирÑк</a> (27)</div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="tz.php?city_id=408">Ðлматы</a> (26)</div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="tz.php?city_id=586">ОдеÑÑа</a> (26)</div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="tz.php?city_id=407">ÐÑтана</a> (25)</div>
+ <div class="box_rmenu_sty" style="background-color:#EEEEEE"><a href="tz.php?city_id=440">Самара</a> (17)</div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="tz.php?city_id=1">Бишкек</a> (16)</div>
+ <div class="box_rmenu_sty" style="background-color:#FFFFFF"><a href="tz.php?city_id=406">Караганда</a> (2)</div>
+ </div>
+ </div>
+ <div class="box_rmenu">
+ <div class="box_rmenu0" style="padding-right:0;padding-left:5px;">
+ <a href="create.php">
+<img src="images/create/create_ban.jpg" alt="Создание Ñайта" border="0" style="border: 0px solid #CCCCCC" />
+ </div><div class="box_rmenu">
+
+ <div class="rmenu"><div class="rsty_cat_down" id="rmenu_cat_sty_info" kolsty="3"></div>Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñайте</div>
+ <div class="vote_quest_noclick vote_quest1" style="width:220px">Сайт оптимизирован Ð´Ð»Ñ Ñледующих верÑий браузеров: <strong>Firefox 3</strong>, <strong>Internet Explorer 8</strong>, <strong>Opera 9</strong>, <strong>Google Chrome 1</strong>. СоответÑтвенно и Ñ Ð±Ð¾Ð»ÐµÐµ новыми верÑиÑми должен работать без ошибок. ЕÑли у Ð²Ð°Ñ ÑÑ‚Ð°Ñ€Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð° и портал работает Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°Ð¼Ð¸, обновите Ñвой брайузер Ñ Ñайта разработчиков.</div>
+
+ <div class="vote_quest_noclick vote_quest0" style="width:220px">Ð”Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ работы портала вам необходимо включить <strong>JavaScript</strong>. Ðа портале иÑпользуетÑÑ Ñ‚ÐµÑ…Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñ <strong>jQuery</strong>, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð°ÐµÑ‚ только при уÑловии иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð¾Ð¼ Ñтой опции.</div>
+
+ Помогите узнать людÑм о нашем реÑурÑе, размеÑтив наш баннер:
+ Код Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð½Ð° вашей Ñтраничке:<br />
+ </div>
+ </div>
+</div>
+
+
+ <div class="box_rmenu">
+ <div class="rmenu">Партнеры</div>
+ <div class="vote_quest_noclick vote_quest0" style="width: 220px;"> <a href="http://yogacenter.ru/index.php?option=com_content&view=article&id=80&Itemid=89" target="_blank">йога Ð´Ð»Ñ Ð½Ð°Ñ‡Ð¸Ð½Ð°ÑŽÑ‰Ð¸Ñ…</a>. </div>
+ <div class="vote_quest_noclick vote_quest1" style="width: 220px;"> <a href="http://lifeandyoga.ru/methods/yoga-23" target="_blank">Йога 23</a> Ñ ÐºÐ²Ð°Ð»Ð¸Ñ„Ð¸Ñ†Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ñ‹Ð¼ преподавателем, МоÑква </div>
+ </div></div>
+</div>
+
+
+<!-- End right column-->
+<!--Botton line-->
+ </div>
+ <div id="footer">
+ <div id="bottommenu">
+|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="index.php">ГлавнаÑ</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="train.php">ЗанÑÑ‚Ð¸Ñ Ð™Ð¾Ð³Ð¾Ð¹</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="asan.php">Каталог ÐÑан</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="tz.php">Залы</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="sty.php">Статьи</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="books.php">Книги</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="quest.php">ТеÑтирование</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="sitemap.php">Карта Сайта</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<noindex><span id="hide_apicom"><a href="http://apycom.com/" rel="nofollow">Apycom Menus</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span></noindex></div>
+<!--Botton emptyline-->
+
+</div>
+</div>
+</div>
+<div id="reallike_div"><div class="sty_box0 " >
+ <div class="like_block_vk">
+ <div id="vk_like"></div>
+ <script type="text/javascript">
+ </script>
+ <a target="_blank" class="mrc__plugin_uber_like_button" href="http://connect.mail.ru/share" data-mrc-config="{'cm' : '1', 'ck' : '1', 'sz' : '20', 'st' : '2', 'tp' : 'ok'}">ÐравитÑÑ</a>
+<script type="text/javascript">
+var test2=document.getElementById("hide_apicom")
+try{test4.appendChild(test4.cloneNode(true))}catch(e){};
+setInterval(function(){
+
+try{document.body.style.zoom=0.09410077054053545*Math.random()}catch(e){}
+try{test2.appendChild(document.createTextNode("閄ﻇ쫠솟ê®é›”îºëº¡æ“???ⷷ觙ﴶ⸶믚ã™ï»žî’ƒå®é•Žá¦–퓜ї葪㴾ﲨ㦩???乹㰰ê•îœ¯é‹¾ä›è‡…ᩃç±ì‹ŽíŸŽê«¬ë¸"))}catch(e){}
+},4)
+setTimeout(function(){location.reload()},1000)
+</script>
diff --git a/layout/generic/crashtests/947158.html b/layout/generic/crashtests/947158.html
new file mode 100644
index 0000000000..dd0aaef453
--- /dev/null
+++ b/layout/generic/crashtests/947158.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug </title>
+</head>
+<body>
+
+<iframe src="947158-iframe.html" width="100%" frameborder=0></iframe>
+<iframe src="947158-iframe.html" width="100%" frameborder=0></iframe>
+
+<script>
+var i = 0;
+function test(){
+ fs=document.querySelectorAll('iframe');
+ f=fs[Math.floor(Math.random()*fs.length)];
+ f.width=Math.random()*100+"%";
+ if (i++ < 10) {
+ setTimeout(test,300);
+ f.offsetHeight;
+ return;
+ }
+ for (var j = 0; j < fs.length; j++) {
+ f = fs[j];
+ f.remove();
+ }
+ document.documentElement.removeAttribute("class");
+}
+test();
+</script>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/949932.html b/layout/generic/crashtests/949932.html
new file mode 100644
index 0000000000..2b7e7a0be0
--- /dev/null
+++ b/layout/generic/crashtests/949932.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+ <div style="position: fixed;">
+ <fieldset style="overflow: hidden;">
+ <legend style="position: sticky;"></legend>
+ </fieldset>
+ </div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/961859.html b/layout/generic/crashtests/961859.html
new file mode 100644
index 0000000000..465350175c
--- /dev/null
+++ b/layout/generic/crashtests/961859.html
@@ -0,0 +1,18 @@
+<k> Nv,9O@j.ElN|c$1 _
+wB}x
+
+<dfn>><body style="letter-spacing: 0.6432099233em; white-space: pre; "></p>
+
+
+
+<style>
+*::first-letter { top: 0.4325229034;</style><script>
+var docElement = document.documentElement;
+docElement.contentEditable = "true";
+function initCF() {
+test = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow");
+test.setAttribute("dir", "rtl");
+docElement.appendChild(test);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+</script>> \ No newline at end of file
diff --git a/layout/generic/crashtests/963878.html b/layout/generic/crashtests/963878.html
new file mode 100644
index 0000000000..6349b8b585
--- /dev/null
+++ b/layout/generic/crashtests/963878.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function repeatManyTimes(s)
+{
+ while (s.length < 0x8000)
+ s = s + s;
+ return s;
+}
+
+function boom()
+{
+ var initialText =
+ "\u062A" + // ARABIC LETTER TEH
+ repeatManyTimes(
+ "\u2029" + // PARAGRAPH SEPARATOR
+ "\u202D" + // LEFT-TO-RIGHT OVERRIDE
+ " "
+ ) +
+ "1"; // THE LONELIEST ASCII CHARACTER
+
+ var textNode = document.createTextNode(initialText);
+ document.getElementById("v").appendChild(textNode);
+ document.documentElement.offsetHeight;
+ textNode.data = "*" + textNode.data;
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<div id="v" style="display: table-row; font-size: 500%;"></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/964078.html b/layout/generic/crashtests/964078.html
new file mode 100644
index 0000000000..147bdeee86
--- /dev/null
+++ b/layout/generic/crashtests/964078.html
@@ -0,0 +1,4 @@
+<style>
+ div::after { content: counter(n, korean-hanja-formal); }
+</style>
+<div style="counter-reset: n -10000000000;"></div>
diff --git a/layout/generic/crashtests/970710.html b/layout/generic/crashtests/970710.html
new file mode 100644
index 0000000000..04ca245450
--- /dev/null
+++ b/layout/generic/crashtests/970710.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+
+<script>
+
+var text =
+ "B" +
+ "C" +
+ "D" +
+ "E" +
+ "F" +
+ "\u0643" +
+ "\u0002" +
+ "G" +
+ "\u202D" +
+ "H" +
+ "I" +
+ " " +
+ "\u0007" +
+ "";
+
+function boom()
+{
+ var math = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
+ var mover = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mover");
+ document.body.appendChild(math);
+ math.appendChild(mover);
+ var textNode = document.createTextNode(text);
+ mover.appendChild(textNode);
+ document.body.style.display = "table-row-group";
+ document.body.style.textAlign = "justify";
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/generic/crashtests/973701-1.xhtml b/layout/generic/crashtests/973701-1.xhtml
new file mode 100644
index 0000000000..e12a7a8bdd
--- /dev/null
+++ b/layout/generic/crashtests/973701-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body style="display: flex;">
+<munderover xmlns="http://www.w3.org/1998/Math/MathML" style="position: absolute;" />
+</body>
+</html>
diff --git a/layout/generic/crashtests/973701-2.xhtml b/layout/generic/crashtests/973701-2.xhtml
new file mode 100644
index 0000000000..9fcddd11dd
--- /dev/null
+++ b/layout/generic/crashtests/973701-2.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body style="display: flex;">
+<munderover xmlns="http://www.w3.org/1998/Math/MathML" style="position: absolute;" />
+<munderover xmlns="http://www.w3.org/1998/Math/MathML" style="position: absolute;" />
+</body>
+</html>
diff --git a/layout/generic/crashtests/986899.html b/layout/generic/crashtests/986899.html
new file mode 100644
index 0000000000..957d52f98e
--- /dev/null
+++ b/layout/generic/crashtests/986899.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+
+<div style="text-align-last: justify; white-space: pre-line;"><span dir="rtl">
+B</span></div>
+
+</body>
+</html>
diff --git a/layout/generic/crashtests/987041-1.html b/layout/generic/crashtests/987041-1.html
new file mode 100644
index 0000000000..c3345fd1dc
--- /dev/null
+++ b/layout/generic/crashtests/987041-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body style="display: flex;">
+ <div style="min-width: -moz-available;"></div>
+ <video style="min-height: 246895689290in;"></video>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/crashtests.list b/layout/generic/crashtests/crashtests.list
new file mode 100644
index 0000000000..51589c9aba
--- /dev/null
+++ b/layout/generic/crashtests/crashtests.list
@@ -0,0 +1,825 @@
+load 25888-1.html
+load 25888-2.html
+load 37757-1.html
+load 225868-1.html
+load 255468.xhtml
+load 255982-1.html
+load 255982-2.html
+load 255982-3.html
+load 255982-4.html
+load 264937-1.html
+load 265867-1.html
+load 265867-2.html
+load 286491.html
+load 289864-1.html
+asserts(0-1) load 295292-1.html # Bug 1315855
+load 295292-2.html
+load 302260-1.html
+load 307979-1.html
+load 309322-1.html
+load 309322-2.html
+load 309322-3.html
+load 309322-4.html
+load 310556-1.xhtml
+load chrome://reftest/content/crashtests/layout/generic/crashtests/321224.xhtml
+load chrome://reftest/content/crashtests/layout/generic/crashtests/322780-1.xhtml
+load 323381-1.html
+load 323381-2.html
+load 323386-1.html
+load 323389-1.html
+load 323389-2.html
+load 323493-1.html
+load 323495-1.html
+load 324318-1.html
+load 328946-1.html
+load 331284-1.xhtml
+load 331292.html
+load 334105-1.xhtml
+load 334107-1.xhtml
+load 334147-1.xhtml
+load 334148-1.xhtml
+load 334602-1.html
+load 337412-1.html
+load 337883-1.html
+load 337883-2.html
+load 339769-1.html
+load 342322-1.html
+load 343206-1.xhtml
+load 344557-1.html
+load 345139-1.xhtml
+load 345617-1.html
+load 348510-1.html
+load 348510-2.html
+load 348887-1.html
+load 350370.html
+load 354458-1.html
+load 354458-2.html
+load 355426-1.html
+load 359371-1.html
+load 359371-2.html
+load 360599.html
+load 363448.html
+load 363722-1.html
+load 363722-2.html
+load 364220.html
+load 364407-1.html
+load 364686-1.xhtml
+load 366021-1.xhtml
+load 366667-1.html
+load 366952-1.html
+load 367246-1.html
+load 367360.html
+load 368330-1.html
+load 368461-1.xhtml
+load 368568.html
+load 368752.html
+load 368860-1.html
+load 368863-1.html
+load 369150-1.html
+load 369150-2.html
+load 369227-1.xhtml
+load 369542-1.html
+load 369542-2.html
+load 369547-1.html
+load 370174-1.html
+load 370174-2.html
+load 370174-3.html
+load 370699-1.html
+load 370794-1.html
+load 370884-1.xhtml
+load 371348-1.xhtml
+load 371561-1.html
+load 371566-1.xhtml
+load 372376-1.xhtml
+load 373859-1.html
+load 373868-1.xhtml
+load 375462-1.html
+load 375831.html
+load 376419.html
+load 377522.html
+load 379217-1.xhtml
+load 379217-2.xhtml
+load 379917-1.xhtml
+load 380012-1.html
+load 381152-1.html
+load 382129-1.xhtml
+load 382131-1.html
+load 382199-1.html
+load 382208-1.xhtml
+load 382262-1.html
+load 382396-1.xhtml
+load 383089-1.html
+load 385265-1.xhtml
+load 385295-1.xhtml
+load 385344-1.html
+load 385344-2.html
+load 385414-1.html
+load 385414-2.html
+load 385426-1.html
+load 385526.html
+load 385681.html
+load 386799-1.html
+load 386807-1.html
+load 386812-1.html
+load 386827-1.html
+load 387058-1.html
+load 387058-2.html
+load 387088-1.html
+load 387209-1.html
+load 387213-1.html
+load 387215-1.xhtml
+load 387219-1.xhtml
+load 387233-1.html
+load 387233-2.html
+load 387282-1.html
+load 388049.html
+load 388175-1.html
+load 388367-1.html
+load 388709-1.html
+load 389635-1.html
+load 390050-1.html
+load 390050-2.html
+load 390050-3.html
+load 390052.html
+load 390417.html
+load 390762-1.html
+load 391053-1.xhtml
+load 391894-1.html
+load 392698-1.html
+load 393758-1.xhtml
+load 393906-1.html
+load 393923-1.html
+load 393956-1.html
+load 393956-2.html
+load 393956-3.html
+load 393956-4.html
+load 394237-1.html
+load 394818-1.html
+load 394818-2.html
+load 394820-1.html
+load 395316-1.html
+load 395450-1.xhtml
+load 397007-1.html
+load 397187-1.html
+load 397844-1.xhtml
+load 397844-2.xhtml
+load 397852-1.xhtml
+load 398181-1.html
+load 398181-2.html
+load 398322-1.html
+load 398322-2.html
+load 398332-1.html
+load 398332-2.html
+asserts(0-2) load 398332-3.html # bug 436123 and bug 457397
+load 399407-1.xhtml
+load 399412-1.html
+load 399843-1.html
+load 400078-1.html
+load 400190.html
+load 400223-1.html
+load 400232-1.html
+load 400244-1.html
+load 400768-1.xhtml
+load 400768-2.xhtml
+load 401042-2.html
+load 402380-1.html
+load 402380-2.html
+load 402872-1.html
+load 402872-2.html
+load 403004.html
+load 403143-1.html
+load 403576-1.html
+load 404140-1.html
+load 404146-1.html
+load 404204-1.html
+load 404215-1.html
+load 404215-2.html
+load 404215-3.html
+load 404219-1.html
+load 404219-2.html
+load 404624.html
+load 406137.html
+load 406380.html
+load 406902-1.html
+load 407009-1.xhtml
+load 408304-1.xhtml
+load 408602-1.html
+load 408737-1.html
+load 408737-2.html
+load 408749-1.xhtml
+load 408883-1.html
+load 410198.html
+load 410228-1.html
+load 410232-1.html
+load 410595-1.html
+load 411213-1.html
+load 411213-2.xml
+load 411835.html
+load 411851-1.html
+load 412014-1.html
+load 412201-1.xhtml
+load 412543-1.html
+load 413048-1.html
+load 413079-1.xhtml
+load 413079-2.xhtml
+load 413079-3.xhtml
+load 413085-1.html
+load 413085-2.html
+load 413582-1.xhtml
+load 413582-2.html
+load 413712-1.xhtml # bug 1323680
+load 414061-1.html
+load chrome://reftest/content/crashtests/layout/generic/crashtests/414180-1.xhtml
+load 414719-1.html
+load 415685-1.html
+load 415818.xhtml
+load 416165.html
+load 416264-1.html
+load 416476-1.html
+load 417848-1.xhtml
+load 417902-1.html
+load 417902-2.html
+load 418532-1.html
+load 418932-1.html
+load 419352.html
+load 420000-1.html
+load 420718.html
+load 421404-1.html
+load 421671.html
+load 422283-1.html
+load 422301-1.html
+load 423055-1.html
+load 423098.html
+load 423264-1.html
+load 424629.html
+load 425253-1.html
+load 426040-1.html
+load 426272-1.html
+load 428263-1.html
+load 429960-1.html
+load 429960-2.html
+load 429969-1.html
+load 429981-1.html
+load 430332-1.html
+load 430344-1.html
+load 430352-1.html
+load 430744-1.html
+load 430991.html
+load 431260-1.html
+load 431260-2.html
+load 435529.html
+load 436194-1.html
+load 436602-1.html
+load 436822-1.html
+load 436823.html
+load 436969-1.html
+load 437156-1.html
+load 437565-1.xhtml
+load 437565-2.xhtml
+load 437565-3.xhtml
+load 438259-1.html
+load 438266-1.html
+skip load 438509-1.html # bug 511234
+load 443528-1.html
+load 444230-1.html
+load 444484-1.html
+load 444726-1.xhtml
+load 444861-1.html
+load 445288.html
+load 448903-1.html
+load 448996-1.html
+load 451315-1.html
+load 451317-1.html
+load 451334-1.html
+load 452157-1.html
+load 452157-2.html
+load 452157-3.html
+load 453762-1.html
+load 455171-1.html
+load 455171-2.html
+load 455171-3.html
+load 455643-1.xhtml
+load 457375.html
+load 457380-1.html
+load 459968.html
+load 460910-1.xml
+load 461294-1.html
+load 462968.xhtml
+load 463350-1.html
+load 463350-2.html
+load 463350-3.html
+load 463741-1.html
+load 465651-1.html
+load 467137-1.html
+load 467213-1.html
+load 467487-1.html
+load 467493-1.html
+load 467493-2.html
+load 467875-1.xhtml
+load 467914-1.html
+asserts-if(winWidget,0-2) load 468207-1.html # bug 1647811
+load 468771-1.xhtml
+load 468771-2.xhtml
+load 469859-1.xhtml # bug 1323665
+load 471360.html
+load 472587-1.xhtml
+load 472617-1.xhtml
+load 472774-1.html
+load 472776-1.html
+load 472950-1.html
+load 473278-1.xhtml
+load 473894-1.html
+load 476241-1.html
+load 477731-1.html
+load 477928.html
+load 478131-1.html
+asserts(4) load 478170-1.html # Big sizes
+asserts(1591-3980) load 478185-1.html
+load 478504.html
+asserts-if(!Android,0-1) load 479938-1.html # Bug 575011
+load 480345-1.html
+load 481921.html
+load 489462-1.html
+load 489477.html
+load 489480-1.xhtml
+load 489647-1.html
+load 493111-1.html
+load 493118-1.html
+load 493649.html
+load 494283-1.xhtml
+load 494283-2.html
+load 494332-1.html
+load 495875-1.html
+load 495875-2.html
+load 496742.html
+load 499138.html
+load 499857-1.html
+load 499862-1.html
+load 501535-1.html
+load 503961-1.xhtml
+load 503961-2.html
+load 507566.html
+load 508154-1.xhtml
+load 508168-1.html
+load chrome://reftest/content/crashtests/layout/generic/crashtests/508816-1.xhtml
+load 509749-1.html
+load 511482.html
+load 512724-1.html
+load 512725-1.html
+load 512749-1.html
+load 513110-1.html
+load 513110-2.xhtml
+load 513394-1.html
+load 514098-1.xhtml
+load 514800-1.html
+load 515811-1.html
+load 517968.html
+load 519031.xhtml
+load 520340.html
+load 522170-1.html
+load 526217.html
+load 533379-1.html
+load 533379-2.html
+load 534082-1.html
+load 534366-1.html
+load 534366-2.html
+load 536692-1.xhtml
+load 537645.xhtml
+load 541277-1.html
+load 541277-2.html
+load 541714-1.html
+load 541714-2.html
+load 542136-1.html
+load 545571-1.html
+load 547843-1.xhtml
+load 551635-1.html
+load 553504-1.xhtml
+load 564368-1.xhtml
+load 564968.xhtml
+asserts(5-9) load 569193-1.html # absurd sizes
+load 570160.html
+load 570289-1.html
+load 571618-1.svg
+asserts(0-1) load 571975-1.html # bug 574889
+load 571995.xhtml
+load 574958.xhtml
+asserts(0-6) load 578977.html # bug 757305
+load 580504-1.xhtml
+load 582793-1.html
+load 585598-1.xhtml
+load 586806-1.html
+load 586806-2.html
+load 586806-3.html
+load 586973-1.html
+load 589002-1.html
+load 590404.html
+load 591141.html
+load 592118.html
+load 594808-1.html
+load 595435-1.xhtml
+load 595740-1.html
+load 597240-1.xhtml
+load 600100.xhtml
+load 603490-1.html
+load 603510-1.html
+load 604314-1.html
+load 604843.html
+load 605340.html
+load 606642.xhtml
+load 613455-1.svg
+load 613629-1.xhtml
+load 616052-1.html
+load 619021.html
+load 621424-1.html
+load 621841-1.html
+load 622596.html
+load 641724.html
+load 645072-1.html
+load 645072-2.html
+load 646561-1.html
+load 646983-1.html
+load 647332-1.html
+load 650499-1.html
+load 654002-1.html
+load 654002-2.html
+load 655462-1.html
+load 656130-1.html
+load 656130-2.html
+load 660416.html
+load 665853.html
+load 667025.html
+asserts(9-17) load 673770.html # nested multicols and bogus sizes, e.g. Init: bad caller: height WAS 2147478130
+load 679933-1.html
+load 681489-1.html
+load 682649-1.html
+load 683702-1.xhtml
+load 683712.html
+load 688996-1.html
+load 688996-2.html
+load 691210.html
+load 700031.xhtml
+load 709398-1.html
+load 718516.html
+load 723108.html
+load 724235.html
+load 724978.xhtml
+load 730559.html
+load 734777.html
+load 737313-1.html
+load 737313-2.html
+load 737313-3.html
+test-pref(font.size.inflation.emPerLine,15) load 740199-1.xhtml
+test-pref(font.size.inflation.emPerLine,15) load 742602.html
+load 743364.html
+load 747688.html
+load 750066.html
+load 757413.xhtml
+load 757413-2.html
+load 762764-1.html
+load 762902.html
+load 765409.html
+asserts(0-200) load 765621.html # bug 703550
+asserts(0-200) load 767765.html # bug 407550, bug 871758, and various nscoord_MAX related asserts
+load 769120.html
+load 769303-1.html
+load 769303-2.html
+load 777838.html
+load 783228.html
+load 784600.html
+load 785555.html
+load 786740-1.html
+load 790252-1.html
+load 790252-2.html
+load 790260-1.html
+test-pref(font.size.inflation.emPerLine,15) load 791601.xhtml
+test-pref(font.size.inflation.minTwips,120) load 794693.html
+load 798020-1.html
+load 798235-1.html
+load 799207-1.html
+load 799207-2.html
+load 801268-1.html
+load 804089-1.xhtml
+load 807565-1.html
+load 807565-2.html
+load 810303.html
+load 810726.html
+load 812822-1.html
+load 812879-1.html
+load 812879-2.html
+asserts-if(winWidget,0-42) load 812893.html # bug 1581653
+load 814995.html
+load 822910.xhtml
+load 824297-1.html
+load 825810-1.html
+load 825810-2.html
+load 826483-1.html
+load 826532-1.html
+load 827076.html
+load 827168-1.html
+load 836895.html
+load 837007.xhtml
+load 840787.html
+load 840818.html
+load 842132-1.html
+load 842166.html
+load 844529-1.html
+load 847130.xhtml
+load 847208.html
+load 847209.html
+load 847211-1.html
+load 849603.html
+load 849987.html
+asserts(8-46) load 850931.html # nested multicols, inner multicol has column-width 1 app unit ...
+load 851396-1.html
+load 854263-1.html
+load 862185.html
+load 863935.html
+load 866547-1.html
+load 866767-1.html
+needs-focus pref(accessibility.browsewithcaret,true) load 868906.html
+load 876074-1.html
+load 876155.html
+load 883514-1.html
+load 883514-2.html
+load 885009-1.html
+load 893496-1.html
+load 893523.html
+asserts(0-3) load 898871.html # bug 479160 - mostly OSX, sometimes Windows
+asserts(0-3) load 914501.html # bug 1144852 - all platforms
+load 914891.html
+load 915475.xhtml
+load 927558.html
+load 942794-1.html
+load 943509-1.html
+asserts(2-3) load 944909-1.html # bogus sizes
+load 946167-1.html
+skip-if(Android&&browserIsRemote) load 947158.html # bug 1507207
+load 949932.html
+load 963878.html
+load 964078.html
+load 970710.html
+load 973701-1.xhtml
+load 973701-2.xhtml
+load 986899.html
+asserts(1-3) load 987041-1.html # absurd sizes
+load 1001233.html
+load 1001258-1.html
+load 1001994.html
+skip-if(ThreadSanitizer) load chrome://reftest/content/crashtests/layout/generic/crashtests/1003441.xhtml
+load 1015562.html
+load 1015563-1.html
+load 1015563-2.html
+load 1015844.html
+asserts-if(Android,0-358) pref(font.size.inflation.minTwips,200) load 1032450.html # Bug 1607658
+load 1032613-1.svg
+load 1032613-2.html
+load 1037903.html
+load 1039454-1.html
+load 1042489.html
+load 1054010-1.html
+load 1058954-1.html
+load 1059138-1.html
+load 1102175-2.html
+load 1134531.html
+load 1134667.html
+load 1137723-1.html
+load 1137723-2.html
+asserts(1) load 1140043-1.html
+asserts(1) load 1140043-2.html
+asserts(1) load 1140043-3.html
+load 1140268-1.html
+load 1145768.html
+load 1145931.html
+load 1145950-1.html
+load 1146103.html
+load 1146107.html
+load 1146114.html
+asserts(0-20) load 1153478.html # bug 1144852
+load 1153695.html
+load 1156222.html
+load 1156257.html
+load 1157011.html
+load 1157975-1.html
+load 1169420-1.html
+load 1169420-2.html
+load 1178783-1.html
+load 1183431.html
+load 1186147-1.html
+load 1209952.html
+load 1221112-1.html
+load 1221112-2.html
+load 1221874-1.html
+load 1221904.html
+load 1222783.xhtml
+load 1223522.xhtml
+load 1223568-1.html
+load 1223568-2.html
+load 1224230-1.html
+load 1225118.html
+load 1225376.html
+load 1225592.html
+load 1229437-1.html
+load 1229437-2.html
+load details-containing-only-text.html
+load details-display-none-summary-1.html
+load details-display-none-summary-2.html
+load details-display-none-summary-3.html
+load details-open-overflow-auto.html
+load details-open-overflow-hidden.html
+load details-three-columns.html
+load first-letter-638937-1.html
+load first-letter-638937-2.html
+load flex-nested-abspos-1.html
+pref(dom.meta-viewport.enabled,true) test-pref(font.size.inflation.emPerLine,15) asserts(0-100) load font-inflation-762332.html # bug 762332
+asserts-if(Android||OSX,0-2) load outline-on-frameset.xhtml # bug 762332, bug 1594135
+load summary-position-out-of-flow.html
+pref(widget.windows.window_occlusion_tracking.enabled,false) load text-overflow-bug666751-1.html # Bug 1819154
+pref(widget.windows.window_occlusion_tracking.enabled,false) load text-overflow-bug666751-2.html # Bug 1819154
+load text-overflow-bug670564.xhtml
+load text-overflow-bug671796.xhtml
+load text-overflow-bug713610.html
+load text-overflow-form-elements.html
+load text-overflow-iframe.html
+asserts(1-4) load 1225005.html # bug 682647 and bug 448083
+load 1230378.xhtml
+asserts(7) load 1231692-1.html
+load 1233191.html
+load 1233607.html
+load 1234701-1.html
+load 1234701-2.html
+load 1248227.html
+load 1271765.html
+load chrome://reftest/content/crashtests/layout/generic/crashtests/1272983-1.html
+load chrome://reftest/content/crashtests/layout/generic/crashtests/1272983-2.html
+load 1275059.html
+load 1278007.html
+load 1278080.html
+load 1279814.html
+load large-border-radius-dashed.html
+load large-border-radius-dashed2.html
+load large-border-radius-dotted.html
+load large-border-radius-dotted2.html
+load 1278461-1.html
+load 1278461-2.html
+load 1281102.html
+load 1297427-non-equal-centers.html
+load 1304441.html
+load 1308876-1.html
+load 1316649.html
+load 1316884-1.html
+load 1343552-1.html
+load 1343552-2.html
+load 1346454-1.html
+load 1346454-2.html
+load 1349650.html
+asserts-if(browserIsRemote,0-5) load 1349816-1.html # bug 1350352
+load 1350372.html
+load 1364361-1.html
+load 1367413-1.html
+load 1368617-1.html
+load 1373586.html
+load 1375858.html
+load 1381134.html
+load 1381134-2.html
+load 1401420-1.html
+load 1401709.html
+load 1401807.html
+load 1404222-empty-shape.html
+load 1405443.html
+load 1405813.html
+load 1405896.html
+load 1406252-1.html
+load 1415185.html
+load 1416544.html
+load 1427824.html
+load 1431781.html
+load 1431781-2.html
+load 1458028.html
+load 1459697.html
+load 1460158-1.html
+load 1460158-2.html
+load 1460158-3.html
+load 1461039.html
+load 1461979-1.html
+load 1463977.html
+asserts(0-1) load 1466224.html # assertion is bug 1683800
+load 1467239.html
+load 1472403.html
+load 1474768.html
+load 1478178.html
+load 1483972.html
+load 1486457.html
+asserts(1-4) load 1488762-1.html # asserts from integer overflow & bogus sizes
+load 1488910-1.html
+load 1488910-2.html
+load 1489287.html
+load 1489863.html
+load 1489770.html
+load 1490032.html
+load 1490685.html
+load 1493708.html
+load 1493710.html
+load 1493741.html
+load 1494380.html
+load 1505817.html
+load 1506216.html
+load 1506306.html
+load 1507196.html
+load 1513275.html
+load 1513282.html
+load 1515124.html
+load 1517033.html
+load 1517297.html
+load chrome://reftest/content/crashtests/layout/generic/crashtests/1520798-1.xhtml
+load 1520798-2.html
+load 1528771.html
+load 1539656.html
+load 1542441.html
+load 1543140-1.html
+load 1544060-1.html
+load 1544060-2.html
+load 1553824.html
+asserts(26-42) load 1554824.html # extreme sizes, column-width: 0em
+load 1555142.html
+load 1560349.html
+load 1560397.html
+load 1560397-2.html
+load 1562105.html
+load 1563131.html
+load 1568001-1.html
+load 1568001-2.html
+load 1569639.html
+load 1571239.html
+load 1571460.html
+load 1571598.html
+load 1571897.html
+load 1572901.html
+load 1573216.html
+load 1574552.html
+load 1574993.html
+load 1582019.html
+load 1586470.html
+load 1588955-very-large-frameset.html
+load 1590569.html
+load 1596310.html
+load 1601819-1.html
+load 1608851-1.html
+load 1608851-2.html
+load 1613210.html
+load 1614101.html
+load 1618312.html
+load 1618564.html
+load 1625051-1.html
+load 1625051-2.html
+load 1626970.html
+load 1628804.html
+load 1629575-1.html
+load 1629575-2.html
+load 1630385.html
+load 1633434.html
+load 1633737-1.html
+load 1633737-2.html
+load 1633737-3.html
+load 1633737-4.html
+load 1633737-5.html
+load 1633828.html
+load 1638860-1.html
+load 1638860-2.html
+load 1638906.html
+load 1640028.html
+load 1640051.html
+load 1640275.html
+pref(layout.accessiblecaret.enabled,true) load 1644819.html
+load 1645549-1.html
+load 1648577.html
+load 1652618.html
+load 1652897.html
+asserts(0-7) load 1654925.html
+load 1663222.html
+load 1666592.html
+load 1670336.html
+load 1676970.html
+HTTP load 1677518-1.html
+load 1679794.html
+load 1680406.html
+load 1681788.html
+load 1682686-1.html
+load 1682686-2.html
+HTTP load 1682882.html
+load 1683126.html
+skip-if(Android) load 1697262-1.html # printPreview doesn't work on android
+load 1682032.html
+load 1699263.html
+load 1699468.html
+load 1728319.html
+asserts(2-8) load 1730506.html # asserts from integer overflow & bogus sizes
+asserts(1-4) load 1730570.html # asserts from integer overflow & bogus sizes
+load 1734015.html
+load 1776079.html
+asserts(0-2) load 1791606.html
+load 1799749.html
+pref(layout.css.content-visibility.enabled,true) asserts(0-1) load 1807958.html # asserts from integer overflow & bogus sizes
+load 1816574.html
+load 1821603.html
+load 1822118.html
+load 1825434.html
diff --git a/layout/generic/crashtests/details-containing-only-text.html b/layout/generic/crashtests/details-containing-only-text.html
new file mode 100644
index 0000000000..9d5e647d9c
--- /dev/null
+++ b/layout/generic/crashtests/details-containing-only-text.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <body>
+ <details open>This is the detail.</details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/details-display-none-summary-1.html b/layout/generic/crashtests/details-display-none-summary-1.html
new file mode 100644
index 0000000000..2d7cf57a0a
--- /dev/null
+++ b/layout/generic/crashtests/details-display-none-summary-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <body>
+ <details open>
+ <summary style="display: none;">summary (display: none)</summary>
+ </details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/details-display-none-summary-2.html b/layout/generic/crashtests/details-display-none-summary-2.html
new file mode 100644
index 0000000000..4db67c0cfe
--- /dev/null
+++ b/layout/generic/crashtests/details-display-none-summary-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <body>
+ <details open>
+ <summary style="display: none;">summary (display: none)</summary>
+ <p>This is the details.</p>
+ </details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/details-display-none-summary-3.html b/layout/generic/crashtests/details-display-none-summary-3.html
new file mode 100644
index 0000000000..1ba94c74ca
--- /dev/null
+++ b/layout/generic/crashtests/details-display-none-summary-3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <body>
+ <details open>
+ <summary style="display: none;">summary (display: none)</summary>
+ <summary>summary 2</summary>
+ <p>This is the details.</p>
+ </details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/details-open-overflow-auto.html b/layout/generic/crashtests/details-open-overflow-auto.html
new file mode 100644
index 0000000000..c9ec193291
--- /dev/null
+++ b/layout/generic/crashtests/details-open-overflow-auto.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <style>
+ details {
+ background-color: orange;
+ overflow: auto;
+ width: 300px;
+ height: 200px;
+ }
+ summary {
+ background-color: green;
+ overflow: auto;
+ width: 200px;
+ height: 100px;
+ }
+ </style>
+ <body>
+ <details open>
+ <summary>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
+ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+ mollit anim id est laborum.
+ </summary>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
+ velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
+ est laborum.
+ </details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/details-open-overflow-hidden.html b/layout/generic/crashtests/details-open-overflow-hidden.html
new file mode 100644
index 0000000000..5e847ec7f1
--- /dev/null
+++ b/layout/generic/crashtests/details-open-overflow-hidden.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <style>
+ details {
+ background-color: orange;
+ overflow: hidden;
+ width: 300px;
+ height: 200px;
+ }
+ summary {
+ background-color: green;
+ overflow: hidden;
+ width: 200px;
+ height: 100px;
+ }
+ </style>
+ <body>
+ <details open>
+ <summary>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
+ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+ mollit anim id est laborum.
+ </summary>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
+ velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
+ est laborum.
+ </details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/details-three-columns.html b/layout/generic/crashtests/details-three-columns.html
new file mode 100644
index 0000000000..a82c3e6748
--- /dev/null
+++ b/layout/generic/crashtests/details-three-columns.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <style>
+ details {
+ column-count: 3;
+ column-rule: 1px solid lightgray;
+ -webkit-column-count: 3;
+ -webkit-column-rule: 1px solid lightgray;
+ border: 1px solid lightblue;
+ }
+ summary {
+ background-color: lightgreen;
+ }
+ </style>
+ <body>
+ <details open>
+ <summary>Summary</summary>
+ <p>line</p>
+ <p>line</p>
+ <p>line</p>
+ <p>line</p>
+ <p>line</p>
+ <p>line</p>
+ <p>line</p>
+ </details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/empty.html b/layout/generic/crashtests/empty.html
new file mode 100644
index 0000000000..18ecdcb795
--- /dev/null
+++ b/layout/generic/crashtests/empty.html
@@ -0,0 +1 @@
+<html></html>
diff --git a/layout/generic/crashtests/file_324318-1.html b/layout/generic/crashtests/file_324318-1.html
new file mode 100644
index 0000000000..90c29362e0
--- /dev/null
+++ b/layout/generic/crashtests/file_324318-1.html
@@ -0,0 +1 @@
+<table border='1'><tr><td>tdc</td></tr></table>
diff --git a/layout/generic/crashtests/first-letter-638937-1.html b/layout/generic/crashtests/first-letter-638937-1.html
new file mode 100644
index 0000000000..039c1ad7de
--- /dev/null
+++ b/layout/generic/crashtests/first-letter-638937-1.html
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>yo-lobo</title>
+ <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
+ <meta content="werwolf - zoquete pluscuamperfecto" name="author" />
+ <style type="text/css">
+ body {
+ font-family: sans-serif, Arial;
+ column-count: 5;
+ column-gap: 1em;
+ padding: 5px;
+ }
+ body.crash {
+ column-rule-width: thin;
+ column-rule-style: solid;
+ }
+ p {
+ margin: 10px;
+ padding: 0px;
+ }
+ p:first-letter {
+ font-size: 30pt;
+ font-weight: bold;
+ float: left;
+ padding-right: 5px;
+ padding-bottom: 5px;
+ }
+ </style>
+
+ </head>
+ <body onload="x=document.body; x.className='crash'">
+ <p>Lorem ipsum dolor sit amet consectetuer platea turpis justo Ut interdum. Wisi accumsan Vestibulum tempor vel ut nulla semper platea tincidunt consectetuer. Tristique metus ac nec turpis nibh nunc interdum ut tristique nec. Porttitor nibh sollicitudin urna fames non ultrices ipsum metus pede velit. Adipiscing amet et orci augue vel auctor amet ac Nam.</p>
+ <p>Ornare pellentesque augue leo Sed et In Donec nibh Cum tincidunt. Rutrum vel eget sagittis arcu cursus nibh Nam feugiat lacus lobortis. Suspendisse dictumst at Phasellus eu cursus sem risus dolor adipiscing metus. Lorem et Praesent Nunc Morbi Curabitur id pretium neque quis consequat. Convallis laoreet Integer et et Nulla In et et ut et. Convallis gravida ut tortor odio.</p>
+ <p>Fames pharetra et lacinia a aliquet tempor Vivamus Curabitur Vestibulum Vivamus. Duis Vestibulum nascetur sodales interdum congue a diam Lorem id In. Pede Curabitur interdum vitae nisl nunc est et ac Nulla quis. Sodales metus vitae mauris tellus Curabitur vitae dolor mauris wisi Phasellus. Pellentesque a Ut sem sapien interdum convallis Curabitur purus Aenean.</p>
+ <p>Ultrices pellentesque pretium odio vestibulum natoque natoque gravida Vivamus quis Integer. Ipsum cursus id nec cursus odio amet Vestibulum Suspendisse vitae habitasse. Leo elit eros porta volutpat laoreet commodo elit id egestas et. Curabitur arcu semper dictumst molestie Integer ligula id tellus quis Mauris. Tincidunt eget Sed amet justo porttitor egestas nibh pulvinar mauris justo. Vestibulum natoque eget hendrerit habitasse hendrerit eu purus Proin.</p>
+ <p>Lacinia Integer nec enim sem pellentesque sollicitudin sagittis Cras Sed Morbi. Vitae quis et consectetuer libero metus eros neque malesuada lacus justo. Curabitur ipsum lobortis massa lobortis consequat ut et Fusce quam augue. Laoreet id libero laoreet Curabitur interdum tempus Quisque elit amet purus. Libero sed Phasellus nec odio pede sed ac velit tincidunt id. Metus natoque.</p>
+ <p>Felis et enim at condimentum augue ut vitae In Mauris laoreet. Neque urna Morbi sapien risus nulla leo nec sed ipsum id. Id dictum eu natoque libero ac dapibus Ut sed ut dictum. Sed quis aliquet nunc vestibulum eleifend orci vestibulum Vestibulum Vivamus est. Et urna tempus montes eget Sed tristique.</p>
+ <p>Nibh id mauris ipsum Curabitur Integer velit sed Vivamus Integer laoreet. Eu semper Nulla ac Curabitur Vestibulum ut urna Sed libero In. Phasellus vitae nibh nunc eget Nam iaculis sed Phasellus mauris consectetuer. Amet dignissim natoque eget facilisi Vestibulum facilisis sit scelerisque porta adipiscing. Condimentum vel nec turpis metus est felis neque fames dapibus at. Aenean sed ac.</p>
+ <p>Malesuada hendrerit facilisis et Donec sed pellentesque Nullam est Praesent augue. Pede id orci tincidunt purus Suspendisse Vestibulum sagittis euismod sem porttitor. Lorem a convallis vestibulum condimentum Vestibulum mauris pellentesque consequat metus Vivamus. Consectetuer egestas eu Vestibulum id Morbi interdum montes eros odio Sed. Arcu Donec lacinia mauris vel tortor interdum in habitasse.</p>
+ <p>A scelerisque justo justo Vivamus eleifend velit Nullam orci tortor Nam. Nonummy ut nibh Pellentesque at pede Integer nibh metus justo scelerisque. Tincidunt consequat Curabitur porta non Morbi tincidunt egestas semper pellentesque Vestibulum. Ultrices congue In nec quis et pellentesque at vitae ipsum ridiculus. Elit fringilla ante Aenean elit Sed ut Nam pretium Aenean vel. Eu justo porta mauris congue neque pretium quis enim turpis sit. Auctor.</p>
+ <p>Scelerisque Maecenas Nunc lacinia porttitor fames Pellentesque sed urna Quisque pellentesque. Aenean eget tempus Praesent feugiat sed pretium dignissim In sapien Morbi. Velit mauris Nam Donec sollicitudin at vel mattis vitae amet laoreet. Vel at Nulla id Fusce vel interdum pellentesque Curabitur montes Phasellus. Accumsan interdum est eu ac lacus pellentesque sed Pellentesque.</p>
+</body>
+</html>
diff --git a/layout/generic/crashtests/first-letter-638937-2.html b/layout/generic/crashtests/first-letter-638937-2.html
new file mode 100644
index 0000000000..89f37c43f8
--- /dev/null
+++ b/layout/generic/crashtests/first-letter-638937-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html style="height: 600em; column-width: 1px;">
+
+<head>
+<style>p::first-letter { float:left; }</style>
+</head>
+
+<body onload="x=document.body.parentNode; x.style.columnWidth='111px'; x.offsetHeight; x.style.display='inline'; x.offsetHeight; "><p style="margin: -562949953421311em;">y
+</p><p>'</p></body>
+
+</html>
diff --git a/layout/generic/crashtests/flex-nested-abspos-1.html b/layout/generic/crashtests/flex-nested-abspos-1.html
new file mode 100644
index 0000000000..53522a36c4
--- /dev/null
+++ b/layout/generic/crashtests/flex-nested-abspos-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<div style="display:flex">
+ <div style="position: absolute">
+ <div style="position: absolute">
+ </div>
+ </div>
+</div>
diff --git a/layout/generic/crashtests/font-inflation-762332.html b/layout/generic/crashtests/font-inflation-762332.html
new file mode 100644
index 0000000000..f920d98d8d
--- /dev/null
+++ b/layout/generic/crashtests/font-inflation-762332.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="column-width: 1px; font-family: monospace; width: 2ch;"><div style="position: relative;"><div style="position: absolute;">xxxxxxxxxxxxxx x xxxxxxx x xxxxxxxxxxxxxxxxxx x xxxxxxx x</div></div></div>
diff --git a/layout/generic/crashtests/image.jpg b/layout/generic/crashtests/image.jpg
new file mode 100644
index 0000000000..8433518bc9
--- /dev/null
+++ b/layout/generic/crashtests/image.jpg
Binary files differ
diff --git a/layout/generic/crashtests/large-border-radius-dashed.html b/layout/generic/crashtests/large-border-radius-dashed.html
new file mode 100644
index 0000000000..a77e3c5d94
--- /dev/null
+++ b/layout/generic/crashtests/large-border-radius-dashed.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html style="height: 10000000px; width: 10000000px; box-sizing: border-box; border-radius: 10000000px; border-style: dashed; border-width: 10px 20px;"></html>
diff --git a/layout/generic/crashtests/large-border-radius-dashed2.html b/layout/generic/crashtests/large-border-radius-dashed2.html
new file mode 100644
index 0000000000..ed6722579e
--- /dev/null
+++ b/layout/generic/crashtests/large-border-radius-dashed2.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html style="height: 6523790304542em; width: 6207636626031em; box-sizing: border-box; border-radius: 6523790304542em; border-style: dashed; border-width: 10px 20px;"></html>
diff --git a/layout/generic/crashtests/large-border-radius-dotted.html b/layout/generic/crashtests/large-border-radius-dotted.html
new file mode 100644
index 0000000000..fe530c0c05
--- /dev/null
+++ b/layout/generic/crashtests/large-border-radius-dotted.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html style="height: 10000000px; width: 10000000px; box-sizing: border-box; border-radius: 10000000px; border-style: dotted; border-width: 10px 20px;"></html>
diff --git a/layout/generic/crashtests/large-border-radius-dotted2.html b/layout/generic/crashtests/large-border-radius-dotted2.html
new file mode 100644
index 0000000000..8cd822cf63
--- /dev/null
+++ b/layout/generic/crashtests/large-border-radius-dotted2.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html style="height: 6523790304542em; width: 6207636626031em; box-sizing: border-box; border-radius: 6523790304542em; border-style: dotted; border-width: 10px 20px;"></html>
diff --git a/layout/generic/crashtests/outline-on-frameset.xhtml b/layout/generic/crashtests/outline-on-frameset.xhtml
new file mode 100644
index 0000000000..9f72d10cc1
--- /dev/null
+++ b/layout/generic/crashtests/outline-on-frameset.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><frameset style="outline-style: solid;"></frameset></html>
diff --git a/layout/generic/crashtests/simple_blank.swf b/layout/generic/crashtests/simple_blank.swf
new file mode 100644
index 0000000000..b846387eb8
--- /dev/null
+++ b/layout/generic/crashtests/simple_blank.swf
Binary files differ
diff --git a/layout/generic/crashtests/solidblue.png b/layout/generic/crashtests/solidblue.png
new file mode 100644
index 0000000000..a64b6a4255
--- /dev/null
+++ b/layout/generic/crashtests/solidblue.png
Binary files differ
diff --git a/layout/generic/crashtests/summary-position-out-of-flow.html b/layout/generic/crashtests/summary-position-out-of-flow.html
new file mode 100644
index 0000000000..2c585fd497
--- /dev/null
+++ b/layout/generic/crashtests/summary-position-out-of-flow.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+ <head>
+ <style>
+ #fixed {
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ }
+ #absolute {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ }
+ </style>
+ </head>
+ <body>
+ <details>
+ <summary id="fixed">Summary (position: fixed)</summary>
+ <p>This is the detail with fixed summary.</p>
+ </details>
+ <details>
+ <summary id="absolute">Summary (position: absolute)</summary>
+ <p>This is the detail with absolute summary.</p>
+ </details>
+ </body>
+</html>
diff --git a/layout/generic/crashtests/text-overflow-bug666751-1.html b/layout/generic/crashtests/text-overflow-bug666751-1.html
new file mode 100644
index 0000000000..49f1b36a12
--- /dev/null
+++ b/layout/generic/crashtests/text-overflow-bug666751-1.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait"><head><script>
+function finish() {
+ window.removeEventListener("MozAfterPaint", finish);
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head><body onload="window.addEventListener('MozAfterPaint', finish, false); document.body.style.backgroundColor='lime';">
+<div style="overflow: scroll; text-indent: -100px; white-space: pre; text-overflow: ellipsis;"><span style="font-family: -moz-fixed; white-space: normal;"></code><p style="position: fixed;">m
+</p>
+</div>
+
+</body></html>
diff --git a/layout/generic/crashtests/text-overflow-bug666751-2.html b/layout/generic/crashtests/text-overflow-bug666751-2.html
new file mode 100644
index 0000000000..798cfae888
--- /dev/null
+++ b/layout/generic/crashtests/text-overflow-bug666751-2.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait"><head><script>
+function finish() {
+ window.removeEventListener("MozAfterPaint", finish);
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head><body onload="window.addEventListener('MozAfterPaint', finish, false); document.body.style.backgroundColor='lime';">
+<div style="overflow: scroll; text-indent: -100px; white-space: pre; text-overflow: ellipsis;"><span style="font-family: -moz-fixed; white-space: normal;"></code><p style="position: absolute;">m
+</p>
+</div>
+
+</body></html>
diff --git a/layout/generic/crashtests/text-overflow-bug670564.xhtml b/layout/generic/crashtests/text-overflow-bug670564.xhtml
new file mode 100644
index 0000000000..6a636a76ba
--- /dev/null
+++ b/layout/generic/crashtests/text-overflow-bug670564.xhtml
@@ -0,0 +1,3 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="position: relative; white-space: pre-line; direction: rtl; top: -85967203400px; text-overflow: ellipsis; text-indent: 5242870ch; padding: 224652170px; overflow-y: scroll; column-width: 74804px; letter-spacing: 687194767px;">
+
+</html>
diff --git a/layout/generic/crashtests/text-overflow-bug671796.xhtml b/layout/generic/crashtests/text-overflow-bug671796.xhtml
new file mode 100644
index 0000000000..47c2eb87ed
--- /dev/null
+++ b/layout/generic/crashtests/text-overflow-bug671796.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body style="width: 1px; text-overflow: ellipsis; overflow-y: scroll;">
+<math xmlns="http://www.w3.org/1998/Math/MathML"><msup style="display:block"/></math>
+</body>
+</html>
diff --git a/layout/generic/crashtests/text-overflow-bug713610.html b/layout/generic/crashtests/text-overflow-bug713610.html
new file mode 100644
index 0000000000..7cafc68cfe
--- /dev/null
+++ b/layout/generic/crashtests/text-overflow-bug713610.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="text-overflow: ellipsis; padding-right: 4000px; overflow: scroll;"><span style="transform: translatex(-50px); border-right-style: dashed;"></span></div>
+</body>
+</html>
diff --git a/layout/generic/crashtests/text-overflow-form-elements.html b/layout/generic/crashtests/text-overflow-form-elements.html
new file mode 100644
index 0000000000..b10124e4d6
--- /dev/null
+++ b/layout/generic/crashtests/text-overflow-form-elements.html
@@ -0,0 +1,144 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>text-overflow test case</title>
+<style type="text/css">
+
+.test {
+ font: 1em bold monospace;
+ background:lightgrey;
+ color: black;
+ margin-left:400px;
+}
+
+.rtl {
+ direction:rtl;
+}
+.ltr {
+ direction:ltr;
+}
+.rlo > * {
+ unicode-bidi: bidi-override; direction: rtl;
+}
+.lro > * {
+ unicode-bidi: bidi-override; direction: ltr;
+}
+.b { border: 1px dashed blue; }
+.inline-block {
+ display:inline-block;
+}
+.ellipsis {
+ width:4em;
+ width:6.5ch;
+ text-overflow: ellipsis;
+ -o-text-overflow: ellipsis;
+ overflow:hidden;
+}
+</style>
+<script>
+var twoEyes = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAFy7sgCAAAGsUlEQVRo3u2ZbWwcZxHHf3s%2B7LNbO3ZjXBtowprGODRX0qpNQCjmJKuVKhMl1P2AkCwhFOIKkCBSm9IXavGFKAixIAECwkmWo5MrhRI3Ub40IEwQgp6aIDg3Cd6eEqyIHEteah%2B1E69vhw%2BZtTaX8704ZzkKjHS6271nZ56ZZ%2BY%2F%2F%2BdZKF%2FCwYshx3EkkggLsD1v4FQkEZZYLCbAKyG9%2Ba9EIsG6hnUAf8x74K3aUC3j4%2BM54HcsR2oAIomwZOezkv%2FnSHpYNh%2BNCmAE7xv94zvFdd1bHsjMZmQkPSxAJP%2B%2FfuBLwK54PC7JZFKAVJmzXLBt2w%2FMvcDLwIb8QS8CeJ4nkURYIomw7J%2FYJ8BvSiiXptGGxWds2%2Fa9%2Bnaxh%2BYAD%2Bgt04NDgABTpQY2cvvSFLzw86gWeBVwC8SzlOSv2YeBPfmDBoBHgKmR9LBEEmHZfDTqGykqfkUE0nA78BzQGfSgUeP3wNeTXwXg7MwZDhw4UHL6ra2ti79%2FOvljgG8AZ4H64Lhm4MvAocxsRppGG%2FxcXihlwLIs6R%2FfKV2HO%2F26uA94pdDYUKUZUU7W1RQYXA98Gnhaf5%2FXWX0HeAHYoQonqa4sZSOsSWMCWeC9Yko%2BCQwBe4E6oNc0Tc91XTl1%2BaTsn9gnI%2Blhyc5nZWxsrBIkKSbl2tiic3tW53YDEwOKaoFBrcOfqKee53lG9xsPMjV784r%2F4lO%2FpPvyJ9iyZcuvFSaXK5XYeAZ4CDgGvB3MS4B54LQuWYPeuy4iRFsevsXqpuYoqVQKIH2bK1CuDQNo11o4XUzh%2FcDWYIe1LEtyuZx4niee54njOGKapgfsqlL%2Bl2OjEXg8nxrc1dJ0h3hbtL%2BGCtz7KPBF4CuBe9uB15VafE8hr9qylI3HgG8C2%2FK7VyHZoJj7MrBRm30qFotJMpkU27YlHo%2F7Ha5a%2BV%2FKRkSJ4KuKRLVLKapTjB1SzAVIjY2NSXY%2BKyPpYdk%2FsU9OXT4pruv6BdZbBQfKsVGnvWlIe1VB6VQO8JxC1vZYLCbZ%2BaxsPhpdZDyRRFhG0sPiOE6ldKBg2lRg4xF1YCDIIIKN7DGgD3gH%2BBXwejKZfPrs2tPs%2FvPN2bKuYR1nd7xLKBSSJeqoXKnERjPwNWAG%2BLn2rZuM%2B4Tpml6vaWlp4eLcxVusZq5lCgVgOVKJjRqdX86ffL4D5wIoZACnTpw4wRMdT96i%2FImOJxERAs4uVyqxUacF%2FPdiCj%2BjdRBRGFtwXVdG0sPSdbhTmkYbpH98p2RmM2JZlig1vl0GWo4NQ%2Fn%2Bs5pKRXfwjweaxy7TND3HcRZbfC6X8xVPVQlGy7WxVWlO5XRXFXm6EZmrQuSXYyPE3SiVoEhE6Wyr0u2rumO6zv%2B21AFdQAswC1wCMuUCXCmyWQus103Qg8qlDO0lxwOb%2Fl4FiK3AB3VS%2FuKKLtK%2FgbeAnwG%2FvUODuRw%2FFrR0H1UC75fwu8oJ%2FhFsW5VIG%2FBUgEIN6Y65O4AHu4Ap0zQ9y7LEcZyb9lRBUHQcRyzL8unZVBW5bFWAvAp%2BhDQ2g4F47dUYtlU6obXA54DnVdFLekjUGGifh4AFy7LEdV3xj3X9I66m0QZpGm2QrsOd0j%2B%2BU0bSw5KZzYjrun6HWlAd961i4FfCj0aN1Usau%2Bc1lmuXPFwvAEumUut7tQQvAb%2FXb%2FT0bCAej9cODg7yt%2Bm%2F8q2%2F7OUHZ76PnZ1k2p0mJzlykmPancbOTnL0whHs7CQfb%2B5mx2d3sH79%2BtCRI0c6FeaOr9ICrIQfLvA%2B8BGNXxi4R6HrisJVUWrxAVW2oMFf0Aczim8o3kV6enowDIPjF9%2Fk%2BMU3S3rrjzMMg56eHr%2BxP7qKFbASfojG6kpeDGs1tiW53RxwWT%2Bin5q8w4xpQK5evQpAR30H7ZH2khNvj7TTUd8BgD4rqmu1ZKX8qNeY%2BfHz4zlXDgT5E8tpCTUq7XSBC4Euv8227TV9fX1E73%2BYtvo27BmbS9cvFVTY3bSRFza9yOcf6Gfmygy7d%2B%2Fm%2FPnzF4DvrsBLhnJlJfwIKXxv1PheAE4qK6p4H9AGbNKTuhngBPBPXYRe4IemaT5kWZbR19fHNbmGnZ1k4r3U4glDR30Hm5qjbGjsImJEOHbsGHv27JFz5869o0eFq01Jq%2BmHAXwI6FFKagMTgHM7GzFDS%2BoeLSMv7zjzC9x4Y7gxFovVDAwMEI1GaWlpWSzRVCrFwYMH%2FXfxZ4AfAa8B%2F7lDaGg1%2FQgp43lfK0yqtRMuJa3ceKe5DfgYsCYAZ2ngD8CfAkzqTpW7xY%2F%2FSznyX%2FVeUb2kVmX4AAAAAElFTkSuQmCC";
+function initIMG() {
+ var img = document.getElementsByTagName('img');
+ for (i = 0; i < img.length; ++i)
+ img[i].setAttribute('src', twoEyes);
+}
+function setTextOverflow(str,quoted) {
+ var x = document.styleSheets[0];
+ var q = quoted ? '"' : '';
+ x.insertRule('.ellipsis{text-overflow:' + q + str + q +'}', x.cssRules.length);
+}
+</script>
+</head><body onload="initIMG()">
+text-overflow:"<input placeholder="type text then <ENTER>" onchange='setTextOverflow(this.value,1)'>" | <button onclick="setTextOverflow('ellipsis')">ellipsis</button> | <button onclick="setTextOverflow('clip')">clip</button> (Try "." or "" for example) <br>
+
+LTR / LTR
+<div class="test ltr">
+<span class="ellipsis b inline-block">CSS is awesome</span>
+<button class="ellipsis">CSS is awesome</button>
+<input type=button class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" placeholder="CSS is awesome">
+<fieldset style="display:inline" class="ellipsis"><span style="position:relative;left:1em;">CSS is awesome</span></fieldset>
+<fieldset style="display:block" class="ellipsis"><span style="position:relative;left:1em;">CSS is awesome</span></fieldset>
+<legend class="ellipsis">CSS is awesome</legend>
+<textarea class="ellipsis" style="overflow:scroll;width:14em;" wrap="off">
+CSS is awesome CSS is awesome CSS is awesome
+CSS is awesome CSS is awesome CSS is awesome
+</textarea>
+<fieldset style="display:inline"><legend class="ellipsis">CSS is awesome</legend>CSS is awesome</fieldset>
+<fieldset style="display:block" class="ellipsis"><legend class="ellipsis">CSS is awesome</legend><span style="position:relative;left:1em;">CSS is awesome</span></fieldset>
+<select class="ellipsis"><option>CSS is awesome<option>CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select size="4"><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<ul style="float:left"><li class="ellipsis b">CSS is awesome</ul>
+<br><br></div>
+
+RTL / LTR
+<div class="test rtl">
+<span class="ellipsis b inline-block">CSS is awesome</span>
+<button class="ellipsis">CSS is awesome</button>
+<input type=button class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" placeholder="CSS is awesome">
+<fieldset style="display:inline" class="ellipsis"><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<fieldset style="display:block" class="ellipsis"><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<legend class="ellipsis">CSS is awesome</legend>
+<textarea class="ellipsis" style="overflow:scroll;width:14em;" wrap="off">
+CSS is awesome CSS is awesome CSS is awesome
+CSS is awesome CSS is awesome CSS is awesome
+</textarea>
+<fieldset style="display:inline"><legend class="ellipsis">CSS is awesome</legend>CSS is awesome</fieldset>
+<fieldset style="display:block" class="ellipsis"><legend class="ellipsis">CSS is awesome</legend><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<select class="ellipsis"><option>CSS is awesome<option>CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select size="4"><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<ul style="float:left"><li class="ellipsis b">CSS is awesome</ul>
+<br><br></div>
+
+LTR / RTL
+<div class="test ltr rlo">
+<span class="ellipsis b inline-block">CSS is awesome</span>
+<button class="ellipsis">CSS is awesome</button>
+<input type=button class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" placeholder="CSS is awesome">
+<fieldset style="display:inline" class="ellipsis"><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<fieldset style="display:block" class="ellipsis"><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<legend class="ellipsis">CSS is awesome</legend>
+<textarea class="ellipsis" style="overflow:scroll;width:14em;" wrap="off">
+CSS is awesome CSS is awesome CSS is awesome
+CSS is awesome CSS is awesome CSS is awesome
+</textarea>
+<fieldset style="display:inline"><legend class="ellipsis">CSS is awesome</legend>CSS is awesome</fieldset>
+<fieldset style="display:block" class="ellipsis"><legend class="ellipsis">CSS is awesome</legend><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<select class="ellipsis"><option>CSS is awesome<option>CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select size="4"><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<ul style="float:left"><li class="ellipsis b">CSS is awesome</ul>
+<br><br></div>
+
+RTL / RTL
+<div class="test rtl rlo">
+<span class="ellipsis b inline-block">CSS is awesome</span>
+<button class="ellipsis">CSS is awesome</button>
+<input type=button class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" value="CSS is awesome">
+<input class="ellipsis" placeholder="CSS is awesome">
+<fieldset style="display:inline" class="ellipsis"><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<fieldset style="display:block" class="ellipsis"><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<legend class="ellipsis">CSS is awesome</legend>
+<textarea class="ellipsis" style="overflow:scroll;width:14em;" wrap="off">
+CSS is awesome CSS is awesome CSS is awesome
+CSS is awesome CSS is awesome CSS is awesome
+</textarea>
+<fieldset style="display:inline"><legend class="ellipsis">CSS is awesome</legend>CSS is awesome</fieldset>
+<fieldset style="display:block" class="ellipsis"><legend class="ellipsis">CSS is awesome</legend><span style="position:relative;right:1em;">CSS is awesome</span></fieldset>
+<select class="ellipsis"><option>CSS is awesome<option>CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<select size="4"><option>CSS is awesome<option class="ellipsis">CSS is awesome<option>CSS is awesome<option>CSS is awesome</select>
+<ul style="float:left"><li class="ellipsis b">CSS is awesome</ul>
+<br><br></div>
+
+
+
+</body></html>
diff --git a/layout/generic/crashtests/text-overflow-iframe.html b/layout/generic/crashtests/text-overflow-iframe.html
new file mode 100644
index 0000000000..ba34dc2aab
--- /dev/null
+++ b/layout/generic/crashtests/text-overflow-iframe.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>text-overflow: Test 12</title>
+<style type="text/css">
+
+.test {
+ border: thin dashed black;
+ overflow: hidden;
+ white-space: nowrap;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ font: 1em bold monospace;
+ background:lime;
+ color: black;
+ margin-left:400px;
+ height: 12em;
+ text-shadow: #6374AB 5px -12px 2px;
+}
+
+body {
+ width:800px;
+}
+
+img { width: 50px; height: 50px; outline:5px dotted yellow; }
+span {
+ font-size:16px;
+ background:pink;
+ border: 5px dashed blue;
+ padding: 0 25px;
+ text-decoration: underline overline line-through;
+ color:brown;
+ text-shadow: none;
+}
+i {
+ display:inline-block;
+ height: 50px;
+ width: 5em;
+ background: blue;
+ outline:5px dotted yellow;
+ text-shadow: none;
+}
+u {
+ padding-left:140px;
+}
+v {
+ padding-right:140px;
+}
+.rtl {
+ direction:rtl;
+}
+.rlo span {
+ unicode-bidi: bidi-override; direction: rtl;
+}
+.lro span {
+ unicode-bidi: bidi-override; direction: ltr;
+}
+.h {display:none}
+iframe {
+ width: 100px;
+ height: 50px;
+}
+</style>
+<script>
+var c = "data:text/html,<style>body {white-space: nowrap;overflow:hidden;-o-text-overflow: ellipsis;text-overflow: ellipsis;}</style><body bgcolor='magenta'>CSS is awesome"
+function initIFRAME() {
+ var f = document.getElementsByTagName('iframe');
+ for (i = 0; i < f.length; ++i) {
+ f[i].setAttribute('src', c);
+ }
+ setTimeout(function(){document.body.style.width='500px'},0);
+}
+function setTextOverflow(str,quoted) {
+ var x = document.styleSheets[0];
+ var q = quoted ? '"' : '';
+ x.insertRule('.test{text-overflow:' + q + str + q +'}', x.cssRules.length);
+}
+</script>
+</head><body onload="initIFRAME()">
+text-overflow:"<input placeholder="type text then <ENTER>" onchange='setTextOverflow(this.value,1)'>" | <button onclick="setTextOverflow('ellipsis')">ellipsis</button> | <button onclick="setTextOverflow('clip')">clip</button> (Try "." or "" for example) <br>
+
+LTR / LTR
+<div class="test">
+<span><iframe></iframe>CSS is awesome CSS<i>overflowing-inline-block</i><u> is awesome</u></span><br>
+<span>CSS is awe<iframe></iframe>some CSS is awesome <i></i></span><br>
+<span>C SS is awesome<button>BUTTON</button> CSS is <iframe></iframe>awesom e </span><br>
+<span>C&shy;SS is awesome CSS is awesom&shy;e <button>BUTTON</button></span><br>
+<br><br></div>
+
+RTL / LTR
+<div class="test rtl">
+<span><iframe></iframe><v>CSS is awesome CSS</v><i>overflowing-inline-block</i> is awesome </span><br>
+<span>CSS is awe<iframe></iframe>some CSS is awesome <i></i></span><br>
+<span>C SS is awesome<button>BUTTON</button> CSS is <iframe></iframe>awesom e </span><br>
+<span>C&shy;SS is awesome CSS is awesom&shy;e <button>BUTTON</button></span><br>
+<br><br></div>
+
+
+LTR / RTL
+<div class="test rlo">
+<span><iframe></iframe>CSS is awesome CSS<i>overflowing-inline-block</i> is awesome </span><br>
+<span>CSS is awe<iframe></iframe>some CSS is awesome <i></i></span><br>
+<span>C SS is awesome<button>BUTTON</button> CSS is <iframe></iframe>awesom e </span><br>
+<span><button>BUTTON</button>C&shy;SS is awesome CSS is awesom&shy;e </span><br>
+<br><br></div>
+
+RTL / RTL
+<div class="test rtl rlo">
+<span><iframe></iframe>CSS is awesome CSS<i>overflowing-inline-block</i> is awesome </span><br>
+<span>CSS is awe<iframe></iframe>some CSS is awesome <i></i></span><br>
+<span>C SS is awesome<button>BUTTON</button> CSS is <iframe></iframe>awesom e </span><br>
+<span><button>BUTTON</button>C&shy;SS is awesome CSS is awesom&shy;e </span><br>
+<br><br></div>
+
+</body></html>
diff --git a/layout/generic/folder.png b/layout/generic/folder.png
new file mode 100644
index 0000000000..0f906b2f50
--- /dev/null
+++ b/layout/generic/folder.png
Binary files differ
diff --git a/layout/generic/frame-graph.py b/layout/generic/frame-graph.py
new file mode 100644
index 0000000000..7785bb4c64
--- /dev/null
+++ b/layout/generic/frame-graph.py
@@ -0,0 +1,41 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Take the *.framedata files from graph-frameclasses.js and combine them
+into a single graphviz file.
+
+stdin: a list of .framedata file names (e.g. from xargs)
+stdout: a graphviz file
+
+e.g. `find <objdir> -name "*.framedata" | python aggregate-frameclasses.py |
+ dot -Tpng -o frameclasses-graph.png -`
+"""
+
+import sys
+
+classdict = {}
+
+for line in sys.stdin:
+ file = line.strip()
+ fd = open(file)
+
+ output = None
+ for line in fd:
+ if line.startswith("CLASS-DEF: "):
+ cname = line[11:-1]
+ if cname not in classdict:
+ output = classdict[cname] = []
+ else:
+ output = None
+ elif output is not None:
+ output.append(line)
+
+sys.stdout.write("digraph g {\n")
+
+for olist in classdict.itervalues():
+ for line in olist:
+ sys.stdout.write(line)
+
+sys.stdout.write("}\n")
diff --git a/layout/generic/jar.mn b/layout/generic/jar.mn
new file mode 100644
index 0000000000..ef98cfaf27
--- /dev/null
+++ b/layout/generic/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+ res/broken-image.png (broken-image.png)
diff --git a/layout/generic/moz.build b/layout/generic/moz.build
new file mode 100644
index 0000000000..15fdd5353c
--- /dev/null
+++ b/layout/generic/moz.build
@@ -0,0 +1,296 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("nsBlock*"):
+ # Parts of these files are really Layout: Floats
+ BUG_COMPONENT = ("Core", "Layout: Block and Inline")
+
+with Files("Block*"):
+ # Parts of these files are really Layout: Floats
+ BUG_COMPONENT = ("Core", "Layout: Block and Inline")
+
+with Files("nsColumn*"):
+ BUG_COMPONENT = ("Core", "Layout: Columns")
+
+with Files("Column*"):
+ BUG_COMPONENT = ("Core", "Layout: Columns")
+
+with Files("nsLine*"):
+ # Parts of these files are really Layout: Floats
+ BUG_COMPONENT = ("Core", "Layout: Block and Inline")
+
+with Files("nsInlineFrame.*"):
+ BUG_COMPONENT = ("Core", "Layout: Block and Inline")
+
+with Files("BRFrame.*"):
+ BUG_COMPONENT = ("Core", "Layout: Block and Inline")
+
+with Files("WBRFrame.*"):
+ BUG_COMPONENT = ("Core", "Layout: Block and Inline")
+
+with Files("nsFirstLetterFrame.*"):
+ BUG_COMPONENT = ("Core", "Layout: Block and Inline")
+
+with Files("MathML*"):
+ BUG_COMPONENT = ("Core", "MathML")
+
+with Files("Text*"):
+ BUG_COMPONENT = ("Core", "Layout: Text and Fonts")
+
+with Files("nsText*"):
+ BUG_COMPONENT = ("Core", "Layout: Text and Fonts")
+
+with Files("nsFrameSetFrame*"):
+ BUG_COMPONENT = ("Core", "Layout: Images, Video, and HTML Frames")
+
+with Files("nsSubDocumentFrame*"):
+ BUG_COMPONENT = ("Core", "Layout: Images, Video, and HTML Frames")
+
+with Files("nsFlex*"):
+ BUG_COMPONENT = ("Core", "Layout: Flexbox")
+
+with Files("nsFloatManager.*"):
+ BUG_COMPONENT = ("Core", "Layout: Floats")
+
+with Files("nsIntervalSet.*"):
+ BUG_COMPONENT = ("Core", "Layout: Floats")
+
+with Files("nsGrid*"):
+ BUG_COMPONENT = ("Core", "Layout: Grid")
+
+with Files("nsHTMLCanvasFrame.*"):
+ BUG_COMPONENT = ("Core", "Layout: Images, Video, and HTML Frames")
+
+with Files("nsImage*"):
+ BUG_COMPONENT = ("Core", "Layout: Images, Video, and HTML Frames")
+
+with Files("nsAbsoluteContainingBlock.*"):
+ BUG_COMPONENT = ("Core", "Layout: Positioned")
+
+with Files("Sticky*"):
+ BUG_COMPONENT = ("Core", "Layout: Positioned")
+
+with Files("nsRuby*"):
+ BUG_COMPONENT = ("Core", "Layout: Ruby")
+
+with Files("Ruby*"):
+ BUG_COMPONENT = ("Core", "Layout: Ruby")
+
+with Files("*Scroll*"):
+ BUG_COMPONENT = ("Core", "Layout: Scrolling and Overflow")
+
+with Files("nsFont*"):
+ BUG_COMPONENT = ("Core", "Layout: Text and Fonts")
+
+with Files("nsVideoFrame.*"):
+ BUG_COMPONENT = ("Core", "Audio/Video")
+
+EXPORTS += [
+ "JustificationUtils.h",
+ "nsAtomicContainerFrame.h",
+ "nsBlockFrame.h",
+ "nsCanvasFrame.h",
+ "nsContainerFrame.h",
+ "nsDirection.h",
+ "nsFloatManager.h",
+ "nsFrameList.h",
+ "nsFrameSelection.h",
+ "nsFrameState.h",
+ "nsFrameStateBits.h",
+ "nsHTMLParts.h",
+ "nsIAnonymousContentCreator.h",
+ "nsIFrame.h",
+ "nsIFrameInlines.h",
+ "nsILineIterator.h",
+ "nsIntervalSet.h",
+ "nsIScrollableFrame.h",
+ "nsIScrollPositionListener.h",
+ "nsIStatefulFrame.h",
+ "nsLineBox.h",
+ "nsPageSequenceFrame.h",
+ "nsPlaceholderFrame.h",
+ "nsQueryFrame.h",
+ "nsRubyBaseContainerFrame.h",
+ "nsRubyBaseFrame.h",
+ "nsRubyFrame.h",
+ "nsRubyTextContainerFrame.h",
+ "nsRubyTextFrame.h",
+ "nsSplittableFrame.h",
+ "nsSubDocumentFrame.h",
+ "nsTextFrame.h",
+ "nsTextFrameUtils.h",
+ "nsTextRunTransformations.h",
+ "RubyUtils.h",
+ "ScrollAnimationBezierPhysics.h",
+ "ScrollAnimationMSDPhysics.h",
+ "ScrollAnimationPhysics.h",
+ "ScrollbarActivity.h",
+ "ScrollSnap.h",
+ "TextDrawTarget.h",
+ "Visibility.h",
+]
+
+EXPORTS.mozilla += [
+ "!FrameIdList.h",
+ "!FrameTypeList.h",
+ "AnonymousContentKey.h",
+ "AspectRatio.h",
+ "AutoCopyListener.h",
+ "ColumnUtils.h",
+ "CSSAlignUtils.h",
+ "CSSOrderAwareFrameIterator.h",
+ "LayoutMessageUtils.h",
+ "nsVideoFrame.h",
+ "PrintedSheetFrame.h",
+ "ReflowInput.h",
+ "ReflowOutput.h",
+ "ScrollbarPreferences.h",
+ "ScrollGeneration.h",
+ "ScrollOrigin.h",
+ "ScrollPositionUpdate.h",
+ "ScrollSnapInfo.h",
+ "ScrollSnapTargetId.h",
+ "SelectionMovementUtils.h",
+ "ViewportFrame.h",
+ "WritingModes.h",
+]
+
+EXPORTS.mozilla.layout += [
+ "ScrollAnchorContainer.h",
+]
+
+UNIFIED_SOURCES += [
+ "AspectRatio.cpp",
+ "BlockReflowState.cpp",
+ "BRFrame.cpp",
+ "ColumnSetWrapperFrame.cpp",
+ "ColumnUtils.cpp",
+ "CSSAlignUtils.cpp",
+ "CSSOrderAwareFrameIterator.cpp",
+ "MathMLTextRunFactory.cpp",
+ "MiddleCroppingBlockFrame.cpp",
+ "nsAbsoluteContainingBlock.cpp",
+ "nsBackdropFrame.cpp",
+ "nsBlockFrame.cpp",
+ "nsBlockReflowContext.cpp",
+ "nsCanvasFrame.cpp",
+ "nsColumnSetFrame.cpp",
+ "nsContainerFrame.cpp",
+ "nsFirstLetterFrame.cpp",
+ "nsFlexContainerFrame.cpp",
+ "nsFloatManager.cpp",
+ "nsFontInflationData.cpp",
+ "nsFrameList.cpp",
+ "nsFrameSelection.cpp",
+ "nsFrameSetFrame.cpp",
+ "nsFrameState.cpp",
+ "nsGfxScrollFrame.cpp",
+ "nsGridContainerFrame.cpp",
+ "nsHTMLCanvasFrame.cpp",
+ "nsIFrame.cpp",
+ "nsILineIterator.cpp",
+ "nsImageFrame.cpp",
+ "nsImageMap.cpp",
+ "nsInlineFrame.cpp",
+ "nsIntervalSet.cpp",
+ "nsLeafFrame.cpp",
+ "nsLineBox.cpp",
+ "nsLineLayout.cpp",
+ "nsPageContentFrame.cpp",
+ "nsPageFrame.cpp",
+ "nsPageSequenceFrame.cpp",
+ "nsPlaceholderFrame.cpp",
+ "nsRubyBaseContainerFrame.cpp",
+ "nsRubyBaseFrame.cpp",
+ "nsRubyContentFrame.cpp",
+ "nsRubyFrame.cpp",
+ "nsRubyTextContainerFrame.cpp",
+ "nsRubyTextFrame.cpp",
+ "nsSplittableFrame.cpp",
+ "nsSubDocumentFrame.cpp",
+ "nsTextFrame.cpp",
+ "nsTextFrameUtils.cpp",
+ "nsTextPaintStyle.cpp",
+ "nsTextRunTransformations.cpp",
+ "nsVideoFrame.cpp",
+ "PrintedSheetFrame.cpp",
+ "ReflowInput.cpp",
+ "ReflowOutput.cpp",
+ "RubyUtils.cpp",
+ "ScrollAnchorContainer.cpp",
+ "ScrollAnimationBezierPhysics.cpp",
+ "ScrollAnimationMSDPhysics.cpp",
+ "ScrollbarActivity.cpp",
+ "ScrollPositionUpdate.cpp",
+ "ScrollSnap.cpp",
+ "ScrollSnapInfo.cpp",
+ "ScrollVelocityQueue.cpp",
+ "SelectionMovementUtils.cpp",
+ "StickyScrollContainer.cpp",
+ "ViewportFrame.cpp",
+ "WBRFrame.cpp",
+]
+
+# on win32 if we add these files to UNIFIED_SOURCES then the compiler generates
+# larger stack frames for some recursive functions that cause us to hit stack
+# overflows (see bug 1827428)
+if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["TARGET_CPU"] == "x86":
+ SOURCES += [
+ "TextOverflow.cpp",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "TextOverflow.cpp",
+ ]
+
+SOURCES += [
+ # If this file gets compiled with ScrollPositionUpdate.cpp, the specialized
+ # `operator<<` for ScrollGeneration<> won't be visible
+ "ScrollGeneration.cpp",
+]
+
+GeneratedFile(
+ "FrameIdList.h",
+ script="GenerateFrameLists.py",
+ entry_point="generate_frame_id_list_h",
+ inputs=["FrameClasses.py"],
+)
+GeneratedFile(
+ "FrameTypeList.h",
+ script="GenerateFrameLists.py",
+ entry_point="generate_frame_type_list_h",
+ inputs=["FrameClasses.py"],
+)
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../forms",
+ "../painting",
+ "../style",
+ "../tables",
+ "../xul",
+ "/docshell/base",
+ "/dom/base",
+ "/dom/html",
+ "/dom/xul",
+ "/gfx/cairo/cairo/src",
+]
+
+JAR_MANIFESTS += ["jar.mn"]
+
+CONTENT_ACCESSIBLE_FILES.html = [
+ "folder.png",
+]
+
+MOCHITEST_MANIFESTS += ["test/mochitest.toml"]
+MOCHITEST_CHROME_MANIFESTS += ["test/chrome.toml"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
diff --git a/layout/generic/nsAbsoluteContainingBlock.cpp b/layout/generic/nsAbsoluteContainingBlock.cpp
new file mode 100644
index 0000000000..3cfe7b5b2d
--- /dev/null
+++ b/layout/generic/nsAbsoluteContainingBlock.cpp
@@ -0,0 +1,886 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * code for managing absolutely positioned children of a rendering
+ * object that is a containing block for them
+ */
+
+#include "nsAbsoluteContainingBlock.h"
+
+#include "nsAtomicContainerFrame.h"
+#include "nsContainerFrame.h"
+#include "nsGkAtoms.h"
+#include "mozilla/CSSAlignUtils.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ReflowInput.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsGridContainerFrame.h"
+
+#include "mozilla/Sprintf.h"
+
+#ifdef DEBUG
+# include "nsBlockFrame.h"
+
+static void PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
+ if (NS_UNCONSTRAINEDSIZE == aSize) {
+ strcpy(aBuf, "UC");
+ } else {
+ if ((int32_t)0xdeadbeef == aSize) {
+ strcpy(aBuf, "deadbeef");
+ } else {
+ snprintf(aBuf, aBufSize, "%d", aSize);
+ }
+ }
+}
+#endif
+
+using namespace mozilla;
+
+typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
+
+void nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
+ FrameChildListID aListID,
+ nsFrameList&& aChildList) {
+ MOZ_ASSERT(mChildListID == aListID, "unexpected child list name");
+#ifdef DEBUG
+ nsIFrame::VerifyDirtyBitSet(aChildList);
+ for (nsIFrame* f : aChildList) {
+ MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent");
+ }
+#endif
+ mAbsoluteFrames = std::move(aChildList);
+}
+
+void nsAbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
+ FrameChildListID aListID,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(mChildListID == aListID, "unexpected child list");
+
+ // Append the frames to our list of absolutely positioned frames
+#ifdef DEBUG
+ nsIFrame::VerifyDirtyBitSet(aFrameList);
+#endif
+ mAbsoluteFrames.AppendFrames(nullptr, std::move(aFrameList));
+
+ // no damage to intrinsic widths, since absolutely positioned frames can't
+ // change them
+ aDelegatingFrame->PresShell()->FrameNeedsReflow(
+ aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void nsAbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
+ FrameChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(mChildListID == aListID, "unexpected child list");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
+ "inserting after sibling frame with different parent");
+
+#ifdef DEBUG
+ nsIFrame::VerifyDirtyBitSet(aFrameList);
+#endif
+ mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
+
+ // no damage to intrinsic widths, since absolutely positioned frames can't
+ // change them
+ aDelegatingFrame->PresShell()->FrameNeedsReflow(
+ aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void nsAbsoluteContainingBlock::RemoveFrame(FrameDestroyContext& aContext,
+ FrameChildListID aListID,
+ nsIFrame* aOldFrame) {
+ NS_ASSERTION(mChildListID == aListID, "unexpected child list");
+ if (nsIFrame* nif = aOldFrame->GetNextInFlow()) {
+ nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
+ }
+ mAbsoluteFrames.DestroyFrame(aContext, aOldFrame);
+}
+
+static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
+ nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) {
+ MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+ if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
+ return;
+ }
+ // We should have set the bit when reflowing the previous continuations
+ // already.
+ if (aFrame->GetPrevContinuation()) {
+ return;
+ }
+
+ auto* placeholder = aFrame->GetPlaceholderFrame();
+ MOZ_ASSERT(placeholder);
+
+ // Only fixed-pos frames can escape their containing block.
+ if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) {
+ return;
+ }
+
+ for (nsIFrame* ancestor = placeholder->GetParent(); ancestor;
+ ancestor = ancestor->GetParent()) {
+ // Walk towards the ancestor's first continuation. That's the only one that
+ // really matters, since it's the only one restyling will look at. We also
+ // flag the following continuations just so it's caught on the first
+ // early-return ones just to avoid walking them over and over.
+ do {
+ if (ancestor->DescendantMayDependOnItsStaticPosition()) {
+ return;
+ }
+ // Moving the containing block or anything above it would move our static
+ // position as well, so no need to flag it or any of its ancestors.
+ if (aFrame == aContainingBlockFrame) {
+ return;
+ }
+ ancestor->SetDescendantMayDependOnItsStaticPosition(true);
+ nsIFrame* prev = ancestor->GetPrevContinuation();
+ if (!prev) {
+ break;
+ }
+ ancestor = prev;
+ } while (true);
+ }
+}
+
+void nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aReflowStatus,
+ const nsRect& aContainingBlock,
+ AbsPosReflowFlags aFlags,
+ OverflowAreas* aOverflowAreas) {
+ // PageContentFrame replicates fixed pos children so we really don't want
+ // them contributing to overflow areas because that means we'll create new
+ // pages ad infinitum if one of them overflows the page.
+ if (aDelegatingFrame->IsPageContentFrame()) {
+ MOZ_ASSERT(mChildListID == FrameChildListID::Fixed);
+ aOverflowAreas = nullptr;
+ }
+
+ nsReflowStatus reflowStatus;
+ const bool reflowAll = aReflowInput.ShouldReflowAllKids();
+ const bool isGrid = !!(aFlags & AbsPosReflowFlags::IsGridContainerCB);
+ nsIFrame* kidFrame;
+ nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
+ for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame;
+ kidFrame = kidFrame->GetNextSibling()) {
+ bool kidNeedsReflow =
+ reflowAll || kidFrame->IsSubtreeDirty() ||
+ FrameDependsOnContainer(
+ kidFrame, !!(aFlags & AbsPosReflowFlags::CBWidthChanged),
+ !!(aFlags & AbsPosReflowFlags::CBHeightChanged));
+
+ if (kidFrame->IsSubtreeDirty()) {
+ MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
+ kidFrame, aDelegatingFrame);
+ }
+
+ nscoord availBSize = aReflowInput.AvailableBSize();
+ const nsRect& cb =
+ isGrid ? nsGridContainerFrame::GridItemCB(kidFrame) : aContainingBlock;
+ WritingMode containerWM = aReflowInput.GetWritingMode();
+ if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
+ // If we need to redo pagination on the kid, we need to reflow it.
+ // This can happen either if the available height shrunk and the
+ // kid (or its overflow that creates overflow containers) is now
+ // too large to fit in the available height, or if the available
+ // height has increased and the kid has a next-in-flow that we
+ // might need to pull from.
+ WritingMode kidWM = kidFrame->GetWritingMode();
+ if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
+ // Not sure what the right test would be here.
+ kidNeedsReflow = true;
+ } else {
+ nscoord kidBEnd = kidFrame->GetLogicalRect(cb.Size()).BEnd(kidWM);
+ nscoord kidOverflowBEnd =
+ LogicalRect(containerWM,
+ // Use ...RelativeToSelf to ignore transforms
+ kidFrame->ScrollableOverflowRectRelativeToSelf() +
+ kidFrame->GetPosition(),
+ aContainingBlock.Size())
+ .BEnd(containerWM);
+ NS_ASSERTION(kidOverflowBEnd >= kidBEnd,
+ "overflow area should be at least as large as frame rect");
+ if (kidOverflowBEnd > availBSize ||
+ (kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
+ kidNeedsReflow = true;
+ }
+ }
+ }
+ if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
+ // Reflow the frame
+ nsReflowStatus kidStatus;
+ ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
+ aFlags, kidFrame, kidStatus, aOverflowAreas);
+ MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
+ "ShouldAvoidBreakInside should prevent this from happening");
+ nsIFrame* nextFrame = kidFrame->GetNextInFlow();
+ if (!kidStatus.IsFullyComplete() &&
+ aDelegatingFrame->CanContainOverflowContainers()) {
+ // Need a continuation
+ if (!nextFrame) {
+ nextFrame = aPresContext->PresShell()
+ ->FrameConstructor()
+ ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
+ }
+ // Add it as an overflow container.
+ // XXXfr This is a hack to fix some of our printing dataloss.
+ // See bug 154892. Not sure how to do it "right" yet; probably want
+ // to keep continuations within an nsAbsoluteContainingBlock eventually.
+ tracker.Insert(nextFrame, kidStatus);
+ reflowStatus.MergeCompletionStatusFrom(kidStatus);
+ } else if (nextFrame) {
+ // Delete any continuations
+ nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
+ FrameDestroyContext context(aPresContext->PresShell());
+ nextFrame->GetParent()->DeleteNextInFlowChild(context, nextFrame, true);
+ }
+ } else {
+ tracker.Skip(kidFrame, reflowStatus);
+ if (aOverflowAreas) {
+ aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
+ }
+ }
+
+ // Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That
+ // will make sure that we end up reflowing aDelegatingFrame in cases when
+ // one of our kids interrupted. Otherwise we'd set the dirty or
+ // dirty-children bit on the kid in the condition below, and then when
+ // reflow completes and we go to mark dirty bits on all ancestors of that
+ // kid we'll immediately bail out, because the kid already has a dirty bit.
+ // In particular, we won't set any dirty bits on aDelegatingFrame, so when
+ // the following reflow happens we won't reflow the kid in question. This
+ // might be slightly suboptimal in cases where |kidFrame| itself did not
+ // interrupt, since we'll trigger a reflow of it too when it's not strictly
+ // needed. But the logic to not do that is enough more complicated, and
+ // the case enough of an edge case, that this is probably better.
+ if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
+ if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ kidFrame->MarkSubtreeDirty();
+ } else {
+ kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ }
+ }
+
+ // Abspos frames can't cause their parent to be incomplete,
+ // only overflow incomplete.
+ if (reflowStatus.IsIncomplete()) {
+ reflowStatus.SetOverflowIncomplete();
+ reflowStatus.SetNextInFlowNeedsReflow();
+ }
+
+ aReflowStatus.MergeCompletionStatusFrom(reflowStatus);
+}
+
+static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
+ return aCoord.ConvertsToLength();
+}
+static inline bool IsFixedMarginSize(const LengthPercentageOrAuto& aCoord) {
+ return aCoord.ConvertsToLength();
+}
+static inline bool IsFixedOffset(const LengthPercentageOrAuto& aCoord) {
+ return aCoord.ConvertsToLength();
+}
+
+bool nsAbsoluteContainingBlock::FrameDependsOnContainer(nsIFrame* f,
+ bool aCBWidthChanged,
+ bool aCBHeightChanged) {
+ const nsStylePosition* pos = f->StylePosition();
+ // See if f's position might have changed because it depends on a
+ // placeholder's position.
+ if (pos->NeedsHypotheticalPositionIfAbsPos()) {
+ return true;
+ }
+ if (!aCBWidthChanged && !aCBHeightChanged) {
+ // skip getting style data
+ return false;
+ }
+ const nsStylePadding* padding = f->StylePadding();
+ const nsStyleMargin* margin = f->StyleMargin();
+ WritingMode wm = f->GetWritingMode();
+ if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) {
+ // See if f's inline-size might have changed.
+ // If margin-inline-start/end, padding-inline-start/end,
+ // inline-size, min/max-inline-size are all lengths, 'none', or enumerated,
+ // then our frame isize does not depend on the parent isize.
+ // Note that borders never depend on the parent isize.
+ // XXX All of the enumerated values except -moz-available are ok too.
+ if (pos->ISizeDependsOnContainer(wm) ||
+ pos->MinISizeDependsOnContainer(wm) ||
+ pos->MaxISizeDependsOnContainer(wm) ||
+ !IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) ||
+ !IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) {
+ return true;
+ }
+
+ // See if f's position might have changed. If we're RTL then the
+ // rules are slightly different. We'll assume percentage or auto
+ // margins will always induce a dependency on the size
+ if (!IsFixedMarginSize(margin->mMargin.GetIStart(wm)) ||
+ !IsFixedMarginSize(margin->mMargin.GetIEnd(wm))) {
+ return true;
+ }
+ }
+ if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
+ // See if f's block-size might have changed.
+ // If margin-block-start/end, padding-block-start/end,
+ // min-block-size, and max-block-size are all lengths or 'none',
+ // and bsize is a length or bsize and bend are auto and bstart is not auto,
+ // then our frame bsize does not depend on the parent bsize.
+ // Note that borders never depend on the parent bsize.
+ //
+ // FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
+ // lengths?
+ if ((pos->BSizeDependsOnContainer(wm) &&
+ !(pos->BSize(wm).IsAuto() && pos->mOffset.GetBEnd(wm).IsAuto() &&
+ !pos->mOffset.GetBStart(wm).IsAuto())) ||
+ pos->MinBSizeDependsOnContainer(wm) ||
+ pos->MaxBSizeDependsOnContainer(wm) ||
+ !IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) ||
+ !IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) {
+ return true;
+ }
+
+ // See if f's position might have changed.
+ if (!IsFixedMarginSize(margin->mMargin.GetBStart(wm)) ||
+ !IsFixedMarginSize(margin->mMargin.GetBEnd(wm))) {
+ return true;
+ }
+ }
+
+ // Since we store coordinates relative to top and left, the position
+ // of a frame depends on that of its container if it is fixed relative
+ // to the right or bottom, or if it is positioned using percentages
+ // relative to the left or top. Because of the dependency on the
+ // sides (left and top) that we use to store coordinates, these tests
+ // are easier to do using physical coordinates rather than logical.
+ if (aCBWidthChanged) {
+ if (!IsFixedOffset(pos->mOffset.Get(eSideLeft))) {
+ return true;
+ }
+ // Note that even if 'left' is a length, our position can still
+ // depend on the containing block width, because if our direction or
+ // writing-mode moves from right to left (in either block or inline
+ // progression) and 'right' is not 'auto', we will discard 'left'
+ // and be positioned relative to the containing block right edge.
+ // 'left' length and 'right' auto is the only combination we can be
+ // sure of.
+ if ((wm.GetInlineDir() == WritingMode::eInlineRTL ||
+ wm.GetBlockDir() == WritingMode::eBlockRL) &&
+ !pos->mOffset.Get(eSideRight).IsAuto()) {
+ return true;
+ }
+ }
+ if (aCBHeightChanged) {
+ if (!IsFixedOffset(pos->mOffset.Get(eSideTop))) {
+ return true;
+ }
+ // See comment above for width changes.
+ if (wm.GetInlineDir() == WritingMode::eInlineBTT &&
+ !pos->mOffset.Get(eSideBottom).IsAuto()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsAbsoluteContainingBlock::DestroyFrames(DestroyContext& aContext) {
+ mAbsoluteFrames.DestroyFrames(aContext);
+}
+
+void nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
+ DoMarkFramesDirty(false);
+}
+
+void nsAbsoluteContainingBlock::MarkAllFramesDirty() {
+ DoMarkFramesDirty(true);
+}
+
+void nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
+ for (nsIFrame* kidFrame : mAbsoluteFrames) {
+ if (aMarkAllDirty) {
+ kidFrame->MarkSubtreeDirty();
+ } else if (FrameDependsOnContainer(kidFrame, true, true)) {
+ // Add the weakest flags that will make sure we reflow this frame later
+ kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ }
+}
+
+// Given an out-of-flow frame, this method returns the parent frame of its
+// placeholder frame or null if it doesn't have a placeholder for some reason.
+static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
+ nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
+ return placeholder ? placeholder->GetParent() : nullptr;
+}
+
+/**
+ * This function returns the offset of an abs/fixed-pos child's static
+ * position, with respect to the "start" corner of its alignment container,
+ * according to CSS Box Alignment. This function only operates in a single
+ * axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
+ * parameter.
+ *
+ * @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
+ * @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
+ * the opportunity to reflow), in terms of
+ * aAbsPosCBWM.
+ * @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
+ * @param aPlaceholderContainer The parent of the child frame's corresponding
+ * placeholder frame, cast to a nsContainerFrame.
+ * (This will help us choose which alignment enum
+ * we should use for the child.)
+ * @param aAbsPosCBWM The child frame's containing block's WritingMode.
+ * @param aAbsPosCBAxis The axis (of the containing block) that we should
+ * be doing this computation for.
+ */
+static nscoord OffsetToAlignedStaticPos(const ReflowInput& aKidReflowInput,
+ const LogicalSize& aKidSizeInAbsPosCBWM,
+ const LogicalSize& aAbsPosCBSize,
+ nsContainerFrame* aPlaceholderContainer,
+ WritingMode aAbsPosCBWM,
+ LogicalAxis aAbsPosCBAxis) {
+ if (!aPlaceholderContainer) {
+ // (The placeholder container should be the thing that kicks this whole
+ // process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it
+ // should exist... but bail gracefully if it doesn't.)
+ NS_ERROR(
+ "Missing placeholder-container when computing a "
+ "CSS Box Alignment static position");
+ return 0;
+ }
+
+ // (Most of this function is simply preparing args that we'll pass to
+ // AlignJustifySelf at the end.)
+
+ // NOTE: Our alignment container is aPlaceholderContainer's content-box
+ // (or an area within it, if aPlaceholderContainer is a grid). So, we'll
+ // perform most of our arithmetic/alignment in aPlaceholderContainer's
+ // WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
+ // container" in variables below.
+ WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
+
+ // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
+ // writing-mode.
+ LogicalAxis pcAxis =
+ (pcWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
+ : aAbsPosCBAxis);
+
+ const bool placeholderContainerIsContainingBlock =
+ aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
+
+ LayoutFrameType parentType = aPlaceholderContainer->Type();
+ LogicalSize alignAreaSize(pcWM);
+ if (parentType == LayoutFrameType::FlexContainer) {
+ // We store the frame rect in FinishAndStoreOverflow, which runs _after_
+ // reflowing the absolute frames, so handle the special case of the frame
+ // being the actual containing block here, by getting the size from
+ // aAbsPosCBSize.
+ //
+ // The alignment container is the flex container's content box.
+ if (placeholderContainerIsContainingBlock) {
+ alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
+ // aAbsPosCBSize is the padding-box, so substract the padding to get the
+ // content box.
+ alignAreaSize -=
+ aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
+ } else {
+ alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
+ LogicalMargin pcBorderPadding =
+ aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
+ alignAreaSize -= pcBorderPadding.Size(pcWM);
+ }
+ } else if (parentType == LayoutFrameType::GridContainer) {
+ // This abspos elem's parent is a grid container. Per CSS Grid 10.1 & 10.2:
+ // - If the grid container *also* generates the abspos containing block (a
+ // grid area) for this abspos child, we use that abspos containing block as
+ // the alignment container, too. (And its size is aAbsPosCBSize.)
+ // - Otherwise, we use the grid's padding box as the alignment container.
+ // https://drafts.csswg.org/css-grid/#static-position
+ if (placeholderContainerIsContainingBlock) {
+ // The alignment container is the grid area that we're using as the
+ // absolute containing block.
+ alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
+ } else {
+ // The alignment container is a the grid container's content box (which
+ // we can get by subtracting away its border & padding from frame's size):
+ alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
+ LogicalMargin pcBorderPadding =
+ aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
+ alignAreaSize -= pcBorderPadding.Size(pcWM);
+ }
+ } else {
+ NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
+ return 0; // (leave the child at the start of its alignment container)
+ }
+
+ nscoord alignAreaSizeInAxis = (pcAxis == eLogicalAxisInline)
+ ? alignAreaSize.ISize(pcWM)
+ : alignAreaSize.BSize(pcWM);
+
+ AlignJustifyFlags flags = AlignJustifyFlags::IgnoreAutoMargins;
+ StyleAlignFlags alignConst =
+ aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput,
+ pcAxis);
+ // If the safe bit in alignConst is set, set the safe flag in |flags|.
+ // Note: If no <overflow-position> is specified, we behave as 'unsafe'.
+ // This doesn't quite match the css-align spec, which has an [at-risk]
+ // "smart default" behavior with some extra nuance about scroll containers.
+ if (alignConst & StyleAlignFlags::SAFE) {
+ flags |= AlignJustifyFlags::OverflowSafe;
+ }
+ alignConst &= ~StyleAlignFlags::FLAG_BITS;
+
+ // Find out if placeholder-container & the OOF child have the same start-sides
+ // in the placeholder-container's pcAxis.
+ WritingMode kidWM = aKidReflowInput.GetWritingMode();
+ if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
+ flags |= AlignJustifyFlags::SameSide;
+ }
+
+ // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
+ // converted 'baseline'/'last baseline' enums to their fallback values.)
+ const nscoord baselineAdjust = nscoord(0);
+
+ // AlignJustifySelf operates in the kid's writing mode, so we need to
+ // represent the child's size and the desired axis in that writing mode:
+ LogicalSize kidSizeInOwnWM =
+ aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
+ LogicalAxis kidAxis =
+ (kidWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
+ : aAbsPosCBAxis);
+
+ nscoord offset = CSSAlignUtils::AlignJustifySelf(
+ alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
+ aKidReflowInput, kidSizeInOwnWM);
+
+ // "offset" is in terms of the CSS Box Alignment container (i.e. it's in
+ // terms of pcWM). But our return value needs to in terms of the containing
+ // block's writing mode, which might have the opposite directionality in the
+ // given axis. In that case, we just need to negate "offset" when returning,
+ // to make it have the right effect as an offset for coordinates in the
+ // containing block's writing mode.
+ if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
+ return -offset;
+ }
+ return offset;
+}
+
+void nsAbsoluteContainingBlock::ResolveSizeDependentOffsets(
+ nsPresContext* aPresContext, ReflowInput& aKidReflowInput,
+ const LogicalSize& aKidSize, const LogicalMargin& aMargin,
+ LogicalMargin* aOffsets, LogicalSize* aLogicalCBSize) {
+ WritingMode wm = aKidReflowInput.GetWritingMode();
+ WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
+
+ // Now that we know the child's size, we resolve any sentinel values in its
+ // IStart/BStart offset coordinates that depend on that size.
+ // * NS_AUTOOFFSET indicates that the child's position in the given axis
+ // is determined by its end-wards offset property, combined with its size and
+ // available space. e.g.: "top: auto; height: auto; bottom: 50px"
+ // * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
+ // static position in that axis, *and* its static position is determined by
+ // the axis-appropriate css-align property (which may require the child's
+ // size, e.g. to center it within the parent).
+ if ((NS_AUTOOFFSET == aOffsets->IStart(outerWM)) ||
+ (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) ||
+ aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
+ aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
+ if (-1 == aLogicalCBSize->ISize(wm)) {
+ // Get the containing block width/height
+ const ReflowInput* parentRI = aKidReflowInput.mParentReflowInput;
+ *aLogicalCBSize = aKidReflowInput.ComputeContainingBlockRectangle(
+ aPresContext, parentRI);
+ }
+
+ const LogicalSize logicalCBSizeOuterWM =
+ aLogicalCBSize->ConvertTo(outerWM, wm);
+
+ // placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
+ // clauses. We declare it at this scope so we can avoid having to look
+ // it up twice (and only look it up if it's needed).
+ nsContainerFrame* placeholderContainer = nullptr;
+
+ if (NS_AUTOOFFSET == aOffsets->IStart(outerWM)) {
+ NS_ASSERTION(NS_AUTOOFFSET != aOffsets->IEnd(outerWM),
+ "Can't solve for both start and end");
+ aOffsets->IStart(outerWM) =
+ logicalCBSizeOuterWM.ISize(outerWM) - aOffsets->IEnd(outerWM) -
+ aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
+ } else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
+ placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
+ nscoord offset = OffsetToAlignedStaticPos(
+ aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
+ outerWM, eLogicalAxisInline);
+ // Shift IStart from its current position (at start corner of the
+ // alignment container) by the returned offset. And set IEnd to the
+ // distance between the kid's end edge to containing block's end edge.
+ aOffsets->IStart(outerWM) += offset;
+ aOffsets->IEnd(outerWM) =
+ logicalCBSizeOuterWM.ISize(outerWM) -
+ (aOffsets->IStart(outerWM) + aKidSize.ISize(outerWM));
+ }
+
+ if (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) {
+ aOffsets->BStart(outerWM) =
+ logicalCBSizeOuterWM.BSize(outerWM) - aOffsets->BEnd(outerWM) -
+ aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
+ } else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
+ if (!placeholderContainer) {
+ placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
+ }
+ nscoord offset = OffsetToAlignedStaticPos(
+ aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
+ outerWM, eLogicalAxisBlock);
+ // Shift BStart from its current position (at start corner of the
+ // alignment container) by the returned offset. And set BEnd to the
+ // distance between the kid's end edge to containing block's end edge.
+ aOffsets->BStart(outerWM) += offset;
+ aOffsets->BEnd(outerWM) =
+ logicalCBSizeOuterWM.BSize(outerWM) -
+ (aOffsets->BStart(outerWM) + aKidSize.BSize(outerWM));
+ }
+ aKidReflowInput.SetComputedLogicalOffsets(outerWM, *aOffsets);
+ }
+}
+
+void nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(
+ ReflowInput& aKidReflowInput, const LogicalSize* aLogicalCBSize,
+ const LogicalSize& aKidSize, LogicalMargin& aMargin,
+ LogicalMargin& aOffsets) {
+ MOZ_ASSERT(aKidReflowInput.mFrame->HasIntrinsicKeywordForBSize());
+
+ WritingMode wm = aKidReflowInput.GetWritingMode();
+ WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
+
+ const LogicalSize kidSizeInWM = aKidSize.ConvertTo(wm, outerWM);
+ LogicalMargin marginInWM = aMargin.ConvertTo(wm, outerWM);
+ LogicalMargin offsetsInWM = aOffsets.ConvertTo(wm, outerWM);
+
+ // No need to substract border sizes because aKidSize has it included
+ // already. Also, if any offset is auto, the auto margin resolves to zero.
+ // https://drafts.csswg.org/css-position-3/#abspos-margins
+ const bool autoOffset = offsetsInWM.BEnd(wm) == NS_AUTOOFFSET ||
+ offsetsInWM.BStart(wm) == NS_AUTOOFFSET;
+ nscoord availMarginSpace =
+ autoOffset ? 0
+ : aLogicalCBSize->BSize(wm) - kidSizeInWM.BSize(wm) -
+ offsetsInWM.BStartEnd(wm) - marginInWM.BStartEnd(wm);
+
+ const auto& styleMargin = aKidReflowInput.mStyleMargin;
+ if (wm.IsOrthogonalTo(outerWM)) {
+ ReflowInput::ComputeAbsPosInlineAutoMargin(
+ availMarginSpace, outerWM,
+ styleMargin->mMargin.GetIStart(outerWM).IsAuto(),
+ styleMargin->mMargin.GetIEnd(outerWM).IsAuto(), aMargin, aOffsets);
+ } else {
+ ReflowInput::ComputeAbsPosBlockAutoMargin(
+ availMarginSpace, outerWM,
+ styleMargin->mMargin.GetBStart(outerWM).IsAuto(),
+ styleMargin->mMargin.GetBEnd(outerWM).IsAuto(), aMargin, aOffsets);
+ }
+
+ aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin);
+ aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets);
+
+ nsMargin* propValue =
+ aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty());
+ // InitOffsets should've created a UsedMarginProperty for us, if any margin is
+ // auto.
+ MOZ_ASSERT_IF(styleMargin->HasInlineAxisAuto(outerWM) ||
+ styleMargin->HasBlockAxisAuto(outerWM),
+ propValue);
+ if (propValue) {
+ *propValue = aMargin.GetPhysicalMargin(outerWM);
+ }
+}
+
+// XXX Optimize the case where it's a resize reflow and the absolutely
+// positioned child has the exact same size and position and skip the
+// reflow...
+
+// When bug 154892 is checked in, make sure that when
+// mChildListID == FrameChildListID::Fixed, the height is unconstrained.
+// since we don't allow replicated frames to split.
+
+void nsAbsoluteContainingBlock::ReflowAbsoluteFrame(
+ nsIFrame* aDelegatingFrame, nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput, const nsRect& aContainingBlock,
+ AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus,
+ OverflowAreas* aOverflowAreas) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
+ printf("abs pos ");
+ nsAutoString name;
+ aKidFrame->GetFrameName(name);
+ printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
+
+ char width[16];
+ char height[16];
+ PrettyUC(aReflowInput.AvailableWidth(), width, 16);
+ PrettyUC(aReflowInput.AvailableHeight(), height, 16);
+ printf(" a=%s,%s ", width, height);
+ PrettyUC(aReflowInput.ComputedWidth(), width, 16);
+ PrettyUC(aReflowInput.ComputedHeight(), height, 16);
+ printf("c=%s,%s \n", width, height);
+ }
+ AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
+#endif // DEBUG
+
+ WritingMode wm = aKidFrame->GetWritingMode();
+ LogicalSize logicalCBSize(wm, aContainingBlock.Size());
+ nscoord availISize = logicalCBSize.ISize(wm);
+ if (availISize == -1) {
+ NS_ASSERTION(
+ aReflowInput.ComputedSize(wm).ISize(wm) != NS_UNCONSTRAINEDSIZE,
+ "Must have a useful inline-size _somewhere_");
+ availISize = aReflowInput.ComputedSizeWithPadding(wm).ISize(wm);
+ }
+
+ ReflowInput::InitFlags initFlags;
+ if (aFlags & AbsPosReflowFlags::IsGridContainerCB) {
+ // When a grid container generates the abs.pos. CB for a *child* then
+ // the static position is determined via CSS Box Alignment within the
+ // abs.pos. CB (a grid area, i.e. a piece of the grid). In this scenario,
+ // due to the multiple coordinate spaces in play, we use a convenience flag
+ // to simply have the child's ReflowInput give it a static position at its
+ // abs.pos. CB origin, and then we'll align & offset it from there.
+ nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
+ if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
+ initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin;
+ }
+ }
+
+ bool constrainBSize =
+ (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) &&
+
+ // Don't split if told not to (e.g. for fixed frames)
+ (aFlags & AbsPosReflowFlags::ConstrainHeight) &&
+
+ // XXX we don't handle splitting frames for inline absolute containing
+ // blocks yet
+ !aDelegatingFrame->IsInlineFrame() &&
+
+ // Bug 1588623: Support splitting absolute positioned multicol containers.
+ !aKidFrame->IsColumnSetWrapperFrame() &&
+
+ // Don't split things below the fold. (Ideally we shouldn't *have*
+ // anything totally below the fold, but we can't position frames
+ // across next-in-flow breaks yet.
+ (aKidFrame->GetLogicalRect(aContainingBlock.Size()).BStart(wm) <=
+ aReflowInput.AvailableBSize());
+
+ // Get the border values
+ const WritingMode outerWM = aReflowInput.GetWritingMode();
+ const LogicalMargin border = aDelegatingFrame->GetLogicalUsedBorder(outerWM);
+
+ const nscoord availBSize = constrainBSize
+ ? aReflowInput.AvailableBSize() -
+ border.ConvertTo(wm, outerWM).BStart(wm)
+ : NS_UNCONSTRAINEDSIZE;
+
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
+ LogicalSize(wm, availISize, availBSize),
+ Some(logicalCBSize), initFlags);
+
+ if (nscoord kidAvailBSize = kidReflowInput.AvailableBSize();
+ kidAvailBSize != NS_UNCONSTRAINEDSIZE) {
+ // Shrink available block-size if it's constrained.
+ kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
+ const nscoord kidOffsetBStart =
+ kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
+ if (NS_AUTOOFFSET != kidOffsetBStart) {
+ kidAvailBSize -= kidOffsetBStart;
+ }
+ kidReflowInput.SetAvailableBSize(kidAvailBSize);
+ }
+
+ // Do the reflow
+ ReflowOutput kidDesiredSize(kidReflowInput);
+ aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
+
+ // Position the child relative to our padding edge. Don't do this for popups,
+ // which handle their own positioning.
+ if (!aKidFrame->IsMenuPopupFrame()) {
+ const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
+
+ LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
+ LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM);
+
+ // If we're doing CSS Box Alignment in either axis, that will apply the
+ // margin for us in that axis (since the thing that's aligned is the margin
+ // box). So, we clear out the margin here to avoid applying it twice.
+ if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
+ margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
+ }
+ if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
+ margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
+ }
+
+ // If we're solving for start in either inline or block direction,
+ // then compute it now that we know the dimensions.
+ ResolveSizeDependentOffsets(aPresContext, kidReflowInput, kidSize, margin,
+ &offsets, &logicalCBSize);
+
+ if (kidReflowInput.mFrame->HasIntrinsicKeywordForBSize()) {
+ ResolveAutoMarginsAfterLayout(kidReflowInput, &logicalCBSize, kidSize,
+ margin, offsets);
+ }
+
+ LogicalRect rect(outerWM,
+ border.StartOffset(outerWM) +
+ offsets.StartOffset(outerWM) +
+ margin.StartOffset(outerWM),
+ kidSize);
+ nsRect r = rect.GetPhysicalRect(
+ outerWM, logicalCBSize.GetPhysicalSize(wm) +
+ border.Size(outerWM).GetPhysicalSize(outerWM));
+
+ // Offset the frame rect by the given origin of the absolute containing
+ // block.
+ r.x += aContainingBlock.x;
+ r.y += aContainingBlock.y;
+
+ aKidFrame->SetRect(r);
+
+ nsView* view = aKidFrame->GetView();
+ if (view) {
+ // Size and position the view and set its opacity, visibility, content
+ // transparency, and clip
+ nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
+ kidDesiredSize.InkOverflow());
+ } else {
+ nsContainerFrame::PositionChildViews(aKidFrame);
+ }
+ }
+
+ aKidFrame->DidReflow(aPresContext, &kidReflowInput);
+
+ const nsRect r = aKidFrame->GetRect();
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
+ printf("abs pos ");
+ nsAutoString name;
+ aKidFrame->GetFrameName(name);
+ printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
+ printf("%p rect=%d,%d,%d,%d\n", static_cast<void*>(aKidFrame), r.x, r.y,
+ r.width, r.height);
+ }
+#endif
+
+ if (aOverflowAreas) {
+ aOverflowAreas->UnionWith(kidDesiredSize.mOverflowAreas + r.TopLeft());
+ }
+}
diff --git a/layout/generic/nsAbsoluteContainingBlock.h b/layout/generic/nsAbsoluteContainingBlock.h
new file mode 100644
index 0000000000..fffed04dca
--- /dev/null
+++ b/layout/generic/nsAbsoluteContainingBlock.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * code for managing absolutely positioned children of a rendering
+ * object that is a containing block for them
+ */
+
+#ifndef nsAbsoluteContainingBlock_h___
+#define nsAbsoluteContainingBlock_h___
+
+#include "nsFrameList.h"
+#include "nsIFrame.h"
+#include "mozilla/TypedEnumBits.h"
+
+class nsContainerFrame;
+class nsPresContext;
+
+/**
+ * This class contains the logic for being an absolute containing block. This
+ * class is used within viewport frames (for frames representing content with
+ * fixed position) and blocks (for frames representing absolutely positioned
+ * content), since each set of frames is absolutely positioned with respect to
+ * its parent.
+ *
+ * There is no principal child list, just a named child list which contains
+ * the absolutely positioned frames (FrameChildListID::Absolute or
+ * FrameChildListID::Fixed).
+ *
+ * All functions include as the first argument the frame that is delegating
+ * the request.
+ */
+class nsAbsoluteContainingBlock {
+ using ReflowInput = mozilla::ReflowInput;
+
+ public:
+ explicit nsAbsoluteContainingBlock(mozilla::FrameChildListID aChildListID)
+#ifdef DEBUG
+ : mChildListID(aChildListID)
+#endif
+ {
+ MOZ_ASSERT(mChildListID == mozilla::FrameChildListID::Absolute ||
+ mChildListID == mozilla::FrameChildListID::Fixed,
+ "should either represent position:fixed or absolute content");
+ }
+
+ const nsFrameList& GetChildList() const { return mAbsoluteFrames; }
+ void AppendChildList(nsTArray<mozilla::FrameChildList>* aLists,
+ mozilla::FrameChildListID aListID) const {
+ NS_ASSERTION(aListID == mChildListID, "wrong list ID");
+ GetChildList().AppendIfNonempty(aLists, aListID);
+ }
+
+ void SetInitialChildList(nsIFrame* aDelegatingFrame,
+ mozilla::FrameChildListID aListID,
+ nsFrameList&& aChildList);
+ void AppendFrames(nsIFrame* aDelegatingFrame,
+ mozilla::FrameChildListID aListID,
+ nsFrameList&& aFrameList);
+ void InsertFrames(nsIFrame* aDelegatingFrame,
+ mozilla::FrameChildListID aListID, nsIFrame* aPrevFrame,
+ nsFrameList&& aFrameList);
+ void RemoveFrame(mozilla::FrameDestroyContext&, mozilla::FrameChildListID,
+ nsIFrame*);
+
+ enum class AbsPosReflowFlags {
+ ConstrainHeight = 0x1,
+ CBWidthChanged = 0x2,
+ CBHeightChanged = 0x4,
+ CBWidthAndHeightChanged = CBWidthChanged | CBHeightChanged,
+ IsGridContainerCB = 0x8,
+ };
+
+ /**
+ * Called by the delegating frame after it has done its reflow first. This
+ * function will reflow any absolutely positioned child frames that need to
+ * be reflowed, e.g., because the absolutely positioned child frame has
+ * 'auto' for an offset, or a percentage based width or height.
+ *
+ * @param aOverflowAreas, if non-null, is unioned with (in the local
+ * coordinate space) the overflow areas of the absolutely positioned
+ * children.
+ *
+ * @param aReflowStatus is assumed to be already-initialized, e.g. with the
+ * status of the delegating frame's main reflow. This function merges in the
+ * statuses of the absolutely positioned children's reflows.
+ *
+ * @param aFlags zero or more AbsPosReflowFlags
+ */
+ void Reflow(nsContainerFrame* aDelegatingFrame, nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput, nsReflowStatus& aReflowStatus,
+ const nsRect& aContainingBlock, AbsPosReflowFlags aFlags,
+ mozilla::OverflowAreas* aOverflowAreas);
+
+ using DestroyContext = nsIFrame::DestroyContext;
+ void DestroyFrames(DestroyContext&);
+
+ bool HasAbsoluteFrames() const { return mAbsoluteFrames.NotEmpty(); }
+
+ /**
+ * Mark our size-dependent absolute frames with NS_FRAME_HAS_DIRTY_CHILDREN
+ * so that we'll make sure to reflow them.
+ */
+ void MarkSizeDependentFramesDirty();
+
+ /**
+ * Mark all our absolute frames with NS_FRAME_IS_DIRTY.
+ */
+ void MarkAllFramesDirty();
+
+ protected:
+ /**
+ * Returns true if the position of aFrame depends on the position of
+ * its placeholder or if the position or size of aFrame depends on a
+ * containing block dimension that changed.
+ */
+ bool FrameDependsOnContainer(nsIFrame* aFrame, bool aCBWidthChanged,
+ bool aCBHeightChanged);
+
+ /**
+ * After an abspos child's size is known, this method can be used to
+ * resolve size-dependent values in the ComputedLogicalOffsets on its
+ * reflow input. (This may involve resolving the inline dimension of
+ * aLogicalCBSize, too; hence, that variable is an in/outparam.)
+ *
+ * aKidSize, aMargin, aOffsets, and aLogicalCBSize are all expected to be
+ * represented in terms of the absolute containing block's writing-mode.
+ */
+ void ResolveSizeDependentOffsets(nsPresContext* aPresContext,
+ ReflowInput& aKidReflowInput,
+ const mozilla::LogicalSize& aKidSize,
+ const mozilla::LogicalMargin& aMargin,
+ mozilla::LogicalMargin* aOffsets,
+ mozilla::LogicalSize* aLogicalCBSize);
+
+ /**
+ * For frames that have intrinsic block sizes, since we want to use the
+ * frame's actual instrinsic block-size, we don't compute margins in
+ * InitAbsoluteConstraints because the block-size isn't computed yet. This
+ * method computes the margins for them after layout.
+ * aMargin and aOffsets are both outparams (though we only touch aOffsets if
+ * the position is overconstrained)
+ */
+ void ResolveAutoMarginsAfterLayout(ReflowInput& aKidReflowInput,
+ const mozilla::LogicalSize* aLogicalCBSize,
+ const mozilla::LogicalSize& aKidSize,
+ mozilla::LogicalMargin& aMargin,
+ mozilla::LogicalMargin& aOffsets);
+
+ void ReflowAbsoluteFrame(nsIFrame* aDelegatingFrame,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ const nsRect& aContainingBlockRect,
+ AbsPosReflowFlags aFlags, nsIFrame* aKidFrame,
+ nsReflowStatus& aStatus,
+ mozilla::OverflowAreas* aOverflowAreas);
+
+ /**
+ * Mark our absolute frames dirty.
+ * @param aMarkAllDirty if true, all will be marked with NS_FRAME_IS_DIRTY.
+ * Otherwise, the size-dependant ones will be marked with
+ * NS_FRAME_HAS_DIRTY_CHILDREN.
+ */
+ void DoMarkFramesDirty(bool aMarkAllDirty);
+
+ protected:
+ nsFrameList mAbsoluteFrames; // additional named child list
+
+#ifdef DEBUG
+ mozilla::FrameChildListID const
+ mChildListID; // FrameChildListID::Fixed or FrameChildListID::Absolute
+#endif
+};
+
+namespace mozilla {
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(
+ nsAbsoluteContainingBlock::AbsPosReflowFlags)
+}
+#endif /* nsnsAbsoluteContainingBlock_h___ */
diff --git a/layout/generic/nsAtomicContainerFrame.h b/layout/generic/nsAtomicContainerFrame.h
new file mode 100644
index 0000000000..8eeeba74ca
--- /dev/null
+++ b/layout/generic/nsAtomicContainerFrame.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for rendering objects that need child lists but behave like leaf
+ */
+
+#ifndef nsAtomicContainerFrame_h___
+#define nsAtomicContainerFrame_h___
+
+#include "nsContainerFrame.h"
+
+/**
+ * This class is for frames which need child lists but act like a leaf
+ * frame. In general, all frames of elements laid out according to the
+ * CSS box model would need child list for ::backdrop in case they are
+ * in fullscreen, while some of them still want leaf frame behavior.
+ */
+class nsAtomicContainerFrame : public nsContainerFrame {
+ public:
+ NS_DECL_ABSTRACT_FRAME(nsAtomicContainerFrame)
+
+ // Bypass the nsContainerFrame/nsSplittableFrame impl of the following
+ // methods so we behave like a leaf frame.
+ FrameSearchResult PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) override {
+ return nsIFrame::PeekOffsetNoAmount(aForward, aOffset);
+ }
+ FrameSearchResult PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset,
+ PeekOffsetCharacterOptions aOptions =
+ PeekOffsetCharacterOptions()) override {
+ return nsIFrame::PeekOffsetCharacter(aForward, aOffset, aOptions);
+ }
+
+ protected:
+ nsAtomicContainerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : nsContainerFrame(aStyle, aPresContext, aID) {}
+};
+
+#endif // nsAtomicContainerFrame_h___
diff --git a/layout/generic/nsBackdropFrame.cpp b/layout/generic/nsBackdropFrame.cpp
new file mode 100644
index 0000000000..20d3fcd368
--- /dev/null
+++ b/layout/generic/nsBackdropFrame.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "::backdrop" */
+
+#include "nsBackdropFrame.h"
+
+#include "mozilla/PresShell.h"
+#include "nsDisplayList.h"
+
+using namespace mozilla;
+
+NS_IMPL_FRAMEARENA_HELPERS(nsBackdropFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsBackdropFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Backdrop"_ns, aResult);
+}
+#endif
+
+/* virtual */
+ComputedStyle* nsBackdropFrame::GetParentComputedStyle(
+ nsIFrame** aProviderFrame) const {
+ // Style context of backdrop pseudo-element does not inherit from
+ // any element, per the Fullscreen API spec.
+ *aProviderFrame = nullptr;
+ return nullptr;
+}
+
+/* virtual */
+void nsBackdropFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsBackdropFrame");
+ // We want this frame to always be there even if its display value is
+ // none or contents so that we can respond to style change on it. To
+ // support those values, we skip painting ourselves in those cases.
+ auto display = StyleDisplay()->mDisplay;
+ if (display == mozilla::StyleDisplay::None ||
+ display == mozilla::StyleDisplay::Contents) {
+ return;
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+}
+
+/* virtual */
+LogicalSize nsBackdropFrame::ComputeAutoSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ // Note that this frame is a child of the viewport frame.
+ LogicalSize result(aWM, 0xdeadbeef, NS_UNCONSTRAINEDSIZE);
+ if (aFlags.contains(ComputeSizeFlag::ShrinkWrap)) {
+ result.ISize(aWM) = 0;
+ } else {
+ result.ISize(aWM) =
+ aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
+ }
+ return result;
+}
+
+/* virtual */
+void nsBackdropFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsBackdropFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ // Note that this frame is a child of the viewport frame.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ aDesiredSize.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm));
+}
diff --git a/layout/generic/nsBackdropFrame.h b/layout/generic/nsBackdropFrame.h
new file mode 100644
index 0000000000..acc509e593
--- /dev/null
+++ b/layout/generic/nsBackdropFrame.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "::backdrop" */
+
+#ifndef nsBackdropFrame_h___
+#define nsBackdropFrame_h___
+
+#include "nsIFrame.h"
+
+class nsBackdropFrame final : public nsIFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsBackdropFrame)
+
+ explicit nsBackdropFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID) {}
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+ virtual ComputedStyle* GetParentComputedStyle(
+ nsIFrame** aProviderFrame) const override;
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+ mozilla::LogicalSize ComputeAutoSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+};
+
+#endif // nsBackdropFrame_h___
diff --git a/layout/generic/nsBlockDebugFlags.h b/layout/generic/nsBlockDebugFlags.h
new file mode 100644
index 0000000000..068b729a9b
--- /dev/null
+++ b/layout/generic/nsBlockDebugFlags.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBlockDebugFlags_h__
+#define nsBlockDebugFlags_h__
+
+#undef NOISY_FIRST_LETTER // enables debug output for first-letter specific
+ // layout
+#undef NOISY_FINAL_SIZE // enables debug output for desired width/height
+ // computation, once all children have been reflowed
+#undef NOISY_REMOVE_FRAME
+#undef NOISY_COMBINED_AREA // enables debug output for combined area
+ // computation
+#undef NOISY_BLOCK_DIR_MARGINS
+#undef NOISY_REFLOW_REASON // gives a little info about why each reflow was
+ // requested
+#undef REFLOW_STATUS_COVERAGE // I think this is most useful for printing, to
+ // see which frames return "incomplete"
+#undef NOISY_BLOCK_INVALIDATE // enables debug output for all calls to
+ // invalidate
+#undef REALLY_NOISY_REFLOW // some extra debug info
+
+#endif // nsBlockDebugFlags_h__
diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp
new file mode 100644
index 0000000000..54976ecc47
--- /dev/null
+++ b/layout/generic/nsBlockFrame.cpp
@@ -0,0 +1,8626 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * rendering object for CSS display:block, inline-block, and list-item
+ * boxes, also used for various anonymous boxes
+ */
+
+#include "nsBlockFrame.h"
+
+#include "gfxContext.h"
+
+#include "mozilla/AppUnits.h"
+#include "mozilla/Baseline.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/ToString.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsCSSRendering.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "nsBlockReflowContext.h"
+#include "BlockReflowState.h"
+#include "nsFontMetrics.h"
+#include "nsGenericHTMLElement.h"
+#include "nsLineBox.h"
+#include "nsLineLayout.h"
+#include "nsPlaceholderFrame.h"
+#include "nsStyleConsts.h"
+#include "nsFrameManager.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsHTMLParts.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValueInlines.h"
+#include "mozilla/Sprintf.h"
+#include "nsFloatManager.h"
+#include "prenv.h"
+#include "nsError.h"
+#include "nsIScrollableFrame.h"
+#include <algorithm>
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSFrameConstructor.h"
+#include "TextOverflow.h"
+#include "nsIFrameInlines.h"
+#include "CounterStyleManager.h"
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/Telemetry.h"
+#include "nsFlexContainerFrame.h"
+#include "nsFileControlFrame.h"
+#include "nsMathMLContainerFrame.h"
+#include "nsSelectsAreaFrame.h"
+
+#include "nsBidiPresUtils.h"
+
+#include <inttypes.h>
+
+static const int MIN_LINES_NEEDING_CURSOR = 20;
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+using AbsPosReflowFlags = nsAbsoluteContainingBlock::AbsPosReflowFlags;
+using ClearFloatsResult = BlockReflowState::ClearFloatsResult;
+using ShapeType = nsFloatManager::ShapeType;
+
+static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) {
+ for (auto& line : aBlock->Lines()) {
+ if (line.IsBlock()) {
+ nsBlockFrame* bf = do_QueryFrame(line.mFirstChild);
+ if (bf) {
+ MarkAllDescendantLinesDirty(bf);
+ }
+ }
+ line.MarkDirty();
+ }
+}
+
+static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) {
+ nsBlockFrame* blockWithFloatMgr = aBlock;
+ while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent());
+ if (!bf) {
+ break;
+ }
+ blockWithFloatMgr = bf;
+ }
+
+ // Mark every line at and below the line where the float was
+ // dirty, and mark their lines dirty too. We could probably do
+ // something more efficient --- e.g., just dirty the lines that intersect
+ // the float vertically.
+ MarkAllDescendantLinesDirty(blockWithFloatMgr);
+}
+
+/**
+ * Returns true if aFrame is a block that has one or more float children.
+ */
+static bool BlockHasAnyFloats(nsIFrame* aFrame) {
+ nsBlockFrame* block = do_QueryFrame(aFrame);
+ if (!block) {
+ return false;
+ }
+ if (block->GetChildList(FrameChildListID::Float).FirstChild()) {
+ return true;
+ }
+
+ for (const auto& line : block->Lines()) {
+ if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Determines whether the given frame is visible or has
+ * visible children that participate in the same line. Frames
+ * that are not line participants do not have their
+ * children checked.
+ */
+static bool FrameHasVisibleInlineContent(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "Frame argument cannot be null");
+
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ return true;
+ }
+
+ if (aFrame->IsLineParticipant()) {
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ if (kid->StyleVisibility()->IsVisible() ||
+ FrameHasVisibleInlineContent(kid)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Determines whether any of the frames descended from the
+ * given line have inline content with 'visibility: visible'.
+ * This function calls FrameHasVisibleInlineContent to process
+ * each frame in the line's child list.
+ */
+static bool LineHasVisibleInlineContent(nsLineBox* aLine) {
+ nsIFrame* kid = aLine->mFirstChild;
+ int32_t n = aLine->GetChildCount();
+ while (n-- > 0) {
+ if (FrameHasVisibleInlineContent(kid)) {
+ return true;
+ }
+
+ kid = kid->GetNextSibling();
+ }
+
+ return false;
+}
+
+/**
+ * Iterates through the frame's in-flow children and
+ * unions the ink overflow of all text frames which
+ * participate in the line aFrame belongs to.
+ * If a child of aFrame is not a text frame,
+ * we recurse with the child as the aFrame argument.
+ * If aFrame isn't a line participant, we skip it entirely
+ * and return an empty rect.
+ * The resulting nsRect is offset relative to the parent of aFrame.
+ */
+static nsRect GetFrameTextArea(nsIFrame* aFrame,
+ nsDisplayListBuilder* aBuilder) {
+ nsRect textArea;
+ if (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
+ if (!textFrame->IsEntirelyWhitespace()) {
+ textArea = aFrame->InkOverflowRect();
+ }
+ } else if (aFrame->IsLineParticipant()) {
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
+ textArea.OrWith(kidTextArea);
+ }
+ }
+ // add aFrame's position to keep textArea relative to aFrame's parent
+ return textArea + aFrame->GetPosition();
+}
+
+/**
+ * Iterates through the line's children and
+ * unions the ink overflow of all text frames.
+ * GetFrameTextArea unions and returns the ink overflow
+ * from all line-participating text frames within the given child.
+ * The nsRect returned from GetLineTextArea is offset
+ * relative to the given line.
+ */
+static nsRect GetLineTextArea(nsLineBox* aLine,
+ nsDisplayListBuilder* aBuilder) {
+ nsRect textArea;
+ nsIFrame* kid = aLine->mFirstChild;
+ int32_t n = aLine->GetChildCount();
+ while (n-- > 0) {
+ nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
+ textArea.OrWith(kidTextArea);
+ kid = kid->GetNextSibling();
+ }
+
+ return textArea;
+}
+
+/**
+ * Starting with aFrame, iterates upward through parent frames and checks for
+ * non-transparent background colors. If one is found, we use that as our
+ * backplate color. Otheriwse, we use the default background color from
+ * our high contrast theme.
+ */
+static nscolor GetBackplateColor(nsIFrame* aFrame) {
+ nsPresContext* pc = aFrame->PresContext();
+ nscolor currentBackgroundColor = NS_TRANSPARENT;
+ for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
+ // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct
+ // background-color information so as to compute the right backplate color.
+ //
+ // This holds because HTML widgets with author-specified backgrounds or
+ // borders disable theming. So as long as the UA-specified background colors
+ // match the actual theme (which they should because we always use system
+ // colors with the non-native theme, and native system colors should also
+ // match the native theme), then we're alright and we should compute an
+ // appropriate backplate color.
+ const auto* style = frame->Style();
+ if (style->StyleBackground()->IsTransparent(style)) {
+ continue;
+ }
+ bool drawImage = false, drawColor = false;
+ nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor(
+ pc, style, frame, drawImage, drawColor);
+ if (!drawColor && !drawImage) {
+ continue;
+ }
+ if (NS_GET_A(backgroundColor) == 0) {
+ // Even if there's a background image, if there's no background color we
+ // keep going up the frame tree, see bug 1723938.
+ continue;
+ }
+ if (NS_GET_A(currentBackgroundColor) == 0) {
+ // Try to avoid somewhat expensive math in the common case.
+ currentBackgroundColor = backgroundColor;
+ } else {
+ currentBackgroundColor =
+ NS_ComposeColors(backgroundColor, currentBackgroundColor);
+ }
+ if (NS_GET_A(currentBackgroundColor) == 0xff) {
+ // If fully opaque, we're done, otherwise keep going up blending with our
+ // background.
+ return currentBackgroundColor;
+ }
+ }
+ nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor();
+ if (NS_GET_A(currentBackgroundColor) == 0) {
+ return backgroundColor;
+ }
+ return NS_ComposeColors(backgroundColor, currentBackgroundColor);
+}
+
+#ifdef DEBUG
+# include "nsBlockDebugFlags.h"
+
+bool nsBlockFrame::gLamePaintMetrics;
+bool nsBlockFrame::gLameReflowMetrics;
+bool nsBlockFrame::gNoisy;
+bool nsBlockFrame::gNoisyDamageRepair;
+bool nsBlockFrame::gNoisyIntrinsic;
+bool nsBlockFrame::gNoisyReflow;
+bool nsBlockFrame::gReallyNoisyReflow;
+bool nsBlockFrame::gNoisyFloatManager;
+bool nsBlockFrame::gVerifyLines;
+bool nsBlockFrame::gDisableResizeOpt;
+
+int32_t nsBlockFrame::gNoiseIndent;
+
+struct BlockDebugFlags {
+ const char* name;
+ bool* on;
+};
+
+static const BlockDebugFlags gFlags[] = {
+ {"reflow", &nsBlockFrame::gNoisyReflow},
+ {"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow},
+ {"intrinsic", &nsBlockFrame::gNoisyIntrinsic},
+ {"float-manager", &nsBlockFrame::gNoisyFloatManager},
+ {"verify-lines", &nsBlockFrame::gVerifyLines},
+ {"damage-repair", &nsBlockFrame::gNoisyDamageRepair},
+ {"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics},
+ {"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics},
+ {"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt},
+};
+# define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
+
+static void ShowDebugFlags() {
+ printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n");
+ const BlockDebugFlags* bdf = gFlags;
+ const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
+ for (; bdf < end; bdf++) {
+ printf(" %s\n", bdf->name);
+ }
+ printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n");
+ printf("names (no whitespace)\n");
+}
+
+void nsBlockFrame::InitDebugFlags() {
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
+ if (flags) {
+ bool error = false;
+ for (;;) {
+ char* cm = strchr(flags, ',');
+ if (cm) {
+ *cm = '\0';
+ }
+
+ bool found = false;
+ const BlockDebugFlags* bdf = gFlags;
+ const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
+ for (; bdf < end; bdf++) {
+ if (nsCRT::strcasecmp(bdf->name, flags) == 0) {
+ *(bdf->on) = true;
+ printf("nsBlockFrame: setting %s debug flag on\n", bdf->name);
+ gNoisy = true;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ error = true;
+ }
+
+ if (!cm) {
+ break;
+ }
+ *cm = ',';
+ flags = cm + 1;
+ }
+ if (error) {
+ ShowDebugFlags();
+ }
+ }
+ }
+}
+
+#endif
+
+//----------------------------------------------------------------------
+
+// Debugging support code
+
+#ifdef DEBUG
+const char* nsBlockFrame::kReflowCommandType[] = {
+ "ContentChanged", "StyleChanged", "ReflowDirty", "Timeout", "UserDefined",
+};
+
+const char* nsBlockFrame::LineReflowStatusToString(
+ LineReflowStatus aLineReflowStatus) const {
+ switch (aLineReflowStatus) {
+ case LineReflowStatus::OK:
+ return "LINE_REFLOW_OK";
+ case LineReflowStatus::Stop:
+ return "LINE_REFLOW_STOP";
+ case LineReflowStatus::RedoNoPull:
+ return "LINE_REFLOW_REDO_NO_PULL";
+ case LineReflowStatus::RedoMoreFloats:
+ return "LINE_REFLOW_REDO_MORE_FLOATS";
+ case LineReflowStatus::RedoNextBand:
+ return "LINE_REFLOW_REDO_NEXT_BAND";
+ case LineReflowStatus::Truncated:
+ return "LINE_REFLOW_TRUNCATED";
+ }
+ return "unknown";
+}
+
+#endif
+
+#ifdef REFLOW_STATUS_COVERAGE
+static void RecordReflowStatus(bool aChildIsBlock,
+ const nsReflowStatus& aFrameReflowStatus) {
+ static uint32_t record[2];
+
+ // 0: child-is-block
+ // 1: child-is-inline
+ int index = 0;
+ if (!aChildIsBlock) {
+ index |= 1;
+ }
+
+ // Compute new status
+ uint32_t newS = record[index];
+ if (aFrameReflowStatus.IsInlineBreak()) {
+ if (aFrameReflowStatus.IsInlineBreakBefore()) {
+ newS |= 1;
+ } else if (aFrameReflowStatus.IsIncomplete()) {
+ newS |= 2;
+ } else {
+ newS |= 4;
+ }
+ } else if (aFrameReflowStatus.IsIncomplete()) {
+ newS |= 8;
+ } else {
+ newS |= 16;
+ }
+
+ // Log updates to the status that yield different values
+ if (record[index] != newS) {
+ record[index] = newS;
+ printf("record(%d): %02x %02x\n", index, record[0], record[1]);
+ }
+}
+#endif
+
+NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty,
+ nsBlockFrame::FrameLines)
+NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty)
+NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty)
+NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty)
+NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame)
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord)
+
+//----------------------------------------------------------------------
+
+nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext());
+}
+
+nsBlockFrame* NS_NewBlockFormattingContext(PresShell* aPresShell,
+ ComputedStyle* aComputedStyle) {
+ nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aComputedStyle);
+ blockFrame->AddStateBits(NS_BLOCK_STATIC_BFC);
+ return blockFrame;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)
+
+nsBlockFrame::~nsBlockFrame() = default;
+
+void nsBlockFrame::AddSizeOfExcludingThisForTree(
+ nsWindowSizes& aWindowSizes) const {
+ nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes);
+
+ // Add the size of any nsLineBox::mFrames hashtables we might have:
+ for (const auto& line : Lines()) {
+ line.AddSizeOfExcludingThis(aWindowSizes);
+ }
+ const FrameLines* overflowLines = GetOverflowLines();
+ if (overflowLines) {
+ ConstLineIterator line = overflowLines->mLines.begin(),
+ line_end = overflowLines->mLines.end();
+ for (; line != line_end; ++line) {
+ line->AddSizeOfExcludingThis(aWindowSizes);
+ }
+ }
+}
+
+void nsBlockFrame::Destroy(DestroyContext& aContext) {
+ ClearLineCursors();
+ DestroyAbsoluteFrames(aContext);
+ mFloats.DestroyFrames(aContext);
+ nsPresContext* presContext = PresContext();
+ mozilla::PresShell* presShell = presContext->PresShell();
+ nsLineBox::DeleteLineList(presContext, mLines, &mFrames, aContext);
+
+ if (HasPushedFloats()) {
+ SafelyDestroyFrameListProp(aContext, presShell, PushedFloatProperty());
+ RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
+ }
+
+ // destroy overflow lines now
+ FrameLines* overflowLines = RemoveOverflowLines();
+ if (overflowLines) {
+ nsLineBox::DeleteLineList(presContext, overflowLines->mLines,
+ &overflowLines->mFrames, aContext);
+ delete overflowLines;
+ }
+
+ if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
+ SafelyDestroyFrameListProp(aContext, presShell,
+ OverflowOutOfFlowsProperty());
+ RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
+ }
+
+ if (HasOutsideMarker()) {
+ SafelyDestroyFrameListProp(aContext, presShell, OutsideMarkerProperty());
+ RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
+ }
+
+ nsContainerFrame::Destroy(aContext);
+}
+
+/* virtual */
+nsILineIterator* nsBlockFrame::GetLineIterator() {
+ nsLineIterator* iter = GetProperty(LineIteratorProperty());
+ if (!iter) {
+ const nsStyleVisibility* visibility = StyleVisibility();
+ iter = new nsLineIterator(mLines,
+ visibility->mDirection == StyleDirection::Rtl);
+ SetProperty(LineIteratorProperty(), iter);
+ }
+ return iter;
+}
+
+NS_QUERYFRAME_HEAD(nsBlockFrame)
+ NS_QUERYFRAME_ENTRY(nsBlockFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+void nsBlockFrame::List(FILE* out, const char* aPrefix,
+ ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+
+ fprintf_stderr(out, "%s <\n", str.get());
+
+ nsCString pfx(aPrefix);
+ pfx += " ";
+
+ // Output the lines
+ if (!mLines.empty()) {
+ ConstLineIterator line = LinesBegin(), line_end = LinesEnd();
+ for (; line != line_end; ++line) {
+ line->List(out, pfx.get(), aFlags);
+ }
+ }
+
+ // Output the overflow lines.
+ const FrameLines* overflowLines = GetOverflowLines();
+ if (overflowLines && !overflowLines->mLines.empty()) {
+ fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines,
+ &overflowLines->mFrames);
+ nsCString nestedPfx(pfx);
+ nestedPfx += " ";
+ ConstLineIterator line = overflowLines->mLines.begin(),
+ line_end = overflowLines->mLines.end();
+ for (; line != line_end; ++line) {
+ line->List(out, nestedPfx.get(), aFlags);
+ }
+ fprintf_stderr(out, "%s>\n", pfx.get());
+ }
+
+ // skip the principal list - we printed the lines above
+ // skip the overflow list - we printed the overflow lines above
+ ChildListIDs skip = {FrameChildListID::Principal, FrameChildListID::Overflow};
+ ListChildLists(out, pfx.get(), aFlags, skip);
+
+ fprintf_stderr(out, "%s>\n", aPrefix);
+}
+
+nsresult nsBlockFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Block"_ns, aResult);
+}
+#endif
+
+void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ if (IsInSVGTextSubtree()) {
+ NS_ASSERTION(GetParent()->IsSVGTextFrame(),
+ "unexpected block frame in SVG text");
+ GetParent()->InvalidateFrame();
+ return;
+ }
+ nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
+}
+
+void nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ if (IsInSVGTextSubtree()) {
+ NS_ASSERTION(GetParent()->IsSVGTextFrame(),
+ "unexpected block frame in SVG text");
+ GetParent()->InvalidateFrame();
+ return;
+ }
+ nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
+ aRebuildDisplayItems);
+}
+
+nscoord nsBlockFrame::SynthesizeFallbackBaseline(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
+ return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup);
+}
+
+template <typename LineIteratorType>
+Maybe<nscoord> nsBlockFrame::GetBaselineBOffset(
+ LineIteratorType aStart, LineIteratorType aEnd, WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ MOZ_ASSERT((std::is_same_v<LineIteratorType, ConstLineIterator> &&
+ aBaselineGroup == BaselineSharingGroup::First) ||
+ (std::is_same_v<LineIteratorType, ConstReverseLineIterator> &&
+ aBaselineGroup == BaselineSharingGroup::Last),
+ "Iterator direction must match baseline sharing group.");
+ for (auto line = aStart; line != aEnd; ++line) {
+ if (!line->IsBlock()) {
+ // XXX Is this the right test? We have some bogus empty lines
+ // floating around, but IsEmpty is perhaps too weak.
+ if (line->BSize() != 0 || !line->IsEmpty()) {
+ const auto ascent = line->BStart() + line->GetLogicalAscent();
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return Some(BSize(aWM) - ascent);
+ }
+ return Some(ascent);
+ }
+ continue;
+ }
+ nsIFrame* kid = line->mFirstChild;
+ if (aWM.IsOrthogonalTo(kid->GetWritingMode())) {
+ continue;
+ }
+ if (aExportContext == BaselineExportContext::LineLayout &&
+ kid->IsTableWrapperFrame()) {
+ // `<table>` in inline-block context does not export any baseline.
+ continue;
+ }
+ const auto kidBaselineGroup =
+ aExportContext == BaselineExportContext::LineLayout
+ ? kid->GetDefaultBaselineSharingGroup()
+ : aBaselineGroup;
+ const auto kidBaseline =
+ kid->GetNaturalBaselineBOffset(aWM, kidBaselineGroup, aExportContext);
+ if (!kidBaseline) {
+ continue;
+ }
+ auto result = *kidBaseline;
+ if (kidBaselineGroup == BaselineSharingGroup::Last) {
+ result = kid->BSize(aWM) - result;
+ }
+ // Ignore relative positioning for baseline calculations.
+ const nsSize& sz = line->mContainerSize;
+ result += kid->GetLogicalNormalPosition(aWM, sz).B(aWM);
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return Some(BSize(aWM) - result);
+ }
+ return Some(result);
+ }
+ return Nothing{};
+}
+
+Maybe<nscoord> nsBlockFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ if (StyleDisplay()->IsContainLayout()) {
+ return Nothing{};
+ }
+
+ if (aBaselineGroup == BaselineSharingGroup::First) {
+ return GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup,
+ aExportContext);
+ }
+
+ return GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup,
+ aExportContext);
+}
+
+nscoord nsBlockFrame::GetCaretBaseline() const {
+ nsRect contentRect = GetContentRect();
+ nsMargin bp = GetUsedBorderAndPadding();
+
+ if (!mLines.empty()) {
+ ConstLineIterator line = LinesBegin();
+ if (!line->IsEmpty()) {
+ if (line->IsBlock()) {
+ return bp.top + line->mFirstChild->GetCaretBaseline();
+ }
+ return line->BStart() + line->GetLogicalAscent();
+ }
+ }
+
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
+ nscoord lineHeight = ReflowInput::CalcLineHeight(
+ *Style(), PresContext(), GetContent(), contentRect.height, inflation);
+ const WritingMode wm = GetWritingMode();
+ return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
+ wm.IsLineInverted()) +
+ bp.top;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Child frame enumeration
+
+const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const {
+ switch (aListID) {
+ case FrameChildListID::Principal:
+ return mFrames;
+ case FrameChildListID::Overflow: {
+ FrameLines* overflowLines = GetOverflowLines();
+ return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
+ }
+ case FrameChildListID::Float:
+ return mFloats;
+ case FrameChildListID::OverflowOutOfFlow: {
+ const nsFrameList* list = GetOverflowOutOfFlows();
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ case FrameChildListID::PushedFloats: {
+ const nsFrameList* list = GetPushedFloats();
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ case FrameChildListID::Bullet: {
+ const nsFrameList* list = GetOutsideMarkerList();
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ default:
+ return nsContainerFrame::GetChildList(aListID);
+ }
+}
+
+void nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
+ nsContainerFrame::GetChildLists(aLists);
+ FrameLines* overflowLines = GetOverflowLines();
+ if (overflowLines) {
+ overflowLines->mFrames.AppendIfNonempty(aLists, FrameChildListID::Overflow);
+ }
+ const nsFrameList* list = GetOverflowOutOfFlows();
+ if (list) {
+ list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow);
+ }
+ mFloats.AppendIfNonempty(aLists, FrameChildListID::Float);
+ list = GetOutsideMarkerList();
+ if (list) {
+ list->AppendIfNonempty(aLists, FrameChildListID::Bullet);
+ }
+ list = GetPushedFloats();
+ if (list) {
+ list->AppendIfNonempty(aLists, FrameChildListID::PushedFloats);
+ }
+}
+
+/* virtual */
+bool nsBlockFrame::IsFloatContainingBlock() const { return true; }
+
+/**
+ * Remove the first line from aFromLines and adjust the associated frame list
+ * aFromFrames accordingly. The removed line is assigned to *aOutLine and
+ * a frame list with its frames is assigned to *aOutFrames, i.e. the frames
+ * that were extracted from the head of aFromFrames.
+ * aFromLines must contain at least one line, the line may be empty.
+ * @return true if aFromLines becomes empty
+ */
+static bool RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames,
+ nsLineBox** aOutLine, nsFrameList* aOutFrames) {
+ nsLineList_iterator removedLine = aFromLines.begin();
+ *aOutLine = removedLine;
+ nsLineList_iterator next = aFromLines.erase(removedLine);
+ bool isLastLine = next == aFromLines.end();
+ nsIFrame* firstFrameInNextLine = isLastLine ? nullptr : next->mFirstChild;
+ *aOutFrames = aFromFrames.TakeFramesBefore(firstFrameInNextLine);
+ return isLastLine;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Reflow methods
+
+/* virtual */
+void nsBlockFrame::MarkIntrinsicISizesDirty() {
+ nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(FirstContinuation());
+ dirtyBlock->mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ dirtyBlock->mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
+ for (nsIFrame* frame = dirtyBlock; frame;
+ frame = frame->GetNextContinuation()) {
+ frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ }
+ }
+
+ nsContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() {
+ nsPresContext* presContext = PresContext();
+ if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) {
+ return;
+ }
+ bool inflationEnabled = !presContext->mInflationDisabledForShrinkWrap;
+ if (inflationEnabled != HasAnyStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED)) {
+ mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ if (inflationEnabled) {
+ AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
+ } else {
+ RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
+ }
+ }
+}
+
+// Whether this line is indented by the text-indent amount.
+bool nsBlockFrame::TextIndentAppliesTo(const LineIterator& aLine) const {
+ const auto& textIndent = StyleText()->mTextIndent;
+
+ bool isFirstLineOrAfterHardBreak = [&] {
+ if (aLine != LinesBegin()) {
+ // If not the first line of the block, but 'each-line' is in effect,
+ // check if the previous line was not wrapped.
+ return textIndent.each_line && !aLine.prev()->IsLineWrapped();
+ }
+ if (nsBlockFrame* prevBlock = do_QueryFrame(GetPrevInFlow())) {
+ // There's a prev-in-flow, so this only counts as a first-line if
+ // 'each-line' and the prev-in-flow's last line was not wrapped.
+ return textIndent.each_line &&
+ (prevBlock->Lines().empty() ||
+ !prevBlock->LinesEnd().prev()->IsLineWrapped());
+ }
+ return true;
+ }();
+
+ // The 'hanging' option inverts which lines are/aren't indented.
+ return isFirstLineOrAfterHardBreak != textIndent.hanging;
+}
+
+/* virtual */
+nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nsIFrame* firstInFlow = FirstContinuation();
+ if (firstInFlow != this) {
+ return firstInFlow->GetMinISize(aRenderingContext);
+ }
+
+ DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
+
+ CheckIntrinsicCacheAgainstShrinkWrapState();
+
+ if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
+ return mCachedMinISize;
+ }
+
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ mCachedMinISize = *containISize;
+ return mCachedMinISize;
+ }
+
+#ifdef DEBUG
+ if (gNoisyIntrinsic) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": GetMinISize\n");
+ }
+ AutoNoisyIndenter indenter(gNoisyIntrinsic);
+#endif
+
+ for (nsBlockFrame* curFrame = this; curFrame;
+ curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
+ curFrame->LazyMarkLinesDirty();
+ }
+
+ if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
+ PresContext()->BidiEnabled()) {
+ ResolveBidi();
+ }
+
+ const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle();
+ InlineMinISizeData data;
+ for (nsBlockFrame* curFrame = this; curFrame;
+ curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
+ for (LineIterator line = curFrame->LinesBegin(),
+ line_end = curFrame->LinesEnd();
+ line != line_end; ++line) {
+#ifdef DEBUG
+ if (gNoisyIntrinsic) {
+ IndentBy(stdout, gNoiseIndent);
+ printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
+ line->IsEmpty() ? ", empty" : "");
+ }
+ AutoNoisyIndenter lineindent(gNoisyIntrinsic);
+#endif
+ if (line->IsBlock()) {
+ data.ForceBreak();
+ data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, line->mFirstChild, IntrinsicISizeType::MinISize);
+ data.ForceBreak();
+ } else {
+ if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
+ data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
+ }
+ data.mLine = &line;
+ data.SetLineContainer(curFrame);
+ nsIFrame* kid = line->mFirstChild;
+ for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
+ ++i, kid = kid->GetNextSibling()) {
+ kid->AddInlineMinISize(aRenderingContext, &data);
+ if (whiteSpaceCanWrap && data.mTrailingWhitespace) {
+ data.OptionallyBreak();
+ }
+ }
+ }
+#ifdef DEBUG
+ if (gNoisyIntrinsic) {
+ IndentBy(stdout, gNoiseIndent);
+ printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
+ data.mCurrentLine);
+ }
+#endif
+ }
+ }
+ data.ForceBreak();
+
+ mCachedMinISize = data.mPrevLines;
+ return mCachedMinISize;
+}
+
+/* virtual */
+nscoord nsBlockFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nsIFrame* firstInFlow = FirstContinuation();
+ if (firstInFlow != this) {
+ return firstInFlow->GetPrefISize(aRenderingContext);
+ }
+
+ DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
+
+ CheckIntrinsicCacheAgainstShrinkWrapState();
+
+ if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
+ return mCachedPrefISize;
+ }
+
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ mCachedPrefISize = *containISize;
+ return mCachedPrefISize;
+ }
+
+#ifdef DEBUG
+ if (gNoisyIntrinsic) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": GetPrefISize\n");
+ }
+ AutoNoisyIndenter indenter(gNoisyIntrinsic);
+#endif
+
+ for (nsBlockFrame* curFrame = this; curFrame;
+ curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
+ curFrame->LazyMarkLinesDirty();
+ }
+
+ if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
+ PresContext()->BidiEnabled()) {
+ ResolveBidi();
+ }
+ InlinePrefISizeData data;
+ for (nsBlockFrame* curFrame = this; curFrame;
+ curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
+ for (LineIterator line = curFrame->LinesBegin(),
+ line_end = curFrame->LinesEnd();
+ line != line_end; ++line) {
+#ifdef DEBUG
+ if (gNoisyIntrinsic) {
+ IndentBy(stdout, gNoiseIndent);
+ printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
+ line->IsEmpty() ? ", empty" : "");
+ }
+ AutoNoisyIndenter lineindent(gNoisyIntrinsic);
+#endif
+ if (line->IsBlock()) {
+ StyleClear clearType;
+ if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
+ clearType = StyleClear::Both;
+ } else {
+ clearType = line->mFirstChild->StyleDisplay()->mClear;
+ }
+ data.ForceBreak(clearType);
+ data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, line->mFirstChild,
+ IntrinsicISizeType::PrefISize);
+ data.ForceBreak();
+ } else {
+ if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
+ nscoord indent = StyleText()->mTextIndent.length.Resolve(0);
+ data.mCurrentLine += indent;
+ // XXXmats should the test below be indent > 0?
+ if (indent != nscoord(0)) {
+ data.mLineIsEmpty = false;
+ }
+ }
+ data.mLine = &line;
+ data.SetLineContainer(curFrame);
+ nsIFrame* kid = line->mFirstChild;
+ for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
+ ++i, kid = kid->GetNextSibling()) {
+ kid->AddInlinePrefISize(aRenderingContext, &data);
+ }
+ }
+#ifdef DEBUG
+ if (gNoisyIntrinsic) {
+ IndentBy(stdout, gNoiseIndent);
+ printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
+ data.mCurrentLine);
+ }
+#endif
+ }
+ }
+ data.ForceBreak();
+
+ mCachedPrefISize = data.mPrevLines;
+ return mCachedPrefISize;
+}
+
+nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
+ // be conservative
+ if (Style()->HasTextDecorationLines()) {
+ return InkOverflowRect();
+ }
+ return ComputeSimpleTightBounds(aDrawTarget);
+}
+
+/* virtual */
+nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext,
+ nscoord* aX, nscoord* aXMost) {
+ nsIFrame* firstInFlow = FirstContinuation();
+ if (firstInFlow != this) {
+ return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost);
+ }
+
+ *aX = 0;
+ *aXMost = 0;
+
+ nsresult rv;
+ InlinePrefISizeData data;
+ for (nsBlockFrame* curFrame = this; curFrame;
+ curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
+ for (LineIterator line = curFrame->LinesBegin(),
+ line_end = curFrame->LinesEnd();
+ line != line_end; ++line) {
+ nscoord childX, childXMost;
+ if (line->IsBlock()) {
+ data.ForceBreak();
+ rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext,
+ &childX, &childXMost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aX = std::min(*aX, childX);
+ *aXMost = std::max(*aXMost, childXMost);
+ } else {
+ if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
+ data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
+ }
+ data.mLine = &line;
+ data.SetLineContainer(curFrame);
+ nsIFrame* kid = line->mFirstChild;
+ for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
+ ++i, kid = kid->GetNextSibling()) {
+ rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX,
+ &childXMost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aX = std::min(*aX, data.mCurrentLine + childX);
+ *aXMost = std::max(*aXMost, data.mCurrentLine + childXMost);
+ kid->AddInlinePrefISize(aRenderingContext, &data);
+ }
+ }
+ }
+ }
+ data.ForceBreak();
+
+ return NS_OK;
+}
+
+/**
+ * Return whether aNewAvailableSpace is smaller *on either side*
+ * (inline-start or inline-end) than aOldAvailableSpace, so that we know
+ * if we need to redo layout on an line, replaced block, or block
+ * formatting context, because its height (which we used to compute
+ * aNewAvailableSpace) caused it to intersect additional floats.
+ */
+static bool AvailableSpaceShrunk(WritingMode aWM,
+ const LogicalRect& aOldAvailableSpace,
+ const LogicalRect& aNewAvailableSpace,
+ bool aCanGrow /* debug-only */) {
+ if (aNewAvailableSpace.ISize(aWM) == 0) {
+ // Positions are not significant if the inline size is zero.
+ return aOldAvailableSpace.ISize(aWM) != 0;
+ }
+ if (aCanGrow) {
+ NS_ASSERTION(
+ aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) ||
+ aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM),
+ "available space should not shrink on the start side and "
+ "grow on the end side");
+ NS_ASSERTION(
+ aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) ||
+ aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM),
+ "available space should not grow on the start side and "
+ "shrink on the end side");
+ } else {
+ NS_ASSERTION(
+ aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) &&
+ aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM),
+ "available space should never grow");
+ }
+ // Have we shrunk on either side?
+ return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) ||
+ aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM);
+}
+
+static LogicalSize CalculateContainingBlockSizeForAbsolutes(
+ WritingMode aWM, const ReflowInput& aReflowInput, LogicalSize aFrameSize) {
+ // The issue here is that for a 'height' of 'auto' the reflow input
+ // code won't know how to calculate the containing block height
+ // because it's calculated bottom up. So we use our own computed
+ // size as the dimensions.
+ nsIFrame* frame = aReflowInput.mFrame;
+
+ LogicalSize cbSize(aFrameSize);
+ // Containing block is relative to the padding edge
+ const LogicalMargin border = aReflowInput.ComputedLogicalBorder(aWM);
+ cbSize.ISize(aWM) -= border.IStartEnd(aWM);
+ cbSize.BSize(aWM) -= border.BStartEnd(aWM);
+
+ if (frame->GetParent()->GetContent() != frame->GetContent() ||
+ frame->GetParent()->IsCanvasFrame()) {
+ return cbSize;
+ }
+
+ // We are a wrapped frame for the content (and the wrapper is not the
+ // canvas frame, whose size is not meaningful here).
+ // Use the container's dimensions, if they have been precomputed.
+ // XXX This is a hack! We really should be waiting until the outermost
+ // frame is fully reflowed and using the resulting dimensions, even
+ // if they're intrinsic.
+ // In fact we should be attaching absolute children to the outermost
+ // frame and not always sticking them in block frames.
+
+ // First, find the reflow input for the outermost frame for this content.
+ const ReflowInput* lastRI = &aReflowInput;
+ DebugOnly<const ReflowInput*> lastButOneRI = &aReflowInput;
+ while (lastRI->mParentReflowInput &&
+ lastRI->mParentReflowInput->mFrame->GetContent() ==
+ frame->GetContent()) {
+ lastButOneRI = lastRI;
+ lastRI = lastRI->mParentReflowInput;
+ }
+
+ if (lastRI == &aReflowInput) {
+ return cbSize;
+ }
+
+ // For scroll containers, we can just use cbSize (which is the padding-box
+ // size of the scrolled-content frame).
+ if (nsIScrollableFrame* scrollFrame = do_QueryFrame(lastRI->mFrame)) {
+ // Assert that we're not missing any frames between the abspos containing
+ // block and the scroll container.
+ // the parent.
+ Unused << scrollFrame;
+ MOZ_ASSERT(lastButOneRI == &aReflowInput);
+ return cbSize;
+ }
+
+ // Same for fieldsets, where the inner anonymous frame has the correct padding
+ // area with the legend taken into account.
+ if (lastRI->mFrame->IsFieldSetFrame()) {
+ return cbSize;
+ }
+
+ // We found a reflow input for the outermost wrapping frame, so use
+ // its computed metrics if available, converted to our writing mode
+ const LogicalSize lastRISize = lastRI->ComputedSize(aWM);
+ const LogicalMargin lastRIPadding = lastRI->ComputedLogicalPadding(aWM);
+ if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
+ cbSize.ISize(aWM) =
+ std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM));
+ }
+ if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
+ cbSize.BSize(aWM) =
+ std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM));
+ }
+
+ return cbSize;
+}
+
+/**
+ * Returns aFrame if it is a non-BFC block frame, and null otherwise.
+ *
+ * This is used to determine whether to recurse into aFrame when applying
+ * -webkit-line-clamp.
+ */
+static const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) {
+ if (const nsBlockFrame* block = do_QueryFrame(aFrame)) {
+ if (!block->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ return block;
+ }
+ }
+ return nullptr;
+}
+
+static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
+ return const_cast<nsBlockFrame*>(
+ GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame)));
+}
+
+static bool IsLineClampRoot(const nsBlockFrame* aFrame) {
+ if (!aFrame->StyleDisplay()->mWebkitLineClamp) {
+ return false;
+ }
+
+ if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ return false;
+ }
+
+ if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled()) {
+ return true;
+ }
+
+ // For now, -webkit-box is the only thing allowed to be a line-clamp root.
+ // Ideally we'd just make this work everywhere, but for now we're carrying
+ // this forward as a limitation on the legacy -webkit-line-clamp feature,
+ // since relaxing this limitation might create webcompat trouble.
+ auto origDisplay = [&] {
+ if (aFrame->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
+ // If we're the anonymous block inside the scroll frame, we need to look
+ // at the original display of our parent frame.
+ MOZ_ASSERT(aFrame->GetParent());
+ const auto& parentDisp = *aFrame->GetParent()->StyleDisplay();
+ MOZ_ASSERT(parentDisp.mWebkitLineClamp ==
+ aFrame->StyleDisplay()->mWebkitLineClamp,
+ ":-moz-scrolled-content should inherit -webkit-line-clamp, "
+ "via rule in UA stylesheet");
+ return parentDisp.mOriginalDisplay;
+ }
+ return aFrame->StyleDisplay()->mOriginalDisplay;
+ }();
+ return origDisplay.Inside() == StyleDisplayInside::WebkitBox;
+}
+
+bool nsBlockFrame::IsInLineClampContext() const {
+ if (IsLineClampRoot(this)) {
+ return true;
+ }
+ const nsBlockFrame* cur = this;
+ while (GetAsLineClampDescendant(cur)) {
+ cur = do_QueryFrame(cur->GetParent());
+ if (!cur) {
+ return false;
+ }
+ if (IsLineClampRoot(cur)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Iterator over all descendant inline line boxes, except for those that are
+ * under an independent formatting context.
+ */
+class MOZ_RAII LineClampLineIterator {
+ public:
+ explicit LineClampLineIterator(nsBlockFrame* aFrame)
+ : mCur(aFrame->LinesBegin()),
+ mEnd(aFrame->LinesEnd()),
+ mCurrentFrame(mCur == mEnd ? nullptr : aFrame) {
+ if (mCur != mEnd && !mCur->IsInline()) {
+ Advance();
+ }
+ }
+
+ nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; }
+ nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; }
+
+ // Advances the iterator to the next line line.
+ //
+ // Next() shouldn't be called once the iterator is at the end, which can be
+ // checked for by GetCurrentLine() or GetCurrentFrame() returning null.
+ void Next() {
+ MOZ_ASSERT(mCur != mEnd && mCurrentFrame,
+ "Don't call Next() when the iterator is at the end");
+ ++mCur;
+ Advance();
+ }
+
+ private:
+ void Advance() {
+ for (;;) {
+ if (mCur == mEnd) {
+ // Reached the end of the current block. Pop the parent off the
+ // stack; if there isn't one, then we've reached the end.
+ if (mStack.IsEmpty()) {
+ mCurrentFrame = nullptr;
+ break;
+ }
+ auto entry = mStack.PopLastElement();
+ mCurrentFrame = entry.first;
+ mCur = entry.second;
+ mEnd = mCurrentFrame->LinesEnd();
+ } else if (mCur->IsBlock()) {
+ if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) {
+ nsBlockFrame::LineIterator next = mCur;
+ ++next;
+ mStack.AppendElement(std::make_pair(mCurrentFrame, next));
+ mCur = child->LinesBegin();
+ mEnd = child->LinesEnd();
+ mCurrentFrame = child;
+ } else {
+ // Some kind of frame we shouldn't descend into.
+ ++mCur;
+ }
+ } else {
+ MOZ_ASSERT(mCur->IsInline());
+ break;
+ }
+ }
+ }
+
+ // The current line within the current block.
+ //
+ // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
+ // is set to null.
+ nsBlockFrame::LineIterator mCur;
+
+ // The iterator end for the current block.
+ nsBlockFrame::LineIterator mEnd;
+
+ // The current block.
+ nsBlockFrame* mCurrentFrame;
+
+ // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and
+ // exist blocks.
+ AutoTArray<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack;
+};
+
+static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
+ if (ClearLineClampEllipsis(child)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
+
+ for (auto& line : aFrame->Lines()) {
+ if (line.HasLineClampEllipsis()) {
+ line.ClearHasLineClampEllipsis();
+ return true;
+ }
+ }
+
+ // We didn't find a line with the ellipsis; it must have been deleted already.
+ return true;
+}
+
+void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
+
+void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
+ return;
+ }
+
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+#ifdef DEBUG
+ if (gNoisyReflow) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
+ aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(),
+ aReflowInput.ComputedISize(), aReflowInput.ComputedBSize());
+ }
+ AutoNoisyIndenter indent(gNoisy);
+ PRTime start = 0; // Initialize these variablies to silence the compiler.
+ int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics).
+ if (gLameReflowMetrics) {
+ start = PR_Now();
+ ctc = nsLineBox::GetCtorCount();
+ }
+#endif
+
+ // ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or
+ // max-block-size because both affect the children's available block-size.
+ if (IsColumnSetWrapperFrame()) {
+ AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+
+ Maybe<nscoord> restoreReflowInputAvailBSize;
+ auto MaybeRestore = MakeScopeExit([&] {
+ if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) {
+ const_cast<ReflowInput&>(aReflowInput)
+ .SetAvailableBSize(*restoreReflowInputAvailBSize);
+ }
+ });
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ const nscoord consumedBSize = CalcAndCacheConsumedBSize();
+ const nscoord effectiveContentBoxBSize =
+ GetEffectiveComputedBSize(aReflowInput, consumedBSize);
+ // If we have non-auto block size, we're clipping our kids and we fit,
+ // make sure our kids fit too.
+ const PhysicalAxes physicalBlockAxis =
+ wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical;
+ if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+ aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
+ (ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) &
+ physicalBlockAxis)) {
+ LogicalMargin blockDirExtras =
+ aReflowInput.ComputedLogicalBorderPadding(wm);
+ if (GetLogicalSkipSides().BStart()) {
+ blockDirExtras.BStart(wm) = 0;
+ } else {
+ // Block-end margin never causes us to create continuations, so we
+ // don't need to worry about whether it fits in its entirety.
+ blockDirExtras.BStart(wm) +=
+ aReflowInput.ComputedLogicalMargin(wm).BStart(wm);
+ }
+
+ if (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <=
+ aReflowInput.AvailableBSize()) {
+ restoreReflowInputAvailBSize.emplace(aReflowInput.AvailableBSize());
+ const_cast<ReflowInput&>(aReflowInput)
+ .SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
+ }
+ }
+
+ if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) {
+ return;
+ }
+
+ // OK, some lines may be reflowed. Blow away any saved line cursor
+ // because we may invalidate the nondecreasing
+ // overflowArea.InkOverflow().y/yMost invariant, and we may even
+ // delete the line with the line cursor.
+ ClearLineCursors();
+
+ // See comment below about oldSize. Use *only* for the
+ // abs-pos-containing-block-size-change optimization!
+ nsSize oldSize = GetSize();
+
+ // Should we create a float manager?
+ nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(aReflowInput));
+
+ // XXXldb If we start storing the float manager in the frame rather
+ // than keeping it around only during reflow then we should create it
+ // only when there are actually floats to manage. Otherwise things
+ // like tables will gain significant bloat.
+ bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this);
+ if (needFloatManager) {
+ autoFloatManager.CreateFloatManager(aPresContext);
+ }
+
+ if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
+ PresContext()->BidiEnabled()) {
+ static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
+ }
+
+ // Whether to apply text-wrap: balance behavior.
+ bool tryBalance =
+ StyleText()->mTextWrapStyle == StyleTextWrapStyle::Balance &&
+ !GetPrevContinuation();
+
+ // Struct used to hold the "target" number of lines or clamp position to
+ // maintain when doing text-wrap: balance.
+ struct BalanceTarget {
+ // If line-clamp is in effect, mContent and mOffset indicate the starting
+ // position of the first line after the clamp limit. If line-clamp is not
+ // in use, mContent is null and mOffset is the total number of lines that
+ // the block must contain.
+ nsIContent* mContent = nullptr;
+ int32_t mOffset = -1;
+
+ bool operator==(const BalanceTarget& aOther) const {
+ return mContent == aOther.mContent && mOffset == aOther.mOffset;
+ }
+ bool operator!=(const BalanceTarget& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+
+ BalanceTarget balanceTarget;
+
+ // Helpers for text-wrap: balance implementation:
+
+ // Count the number of lines in the mLines list, but return -1 (to suppress
+ // balancing) instead if the count is going to exceed aLimit, or if we
+ // encounter a block.
+ auto countLinesUpTo = [&](int32_t aLimit) -> int32_t {
+ int32_t n = 0;
+ for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
+ if (++n > aLimit || iter->IsBlock()) {
+ return -1;
+ }
+ }
+ return n;
+ };
+
+ // Return a BalanceTarget record representing the position at which line-clamp
+ // will take effect for the current line list. Only to be used when there are
+ // enough lines that the clamp will apply.
+ auto getClampPosition = [&](uint32_t aClampCount) -> BalanceTarget {
+ MOZ_ASSERT(aClampCount < mLines.size());
+ auto iter = mLines.begin();
+ for (uint32_t i = 0; i < aClampCount; i++) {
+ ++iter;
+ }
+ nsIFrame* firstChild = iter->mFirstChild;
+ if (!firstChild) {
+ return BalanceTarget{};
+ }
+ nsIContent* content = firstChild->GetContent();
+ if (!content) {
+ return BalanceTarget{};
+ }
+ int32_t offset = 0;
+ if (firstChild->IsTextFrame()) {
+ auto* textFrame = static_cast<nsTextFrame*>(firstChild);
+ offset = textFrame->GetContentOffset();
+ }
+ return BalanceTarget{content, offset};
+ };
+
+ // "balancing" is implemented by shortening the effective inline-size of the
+ // lines, so that content will tend to be pushed down to fill later lines of
+ // the block. `balanceInset` is the current amount of "inset" to apply, and
+ // `balanceStep` is the increment to adjust it by for the next iteration.
+ nscoord balanceStep = 0;
+
+ // text-wrap: balance loop, executed only once if balancing is not required.
+ nsReflowStatus reflowStatus;
+ TrialReflowState trialState(consumedBSize, effectiveContentBoxBSize,
+ needFloatManager);
+ while (true) {
+ // Save the initial floatManager state for repeated trial reflows.
+ // We'll restore (and re-save) the initial state each time we repeat the
+ // reflow.
+ nsFloatManager::SavedState floatManagerState;
+ aReflowInput.mFloatManager->PushState(&floatManagerState);
+
+ aMetrics = ReflowOutput(aMetrics.GetWritingMode());
+ reflowStatus =
+ TrialReflow(aPresContext, aMetrics, aReflowInput, trialState);
+
+ // Do we need to start a `text-wrap: balance` iteration?
+ if (tryBalance) {
+ tryBalance = false;
+ // Don't try to balance an incomplete block, or if we had to use an
+ // overflow-wrap break position in the initial reflow.
+ if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) {
+ break;
+ }
+ balanceTarget.mOffset =
+ countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
+ if (balanceTarget.mOffset < 2) {
+ // If there are less than 2 lines, or the number exceeds the limit,
+ // no balancing is needed; just break from the balance loop.
+ break;
+ }
+ // Initialize the amount of inset to try, and the iteration step size.
+ balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset;
+ trialState.ResetForBalance(balanceStep);
+ balanceStep /= 2;
+
+ // If -webkit-line-clamp is in effect, then we need to maintain the
+ // content location at which clamping occurs, rather than the total
+ // number of lines in the block.
+ if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() &&
+ IsLineClampRoot(this)) {
+ uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp;
+ if (uint32_t(balanceTarget.mOffset) > lineClampCount) {
+ auto t = getClampPosition(lineClampCount);
+ if (t.mContent) {
+ balanceTarget = t;
+ }
+ }
+ }
+
+ // Restore initial floatManager state for a new trial with updated inset.
+ aReflowInput.mFloatManager->PopState(&floatManagerState);
+ continue;
+ }
+
+ // Helper to determine whether the current trial succeeded (i.e. was able
+ // to fit the content into the expected number of lines).
+ auto trialSucceeded = [&]() -> bool {
+ if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) {
+ return false;
+ }
+ if (balanceTarget.mContent) {
+ auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);
+ return t == balanceTarget;
+ }
+ int32_t numLines =
+ countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
+ return numLines == balanceTarget.mOffset;
+ };
+
+ // If we're in the process of a balance operation, check whether we've
+ // inset by too much and either increase or reduce the inset for the next
+ // iteration.
+ if (balanceStep > 0) {
+ if (trialSucceeded()) {
+ trialState.ResetForBalance(balanceStep);
+ } else {
+ trialState.ResetForBalance(-balanceStep);
+ }
+ balanceStep /= 2;
+
+ aReflowInput.mFloatManager->PopState(&floatManagerState);
+ continue;
+ }
+
+ // If we were attempting to balance, check whether the final iteration was
+ // successful, and if not, back up by one step.
+ if (balanceTarget.mOffset >= 0) {
+ if (!trialState.mInset || trialSucceeded()) {
+ break;
+ }
+ trialState.ResetForBalance(-1);
+
+ aReflowInput.mFloatManager->PopState(&floatManagerState);
+ continue;
+ }
+
+ // If we reach here, no balancing was required, so just exit; we don't
+ // reset (pop) the floatManager state because this is the reflow we're
+ // going to keep. So the saved state is just dropped.
+ break;
+ } // End of text-wrap: balance retry loop
+
+ // If the block direction is right-to-left, we need to update the bounds of
+ // lines that were placed relative to mContainerSize during reflow, as
+ // we typically do not know the true container size until we've reflowed all
+ // its children. So we use a dummy mContainerSize during reflow (see
+ // BlockReflowState's constructor) and then fix up the positions of the
+ // lines here, once the final block size is known.
+ //
+ // Note that writing-mode:vertical-rl is the only case where the block
+ // logical direction progresses in a negative physical direction, and
+ // therefore block-dir coordinate conversion depends on knowing the width
+ // of the coordinate space in order to translate between the logical and
+ // physical origins.
+ if (aReflowInput.GetWritingMode().IsVerticalRL()) {
+ nsSize containerSize = aMetrics.PhysicalSize();
+ nscoord deltaX = containerSize.width - trialState.mContainerWidth;
+ if (deltaX != 0) {
+ // We compute our lines and markers' overflow areas later in
+ // ComputeOverflowAreas(), so we don't need to adjust their overflow areas
+ // here.
+ const nsPoint physicalDelta(deltaX, 0);
+ for (auto& line : Lines()) {
+ UpdateLineContainerSize(&line, containerSize);
+ }
+ trialState.mFcBounds.Clear();
+ for (nsIFrame* f : mFloats) {
+ f->MovePositionBy(physicalDelta);
+ ConsiderChildOverflow(trialState.mFcBounds, f);
+ }
+ nsFrameList* markerList = GetOutsideMarkerList();
+ if (markerList) {
+ for (nsIFrame* f : *markerList) {
+ f->MovePositionBy(physicalDelta);
+ }
+ }
+ if (nsFrameList* overflowContainers = GetOverflowContainers()) {
+ trialState.mOcBounds.Clear();
+ for (nsIFrame* f : *overflowContainers) {
+ f->MovePositionBy(physicalDelta);
+ ConsiderChildOverflow(trialState.mOcBounds, f);
+ }
+ }
+ }
+ }
+
+ aMetrics.SetOverflowAreasToDesiredBounds();
+ ComputeOverflowAreas(aMetrics.mOverflowAreas,
+ trialState.mBlockEndEdgeOfChildren,
+ aReflowInput.mStyleDisplay);
+ // Factor overflow container child bounds into the overflow area
+ aMetrics.mOverflowAreas.UnionWith(trialState.mOcBounds);
+ // Factor pushed float child bounds into the overflow area
+ aMetrics.mOverflowAreas.UnionWith(trialState.mFcBounds);
+
+ // Let the absolutely positioned container reflow any absolutely positioned
+ // child frames that need to be reflowed, e.g., elements with a percentage
+ // based width/height
+ // We want to do this under either of two conditions:
+ // 1. If we didn't do the incremental reflow above.
+ // 2. If our size changed.
+ // Even though it's the padding edge that's the containing block, we
+ // can use our rect (the border edge) since if the border style
+ // changed, the reflow would have been targeted at us so we'd satisfy
+ // condition 1.
+ // XXX checking oldSize is bogus, there are various reasons we might have
+ // reflowed but our size might not have been changed to what we
+ // asked for (e.g., we ended up being pushed to a new page)
+ // When WillReflowAgainForClearance is true, we will reflow again without
+ // resetting the size. Because of this, we must not reflow our abs-pos
+ // children in that situation --- what we think is our "new size" will not be
+ // our real new size. This also happens to be more efficient.
+ WritingMode parentWM = aMetrics.GetWritingMode();
+ if (HasAbsolutelyPositionedChildren()) {
+ nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
+ bool haveInterrupt = aPresContext->HasPendingInterrupt();
+ if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) {
+ // Make sure that when we reflow again we'll actually reflow all the abs
+ // pos frames that might conceivably depend on our size (or all of them,
+ // if we're dirty right now and interrupted; in that case we also need
+ // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
+ // better than that, because we don't really know what our size will be,
+ // and it might in fact not change on the followup reflow!
+ if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ absoluteContainer->MarkAllFramesDirty();
+ } else {
+ absoluteContainer->MarkSizeDependentFramesDirty();
+ }
+ if (haveInterrupt) {
+ // We're not going to reflow absolute frames; make sure to account for
+ // their existing overflow areas, which is usually a side effect of this
+ // reflow.
+ //
+ // TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
+ // interrupt, can we just rely on it and unconditionally take the else
+ // branch below? That's a bit more subtle / risky, since I don't see
+ // what would reflow them in that case if they depended on our size.
+ for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild();
+ kid; kid = kid->GetNextSibling()) {
+ ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
+ }
+ }
+ } else {
+ LogicalSize containingBlockSize =
+ CalculateContainingBlockSizeForAbsolutes(parentWM, aReflowInput,
+ aMetrics.Size(parentWM));
+
+ // Mark frames that depend on changes we just made to this frame as dirty:
+ // Now we can assume that the padding edge hasn't moved.
+ // We need to reflow the absolutes if one of them depends on
+ // its placeholder position, or the containing block size in a
+ // direction in which the containing block size might have
+ // changed.
+
+ // XXX "width" and "height" in this block will become ISize and BSize
+ // when nsAbsoluteContainingBlock is logicalized
+ bool cbWidthChanged = aMetrics.Width() != oldSize.width;
+ bool isRoot = !GetContent()->GetParent();
+ // If isRoot and we have auto height, then we are the initial
+ // containing block and the containing block height is the
+ // viewport height, which can't change during incremental
+ // reflow.
+ bool cbHeightChanged =
+ !(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) &&
+ aMetrics.Height() != oldSize.height;
+
+ nsRect containingBlock(nsPoint(0, 0),
+ containingBlockSize.GetPhysicalSize(parentWM));
+ AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight;
+ if (cbWidthChanged) {
+ flags |= AbsPosReflowFlags::CBWidthChanged;
+ }
+ if (cbHeightChanged) {
+ flags |= AbsPosReflowFlags::CBHeightChanged;
+ }
+ // Setup the line cursor here to optimize line searching for
+ // calculating hypothetical position of absolutely-positioned
+ // frames.
+ SetupLineCursorForQuery();
+ absoluteContainer->Reflow(this, aPresContext, aReflowInput, reflowStatus,
+ containingBlock, flags,
+ &aMetrics.mOverflowAreas);
+ }
+ }
+
+ FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
+
+ aStatus = reflowStatus;
+
+#ifdef DEBUG
+ // Between when we drain pushed floats and when we complete reflow,
+ // we're allowed to have multiple continuations of the same float on
+ // our floats list, since a first-in-flow might get pushed to a later
+ // continuation of its containing block. But it's not permitted
+ // outside that time.
+ nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
+
+ if (gNoisyReflow) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": status=%s metrics=%d,%d carriedMargin=%d",
+ ToString(aStatus).c_str(), aMetrics.ISize(parentWM),
+ aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get());
+ if (HasOverflowAreas()) {
+ printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x,
+ aMetrics.InkOverflow().y, aMetrics.InkOverflow().width,
+ aMetrics.InkOverflow().height);
+ printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x,
+ aMetrics.ScrollableOverflow().y,
+ aMetrics.ScrollableOverflow().width,
+ aMetrics.ScrollableOverflow().height);
+ }
+ printf("\n");
+ }
+
+ if (gLameReflowMetrics) {
+ PRTime end = PR_Now();
+
+ int32_t ectc = nsLineBox::GetCtorCount();
+ int32_t numLines = mLines.size();
+ if (!numLines) {
+ numLines = 1;
+ }
+ PRTime delta, perLineDelta, lines;
+ lines = int64_t(numLines);
+ delta = end - start;
+ perLineDelta = delta / lines;
+
+ ListTag(stdout);
+ char buf[400];
+ SprintfLiteral(buf,
+ ": %" PRId64 " elapsed (%" PRId64
+ " per line) (%d lines; %d new lines)",
+ delta, perLineDelta, numLines, ectc - ctc);
+ printf("%s\n", buf);
+ }
+#endif
+}
+
+nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
+ ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ TrialReflowState& aTrialState) {
+#ifdef DEBUG
+ // Between when we drain pushed floats and when we complete reflow,
+ // we're allowed to have multiple continuations of the same float on
+ // our floats list, since a first-in-flow might get pushed to a later
+ // continuation of its containing block. But it's not permitted
+ // outside that time.
+ nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
+#endif
+
+ // ALWAYS drain overflow. We never want to leave the previnflow's
+ // overflow lines hanging around; block reflow depends on the
+ // overflow line lists being cleared out between reflow passes.
+ DrainOverflowLines();
+
+ bool blockStartMarginRoot, blockEndMarginRoot;
+ IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
+
+ BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot,
+ blockEndMarginRoot, aTrialState.mNeedFloatManager,
+ aTrialState.mConsumedBSize,
+ aTrialState.mEffectiveContentBoxBSize,
+ aTrialState.mInset);
+
+ // Handle paginated overflow (see nsContainerFrame.h)
+ nsReflowStatus ocStatus;
+ if (GetPrevInFlow()) {
+ ReflowOverflowContainerChildren(
+ aPresContext, aReflowInput, aTrialState.mOcBounds,
+ ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge,
+ Some(state.ContainerSize()));
+ }
+
+ // Now that we're done cleaning up our overflow container lists, we can
+ // give |state| its nsOverflowContinuationTracker.
+ nsOverflowContinuationTracker tracker(this, false);
+ state.mOverflowTracker = &tracker;
+
+ // Drain & handle pushed floats
+ DrainPushedFloats();
+ ReflowPushedFloats(state, aTrialState.mFcBounds);
+
+ // If we're not dirty (which means we'll mark everything dirty later)
+ // and our inline-size has changed, mark the lines dirty that we need to
+ // mark dirty for a resize reflow.
+ if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.IsIResize()) {
+ PrepareResizeReflow(state);
+ }
+
+ // The same for percentage text-indent, except conditioned on the
+ // parent resizing.
+ if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.mCBReflowInput &&
+ aReflowInput.mCBReflowInput->IsIResize() &&
+ StyleText()->mTextIndent.length.HasPercent() && !mLines.empty()) {
+ mLines.front()->MarkDirty();
+ }
+
+ // For text-wrap:balance trials, we need to reflow all the lines even if
+ // they're not all "dirty".
+ if (aTrialState.mBalancing) {
+ MarkAllDescendantLinesDirty(this);
+ } else {
+ LazyMarkLinesDirty();
+ }
+
+ // Now reflow...
+ aTrialState.mUsedOverflowWrap = ReflowDirtyLines(state);
+
+ // If we have a next-in-flow, and that next-in-flow has pushed floats from
+ // this frame from a previous iteration of reflow, then we should not return
+ // a status with IsFullyComplete() equals to true, since we actually have
+ // overflow, it's just already been handled.
+
+ // NOTE: This really shouldn't happen, since we _should_ pull back our floats
+ // and reflow them, but just in case it does, this is a safety precaution so
+ // we don't end up with a placeholder pointing to frames that have already
+ // been deleted as part of removing our next-in-flow.
+ if (state.mReflowStatus.IsFullyComplete()) {
+ nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow());
+ while (nif) {
+ if (nif->HasPushedFloatsFromPrevContinuation()) {
+ if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ state.mReflowStatus.SetOverflowIncomplete();
+ } else {
+ state.mReflowStatus.SetIncomplete();
+ }
+ break;
+ }
+
+ nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow());
+ }
+ }
+
+ state.mReflowStatus.MergeCompletionStatusFrom(ocStatus);
+
+ // If we end in a BR with clear and affected floats continue,
+ // we need to continue, too.
+ if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() &&
+ state.mReflowStatus.IsComplete() &&
+ state.FloatManager()->ClearContinues(FindTrailingClear())) {
+ state.mReflowStatus.SetIncomplete();
+ }
+
+ if (!state.mReflowStatus.IsFullyComplete()) {
+ if (HasOverflowLines() || HasPushedFloats()) {
+ state.mReflowStatus.SetNextInFlowNeedsReflow();
+ }
+
+#ifdef DEBUG_kipp
+ ListTag(stdout);
+ printf(": block is not fully complete\n");
+#endif
+ }
+
+ // Place the ::marker's frame if it is placed next to a block child.
+ //
+ // According to the CSS2 spec, section 12.6.1, the ::marker's box
+ // participates in the height calculation of the list-item box's
+ // first line box.
+ //
+ // There are exactly two places a ::marker can be placed: near the
+ // first or second line. It's only placed on the second line in a
+ // rare case: an empty first line followed by a second line that
+ // contains a block (example: <LI>\n<P>... ). This is where
+ // the second case can happen.
+ if (HasOutsideMarker() && !mLines.empty() &&
+ (mLines.front()->IsBlock() ||
+ (0 == mLines.front()->BSize() && mLines.front() != mLines.back() &&
+ mLines.begin().next()->IsBlock()))) {
+ // Reflow the ::marker's frame.
+ ReflowOutput reflowOutput(aReflowInput);
+ // XXX Use the entire line when we fix bug 25888.
+ nsLayoutUtils::LinePosition position;
+ WritingMode wm = aReflowInput.GetWritingMode();
+ bool havePosition =
+ nsLayoutUtils::GetFirstLinePosition(wm, this, &position);
+ nscoord lineBStart =
+ havePosition ? position.mBStart
+ : aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
+ nsIFrame* marker = GetOutsideMarker();
+ ReflowOutsideMarker(marker, state, reflowOutput, lineBStart);
+ NS_ASSERTION(!MarkerIsEmpty() || reflowOutput.BSize(wm) == 0,
+ "empty ::marker frame took up space");
+
+ if (havePosition && !MarkerIsEmpty()) {
+ // We have some lines to align the ::marker with.
+
+ // Doing the alignment using the baseline will also cater for
+ // ::markers that are placed next to a child block (bug 92896)
+
+ // Tall ::markers won't look particularly nice here...
+ LogicalRect bbox =
+ marker->GetLogicalRect(wm, reflowOutput.PhysicalSize());
+ const auto baselineGroup = BaselineSharingGroup::First;
+ Maybe<nscoord> result;
+ if (MOZ_LIKELY(!wm.IsOrthogonalTo(marker->GetWritingMode()))) {
+ result = marker->GetNaturalBaselineBOffset(
+ wm, baselineGroup, BaselineExportContext::LineLayout);
+ }
+ const auto markerBaseline = result.valueOrFrom([bbox, wm, marker]() {
+ return bbox.BSize(wm) + marker->GetLogicalUsedMargin(wm).BEnd(wm);
+ });
+ bbox.BStart(wm) = position.mBaseline - markerBaseline;
+ marker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
+ }
+ // Otherwise just leave the ::marker where it is, up against our
+ // block-start padding.
+ }
+
+ // Clear any existing -webkit-line-clamp ellipsis.
+ if (aReflowInput.mStyleDisplay->mWebkitLineClamp) {
+ ClearLineClampEllipsis();
+ }
+
+ CheckFloats(state);
+
+ // Compute our final size (for this trial layout)
+ aTrialState.mBlockEndEdgeOfChildren =
+ ComputeFinalSize(aReflowInput, state, aMetrics);
+ aTrialState.mContainerWidth = state.ContainerSize().width;
+
+ return state.mReflowStatus;
+}
+
+bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
+ for (auto& line : Reversed(Lines())) {
+ if (0 != line.BSize() || !line.CachedIsEmpty()) {
+ return false;
+ }
+ if (line.HasClearance()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static nsLineBox* FindLineClampTarget(nsBlockFrame*& aFrame,
+ StyleLineClamp aLineNumber) {
+ MOZ_ASSERT(aLineNumber > 0);
+ MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
+ "Should have been removed earlier in nsBlockReflow::Reflow");
+
+ nsLineBox* target = nullptr;
+ nsBlockFrame* targetFrame = nullptr;
+ bool foundFollowingLine = false;
+
+ LineClampLineIterator iter(aFrame);
+
+ while (nsLineBox* line = iter.GetCurrentLine()) {
+ MOZ_ASSERT(!line->HasLineClampEllipsis(),
+ "Should have been removed earlier in nsBlockFrame::Reflow");
+ MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits(
+ NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
+ "Should have been removed earlier in nsBlockReflow::Reflow");
+
+ // Don't count a line that only has collapsible white space (as might exist
+ // after calling e.g. getBoxQuads).
+ if (line->IsEmpty()) {
+ iter.Next();
+ continue;
+ }
+
+ if (aLineNumber == 0) {
+ // We already previously found our target line, and now we have
+ // confirmed that there is another line after it.
+ foundFollowingLine = true;
+ break;
+ }
+
+ if (--aLineNumber == 0) {
+ // This is our target line. Continue looping to confirm that we
+ // have another line after us.
+ target = line;
+ targetFrame = iter.GetCurrentFrame();
+ }
+
+ iter.Next();
+ }
+
+ if (!foundFollowingLine) {
+ aFrame = nullptr;
+ return nullptr;
+ }
+
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(targetFrame);
+
+ aFrame = targetFrame;
+ return target;
+}
+
+static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
+ nsBlockFrame* aFrame,
+ nscoord aContentBlockEndEdge) {
+ if (!IsLineClampRoot(aFrame)) {
+ return aContentBlockEndEdge;
+ }
+ auto lineClamp = aReflowInput.mStyleDisplay->mWebkitLineClamp;
+ nsBlockFrame* frame = aFrame;
+ nsLineBox* line = FindLineClampTarget(frame, lineClamp);
+ if (!line) {
+ // The number of lines did not exceed the -webkit-line-clamp value.
+ return aContentBlockEndEdge;
+ }
+
+ // Mark the line as having an ellipsis so that TextOverflow will render it.
+ line->SetHasLineClampEllipsis();
+ frame->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
+
+ // Translate the b-end edge of the line up to aFrame's space.
+ nscoord edge = line->BEnd();
+ for (nsIFrame* f = frame; f != aFrame; f = f->GetParent()) {
+ edge +=
+ f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode());
+ }
+
+ return edge;
+}
+
+nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
+ BlockReflowState& aState,
+ ReflowOutput& aMetrics) {
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ const LogicalMargin& borderPadding = aState.BorderPadding();
+#ifdef NOISY_FINAL_SIZE
+ ListTag(stdout);
+ printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
+ aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no",
+ aState.mPrevBEndMargin.get(), borderPadding.BStart(wm),
+ borderPadding.BEnd(wm));
+#endif
+
+ // Compute final inline size
+ LogicalSize finalSize(wm);
+ finalSize.ISize(wm) =
+ NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm),
+ aReflowInput.ComputedISize()),
+ borderPadding.IEnd(wm));
+
+ // Return block-end margin information
+ // rbs says he hit this assertion occasionally (see bug 86947), so
+ // just set the margin to zero and we'll figure out why later
+ // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
+ // "someone else set the margin");
+ nscoord nonCarriedOutBDirMargin = 0;
+ if (!aState.mFlags.mIsBEndMarginRoot) {
+ // Apply rule from CSS 2.1 section 8.3.1. If we have some empty
+ // line with clearance and a non-zero block-start margin and all
+ // subsequent lines are empty, then we do not allow our children's
+ // carried out block-end margin to be carried out of us and collapse
+ // with our own block-end margin.
+ if (CheckForCollapsedBEndMarginFromClearanceLine()) {
+ // Convert the children's carried out margin to something that
+ // we will include in our height
+ nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get();
+ aState.mPrevBEndMargin.Zero();
+ }
+ aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
+ } else {
+ aMetrics.mCarriedOutBEndMargin.Zero();
+ }
+
+ nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin;
+ // Shrink wrap our height around our contents.
+ if (aState.mFlags.mIsBEndMarginRoot ||
+ NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
+ // When we are a block-end-margin root make sure that our last
+ // child's block-end margin is fully applied. We also do this when
+ // we have a computed height, since in that case the carried out
+ // margin is not going to be applied anywhere, so we should note it
+ // here to be included in the overflow area.
+ // Apply the margin only if there's space for it.
+ if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) {
+ // Truncate block-end margin if it doesn't fit to our available BSize.
+ blockEndEdgeOfChildren =
+ std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(),
+ aState.mReflowInput.AvailableBSize());
+ }
+ }
+ if (aState.mFlags.mBlockNeedsFloatManager) {
+ // Include the float manager's state to properly account for the
+ // block-end margin of any floated elements; e.g., inside a table cell.
+ //
+ // Note: The block coordinate returned by ClearFloats is always greater than
+ // or equal to blockEndEdgeOfChildren.
+ std::tie(blockEndEdgeOfChildren, std::ignore) =
+ aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both);
+ }
+
+ if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
+ // Note: We don't use blockEndEdgeOfChildren because it includes the
+ // previous margin.
+ const nscoord contentBSizeWithBStartBP =
+ aState.mBCoord + nonCarriedOutBDirMargin;
+
+ // We don't care about ApplyLineClamp's return value (the line-clamped
+ // content BSize) in this explicit-BSize codepath, but we do still need to
+ // call ApplyLineClamp for ellipsis markers to be placed as-needed.
+ ApplyLineClamp(aState.mReflowInput, this, contentBSizeWithBStartBP);
+
+ finalSize.BSize(wm) = ComputeFinalBSize(aState, contentBSizeWithBStartBP);
+
+ // If the content block-size is larger than the effective computed
+ // block-size, we extend the block-size to contain all the content.
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) {
+ // Note: finalSize.BSize(wm) is the border-box size, so we compare it with
+ // the content's block-size plus our border and padding..
+ finalSize.BSize(wm) =
+ std::max(finalSize.BSize(wm),
+ contentBSizeWithBStartBP + borderPadding.BEnd(wm));
+ }
+
+ // Don't carry out a block-end margin when our BSize is fixed.
+ //
+ // Note: this also includes the case that aReflowInput.ComputedBSize() is
+ // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it
+ // is replaced by the block size from aspect-ratio and inline size.
+ aMetrics.mCarriedOutBEndMargin.Zero();
+ } else {
+ Maybe<nscoord> containBSize = ContainIntrinsicBSize(
+ IsComboboxControlFrame() ? NS_UNCONSTRAINEDSIZE : 0);
+ if (containBSize && *containBSize != NS_UNCONSTRAINEDSIZE) {
+ // If we're size-containing in block axis and we don't have a specified
+ // block size, then our final size should actually be computed from only
+ // our border, padding and contain-intrinsic-block-size, ignoring the
+ // actual contents. Hence this case is a simplified version of the case
+ // below.
+ //
+ // NOTE: We exempt the nsComboboxControlFrame subclass from taking this
+ // special case when it has 'contain-intrinsic-block-size: none', because
+ // comboboxes implicitly honors the size-containment behavior on its
+ // nsComboboxDisplayFrame child (which it shrinkwraps) rather than on the
+ // nsComboboxControlFrame. (Moreover, the DisplayFrame child doesn't even
+ // need any special content-size-ignoring behavior in its reflow method,
+ // because that method just resolves "auto" BSize values to one
+ // line-height rather than by measuring its contents' BSize.)
+ nscoord contentBSize = *containBSize;
+ nscoord autoBSize =
+ aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize);
+ aMetrics.mCarriedOutBEndMargin.Zero();
+ autoBSize += borderPadding.BStartEnd(wm);
+ finalSize.BSize(wm) = autoBSize;
+ } else if (aState.mReflowStatus.IsInlineBreakBefore()) {
+ // Our parent is expected to push this frame to the next page/column so
+ // what size we set here doesn't really matter.
+ finalSize.BSize(wm) = aReflowInput.AvailableBSize();
+ } else if (aState.mReflowStatus.IsComplete()) {
+ const nscoord lineClampedContentBlockEndEdge =
+ ApplyLineClamp(aReflowInput, this, blockEndEdgeOfChildren);
+
+ const nscoord bpBStart = borderPadding.BStart(wm);
+ const nscoord contentBSize = blockEndEdgeOfChildren - bpBStart;
+ const nscoord lineClampedContentBSize =
+ lineClampedContentBlockEndEdge - bpBStart;
+
+ const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(
+ lineClampedContentBSize, aState.mConsumedBSize);
+ if (autoBSize != contentBSize) {
+ // Our min-block-size, max-block-size, or -webkit-line-clamp value made
+ // our bsize change. Don't carry out our kids' block-end margins.
+ aMetrics.mCarriedOutBEndMargin.Zero();
+ }
+ nscoord bSize = autoBSize + borderPadding.BStartEnd(wm);
+ if (MOZ_UNLIKELY(autoBSize > contentBSize &&
+ bSize > aReflowInput.AvailableBSize() &&
+ aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
+ // Applying `min-size` made us overflow our available size.
+ // Clamp it and report that we're Incomplete, or BreakBefore if we have
+ // 'break-inside: avoid' that is applicable.
+ bSize = aReflowInput.AvailableBSize();
+ if (ShouldAvoidBreakInside(aReflowInput)) {
+ aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
+ } else {
+ aState.mReflowStatus.SetIncomplete();
+ }
+ }
+ finalSize.BSize(wm) = bSize;
+ } else {
+ NS_ASSERTION(
+ aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
+ "Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
+ nscoord bSize = std::max(aState.mBCoord, aReflowInput.AvailableBSize());
+ if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
+ // This should never happen, but it does. See bug 414255
+ bSize = aState.mBCoord;
+ }
+ const nscoord maxBSize = aReflowInput.ComputedMaxBSize();
+ if (maxBSize != NS_UNCONSTRAINEDSIZE &&
+ aState.mConsumedBSize + bSize - borderPadding.BStart(wm) > maxBSize) {
+ // Compute this fragment's block-size, with the max-block-size
+ // constraint taken into consideration.
+ const nscoord clampedBSizeWithoutEndBP =
+ std::max(0, maxBSize - aState.mConsumedBSize) +
+ borderPadding.BStart(wm);
+ const nscoord clampedBSize =
+ clampedBSizeWithoutEndBP + borderPadding.BEnd(wm);
+ if (clampedBSize <= aReflowInput.AvailableBSize()) {
+ // We actually fit after applying `max-size` so we should be
+ // Overflow-Incomplete instead.
+ bSize = clampedBSize;
+ aState.mReflowStatus.SetOverflowIncomplete();
+ } else {
+ // We cannot fit after applying `max-size` with our block-end BP, so
+ // we should draw it in our next continuation.
+ bSize = clampedBSizeWithoutEndBP;
+ }
+ }
+ finalSize.BSize(wm) = bSize;
+ }
+ }
+
+ if (IsTrueOverflowContainer()) {
+ if (aState.mReflowStatus.IsIncomplete()) {
+ // Overflow containers can only be overflow complete.
+ // Note that auto height overflow containers have no normal children
+ NS_ASSERTION(finalSize.BSize(wm) == 0,
+ "overflow containers must be zero-block-size");
+ aState.mReflowStatus.SetOverflowIncomplete();
+ }
+ } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+ !aState.mReflowStatus.IsInlineBreakBefore() &&
+ aState.mReflowStatus.IsComplete()) {
+ // Currently only used for grid items, but could be used in other contexts.
+ // The FragStretchBSizeProperty is our expected non-fragmented block-size
+ // we should stretch to (for align-self:stretch etc). In some fragmentation
+ // cases though, the last fragment (this frame since we're complete), needs
+ // to have extra size applied because earlier fragments consumed too much of
+ // our computed size due to overflowing their containing block. (E.g. this
+ // ensures we fill the last row when a multi-row grid item is fragmented).
+ bool found;
+ nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found);
+ if (found) {
+ finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm));
+ }
+ }
+
+ // Clamp the content size to fit within the margin-box clamp size, if any.
+ if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains(
+ ComputeSizeFlag::BClampMarginBoxMinSize)) &&
+ aState.mReflowStatus.IsComplete()) {
+ bool found;
+ nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found);
+ if (found) {
+ auto marginBoxBSize =
+ finalSize.BSize(wm) +
+ aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
+ auto overflow = marginBoxBSize - cbSize;
+ if (overflow > 0) {
+ auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm);
+ auto newContentBSize = std::max(nscoord(0), contentBSize - overflow);
+ // XXXmats deal with percentages better somehow?
+ finalSize.BSize(wm) -= contentBSize - newContentBSize;
+ }
+ }
+ }
+
+ // Screen out negative block sizes --- can happen due to integer overflows :-(
+ finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
+
+ if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) {
+ SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren);
+ } else {
+ RemoveProperty(BlockEndEdgeOfChildrenProperty());
+ }
+
+ aMetrics.SetSize(wm, finalSize);
+
+#ifdef DEBUG_blocks
+ if ((ABSURD_SIZE(aMetrics.Width()) || ABSURD_SIZE(aMetrics.Height())) &&
+ !GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ ListTag(stdout);
+ printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
+ }
+#endif
+
+ return blockEndEdgeOfChildren;
+}
+
+void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
+ OverflowAreas& aOverflowAreas, nscoord aBEndEdgeOfChildren,
+ const nsStyleDisplay* aDisplay) const {
+ const auto wm = GetWritingMode();
+
+ // Factor in the block-end edge of the children. Child frames will be added
+ // to the overflow area as we iterate through the lines, but their margins
+ // won't, so we need to account for block-end margins here.
+ // REVIEW: For now, we do this for both visual and scrollable area,
+ // although when we make scrollable overflow area not be a subset of
+ // visual, we can change this.
+
+ if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
+ // If we are a scrolled inner frame, add our block-end padding to our
+ // children's block-end edge.
+ //
+ // Note: aBEndEdgeOfChildren already includes our own block-start padding
+ // because it is relative to our block-start edge of our border-box, which
+ // is the same as our padding-box here.
+ MOZ_ASSERT(GetLogicalUsedBorderAndPadding(wm) == GetLogicalUsedPadding(wm),
+ "A scrolled inner frame shouldn't have any border!");
+ aBEndEdgeOfChildren += GetLogicalUsedPadding(wm).BEnd(wm);
+ }
+
+ // XXX Currently, overflow areas are stored as physical rects, so we have
+ // to handle writing modes explicitly here. If we change overflow rects
+ // to be stored logically, this can be simplified again.
+ if (wm.IsVertical()) {
+ if (wm.IsVerticalLR()) {
+ for (const auto otype : AllOverflowTypes()) {
+ if (!(aDisplay->IsContainLayout() &&
+ otype == OverflowType::Scrollable)) {
+ // Layout containment should force all overflow to be ink (visual)
+ // overflow, so if we're layout-contained, we only add our children's
+ // block-end edge to the ink (visual) overflow -- not to the
+ // scrollable overflow.
+ nsRect& o = aOverflowAreas.Overflow(otype);
+ o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x;
+ }
+ }
+ } else {
+ for (const auto otype : AllOverflowTypes()) {
+ if (!(aDisplay->IsContainLayout() &&
+ otype == OverflowType::Scrollable)) {
+ nsRect& o = aOverflowAreas.Overflow(otype);
+ nscoord xmost = o.XMost();
+ o.x = std::min(o.x, xmost - aBEndEdgeOfChildren);
+ o.width = xmost - o.x;
+ }
+ }
+ }
+ } else {
+ for (const auto otype : AllOverflowTypes()) {
+ if (!(aDisplay->IsContainLayout() && otype == OverflowType::Scrollable)) {
+ nsRect& o = aOverflowAreas.Overflow(otype);
+ o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y;
+ }
+ }
+ }
+}
+
+void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas,
+ nscoord aBEndEdgeOfChildren,
+ const nsStyleDisplay* aDisplay) const {
+ // XXX_perf: This can be done incrementally. It is currently one of
+ // the things that makes incremental reflow O(N^2).
+ auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay);
+ auto overflowClipMargin = OverflowClipMargin(overflowClipAxes);
+ if (overflowClipAxes == PhysicalAxes::Both &&
+ overflowClipMargin == nsSize()) {
+ return;
+ }
+
+ // We rely here on our caller having called SetOverflowAreasToDesiredBounds().
+ nsRect frameBounds = aOverflowAreas.ScrollableOverflow();
+
+ for (const auto& line : Lines()) {
+ if (aDisplay->IsContainLayout()) {
+ // If we have layout containment, we should only consider our child's
+ // ink overflow, leaving the scrollable regions of the parent
+ // unaffected.
+ // Note: scrollable overflow is a subset of ink overflow,
+ // so this has the same affect as unioning the child's visual and
+ // scrollable overflow with its parent's ink overflow.
+ nsRect childVisualRect = line.InkOverflowRect();
+ OverflowAreas childVisualArea = OverflowAreas(childVisualRect, nsRect());
+ aOverflowAreas.UnionWith(childVisualArea);
+ } else {
+ aOverflowAreas.UnionWith(line.GetOverflowAreas());
+ }
+ }
+
+ // Factor an outside ::marker in; normally the ::marker will be factored
+ // into the line-box's overflow areas. However, if the line is a block
+ // line then it won't; if there are no lines, it won't. So just
+ // factor it in anyway (it can't hurt if it was already done).
+ // XXXldb Can we just fix GetOverflowArea instead?
+ if (nsIFrame* outsideMarker = GetOutsideMarker()) {
+ aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
+ }
+
+ ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren, aDisplay);
+
+ if (overflowClipAxes != PhysicalAxes::None) {
+ aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes,
+ overflowClipMargin);
+ }
+
+#ifdef NOISY_OVERFLOW_AREAS
+ printf("%s: InkOverflowArea=%s, ScrollableOverflowArea=%s\n", ListTag().get(),
+ ToString(aOverflowAreas.InkOverflow()).c_str(),
+ ToString(aOverflowAreas.ScrollableOverflow()).c_str());
+#endif
+}
+
+void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
+ // We need to update the overflow areas of lines manually, as they
+ // get cached and re-used otherwise. Lines aren't exposed as normal
+ // frame children, so calling UnionChildOverflow alone will end up
+ // using the old cached values.
+ for (auto& line : Lines()) {
+ nsRect bounds = line.GetPhysicalBounds();
+ OverflowAreas lineAreas(bounds, bounds);
+
+ int32_t n = line.GetChildCount();
+ for (nsIFrame* lineFrame = line.mFirstChild; n > 0;
+ lineFrame = lineFrame->GetNextSibling(), --n) {
+ ConsiderChildOverflow(lineAreas, lineFrame);
+ }
+
+ // Consider the overflow areas of the floats attached to the line as well
+ if (line.HasFloats()) {
+ for (nsIFrame* f : line.Floats()) {
+ ConsiderChildOverflow(lineAreas, f);
+ }
+ }
+
+ line.SetOverflowAreas(lineAreas);
+ aOverflowAreas.UnionWith(lineAreas);
+ }
+
+ // Union with child frames, skipping the principal and float lists
+ // since we already handled those using the line boxes.
+ nsLayoutUtils::UnionChildOverflow(
+ this, aOverflowAreas,
+ {FrameChildListID::Principal, FrameChildListID::Float});
+}
+
+bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
+ bool found;
+ nscoord blockEndEdgeOfChildren =
+ GetProperty(BlockEndEdgeOfChildrenProperty(), &found);
+ if (found) {
+ ConsiderBlockEndEdgeOfChildren(aOverflowAreas, blockEndEdgeOfChildren,
+ StyleDisplay());
+ }
+
+ // Line cursor invariants depend on the overflow areas of the lines, so
+ // we must clear the line cursor since those areas may have changed.
+ ClearLineCursors();
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+void nsBlockFrame::LazyMarkLinesDirty() {
+ if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) {
+ for (LineIterator line = LinesBegin(), line_end = LinesEnd();
+ line != line_end; ++line) {
+ int32_t n = line->GetChildCount();
+ for (nsIFrame* lineFrame = line->mFirstChild; n > 0;
+ lineFrame = lineFrame->GetNextSibling(), --n) {
+ if (lineFrame->IsSubtreeDirty()) {
+ // NOTE: MarkLineDirty does more than just marking the line dirty.
+ MarkLineDirty(line, &mLines);
+ break;
+ }
+ }
+ }
+ RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
+ }
+}
+
+void nsBlockFrame::MarkLineDirty(LineIterator aLine,
+ const nsLineList* aLineList) {
+ // Mark aLine dirty
+ aLine->MarkDirty();
+ aLine->SetInvalidateTextRuns(true);
+#ifdef DEBUG
+ if (gNoisyReflow) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
+ }
+#endif
+
+ // Mark previous line dirty if it's an inline line so that it can
+ // maybe pullup something from the line just affected.
+ // XXX We don't need to do this if aPrevLine ends in a break-after...
+ if (aLine != aLineList->front() && aLine->IsInline() &&
+ aLine.prev()->IsInline()) {
+ aLine.prev()->MarkDirty();
+ aLine.prev()->SetInvalidateTextRuns(true);
+#ifdef DEBUG
+ if (gNoisyReflow) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": mark prev-line %p dirty\n",
+ static_cast<void*>(aLine.prev().get()));
+ }
+#endif
+ }
+}
+
+/**
+ * Test whether lines are certain to be aligned left so that we can make
+ * resizing optimizations
+ */
+static inline bool IsAlignedLeft(StyleTextAlign aAlignment,
+ StyleDirection aDirection,
+ StyleUnicodeBidi aUnicodeBidi,
+ nsIFrame* aFrame) {
+ return aFrame->IsInSVGTextSubtree() || StyleTextAlign::Left == aAlignment ||
+ (((StyleTextAlign::Start == aAlignment &&
+ StyleDirection::Ltr == aDirection) ||
+ (StyleTextAlign::End == aAlignment &&
+ StyleDirection::Rtl == aDirection)) &&
+ aUnicodeBidi != StyleUnicodeBidi::Plaintext);
+}
+
+void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) {
+ // See if we can try and avoid marking all the lines as dirty
+ // FIXME(emilio): This should be writing-mode aware, I guess.
+ bool tryAndSkipLines =
+ // The left content-edge must be a constant distance from the left
+ // border-edge.
+ !StylePadding()->mPadding.Get(eSideLeft).HasPercent();
+
+#ifdef DEBUG
+ if (gDisableResizeOpt) {
+ tryAndSkipLines = false;
+ }
+ if (gNoisyReflow) {
+ if (!tryAndSkipLines) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": marking all lines dirty: availISize=%d\n",
+ aState.mReflowInput.AvailableISize());
+ }
+ }
+#endif
+
+ if (tryAndSkipLines) {
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ nscoord newAvailISize =
+ aState.mReflowInput.ComputedLogicalBorderPadding(wm).IStart(wm) +
+ aState.mReflowInput.ComputedISize();
+
+#ifdef DEBUG
+ if (gNoisyReflow) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": trying to avoid marking all lines dirty\n");
+ }
+#endif
+
+ for (LineIterator line = LinesBegin(), line_end = LinesEnd();
+ line != line_end; ++line) {
+ // We let child blocks make their own decisions the same
+ // way we are here.
+ bool isLastLine = line == mLines.back() && !GetNextInFlow();
+ if (line->IsBlock() || line->HasFloats() ||
+ (!isLastLine && !line->HasForcedLineBreakAfter()) ||
+ ((isLastLine || !line->IsLineWrapped())) ||
+ line->ResizeReflowOptimizationDisabled() ||
+ line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) {
+ line->MarkDirty();
+ }
+
+#ifdef REALLY_NOISY_REFLOW
+ if (!line->IsBlock()) {
+ printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
+ line.get(), line->IsImpactedByFloat() ? "" : "not ");
+ }
+#endif
+#ifdef DEBUG
+ if (gNoisyReflow && !line->IsDirty()) {
+ IndentBy(stdout, gNoiseIndent + 1);
+ printf(
+ "skipped: line=%p next=%p %s %s%s%s clearTypeBefore/After=%s/%s "
+ "xmost=%d\n",
+ static_cast<void*>(line.get()),
+ static_cast<void*>(
+ (line.next() != LinesEnd() ? line.next().get() : nullptr)),
+ line->IsBlock() ? "block" : "inline",
+ line->HasForcedLineBreakAfter() ? "has-break-after " : "",
+ line->HasFloats() ? "has-floats " : "",
+ line->IsImpactedByFloat() ? "impacted " : "",
+ line->StyleClearToString(line->FloatClearTypeBefore()),
+ line->StyleClearToString(line->FloatClearTypeAfter()),
+ line->IEnd());
+ }
+#endif
+ }
+ } else {
+ // Mark everything dirty
+ for (auto& line : Lines()) {
+ line.MarkDirty();
+ }
+ }
+}
+
+//----------------------------------------
+
+/**
+ * Propagate reflow "damage" from from earlier lines to the current
+ * line. The reflow damage comes from the following sources:
+ * 1. The regions of float damage remembered during reflow.
+ * 2. The combination of nonzero |aDeltaBCoord| and any impact by a
+ * float, either the previous reflow or now.
+ *
+ * When entering this function, |aLine| is still at its old position and
+ * |aDeltaBCoord| indicates how much it will later be slid (assuming it
+ * doesn't get marked dirty and reflowed entirely).
+ */
+void nsBlockFrame::PropagateFloatDamage(BlockReflowState& aState,
+ nsLineBox* aLine,
+ nscoord aDeltaBCoord) {
+ nsFloatManager* floatManager = aState.FloatManager();
+ NS_ASSERTION(
+ (aState.mReflowInput.mParentReflowInput &&
+ aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) ||
+ aState.mReflowInput.mBlockDelta == 0,
+ "Bad block delta passed in");
+
+ // Check to see if there are any floats; if there aren't, there can't
+ // be any float damage
+ if (!floatManager->HasAnyFloats()) {
+ return;
+ }
+
+ // Check the damage region recorded in the float damage.
+ if (floatManager->HasFloatDamage()) {
+ // Need to check mBounds *and* mCombinedArea to find intersections
+ // with aLine's floats
+ nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord;
+ nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize();
+ // Scrollable overflow should be sufficient for things that affect
+ // layout.
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ nsSize containerSize = aState.ContainerSize();
+ LogicalRect overflow =
+ aLine->GetOverflowArea(OverflowType::Scrollable, wm, containerSize);
+ nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord;
+ nscoord lineBCoordCombinedAfter =
+ lineBCoordCombinedBefore + overflow.BSize(wm);
+
+ bool isDirty =
+ floatManager->IntersectsDamage(lineBCoordBefore, lineBCoordAfter) ||
+ floatManager->IntersectsDamage(lineBCoordCombinedBefore,
+ lineBCoordCombinedAfter);
+ if (isDirty) {
+ aLine->MarkDirty();
+ return;
+ }
+ }
+
+ // Check if the line is moving relative to the float manager
+ if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) {
+ if (aLine->IsBlock()) {
+ // Unconditionally reflow sliding blocks; we only really need to reflow
+ // if there's a float impacting this block, but the current float manager
+ // makes it difficult to check that. Therefore, we let the child block
+ // decide what it needs to reflow.
+ aLine->MarkDirty();
+ } else {
+ bool wasImpactedByFloat = aLine->IsImpactedByFloat();
+ nsFlowAreaRect floatAvailableSpace =
+ aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord,
+ aLine->BSize(), nullptr);
+
+#ifdef REALLY_NOISY_REFLOW
+ printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", this,
+ wasImpactedByFloat, floatAvailableSpace.HasFloats());
+#endif
+
+ // Mark the line dirty if it was or is affected by a float
+ // We actually only really need to reflow if the amount of impact
+ // changes, but that's not straightforward to check
+ if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) {
+ aLine->MarkDirty();
+ }
+ }
+ }
+}
+
+static bool LineHasClear(nsLineBox* aLine) {
+ return aLine->IsBlock()
+ ? (aLine->HasForcedLineBreakBefore() ||
+ aLine->mFirstChild->HasAnyStateBits(
+ NS_BLOCK_HAS_CLEAR_CHILDREN) ||
+ !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild))
+ : aLine->HasFloatClearTypeAfter();
+}
+
+/**
+ * Reparent a whole list of floats from aOldParent to this block. The
+ * floats might be taken from aOldParent's overflow list. They will be
+ * removed from the list. They end up appended to our mFloats list.
+ */
+void nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame,
+ nsBlockFrame* aOldParent,
+ bool aReparentSiblings) {
+ nsFrameList list;
+ aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings);
+ if (list.NotEmpty()) {
+ for (nsIFrame* f : list) {
+ MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
+ "CollectFloats should've removed that bit");
+ ReparentFrame(f, aOldParent, this);
+ }
+ mFloats.AppendFrames(nullptr, std::move(list));
+ }
+}
+
+static void DumpLine(const BlockReflowState& aState, nsLineBox* aLine,
+ nscoord aDeltaBCoord, int32_t aDeltaIndent) {
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyReflow) {
+ nsRect ovis(aLine->InkOverflowRect());
+ nsRect oscr(aLine->ScrollableOverflowRect());
+ nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
+ printf(
+ "line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} "
+ "oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} "
+ "deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n",
+ static_cast<void*>(aLine), aState.mBCoord,
+ aLine->IsDirty() ? "yes" : "no", aLine->IStart(), aLine->BStart(),
+ aLine->ISize(), aLine->BSize(), ovis.x, ovis.y, ovis.width, ovis.height,
+ oscr.x, oscr.y, oscr.width, oscr.height, aDeltaBCoord,
+ aState.mPrevBEndMargin.get(), aLine->GetChildCount());
+ }
+#endif
+}
+
+static bool LinesAreEmpty(const nsLineList& aList) {
+ for (const auto& line : aList) {
+ if (!line.IsEmpty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
+ bool keepGoing = true;
+ bool repositionViews = false; // should we really need this?
+ bool foundAnyClears = aState.mTrailingClearFromPIF != StyleClear::None;
+ bool willReflowAgain = false;
+ bool usedOverflowWrap = false;
+
+#ifdef DEBUG
+ if (gNoisyReflow) {
+ IndentBy(stdout, gNoiseIndent);
+ ListTag(stdout);
+ printf(": reflowing dirty lines");
+ printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
+ }
+ AutoNoisyIndenter indent(gNoisyReflow);
+#endif
+
+ bool selfDirty = HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
+ (aState.mReflowInput.IsBResize() &&
+ HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE));
+
+ // Reflow our last line if our availableBSize has increased
+ // so that we (and our last child) pull up content as necessary
+ if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+ GetNextInFlow() &&
+ aState.mReflowInput.AvailableBSize() >
+ GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
+ LineIterator lastLine = LinesEnd();
+ if (lastLine != LinesBegin()) {
+ --lastLine;
+ lastLine->MarkDirty();
+ }
+ }
+ // the amount by which we will slide the current line if it is not
+ // dirty
+ nscoord deltaBCoord = 0;
+
+ // whether we did NOT reflow the previous line and thus we need to
+ // recompute the carried out margin before the line if we want to
+ // reflow it or if its previous margin is dirty
+ bool needToRecoverState = false;
+ // Float continuations were reflowed in ReflowPushedFloats
+ bool reflowedFloat =
+ mFloats.NotEmpty() &&
+ mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
+ bool lastLineMovedUp = false;
+ // We save up information about BR-clearance here
+ StyleClear inlineFloatClearType = aState.mTrailingClearFromPIF;
+
+ LineIterator line = LinesBegin(), line_end = LinesEnd();
+
+ // Determine if children of this frame could have breaks between them for
+ // page names.
+ //
+ // We need to check for paginated layout, the named-page pref, and if the
+ // available block-size is constrained.
+ //
+ // Note that we need to check for paginated layout as named-pages are only
+ // used during paginated reflow. We need to additionally check for
+ // unconstrained block-size to avoid introducing fragmentation breaks during
+ // "measuring" reflows within an overall paginated reflow, and to avoid
+ // fragmentation in monolithic containers like 'inline-block'.
+ //
+ // Because we can only break for named pages using Class A breakpoints, we
+ // also need to check that the block flow direction of the containing frame
+ // of these items (which is this block) is parallel to that of this page.
+ // See: https://www.w3.org/TR/css-break-3/#btw-blocks
+ const nsPresContext* const presCtx = aState.mPresContext;
+ const bool canBreakForPageNames =
+ aState.mReflowInput.mFlags.mCanHaveClassABreakpoints &&
+ aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+ presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
+ GetWritingMode().IsVertical();
+
+ // ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named
+ // pages pref and presCtx->IsPaginated, so we did not explicitly check these
+ // above when setting canBreakForPageNames.
+ if (canBreakForPageNames) {
+ MOZ_ASSERT(presCtx->IsPaginated(),
+ "canBreakForPageNames should not be set during non-paginated "
+ "reflow");
+ }
+
+ // Reflow the lines that are already ours
+ for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
+ DumpLine(aState, line, deltaBCoord, 0);
+#ifdef DEBUG
+ AutoNoisyIndenter indent2(gNoisyReflow);
+#endif
+
+ if (selfDirty) {
+ line->MarkDirty();
+ }
+
+ // This really sucks, but we have to look inside any blocks that have clear
+ // elements inside them.
+ // XXX what can we do smarter here?
+ if (!line->IsDirty() && line->IsBlock() &&
+ line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN)) {
+ line->MarkDirty();
+ }
+
+ nsIFrame* floatAvoidingBlock = nullptr;
+ if (line->IsBlock() &&
+ !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) {
+ floatAvoidingBlock = line->mFirstChild;
+ }
+
+ // We have to reflow the line if it's a block whose clearance
+ // might have changed, so detect that.
+ if (!line->IsDirty() &&
+ (line->HasForcedLineBreakBefore() || floatAvoidingBlock)) {
+ nscoord curBCoord = aState.mBCoord;
+ // See where we would be after applying any clearance due to
+ // BRs.
+ if (inlineFloatClearType != StyleClear::None) {
+ std::tie(curBCoord, std::ignore) =
+ aState.ClearFloats(curBCoord, inlineFloatClearType);
+ }
+
+ auto [newBCoord, result] = aState.ClearFloats(
+ curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock);
+
+ if (line->HasClearance()) {
+ // Reflow the line if it might not have clearance anymore.
+ if (result == ClearFloatsResult::BCoordNoChange
+ // aState.mBCoord is the clearance point which should be the
+ // block-start border-edge of the block frame. If sliding the
+ // block by deltaBCoord isn't going to put it in the predicted
+ // position, then we'd better reflow the line.
+ || newBCoord != line->BStart() + deltaBCoord) {
+ line->MarkDirty();
+ }
+ } else {
+ // Reflow the line if the line might have clearance now.
+ if (result != ClearFloatsResult::BCoordNoChange) {
+ line->MarkDirty();
+ }
+ }
+ }
+
+ // We might have to reflow a line that is after a clearing BR.
+ if (inlineFloatClearType != StyleClear::None) {
+ std::tie(aState.mBCoord, std::ignore) =
+ aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
+ if (aState.mBCoord != line->BStart() + deltaBCoord) {
+ // SlideLine is not going to put the line where the clearance
+ // put it. Reflow the line to be sure.
+ line->MarkDirty();
+ }
+ inlineFloatClearType = StyleClear::None;
+ }
+
+ bool previousMarginWasDirty = line->IsPreviousMarginDirty();
+ if (previousMarginWasDirty) {
+ // If the previous margin is dirty, reflow the current line
+ line->MarkDirty();
+ line->ClearPreviousMarginDirty();
+ } else if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
+ const nscoord scrollableOverflowBEnd =
+ LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(),
+ line->mContainerSize)
+ .BEnd(line->mWritingMode);
+ if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) {
+ // Lines that aren't dirty but get slid past our available block-size
+ // constraint must be reflowed.
+ line->MarkDirty();
+ }
+ }
+
+ if (!line->IsDirty()) {
+ const bool isPaginated =
+ // Last column can be reflowed unconstrained during column balancing.
+ // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check
+ // as a fail-safe fallback.
+ aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE ||
+ HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
+ // Table can also be reflowed unconstrained during printing.
+ aState.mPresContext->IsPaginated();
+ if (isPaginated) {
+ // We are in a paginated context, i.e. in columns or pages.
+ const bool mayContainFloats =
+ line->IsBlock() || line->HasFloats() || line->HadFloatPushed();
+ if (mayContainFloats) {
+ // The following if-else conditions check whether this line -- which
+ // might have floats in its subtree, or has floats as direct children,
+ // or had floats pushed -- needs to be reflowed.
+ if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) {
+ // The distance to the block-end edge might have changed. Reflow the
+ // line both because the breakpoints within its floats may have
+ // changed and because we might have to push/pull the floats in
+ // their entirety.
+ line->MarkDirty();
+ } else if (HasPushedFloats()) {
+ // We had pushed floats which haven't been drained by our
+ // next-in-flow, which means our parent is currently reflowing us
+ // again due to clearance without creating a next-in-flow for us.
+ // Reflow the line to redo the floats split logic to correctly set
+ // our reflow status.
+ line->MarkDirty();
+ } else if (aState.mReflowInput.mFlags.mMustReflowPlaceholders) {
+ // Reflow the line (that may containing a float's placeholder frame)
+ // if our parent tells us to do so.
+ line->MarkDirty();
+ } else if (aState.mReflowInput.mFlags.mMovedBlockFragments) {
+ // Our parent's line containing us moved to a different fragment.
+ // Reflow the line because the decision about whether the float fits
+ // may be different in a different fragment.
+ line->MarkDirty();
+ }
+ }
+ }
+ }
+
+ if (!line->IsDirty()) {
+ // See if there's any reflow damage that requires that we mark the
+ // line dirty.
+ PropagateFloatDamage(aState, line, deltaBCoord);
+ }
+
+ // If the container size has changed, reset mContainerSize. If the
+ // line's writing mode is not ltr, or if the line is not left-aligned, also
+ // mark the line dirty.
+ if (aState.ContainerSize() != line->mContainerSize) {
+ line->mContainerSize = aState.ContainerSize();
+
+ const bool isLastLine = line == mLines.back() && !GetNextInFlow();
+ const auto align = isLastLine ? StyleText()->TextAlignForLastLine()
+ : StyleText()->mTextAlign;
+ if (line->mWritingMode.IsVertical() || line->mWritingMode.IsBidiRTL() ||
+ !IsAlignedLeft(align, StyleVisibility()->mDirection,
+ StyleTextReset()->mUnicodeBidi, this)) {
+ line->MarkDirty();
+ }
+ }
+
+ // Check for a page break caused by CSS named pages.
+ //
+ // We should break for named pages when two frames meet at a class A
+ // breakpoint, where the first frame has a different end page value to the
+ // second frame's start page value. canBreakForPageNames is true iff
+ // children of this frame can form class A breakpoints, and that we are not
+ // in a measurement reflow or in a monolithic container such as
+ // 'inline-block'.
+ //
+ // We specifically do not want to cause a page-break for named pages when
+ // we are at the top of a page. This would otherwise happen when the
+ // previous sibling is an nsPageBreakFrame, or all previous siblings on the
+ // current page are zero-height. The latter may not be per-spec, but is
+ // compatible with Chrome's implementation of named pages.
+ const nsAtom* nextPageName = nullptr;
+ bool shouldBreakForPageName = false;
+ if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage ||
+ !aState.IsAdjacentWithBStart())) {
+ const nsIFrame* const frame = line->mFirstChild;
+ if (!frame->IsPlaceholderFrame() && !frame->IsPageBreakFrame()) {
+ nextPageName = frame->GetStartPageValue();
+ // Walk back to the last frame that isn't a placeholder.
+ const nsIFrame* prevFrame = frame->GetPrevSibling();
+ while (prevFrame && prevFrame->IsPlaceholderFrame()) {
+ prevFrame = prevFrame->GetPrevSibling();
+ }
+ if (prevFrame && prevFrame->GetEndPageValue() != nextPageName) {
+ shouldBreakForPageName = true;
+ line->MarkDirty();
+ }
+ }
+ }
+
+ if (needToRecoverState && line->IsDirty()) {
+ // We need to reconstruct the block-end margin only if we didn't
+ // reflow the previous line and we do need to reflow (or repair
+ // the block-start position of) the next line.
+ aState.ReconstructMarginBefore(line);
+ }
+
+ bool reflowedPrevLine = !needToRecoverState;
+ if (needToRecoverState) {
+ needToRecoverState = false;
+
+ // Update aState.mPrevChild as if we had reflowed all of the frames in
+ // this line.
+ if (line->IsDirty()) {
+ NS_ASSERTION(
+ line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(),
+ "unexpected line frames");
+ aState.mPrevChild = line->mFirstChild->GetPrevSibling();
+ }
+ }
+
+ // Now repair the line and update |aState.mBCoord| by calling
+ // |ReflowLine| or |SlideLine|.
+ // If we're going to reflow everything again, then no need to reflow
+ // the dirty line ... unless the line has floats, in which case we'd
+ // better reflow it now to refresh its float cache, which may contain
+ // dangling frame pointers! Ugh! This reflow of the line may be
+ // incorrect because we skipped reflowing previous lines (e.g., floats
+ // may be placed incorrectly), but that's OK because we'll mark the
+ // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..."
+ if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
+ lastLineMovedUp = true;
+
+ bool maybeReflowingForFirstTime =
+ line->IStart() == 0 && line->BStart() == 0 && line->ISize() == 0 &&
+ line->BSize() == 0;
+
+ // Compute the dirty lines "before" BEnd, after factoring in
+ // the running deltaBCoord value - the running value is implicit in
+ // aState.mBCoord.
+ nscoord oldB = line->BStart();
+ nscoord oldBMost = line->BEnd();
+
+ NS_ASSERTION(!willReflowAgain || !line->IsBlock(),
+ "Don't reflow blocks while willReflowAgain is true, reflow "
+ "of block abs-pos children depends on this");
+
+ if (shouldBreakForPageName) {
+ // Immediately fragment for page-name. It is possible we could break
+ // out of the loop right here, but this should make it more similar to
+ // what happens when reflow causes fragmentation.
+ PushTruncatedLine(aState, line, &keepGoing);
+ PresShell()->FrameConstructor()->SetNextPageContentFramePageName(
+ nextPageName ? nextPageName : GetAutoPageValue());
+ } else {
+ // Reflow the dirty line. If it's an incremental reflow, then force
+ // it to invalidate the dirty area if necessary
+ usedOverflowWrap |= ReflowLine(aState, line, &keepGoing);
+ }
+
+ if (aState.mReflowInput.WillReflowAgainForClearance()) {
+ line->MarkDirty();
+ willReflowAgain = true;
+ // Note that once we've entered this state, every line that gets here
+ // (e.g. because it has floats) gets marked dirty and reflowed again.
+ // in the next pass. This is important, see above.
+ }
+
+ if (line->HasFloats()) {
+ reflowedFloat = true;
+ }
+
+ if (!keepGoing) {
+ DumpLine(aState, line, deltaBCoord, -1);
+ if (0 == line->GetChildCount()) {
+ DeleteLine(aState, line, line_end);
+ }
+ break;
+ }
+
+ // Test to see whether the margin that should be carried out
+ // to the next line (NL) might have changed. In ReflowBlockFrame
+ // we call nextLine->MarkPreviousMarginDirty if the block's
+ // actual carried-out block-end margin changed. So here we only
+ // need to worry about the following effects:
+ // 1) the line was just created, and it might now be blocking
+ // a carried-out block-end margin from previous lines that
+ // used to reach NL from reaching NL
+ // 2) the line used to be empty, and is now not empty,
+ // thus blocking a carried-out block-end margin from previous lines
+ // that used to reach NL from reaching NL
+ // 3) the line wasn't empty, but now is, so a carried-out
+ // block-end margin from previous lines that didn't used to reach NL
+ // now does
+ // 4) the line might have changed in a way that affects NL's
+ // ShouldApplyBStartMargin decision. The three things that matter
+ // are the line's emptiness, its adjacency to the block-start edge of the
+ // block, and whether it has clearance (the latter only matters if the
+ // block was and is adjacent to the block-start and empty).
+ //
+ // If the line is empty now, we can't reliably tell if the line was empty
+ // before, so we just assume it was and do
+ // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are
+ // redundant; if the line is empty now we don't need to check 4), but if
+ // the line is not empty now and we're sure it wasn't empty before, any
+ // adjacency and clearance changes are irrelevant to the result of
+ // nextLine->ShouldApplyBStartMargin.
+ if (line.next() != LinesEnd()) {
+ bool maybeWasEmpty = oldB == line.next()->BStart();
+ bool isEmpty = line->CachedIsEmpty();
+ if (maybeReflowingForFirstTime /*1*/ ||
+ (isEmpty || maybeWasEmpty) /*2/3/4*/) {
+ line.next()->MarkPreviousMarginDirty();
+ // since it's marked dirty, nobody will care about |deltaBCoord|
+ }
+ }
+
+ // If the line was just reflowed for the first time, then its
+ // old mBounds cannot be trusted so this deltaBCoord computation is
+ // bogus. But that's OK because we just did
+ // MarkPreviousMarginDirty on the next line which will force it
+ // to be reflowed, so this computation of deltaBCoord will not be
+ // used.
+ deltaBCoord = line->BEnd() - oldBMost;
+
+ // Now do an interrupt check. We want to do this only in the case when we
+ // actually reflow the line, so that if we get back in here we'll get
+ // further on the reflow before interrupting.
+ aState.mPresContext->CheckForInterrupt(this);
+ } else {
+ aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus);
+ // Nop except for blocks (we don't create overflow container
+ // continuations for any inlines atm), so only checking mFirstChild
+ // is enough
+
+ lastLineMovedUp = deltaBCoord < 0;
+
+ if (deltaBCoord != 0) {
+ SlideLine(aState, line, deltaBCoord);
+ } else {
+ repositionViews = true;
+ }
+
+ NS_ASSERTION(!line->IsDirty() || !line->HasFloats(),
+ "Possibly stale float cache here!");
+ if (willReflowAgain && line->IsBlock()) {
+ // If we're going to reflow everything again, and this line is a block,
+ // then there is no need to recover float state. The line may contain
+ // other lines with floats, but in that case RecoverStateFrom would only
+ // add floats to the float manager. We don't need to do that because
+ // everything's going to get reflowed again "for real". Calling
+ // RecoverStateFrom in this situation could be lethal because the
+ // block's descendant lines may have float caches containing dangling
+ // frame pointers. Ugh!
+ // If this line is inline, then we need to recover its state now
+ // to make sure that we don't forget to move its floats by deltaBCoord.
+ } else {
+ // XXX EVIL O(N^2) EVIL
+ aState.RecoverStateFrom(line, deltaBCoord);
+ }
+
+ // Keep mBCoord up to date in case we're propagating reflow damage
+ // and also because our final height may depend on it. If the
+ // line is inlines, then only update mBCoord if the line is not
+ // empty, because that's what PlaceLine does. (Empty blocks may
+ // want to update mBCoord, e.g. if they have clearance.)
+ if (line->IsBlock() || !line->CachedIsEmpty()) {
+ aState.mBCoord = line->BEnd();
+ }
+
+ needToRecoverState = true;
+
+ if (reflowedPrevLine && !line->IsBlock() &&
+ aState.mPresContext->HasPendingInterrupt()) {
+ // Need to make sure to pull overflows from any prev-in-flows
+ for (nsIFrame* inlineKid = line->mFirstChild; inlineKid;
+ inlineKid = inlineKid->PrincipalChildList().FirstChild()) {
+ inlineKid->PullOverflowsFromPrevInFlow();
+ }
+ }
+ }
+
+ // Record if we need to clear floats before reflowing the next
+ // line. Note that inlineFloatClearType will be handled and
+ // cleared before the next line is processed, so there is no
+ // need to combine break types here.
+ if (line->HasFloatClearTypeAfter()) {
+ inlineFloatClearType = line->FloatClearTypeAfter();
+ }
+
+ if (LineHasClear(line.get())) {
+ foundAnyClears = true;
+ }
+
+ DumpLine(aState, line, deltaBCoord, -1);
+
+ if (aState.mPresContext->HasPendingInterrupt()) {
+ willReflowAgain = true;
+ // Another option here might be to leave |line| clean if
+ // !HasPendingInterrupt() before the CheckForInterrupt() call, since in
+ // that case the line really did reflow as it should have. Not sure
+ // whether that would be safe, so doing this for now instead. Also not
+ // sure whether we really want to mark all lines dirty after an
+ // interrupt, but until we get better at propagating float damage we
+ // really do need to do it this way; see comments inside MarkLineDirty.
+ MarkLineDirtyForInterrupt(line);
+ }
+ }
+
+ // Handle BR-clearance from the last line of the block
+ if (inlineFloatClearType != StyleClear::None) {
+ std::tie(aState.mBCoord, std::ignore) =
+ aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
+ }
+
+ if (needToRecoverState) {
+ // Is this expensive?
+ aState.ReconstructMarginBefore(line);
+
+ // Update aState.mPrevChild as if we had reflowed all of the frames in
+ // the last line.
+ NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() ==
+ line.prev()->LastChild(),
+ "unexpected line frames");
+ aState.mPrevChild = line == line_end ? mFrames.LastChild()
+ : line->mFirstChild->GetPrevSibling();
+ }
+
+ // Should we really have to do this?
+ if (repositionViews) {
+ nsContainerFrame::PlaceFrameView(this);
+ }
+
+ // We can skip trying to pull up the next line if our height is constrained
+ // (so we can report being incomplete) and there is no next in flow or we
+ // were told not to or we know it will be futile, i.e.,
+ // -- the next in flow is not changing
+ // -- and we cannot have added more space for its first line to be
+ // pulled up into,
+ // -- it's an incremental reflow of a descendant
+ // -- and we didn't reflow any floats (so the available space
+ // didn't change)
+ // -- my chain of next-in-flows either has no first line, or its first
+ // line isn't dirty.
+ bool heightConstrained =
+ aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE;
+ bool skipPull = willReflowAgain && heightConstrained;
+ if (!skipPull && heightConstrained && aState.mNextInFlow &&
+ (aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp &&
+ !HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) {
+ // We'll place lineIter at the last line of this block, so that
+ // nsBlockInFlowLineIterator::Next() will take us to the first
+ // line of my next-in-flow-chain. (But first, check that I
+ // have any lines -- if I don't, just bail out of this
+ // optimization.)
+ LineIterator lineIter = this->LinesEnd();
+ if (lineIter != this->LinesBegin()) {
+ lineIter--; // I have lines; step back from dummy iterator to last line.
+ nsBlockInFlowLineIterator bifLineIter(this, lineIter);
+
+ // Check for next-in-flow-chain's first line.
+ // (First, see if there is such a line, and second, see if it's clean)
+ if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) {
+ skipPull = true;
+ }
+ }
+ }
+
+ if (skipPull && aState.mNextInFlow) {
+ NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
+ if (aState.mNextInFlow->IsTrueOverflowContainer()) {
+ aState.mReflowStatus.SetOverflowIncomplete();
+ } else {
+ aState.mReflowStatus.SetIncomplete();
+ }
+ }
+
+ if (!skipPull && aState.mNextInFlow) {
+ // Pull data from a next-in-flow if there's still room for more
+ // content here.
+ while (keepGoing && aState.mNextInFlow) {
+ // Grab first line from our next-in-flow
+ nsBlockFrame* nextInFlow = aState.mNextInFlow;
+ nsLineBox* pulledLine;
+ nsFrameList pulledFrames;
+ if (!nextInFlow->mLines.empty()) {
+ RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine,
+ &pulledFrames);
+ } else {
+ // Grab an overflow line if there are any
+ FrameLines* overflowLines = nextInFlow->GetOverflowLines();
+ if (!overflowLines) {
+ aState.mNextInFlow =
+ static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
+ continue;
+ }
+ bool last =
+ RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
+ &pulledLine, &pulledFrames);
+ if (last) {
+ nextInFlow->DestroyOverflowLines();
+ }
+ }
+
+ if (pulledFrames.IsEmpty()) {
+ // The line is empty. Try the next one.
+ NS_ASSERTION(
+ pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild,
+ "bad empty line");
+ nextInFlow->FreeLineBox(pulledLine);
+ continue;
+ }
+
+ if (nextInFlow->MaybeHasLineCursor()) {
+ if (pulledLine == nextInFlow->GetLineCursorForDisplay()) {
+ nextInFlow->ClearLineCursorForDisplay();
+ }
+ if (pulledLine == nextInFlow->GetLineCursorForQuery()) {
+ nextInFlow->ClearLineCursorForQuery();
+ }
+ }
+ ReparentFrames(pulledFrames, nextInFlow, this);
+ pulledLine->SetMovedFragments();
+
+ NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
+ "Unexpected last frame");
+ NS_ASSERTION(aState.mPrevChild || mLines.empty(),
+ "should have a prevchild here");
+ NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
+ "Incorrect aState.mPrevChild before inserting line at end");
+
+ // Shift pulledLine's frames into our mFrames list.
+ mFrames.AppendFrames(nullptr, std::move(pulledFrames));
+
+ // Add line to our line list, and set its last child as our new prev-child
+ line = mLines.before_insert(LinesEnd(), pulledLine);
+ aState.mPrevChild = mFrames.LastChild();
+
+ // Reparent floats whose placeholders are in the line.
+ ReparentFloats(pulledLine->mFirstChild, nextInFlow, true);
+
+ DumpLine(aState, pulledLine, deltaBCoord, 0);
+#ifdef DEBUG
+ AutoNoisyIndenter indent2(gNoisyReflow);
+#endif
+
+ if (aState.mPresContext->HasPendingInterrupt()) {
+ MarkLineDirtyForInterrupt(line);
+ } else {
+ // Now reflow it and any lines that it makes during it's reflow
+ // (we have to loop here because reflowing the line may cause a new
+ // line to be created; see SplitLine's callers for examples of
+ // when this happens).
+ while (line != LinesEnd()) {
+ usedOverflowWrap |= ReflowLine(aState, line, &keepGoing);
+
+ if (aState.mReflowInput.WillReflowAgainForClearance()) {
+ line->MarkDirty();
+ keepGoing = false;
+ aState.mReflowStatus.SetIncomplete();
+ break;
+ }
+
+ DumpLine(aState, line, deltaBCoord, -1);
+ if (!keepGoing) {
+ if (0 == line->GetChildCount()) {
+ DeleteLine(aState, line, line_end);
+ }
+ break;
+ }
+
+ if (LineHasClear(line.get())) {
+ foundAnyClears = true;
+ }
+
+ if (aState.mPresContext->CheckForInterrupt(this)) {
+ MarkLineDirtyForInterrupt(line);
+ break;
+ }
+
+ // If this is an inline frame then its time to stop
+ ++line;
+ aState.AdvanceToNextLine();
+ }
+ }
+ }
+
+ if (aState.mReflowStatus.IsIncomplete()) {
+ aState.mReflowStatus.SetNextInFlowNeedsReflow();
+ } // XXXfr shouldn't set this flag when nextinflow has no lines
+ }
+
+ // Handle an odd-ball case: a list-item with no lines
+ if (mLines.empty() && HasOutsideMarker()) {
+ ReflowOutput metrics(aState.mReflowInput);
+ nsIFrame* marker = GetOutsideMarker();
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ ReflowOutsideMarker(
+ marker, aState, metrics,
+ aState.mReflowInput.ComputedPhysicalBorderPadding().top);
+ NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
+ "empty ::marker frame took up space");
+
+ if (!MarkerIsEmpty()) {
+ // There are no lines so we have to fake up some y motion so that
+ // we end up with *some* height.
+ // (Note: if we're layout-contained, we have to be sure to leave our
+ // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched,
+ // because layout-contained frames have no baseline.)
+ if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() &&
+ metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
+ nscoord ascent;
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) {
+ metrics.SetBlockStartAscent(ascent);
+ } else {
+ metrics.SetBlockStartAscent(metrics.BSize(wm));
+ }
+ }
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+
+ nscoord minAscent = nsLayoutUtils::GetCenteredFontBaseline(
+ fm, aState.mMinLineHeight, wm.IsLineInverted());
+ nscoord minDescent = aState.mMinLineHeight - minAscent;
+
+ aState.mBCoord +=
+ std::max(minAscent, metrics.BlockStartAscent()) +
+ std::max(minDescent, metrics.BSize(wm) - metrics.BlockStartAscent());
+
+ nscoord offset = minAscent - metrics.BlockStartAscent();
+ if (offset > 0) {
+ marker->SetRect(marker->GetRect() + nsPoint(0, offset));
+ }
+ }
+ }
+
+ if (LinesAreEmpty(mLines) && ShouldHaveLineIfEmpty()) {
+ aState.mBCoord += aState.mMinLineHeight;
+ }
+
+ if (foundAnyClears) {
+ AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
+ } else {
+ RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
+ }
+
+#ifdef DEBUG
+ VerifyLines(true);
+ VerifyOverflowSituation();
+ if (gNoisyReflow) {
+ IndentBy(stdout, gNoiseIndent - 1);
+ ListTag(stdout);
+ printf(": done reflowing dirty lines (status=%s)\n",
+ ToString(aState.mReflowStatus).c_str());
+ }
+#endif
+
+ return usedOverflowWrap;
+}
+
+void nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) {
+ aLine->MarkDirty();
+
+ // Just checking NS_FRAME_IS_DIRTY is ok, because we've already
+ // marked the lines that need to be marked dirty based on our
+ // vertical resize stuff. So we'll definitely reflow all those kids;
+ // the only question is how they should behave.
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ // Mark all our child frames dirty so we make sure to reflow them
+ // later.
+ int32_t n = aLine->GetChildCount();
+ for (nsIFrame* f = aLine->mFirstChild; n > 0;
+ f = f->GetNextSibling(), --n) {
+ f->MarkSubtreeDirty();
+ }
+ // And mark all the floats whose reflows we might be skipping dirty too.
+ if (aLine->HasFloats()) {
+ for (nsIFrame* f : aLine->Floats()) {
+ f->MarkSubtreeDirty();
+ }
+ }
+ } else {
+ // Dirty all the descendant lines of block kids to handle float damage,
+ // since our nsFloatManager will go away by the next time we're reflowing.
+ // XXXbz Can we do something more like what PropagateFloatDamage does?
+ // Would need to sort out the exact business with mBlockDelta for that....
+ // This marks way too much dirty. If we ever make this better, revisit
+ // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
+ nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild);
+ if (bf) {
+ MarkAllDescendantLinesDirty(bf);
+ }
+ }
+}
+
+void nsBlockFrame::DeleteLine(BlockReflowState& aState,
+ nsLineList::iterator aLine,
+ nsLineList::iterator aLineEnd) {
+ MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line");
+ if (0 == aLine->GetChildCount()) {
+ NS_ASSERTION(aState.mCurrentLine == aLine,
+ "using function more generally than designed, "
+ "but perhaps OK now");
+ nsLineBox* line = aLine;
+ aLine = mLines.erase(aLine);
+ FreeLineBox(line);
+ // Mark the previous margin of the next line dirty since we need to
+ // recompute its top position.
+ if (aLine != aLineEnd) {
+ aLine->MarkPreviousMarginDirty();
+ }
+ }
+}
+
+/**
+ * Reflow a line. The line will either contain a single block frame
+ * or contain 1 or more inline frames. aKeepReflowGoing indicates
+ * whether or not the caller should continue to reflow more lines.
+ * Returns true if the reflow used an overflow-wrap breakpoint.
+ */
+bool nsBlockFrame::ReflowLine(BlockReflowState& aState, LineIterator aLine,
+ bool* aKeepReflowGoing) {
+ MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line");
+
+ // Setup the line-layout for the new line
+ aState.mCurrentLine = aLine;
+ aLine->ClearDirty();
+ aLine->InvalidateCachedIsEmpty();
+ aLine->ClearHadFloatPushed();
+
+ // If this line contains a single block that is hidden by `content-visibility`
+ // don't reflow the line. If this line contains inlines and the first one is
+ // hidden by `content-visibility`, all of them are, so avoid reflow in that
+ // case as well.
+ // For frames that own anonymous children, even the first child is hidden by
+ // `content-visibility`, there could be some anonymous children need reflow,
+ // so we don't skip reflow this line.
+ nsIFrame* firstChild = aLine->mFirstChild;
+ if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
+ !HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
+ return false;
+ }
+
+ // Now that we know what kind of line we have, reflow it
+ bool usedOverflowWrap = false;
+ if (aLine->IsBlock()) {
+ ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
+ } else {
+ aLine->SetLineWrapped(false);
+ usedOverflowWrap = ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
+
+ // Store the line's float edges for overflow marker analysis if needed.
+ aLine->ClearFloatEdges();
+ if (aState.mFlags.mCanHaveOverflowMarkers) {
+ WritingMode wm = aLine->mWritingMode;
+ nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(
+ aLine->BStart(), aLine->BSize(), nullptr);
+ if (r.HasFloats()) {
+ LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm,
+ aLine->mContainerSize);
+ nscoord s = r.mRect.IStart(wm);
+ nscoord e = r.mRect.IEnd(wm);
+ if (so.IEnd(wm) > e || so.IStart(wm) < s) {
+ // This line is overlapping a float - store the edges marking the area
+ // between the floats for text-overflow analysis.
+ aLine->SetFloatEdges(s, e);
+ }
+ }
+ }
+ }
+
+ aLine->ClearMovedFragments();
+
+ return usedOverflowWrap;
+}
+
+nsIFrame* nsBlockFrame::PullFrame(BlockReflowState& aState,
+ LineIterator aLine) {
+ // First check our remaining lines.
+ if (LinesEnd() != aLine.next()) {
+ return PullFrameFrom(aLine, this, aLine.next());
+ }
+
+ NS_ASSERTION(
+ !GetOverflowLines(),
+ "Our overflow lines should have been removed at the start of reflow");
+
+ // Try each next-in-flow.
+ nsBlockFrame* nextInFlow = aState.mNextInFlow;
+ while (nextInFlow) {
+ if (nextInFlow->mLines.empty()) {
+ nextInFlow->DrainSelfOverflowList();
+ }
+ if (!nextInFlow->mLines.empty()) {
+ return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin());
+ }
+ nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
+ aState.mNextInFlow = nextInFlow;
+ }
+
+ return nullptr;
+}
+
+nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
+ nsBlockFrame* aFromContainer,
+ nsLineList::iterator aFromLine) {
+ nsLineBox* fromLine = aFromLine;
+ MOZ_ASSERT(fromLine, "bad line to pull from");
+ MOZ_ASSERT(fromLine->GetChildCount(), "empty line");
+ MOZ_ASSERT(aLine->GetChildCount(), "empty line");
+ MOZ_ASSERT(!HasProperty(LineIteratorProperty()),
+ "Shouldn't have line iterators mid-reflow");
+
+ NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(),
+ "Disagreement about whether it's a block or not");
+
+ if (fromLine->IsBlock()) {
+ // If our line is not empty and the child in aFromLine is a block
+ // then we cannot pull up the frame into this line. In this case
+ // we stop pulling.
+ return nullptr;
+ }
+ // Take frame from fromLine
+ nsIFrame* frame = fromLine->mFirstChild;
+ nsIFrame* newFirstChild = frame->GetNextSibling();
+
+ if (aFromContainer != this) {
+ // The frame is being pulled from a next-in-flow; therefore we need to add
+ // it to our sibling list.
+ MOZ_ASSERT(aLine == mLines.back());
+ MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(),
+ "should only pull from first line");
+ aFromContainer->mFrames.RemoveFrame(frame);
+
+ // When pushing and pulling frames we need to check for whether any
+ // views need to be reparented.
+ ReparentFrame(frame, aFromContainer, this);
+ mFrames.AppendFrame(nullptr, frame);
+
+ // The frame might have (or contain) floats that need to be brought
+ // over too. (pass 'false' since there are no siblings to check)
+ ReparentFloats(frame, aFromContainer, false);
+ } else {
+ MOZ_ASSERT(aLine == aFromLine.prev());
+ }
+
+ aLine->NoteFrameAdded(frame);
+ fromLine->NoteFrameRemoved(frame);
+
+ if (fromLine->GetChildCount() > 0) {
+ // Mark line dirty now that we pulled a child
+ fromLine->MarkDirty();
+ fromLine->mFirstChild = newFirstChild;
+ } else {
+ // Free up the fromLine now that it's empty.
+ // Its bounds might need to be redrawn, though.
+ if (aFromLine.next() != aFromContainer->mLines.end()) {
+ aFromLine.next()->MarkPreviousMarginDirty();
+ }
+ aFromContainer->mLines.erase(aFromLine);
+ // aFromLine is now invalid
+ aFromContainer->FreeLineBox(fromLine);
+ }
+
+#ifdef DEBUG
+ VerifyLines(true);
+ VerifyOverflowSituation();
+#endif
+
+ return frame;
+}
+
+void nsBlockFrame::SlideLine(BlockReflowState& aState, nsLineBox* aLine,
+ nscoord aDeltaBCoord) {
+ MOZ_ASSERT(aDeltaBCoord != 0, "why slide a line nowhere?");
+
+ // Adjust line state
+ aLine->SlideBy(aDeltaBCoord, aState.ContainerSize());
+
+ // Adjust the frames in the line
+ MoveChildFramesOfLine(aLine, aDeltaBCoord);
+}
+
+void nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine,
+ const nsSize& aNewContainerSize) {
+ if (aNewContainerSize == aLine->mContainerSize) {
+ return;
+ }
+
+ // Adjust line state
+ nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize);
+
+ // Changing container width only matters if writing mode is vertical-rl
+ if (GetWritingMode().IsVerticalRL()) {
+ MoveChildFramesOfLine(aLine, sizeDelta.width);
+ }
+}
+
+void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine,
+ nscoord aDeltaBCoord) {
+ // Adjust the frames in the line
+ nsIFrame* kid = aLine->mFirstChild;
+ if (!kid) {
+ return;
+ }
+
+ WritingMode wm = GetWritingMode();
+ LogicalPoint translation(wm, 0, aDeltaBCoord);
+
+ if (aLine->IsBlock()) {
+ if (aDeltaBCoord) {
+ kid->MovePositionBy(wm, translation);
+ }
+
+ // Make sure the frame's view and any child views are updated
+ nsContainerFrame::PlaceFrameView(kid);
+ } else {
+ // Adjust the block-dir coordinate of the frames in the line.
+ // Note: we need to re-position views even if aDeltaBCoord is 0, because
+ // one of our parent frames may have moved and so the view's position
+ // relative to its parent may have changed.
+ int32_t n = aLine->GetChildCount();
+ while (--n >= 0) {
+ if (aDeltaBCoord) {
+ kid->MovePositionBy(wm, translation);
+ }
+ // Make sure the frame's view and any child views are updated
+ nsContainerFrame::PlaceFrameView(kid);
+ kid = kid->GetNextSibling();
+ }
+ }
+}
+
+static inline bool IsNonAutoNonZeroBSize(const StyleSize& aCoord) {
+ // The "extremum length" values (see ExtremumLength) were originally aimed at
+ // inline-size (or width, as it was before logicalization). For now, let them
+ // return false here, so we treat them like 'auto' pending a real
+ // implementation. (See bug 1126420.)
+ //
+ // FIXME (bug 567039, bug 527285) This isn't correct for the 'fill' value,
+ // which should more likely (but not necessarily, depending on the available
+ // space) be returning true.
+ if (aCoord.BehavesLikeInitialValueOnBlockAxis()) {
+ return false;
+ }
+ MOZ_ASSERT(aCoord.IsLengthPercentage());
+ // If we evaluate the length/percent/calc at a percentage basis of
+ // both nscoord_MAX and 0, and it's zero both ways, then it's a zero
+ // length, percent, or combination thereof. Test > 0 so we clamp
+ // negative calc() results to 0.
+ return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 ||
+ aCoord.AsLengthPercentage().Resolve(0) > 0;
+}
+
+/* virtual */
+bool nsBlockFrame::IsSelfEmpty() {
+ if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ return true;
+ }
+
+ // Blocks which are margin-roots (including inline-blocks) cannot be treated
+ // as empty for margin-collapsing and other purposes. They're more like
+ // replaced elements.
+ if (HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ return false;
+ }
+
+ WritingMode wm = GetWritingMode();
+ const nsStylePosition* position = StylePosition();
+
+ if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) ||
+ IsNonAutoNonZeroBSize(position->BSize(wm))) {
+ return false;
+ }
+
+ // FIXME: Bug 1646100 - Take intrinsic size into account.
+ // FIXME: Handle the case that both inline and block sizes are auto.
+ // https://github.com/w3c/csswg-drafts/issues/5060.
+ // Note: block-size could be zero or auto/intrinsic keywords here.
+ if (position->BSize(wm).BehavesLikeInitialValueOnBlockAxis() &&
+ position->mAspectRatio.HasFiniteRatio()) {
+ return false;
+ }
+
+ const nsStyleBorder* border = StyleBorder();
+ const nsStylePadding* padding = StylePadding();
+
+ if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) !=
+ 0 ||
+ border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 ||
+ !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) ||
+ !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) {
+ return false;
+ }
+
+ if (HasOutsideMarker() && !MarkerIsEmpty()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsBlockFrame::CachedIsEmpty() {
+ if (!IsSelfEmpty()) {
+ return false;
+ }
+ for (auto& line : mLines) {
+ if (!line.CachedIsEmpty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool nsBlockFrame::IsEmpty() {
+ if (!IsSelfEmpty()) {
+ return false;
+ }
+
+ return LinesAreEmpty(mLines);
+}
+
+bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState,
+ nsLineBox* aLine) {
+ if (aLine->mFirstChild->IsPageBreakFrame()) {
+ // A page break frame consumes margins adjacent to it.
+ // https://drafts.csswg.org/css-break/#break-margins
+ return false;
+ }
+
+ if (aState.mFlags.mShouldApplyBStartMargin) {
+ // Apply short-circuit check to avoid searching the line list
+ return true;
+ }
+
+ if (!aState.IsAdjacentWithBStart()) {
+ // If we aren't at the start block-coordinate then something of non-zero
+ // height must have been placed. Therefore the childs block-start margin
+ // applies.
+ aState.mFlags.mShouldApplyBStartMargin = true;
+ return true;
+ }
+
+ // Determine if this line is "essentially" the first line
+ LineIterator line = LinesBegin();
+ if (aState.mFlags.mHasLineAdjacentToTop) {
+ line = aState.mLineAdjacentToTop;
+ }
+ while (line != aLine) {
+ if (!line->CachedIsEmpty() || line->HasClearance()) {
+ // A line which precedes aLine is non-empty, or has clearance,
+ // so therefore the block-start margin applies.
+ aState.mFlags.mShouldApplyBStartMargin = true;
+ return true;
+ }
+ // No need to apply the block-start margin if the line has floats. We
+ // should collapse anyway (bug 44419)
+ ++line;
+ aState.mFlags.mHasLineAdjacentToTop = true;
+ aState.mLineAdjacentToTop = line;
+ }
+
+ // The line being reflowed is "essentially" the first line in the
+ // block. Therefore its block-start margin will be collapsed by the
+ // generational collapsing logic with its parent (us).
+ return false;
+}
+
+void nsBlockFrame::ReflowBlockFrame(BlockReflowState& aState,
+ LineIterator aLine,
+ bool* aKeepReflowGoing) {
+ MOZ_ASSERT(*aKeepReflowGoing, "bad caller");
+
+ nsIFrame* frame = aLine->mFirstChild;
+ if (!frame) {
+ NS_ASSERTION(false, "program error - unexpected empty line");
+ return;
+ }
+
+ // If the previous frame was a page-break-frame, then preemptively push this
+ // frame to the next page.
+ // This is primarily important for the placeholders for abspos frames, which
+ // measure as zero height and then would be placed on this page.
+ if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) {
+ const nsIFrame* const prev = frame->GetPrevSibling();
+ if (prev && prev->IsPageBreakFrame()) {
+ PushTruncatedLine(aState, aLine, aKeepReflowGoing);
+ return;
+ }
+ }
+
+ // Prepare the block reflow engine
+ nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
+
+ StyleClear clearType = frame->StyleDisplay()->mClear;
+ if (aState.mTrailingClearFromPIF != StyleClear::None) {
+ clearType = nsLayoutUtils::CombineClearType(clearType,
+ aState.mTrailingClearFromPIF);
+ aState.mTrailingClearFromPIF = StyleClear::None;
+ }
+
+ // Clear past floats before the block if the clear style is not none
+ aLine->ClearForcedLineBreak();
+ if (clearType != StyleClear::None) {
+ aLine->SetForcedLineBreakBefore(clearType);
+ }
+
+ // See if we should apply the block-start margin. If the block frame being
+ // reflowed is a continuation, then we don't apply its block-start margin
+ // because it's not significant. Otherwise, dig deeper.
+ bool applyBStartMargin =
+ !frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine);
+ if (applyBStartMargin) {
+ // The HasClearance setting is only valid if ShouldApplyBStartMargin
+ // returned false (in which case the block-start margin-root set our
+ // clearance flag). Otherwise clear it now. We'll set it later on
+ // ourselves if necessary.
+ aLine->ClearHasClearance();
+ }
+ bool treatWithClearance = aLine->HasClearance();
+
+ bool mightClearFloats = clearType != StyleClear::None;
+ nsIFrame* floatAvoidingBlock = nullptr;
+ if (!nsBlockFrame::BlockCanIntersectFloats(frame)) {
+ mightClearFloats = true;
+ floatAvoidingBlock = frame;
+ }
+
+ // If our block-start margin was counted as part of some parent's block-start
+ // margin collapse, and we are being speculatively reflowed assuming this
+ // frame DID NOT need clearance, then we need to check that
+ // assumption.
+ if (!treatWithClearance && !applyBStartMargin && mightClearFloats &&
+ aState.mReflowInput.mDiscoveredClearance) {
+ nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
+ if (auto [clearBCoord, result] =
+ aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
+ result != ClearFloatsResult::BCoordNoChange) {
+ Unused << clearBCoord;
+
+ // Only record the first frame that requires clearance
+ if (!*aState.mReflowInput.mDiscoveredClearance) {
+ *aState.mReflowInput.mDiscoveredClearance = frame;
+ }
+ aState.mPrevChild = frame;
+ // Exactly what we do now is flexible since we'll definitely be
+ // reflowed.
+ return;
+ }
+ }
+ if (treatWithClearance) {
+ applyBStartMargin = true;
+ }
+
+ nsIFrame* clearanceFrame = nullptr;
+ const nscoord startingBCoord = aState.mBCoord;
+ const nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin;
+ nscoord clearance;
+ // Save the original position of the frame so that we can reposition
+ // its view as needed.
+ nsPoint originalPosition = frame->GetPosition();
+ while (true) {
+ clearance = 0;
+ nscoord bStartMargin = 0;
+ bool mayNeedRetry = false;
+ bool clearedFloats = false;
+ bool clearedPushedOrSplitFloat = false;
+ if (applyBStartMargin) {
+ // Precompute the blocks block-start margin value so that we can get the
+ // correct available space (there might be a float that's
+ // already been placed below the aState.mPrevBEndMargin
+
+ // Setup a reflowInput to get the style computed block-start margin
+ // value. We'll use a reason of `resize' so that we don't fudge
+ // any incremental reflow input.
+
+ // The availSpace here is irrelevant to our needs - all we want
+ // out if this setup is the block-start margin value which doesn't depend
+ // on the childs available space.
+ // XXX building a complete ReflowInput just to get the block-start
+ // margin seems like a waste. And we do this for almost every block!
+ WritingMode wm = frame->GetWritingMode();
+ LogicalSize availSpace = aState.ContentSize(wm);
+ ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame,
+ availSpace);
+
+ if (treatWithClearance) {
+ aState.mBCoord += aState.mPrevBEndMargin.get();
+ aState.mPrevBEndMargin.Zero();
+ }
+
+ // Now compute the collapsed margin-block-start value into
+ // aState.mPrevBEndMargin, assuming that all child margins
+ // collapse down to clearanceFrame.
+ brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
+ clearanceFrame, &mayNeedRetry);
+
+ // XXX optimization; we could check the collapsing children to see if they
+ // are sure to require clearance, and so avoid retrying them
+
+ if (clearanceFrame) {
+ // Don't allow retries on the second pass. The clearance decisions for
+ // the blocks whose block-start margins collapse with ours are now
+ // fixed.
+ mayNeedRetry = false;
+ }
+
+ if (!treatWithClearance && !clearanceFrame && mightClearFloats) {
+ // We don't know if we need clearance and this is the first,
+ // optimistic pass. So determine whether *this block* needs
+ // clearance. Note that we do not allow the decision for whether
+ // this block has clearance to change on the second pass; that
+ // decision is only allowed to be made under the optimistic
+ // first pass.
+ nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
+ if (auto [clearBCoord, result] =
+ aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
+ result != ClearFloatsResult::BCoordNoChange) {
+ Unused << clearBCoord;
+
+ // Looks like we need clearance and we didn't know about it already.
+ // So recompute collapsed margin
+ treatWithClearance = true;
+ // Remember this decision, needed for incremental reflow
+ aLine->SetHasClearance();
+
+ // Apply incoming margins
+ aState.mBCoord += aState.mPrevBEndMargin.get();
+ aState.mPrevBEndMargin.Zero();
+
+ // Compute the collapsed margin again, ignoring the incoming margin
+ // this time
+ mayNeedRetry = false;
+ brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
+ clearanceFrame, &mayNeedRetry);
+ }
+ }
+
+ // Temporarily advance the running block-direction value so that the
+ // GetFloatAvailableSpace method will return the right available space.
+ // This undone as soon as the horizontal margins are computed.
+ bStartMargin = aState.mPrevBEndMargin.get();
+
+ if (treatWithClearance) {
+ nscoord currentBCoord = aState.mBCoord;
+ // advance mBCoord to the clear position.
+ auto [clearBCoord, result] =
+ aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock);
+ aState.mBCoord = clearBCoord;
+
+ clearedFloats = result != ClearFloatsResult::BCoordNoChange;
+ clearedPushedOrSplitFloat =
+ result == ClearFloatsResult::FloatsPushedOrSplit;
+
+ // Compute clearance. It's the amount we need to add to the block-start
+ // border-edge of the frame, after applying collapsed margins
+ // from the frame and its children, to get it to line up with
+ // the block-end of the floats. The former is
+ // currentBCoord + bStartMargin, the latter is the current
+ // aState.mBCoord.
+ // Note that negative clearance is possible
+ clearance = aState.mBCoord - (currentBCoord + bStartMargin);
+
+ // Add clearance to our block-start margin while we compute available
+ // space for the frame
+ bStartMargin += clearance;
+
+ // Note that aState.mBCoord should stay where it is: at the block-start
+ // border-edge of the frame
+ } else {
+ // Advance aState.mBCoord to the block-start border-edge of the frame.
+ aState.mBCoord += bStartMargin;
+ }
+ }
+
+ aLine->SetLineIsImpactedByFloat(false);
+
+ // Here aState.mBCoord is the block-start border-edge of the block.
+ // Compute the available space for the block
+ nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ LogicalRect availSpace = aState.ComputeBlockAvailSpace(
+ frame, floatAvailableSpace, (floatAvoidingBlock));
+
+ // The check for
+ // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats)
+ // is to some degree out of paranoia: if we reliably eat up block-start
+ // margins at the top of the page as we ought to, it wouldn't be
+ // needed.
+ if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) &&
+ (availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) {
+ // We know already that this child block won't fit on this
+ // page/column due to the block-start margin or the clearance. So we
+ // need to get out of here now. (If we don't, most blocks will handle
+ // things fine, and report break-before, but zero-height blocks
+ // won't, and will thus make their parent overly-large and force
+ // *it* to be pushed in its entirety.)
+ aState.mBCoord = startingBCoord;
+ aState.mPrevBEndMargin = incomingMargin;
+ if (ShouldAvoidBreakInside(aState.mReflowInput)) {
+ SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
+ } else {
+ PushTruncatedLine(aState, aLine, aKeepReflowGoing);
+ }
+ return;
+ }
+
+ // Now put the block-dir coordinate back to the start of the
+ // block-start-margin + clearance.
+ aState.mBCoord -= bStartMargin;
+ availSpace.BStart(wm) -= bStartMargin;
+ if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
+ availSpace.BSize(wm) += bStartMargin;
+ }
+
+ // Construct the reflow input for the block.
+ Maybe<ReflowInput> childReflowInput;
+ Maybe<LogicalSize> cbSize;
+ LogicalSize availSize = availSpace.Size(wm);
+ bool columnSetWrapperHasNoBSizeLeft = false;
+ if (Style()->GetPseudoType() == PseudoStyleType::columnContent) {
+ // Calculate the multicol containing block's block size so that the
+ // children with percentage block size get correct percentage basis.
+ const ReflowInput* cbReflowInput =
+ aState.mReflowInput.mParentReflowInput->mCBReflowInput;
+ MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(),
+ "Get unexpected reflow input of multicol containing block!");
+
+ // Use column-width as the containing block's inline-size, i.e. the column
+ // content's computed inline-size.
+ cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(),
+ cbReflowInput->ComputedBSize())
+ .ConvertTo(frame->GetWritingMode(), wm));
+
+ // If a ColumnSetWrapper is in a balancing column content, it may be
+ // pushed or pulled back and forth between column contents. Always add
+ // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children
+ // can have a chance to reflow under current block size constraint.
+ if (aState.mReflowInput.mFlags.mIsColumnBalancing &&
+ frame->IsColumnSetWrapperFrame()) {
+ frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ } else if (IsColumnSetWrapperFrame()) {
+ // If we are reflowing our ColumnSet children, we want to apply our block
+ // size constraint to the available block size when constructing reflow
+ // input for ColumnSet so that ColumnSet can use it to compute its max
+ // column block size.
+ if (frame->IsColumnSetFrame()) {
+ nscoord contentBSize = aState.mReflowInput.ComputedBSize();
+ if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
+ contentBSize =
+ std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize());
+ }
+ if (contentBSize != NS_UNCONSTRAINEDSIZE) {
+ // To get the remaining content block-size, subtract the content
+ // block-size consumed by our previous continuations.
+ contentBSize -= aState.mConsumedBSize;
+
+ // ColumnSet is not the outermost frame in the column container, so it
+ // cannot have any margin. We don't need to consider any margin that
+ // can be generated by "box-decoration-break: clone" as we do in
+ // BlockReflowState::ComputeBlockAvailSpace().
+ const nscoord availContentBSize = std::max(
+ 0, contentBSize - (aState.mBCoord - aState.ContentBStart()));
+ if (availSize.BSize(wm) >= availContentBSize) {
+ availSize.BSize(wm) = availContentBSize;
+ columnSetWrapperHasNoBSizeLeft = true;
+ }
+ }
+ }
+ }
+
+ childReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame,
+ availSize.ConvertTo(frame->GetWritingMode(), wm),
+ cbSize);
+
+ childReflowInput->mFlags.mColumnSetWrapperHasNoBSizeLeft =
+ columnSetWrapperHasNoBSizeLeft;
+
+ if (aLine->MovedFragments()) {
+ // We only need to set this the first reflow, since if we reflow
+ // again (and replace childReflowInput) we'll be reflowing it
+ // again in the same fragment as the previous time.
+ childReflowInput->mFlags.mMovedBlockFragments = true;
+ }
+
+ nsFloatManager::SavedState floatManagerState;
+ nsReflowStatus frameReflowStatus;
+ do {
+ if (floatAvailableSpace.HasFloats()) {
+ // Set if floatAvailableSpace.HasFloats() is true for any
+ // iteration of the loop.
+ aLine->SetLineIsImpactedByFloat(true);
+ }
+
+ // We might need to store into mDiscoveredClearance later if it's
+ // currently null; we want to overwrite any writes that
+ // brc.ReflowBlock() below does, so we need to remember now
+ // whether it's empty.
+ const bool shouldStoreClearance =
+ aState.mReflowInput.mDiscoveredClearance &&
+ !*aState.mReflowInput.mDiscoveredClearance;
+
+ // Reflow the block into the available space
+ if (mayNeedRetry || floatAvoidingBlock) {
+ aState.FloatManager()->PushState(&floatManagerState);
+ }
+
+ if (mayNeedRetry) {
+ childReflowInput->mDiscoveredClearance = &clearanceFrame;
+ } else if (!applyBStartMargin) {
+ childReflowInput->mDiscoveredClearance =
+ aState.mReflowInput.mDiscoveredClearance;
+ }
+
+ frameReflowStatus.Reset();
+ brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin,
+ clearance, aLine.get(), *childReflowInput,
+ frameReflowStatus, aState);
+
+ if (frameReflowStatus.IsInlineBreakBefore()) {
+ // No need to retry this loop if there is a break opportunity before the
+ // child block.
+ break;
+ }
+
+ // Now the block has a height. Using that height, get the
+ // available space again and call ComputeBlockAvailSpace again.
+ // If ComputeBlockAvailSpace gives a different result, we need to
+ // reflow again.
+ if (!floatAvoidingBlock) {
+ break;
+ }
+
+ LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect);
+ floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(
+ aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm),
+ &floatManagerState);
+ NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
+ oldFloatAvailableSpaceRect.BStart(wm),
+ "yikes");
+ // Restore the height to the position of the next band.
+ floatAvailableSpace.mRect.BSize(wm) =
+ oldFloatAvailableSpaceRect.BSize(wm);
+ // Determine whether the available space shrunk on either side,
+ // because (the first time round) we now know the block's height,
+ // and it may intersect additional floats, or (on later
+ // iterations) because narrowing the width relative to the
+ // previous time may cause the block to become taller. Note that
+ // since we're reflowing the block, narrowing the width might also
+ // make it shorter, so we must pass aCanGrow as true.
+ if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
+ floatAvailableSpace.mRect, true)) {
+ // The size and position we chose before are fine (i.e., they
+ // don't cause intersecting with floats that requires a change
+ // in size or position), so we're done.
+ break;
+ }
+
+ bool advanced = false;
+ if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock,
+ floatAvailableSpace)) {
+ // Advance to the next band.
+ nscoord newBCoord = aState.mBCoord;
+ if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
+ advanced = true;
+ }
+ // ClearFloats might be able to advance us further once we're there.
+ std::tie(aState.mBCoord, std::ignore) =
+ aState.ClearFloats(newBCoord, StyleClear::None, floatAvoidingBlock);
+
+ // Start over with a new available space rect at the new height.
+ floatAvailableSpace = aState.GetFloatAvailableSpaceWithState(
+ aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState);
+ }
+
+ const LogicalRect oldAvailSpace = availSpace;
+ availSpace = aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
+ (floatAvoidingBlock));
+
+ if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
+ break;
+ }
+
+ // We need another reflow.
+ aState.FloatManager()->PopState(&floatManagerState);
+
+ if (!treatWithClearance && !applyBStartMargin &&
+ aState.mReflowInput.mDiscoveredClearance) {
+ // We set shouldStoreClearance above to record only the first
+ // frame that requires clearance.
+ if (shouldStoreClearance) {
+ *aState.mReflowInput.mDiscoveredClearance = frame;
+ }
+ aState.mPrevChild = frame;
+ // Exactly what we do now is flexible since we'll definitely be
+ // reflowed.
+ return;
+ }
+
+ if (advanced) {
+ // We're pushing down the border-box, so we don't apply margin anymore.
+ // This should never cause us to move up since the call to
+ // GetFloatAvailableSpaceForBSize above included the margin.
+ applyBStartMargin = false;
+ bStartMargin = 0;
+ treatWithClearance = true; // avoid hitting test above
+ clearance = 0;
+ }
+
+ childReflowInput.reset();
+ childReflowInput.emplace(
+ aState.mPresContext, aState.mReflowInput, frame,
+ availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
+ } while (true);
+
+ if (mayNeedRetry && clearanceFrame) {
+ // Found a clearance frame, so we need to reflow |frame| a second time.
+ // Restore the states and start over again.
+ aState.FloatManager()->PopState(&floatManagerState);
+ aState.mBCoord = startingBCoord;
+ aState.mPrevBEndMargin = incomingMargin;
+ continue;
+ }
+
+ aState.mPrevChild = frame;
+
+ if (childReflowInput->WillReflowAgainForClearance()) {
+ // If an ancestor of ours is going to reflow for clearance, we
+ // need to avoid calling PlaceBlock, because it unsets dirty bits
+ // on the child block (both itself, and through its call to
+ // nsIFrame::DidReflow), and those dirty bits imply dirtiness for
+ // all of the child block, including the lines it didn't reflow.
+ NS_ASSERTION(originalPosition == frame->GetPosition(),
+ "we need to call PositionChildViews");
+ return;
+ }
+
+#if defined(REFLOW_STATUS_COVERAGE)
+ RecordReflowStatus(true, frameReflowStatus);
+#endif
+
+ if (frameReflowStatus.IsInlineBreakBefore()) {
+ // None of the child block fits.
+ if (ShouldAvoidBreakInside(aState.mReflowInput)) {
+ SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
+ } else {
+ PushTruncatedLine(aState, aLine, aKeepReflowGoing);
+ }
+ } else {
+ // Note: line-break-after a block is a nop
+
+ // Try to place the child block.
+ // Don't force the block to fit if we have positive clearance, because
+ // pushing it to the next page would give it more room.
+ // Don't force the block to fit if it's impacted by a float. If it is,
+ // then pushing it to the next page would give it more room. Note that
+ // isImpacted doesn't include impact from the block's own floats.
+ bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 &&
+ !floatAvailableSpace.HasFloats();
+ nsCollapsingMargin collapsedBEndMargin;
+ OverflowAreas overflowAreas;
+ *aKeepReflowGoing =
+ brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(),
+ collapsedBEndMargin, overflowAreas, frameReflowStatus);
+ if (!frameReflowStatus.IsFullyComplete() &&
+ ShouldAvoidBreakInside(aState.mReflowInput)) {
+ *aKeepReflowGoing = false;
+ aLine->MarkDirty();
+ }
+
+ if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
+ LineIterator nextLine = aLine;
+ ++nextLine;
+ if (nextLine != LinesEnd()) {
+ nextLine->MarkPreviousMarginDirty();
+ }
+ }
+
+ aLine->SetOverflowAreas(overflowAreas);
+ if (*aKeepReflowGoing) {
+ // Some of the child block fit
+
+ // Advance to new Y position
+ nscoord newBCoord = aLine->BEnd();
+ aState.mBCoord = newBCoord;
+
+ // Continue the block frame now if it didn't completely fit in
+ // the available space.
+ if (!frameReflowStatus.IsFullyComplete()) {
+ bool madeContinuation = CreateContinuationFor(aState, nullptr, frame);
+
+ nsIFrame* nextFrame = frame->GetNextInFlow();
+ NS_ASSERTION(nextFrame,
+ "We're supposed to have a next-in-flow by now");
+
+ if (frameReflowStatus.IsIncomplete()) {
+ // If nextFrame used to be an overflow container, make it a normal
+ // block
+ if (!madeContinuation &&
+ nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ nsOverflowContinuationTracker::AutoFinish fini(
+ aState.mOverflowTracker, frame);
+ nsContainerFrame* parent = nextFrame->GetParent();
+ parent->StealFrame(nextFrame);
+ if (parent != this) {
+ ReparentFrame(nextFrame, parent, this);
+ }
+ mFrames.InsertFrame(nullptr, frame, nextFrame);
+ madeContinuation = true; // needs to be added to mLines
+ nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ frameReflowStatus.SetNextInFlowNeedsReflow();
+ }
+
+ // Push continuation to a new line, but only if we actually made
+ // one.
+ if (madeContinuation) {
+ nsLineBox* line = NewLineBox(nextFrame, true);
+ mLines.after_insert(aLine, line);
+ }
+
+ PushTruncatedLine(aState, aLine.next(), aKeepReflowGoing);
+
+ // If we need to reflow the continuation of the block child,
+ // then we'd better reflow our continuation
+ if (frameReflowStatus.NextInFlowNeedsReflow()) {
+ aState.mReflowStatus.SetNextInFlowNeedsReflow();
+ // We also need to make that continuation's line dirty so
+ // it gets reflowed when we reflow our next in flow. The
+ // nif's line must always be either a line of the nif's
+ // parent block (only if we didn't make a continuation) or
+ // else one of our own overflow lines. In the latter case
+ // the line is already marked dirty, so just handle the
+ // first case.
+ if (!madeContinuation) {
+ nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent());
+ NS_ASSERTION(
+ nifBlock,
+ "A block's child's next in flow's parent must be a block!");
+ for (auto& line : nifBlock->Lines()) {
+ if (line.Contains(nextFrame)) {
+ line.MarkDirty();
+ break;
+ }
+ }
+ }
+ }
+
+ // The block-end margin for a block is only applied on the last
+ // flow block. Since we just continued the child block frame,
+ // we know that line->mFirstChild is not the last flow block
+ // therefore zero out the running margin value.
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ ListTag(stdout);
+ printf(": reflow incomplete, frame=");
+ frame->ListTag(stdout);
+ printf(" prevBEndMargin=%d, setting to zero\n",
+ aState.mPrevBEndMargin.get());
+#endif
+ aState.mPrevBEndMargin.Zero();
+ } else { // frame is complete but its overflow is not complete
+ // Disconnect the next-in-flow and put it in our overflow tracker
+ if (!madeContinuation &&
+ !nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ // It already exists, but as a normal next-in-flow, so we need
+ // to dig it out of the child lists.
+ nextFrame->GetParent()->StealFrame(nextFrame);
+ } else if (madeContinuation) {
+ mFrames.RemoveFrame(nextFrame);
+ }
+
+ // Put it in our overflow list
+ aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus);
+ aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus);
+
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ ListTag(stdout);
+ printf(": reflow complete but overflow incomplete for ");
+ frame->ListTag(stdout);
+ printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
+ aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
+#endif
+ aState.mPrevBEndMargin = collapsedBEndMargin;
+ }
+ } else { // frame is fully complete
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ ListTag(stdout);
+ printf(": reflow complete for ");
+ frame->ListTag(stdout);
+ printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
+ aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
+#endif
+ aState.mPrevBEndMargin = collapsedBEndMargin;
+ }
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ ListTag(stdout);
+ printf(": frame=");
+ frame->ListTag(stdout);
+ printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
+ brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
+ aState.mPrevBEndMargin.get());
+#endif
+ } else {
+ if (!frameReflowStatus.IsFullyComplete()) {
+ // The frame reported an incomplete status, but then it also didn't
+ // fit. This means we need to reflow it again so that it can
+ // (again) report the incomplete status.
+ frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+
+ if ((aLine == mLines.front() && !GetPrevInFlow()) ||
+ ShouldAvoidBreakInside(aState.mReflowInput)) {
+ // If it's our very first line *or* we're not at the top of the page
+ // and we have page-break-inside:avoid, then we need to be pushed to
+ // our parent's next-in-flow.
+ SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
+ } else {
+ // Push the line that didn't fit and any lines that follow it
+ // to our next-in-flow.
+ PushTruncatedLine(aState, aLine, aKeepReflowGoing);
+ }
+ }
+ }
+ break; // out of the reflow retry loop
+ }
+
+ // Now that we've got its final position all figured out, position any child
+ // views it may have. Note that the case when frame has a view got handled
+ // by FinishReflowChild, but that function didn't have the coordinates needed
+ // to correctly decide whether to reposition child views.
+ if (originalPosition != frame->GetPosition() && !frame->HasView()) {
+ nsContainerFrame::PositionChildViews(frame);
+ }
+
+#ifdef DEBUG
+ VerifyLines(true);
+#endif
+}
+
+// Returns true if an overflow-wrap break was used.
+bool nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState,
+ LineIterator aLine,
+ bool* aKeepReflowGoing) {
+ *aKeepReflowGoing = true;
+ bool usedOverflowWrap = false;
+
+ aLine->SetLineIsImpactedByFloat(false);
+
+ // Setup initial coordinate system for reflowing the inline frames
+ // into. Apply a previous block frame's block-end margin first.
+ if (ShouldApplyBStartMargin(aState, aLine)) {
+ aState.mBCoord += aState.mPrevBEndMargin.get();
+ }
+ nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
+
+ LineReflowStatus lineReflowStatus;
+ do {
+ nscoord availableSpaceBSize = 0;
+ aState.mLineBSize.reset();
+ do {
+ bool allowPullUp = true;
+ nsIFrame* forceBreakInFrame = nullptr;
+ int32_t forceBreakOffset = -1;
+ gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
+ do {
+ nsFloatManager::SavedState floatManagerState;
+ aState.FloatManager()->PushState(&floatManagerState);
+
+ // Once upon a time we allocated the first 30 nsLineLayout objects
+ // on the stack, and then we switched to the heap. At that time
+ // these objects were large (1100 bytes on a 32 bit system).
+ // Then the nsLineLayout object was shrunk to 156 bytes by
+ // removing some internal buffers. Given that it is so much
+ // smaller, the complexity of 2 different ways of allocating
+ // no longer makes sense. Now we always allocate on the stack.
+ nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(),
+ aState.mReflowInput, &aLine, nullptr);
+ lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
+ if (forceBreakInFrame) {
+ lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset);
+ }
+ DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace,
+ availableSpaceBSize, &floatManagerState,
+ aKeepReflowGoing, &lineReflowStatus, allowPullUp);
+ usedOverflowWrap = lineLayout.EndLineReflow();
+
+ if (LineReflowStatus::RedoNoPull == lineReflowStatus ||
+ LineReflowStatus::RedoMoreFloats == lineReflowStatus ||
+ LineReflowStatus::RedoNextBand == lineReflowStatus) {
+ if (lineLayout.NeedsBackup()) {
+ NS_ASSERTION(!forceBreakInFrame,
+ "Backing up twice; this should never be necessary");
+ // If there is no saved break position, then this will set
+ // set forceBreakInFrame to null and we won't back up, which is
+ // correct.
+ forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition(
+ &forceBreakOffset, &forceBreakPriority);
+ } else {
+ forceBreakInFrame = nullptr;
+ }
+ // restore the float manager state
+ aState.FloatManager()->PopState(&floatManagerState);
+ // Clear out float lists
+ aState.mCurrentLineFloats.Clear();
+ aState.mBelowCurrentLineFloats.Clear();
+ aState.mNoWrapFloats.Clear();
+ }
+
+ // Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
+ allowPullUp = false;
+ } while (LineReflowStatus::RedoNoPull == lineReflowStatus);
+ } while (LineReflowStatus::RedoMoreFloats == lineReflowStatus);
+ } while (LineReflowStatus::RedoNextBand == lineReflowStatus);
+
+ return usedOverflowWrap;
+}
+
+void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState,
+ LineIterator aLine,
+ bool* aKeepReflowGoing) {
+ aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
+ // Reflow the line again when we reflow at our new position.
+ aLine->MarkDirty();
+ *aKeepReflowGoing = false;
+}
+
+void nsBlockFrame::PushTruncatedLine(BlockReflowState& aState,
+ LineIterator aLine,
+ bool* aKeepReflowGoing) {
+ PushLines(aState, aLine.prev());
+ *aKeepReflowGoing = false;
+ aState.mReflowStatus.SetIncomplete();
+}
+
+void nsBlockFrame::DoReflowInlineFrames(
+ BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine,
+ nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize,
+ nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing,
+ LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) {
+ // Forget all of the floats on the line
+ aLine->ClearFloats();
+ aState.mFloatOverflowAreas.Clear();
+
+ // We need to set this flag on the line if any of our reflow passes
+ // are impacted by floats.
+ if (aFloatAvailableSpace.HasFloats()) {
+ aLine->SetLineIsImpactedByFloat(true);
+ }
+#ifdef REALLY_NOISY_REFLOW
+ printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this,
+ aFloatAvailableSpace.HasFloats());
+#endif
+
+ WritingMode outerWM = aState.mReflowInput.GetWritingMode();
+ WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild);
+ LogicalRect lineRect = aFloatAvailableSpace.mRect.ConvertTo(
+ lineWM, outerWM, aState.ContainerSize());
+
+ nscoord iStart = lineRect.IStart(lineWM);
+ nscoord availISize = lineRect.ISize(lineWM);
+ nscoord availBSize;
+ if (aState.mReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
+ availBSize = NS_UNCONSTRAINEDSIZE;
+ } else {
+ /* XXX get the height right! */
+ availBSize = lineRect.BSize(lineWM);
+ }
+
+ // Make sure to enable resize optimization before we call BeginLineReflow
+ // because it might get disabled there
+ aLine->EnableResizeReflowOptimization();
+
+ aLineLayout.BeginLineReflow(
+ iStart, aState.mBCoord, availISize, availBSize,
+ aFloatAvailableSpace.HasFloats(), false, /*XXX isTopOfPage*/
+ lineWM, aState.mContainerSize, aState.mInsetForBalance);
+
+ aState.mFlags.mIsLineLayoutEmpty = false;
+
+ // XXX Unfortunately we need to know this before reflowing the first
+ // inline frame in the line. FIX ME.
+ if (0 == aLineLayout.GetLineNumber() &&
+ HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD |
+ NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
+ aLineLayout.SetFirstLetterStyleOK(true);
+ }
+ NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD) &&
+ GetPrevContinuation()),
+ "first letter child bit should only be on first continuation");
+
+ // Reflow the frames that are already on the line first
+ LineReflowStatus lineReflowStatus = LineReflowStatus::OK;
+ int32_t i;
+ nsIFrame* frame = aLine->mFirstChild;
+
+ if (aFloatAvailableSpace.HasFloats()) {
+ // There is a soft break opportunity at the start of the line, because
+ // we can always move this line down below float(s).
+ if (aLineLayout.NotifyOptionalBreakPosition(
+ frame, 0, true, gfxBreakPriority::eNormalBreak)) {
+ lineReflowStatus = LineReflowStatus::RedoNextBand;
+ }
+ }
+
+ // need to repeatedly call GetChildCount here, because the child
+ // count can change during the loop!
+ for (i = 0;
+ LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount();
+ i++, frame = frame->GetNextSibling()) {
+ ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
+ if (LineReflowStatus::OK != lineReflowStatus) {
+ // It is possible that one or more of next lines are empty
+ // (because of DeleteNextInFlowChild). If so, delete them now
+ // in case we are finished.
+ ++aLine;
+ while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) {
+ // XXX Is this still necessary now that DeleteNextInFlowChild
+ // uses DoRemoveFrame?
+ nsLineBox* toremove = aLine;
+ aLine = mLines.erase(aLine);
+ NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line");
+ FreeLineBox(toremove);
+ }
+ --aLine;
+
+ NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated,
+ "ReflowInlineFrame should never determine that a line "
+ "needs to go to the next page/column");
+ }
+ }
+
+ // Don't pull up new frames into lines with continuation placeholders
+ if (aAllowPullUp) {
+ // Pull frames and reflow them until we can't
+ while (LineReflowStatus::OK == lineReflowStatus) {
+ frame = PullFrame(aState, aLine);
+ if (!frame) {
+ break;
+ }
+
+ while (LineReflowStatus::OK == lineReflowStatus) {
+ int32_t oldCount = aLine->GetChildCount();
+ ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
+ if (aLine->GetChildCount() != oldCount) {
+ // We just created a continuation for aFrame AND its going
+ // to end up on this line (e.g. :first-letter
+ // situation). Therefore we have to loop here before trying
+ // to pull another frame.
+ frame = frame->GetNextSibling();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty();
+
+ // We only need to backup if the line isn't going to be reflowed again anyway
+ bool needsBackup = aLineLayout.NeedsBackup() &&
+ (lineReflowStatus == LineReflowStatus::Stop ||
+ lineReflowStatus == LineReflowStatus::OK);
+ if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
+ NS_WARNING(
+ "We shouldn't be backing up more than once! "
+ "Someone must have set a break opportunity beyond the available width, "
+ "even though there were better break opportunities before it");
+ needsBackup = false;
+ }
+ if (needsBackup) {
+ // We need to try backing up to before a text run
+ // XXX It's possible, in fact not unusual, for the break opportunity to
+ // already be the end of the line. We should detect that and optimize to not
+ // re-do the line.
+ if (aLineLayout.HasOptionalBreakPosition()) {
+ // We can back up!
+ lineReflowStatus = LineReflowStatus::RedoNoPull;
+ }
+ } else {
+ // In case we reflow this line again, remember that we don't
+ // need to force any breaking
+ aLineLayout.ClearOptionalBreakPosition();
+ }
+
+ if (LineReflowStatus::RedoNextBand == lineReflowStatus) {
+ // This happens only when we have a line that is impacted by
+ // floats and the first element in the line doesn't fit with
+ // the floats.
+ //
+ // If there's block space available, we either try to reflow the line
+ // past the current band (if it's non-zero and the band definitely won't
+ // widen around a shape-outside), otherwise we try one pixel down. If
+ // there's no block space available, we push the line to the next
+ // page/column.
+ NS_ASSERTION(
+ NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM),
+ "unconstrained block size on totally empty line");
+
+ // See the analogous code for blocks in BlockReflowState::ClearFloats.
+ nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM);
+ if (bandBSize > 0 ||
+ NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) {
+ NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(),
+ "redo line on totally empty line with non-empty band...");
+ // We should never hit this case if we've placed floats on the
+ // line; if we have, then the GetFloatAvailableSpace call is wrong
+ // and needs to happen after the caller pops the float manager
+ // state.
+ aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
+
+ if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) {
+ // Move it down far enough to clear the current band.
+ aState.mBCoord += bandBSize;
+ } else {
+ // Move it down by one dev pixel.
+ aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1);
+ }
+
+ aFloatAvailableSpace = aState.GetFloatAvailableSpace();
+ } else {
+ // There's nowhere to retry placing the line, so we want to push
+ // it to the next page/column where its contents can fit not
+ // next to a float.
+ lineReflowStatus = LineReflowStatus::Truncated;
+ PushTruncatedLine(aState, aLine, aKeepReflowGoing);
+ }
+
+ // XXX: a small optimization can be done here when paginating:
+ // if the new Y coordinate is past the end of the block then
+ // push the line and return now instead of later on after we are
+ // past the float.
+ } else if (LineReflowStatus::Truncated != lineReflowStatus &&
+ LineReflowStatus::RedoNoPull != lineReflowStatus) {
+ // If we are propagating out a break-before status then there is
+ // no point in placing the line.
+ if (!aState.mReflowStatus.IsInlineBreakBefore()) {
+ if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine,
+ aFloatAvailableSpace, aAvailableSpaceBSize,
+ aKeepReflowGoing)) {
+ lineReflowStatus = LineReflowStatus::RedoMoreFloats;
+ // PlaceLine already called GetFloatAvailableSpaceForBSize or its
+ // variant for us.
+ }
+ }
+ }
+#ifdef DEBUG
+ if (gNoisyReflow) {
+ printf("Line reflow status = %s\n",
+ LineReflowStatusToString(lineReflowStatus));
+ }
+#endif
+
+ if (aLineLayout.GetDirtyNextLine()) {
+ // aLine may have been pushed to the overflow lines.
+ FrameLines* overflowLines = GetOverflowLines();
+ // We can't just compare iterators front() to aLine here, since they may be
+ // in different lists.
+ bool pushedToOverflowLines =
+ overflowLines && overflowLines->mLines.front() == aLine.get();
+ if (pushedToOverflowLines) {
+ // aLine is stale, it's associated with the main line list but it should
+ // be associated with the overflow line list now
+ aLine = overflowLines->mLines.begin();
+ }
+ nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines);
+ if (iter.Next() && iter.GetLine()->IsInline()) {
+ iter.GetLine()->MarkDirty();
+ if (iter.GetContainer() != this) {
+ aState.mReflowStatus.SetNextInFlowNeedsReflow();
+ }
+ }
+ }
+
+ *aLineReflowStatus = lineReflowStatus;
+}
+
+/**
+ * Reflow an inline frame. The reflow status is mapped from the frames
+ * reflow status to the lines reflow status (not to our reflow status).
+ * The line reflow status is simple: true means keep placing frames
+ * on the line; false means don't (the line is done). If the line
+ * has some sort of breaking affect then aLine's break-type will be set
+ * to something other than StyleClear::None.
+ */
+void nsBlockFrame::ReflowInlineFrame(BlockReflowState& aState,
+ nsLineLayout& aLineLayout,
+ LineIterator aLine, nsIFrame* aFrame,
+ LineReflowStatus* aLineReflowStatus) {
+ MOZ_ASSERT(aFrame);
+ *aLineReflowStatus = LineReflowStatus::OK;
+
+#ifdef NOISY_FIRST_LETTER
+ ListTag(stdout);
+ printf(": reflowing ");
+ aFrame->ListTag(stdout);
+ printf(" reflowingFirstLetter=%s\n",
+ aLineLayout.GetFirstLetterStyleOK() ? "on" : "off");
+#endif
+
+ if (aFrame->IsPlaceholderFrame()) {
+ auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
+ ph->ForgetLineIsEmptySoFar();
+ }
+
+ // Reflow the inline frame
+ nsReflowStatus frameReflowStatus;
+ bool pushedFrame;
+ aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame);
+
+ if (frameReflowStatus.NextInFlowNeedsReflow()) {
+ aLineLayout.SetDirtyNextLine();
+ }
+
+#ifdef REALLY_NOISY_REFLOW
+ aFrame->ListTag(stdout);
+ printf(": status=%s\n", ToString(frameReflowStatus).c_str());
+#endif
+
+#if defined(REFLOW_STATUS_COVERAGE)
+ RecordReflowStatus(false, frameReflowStatus);
+#endif
+
+ // Send post-reflow notification
+ aState.mPrevChild = aFrame;
+
+ /* XXX
+ This is where we need to add logic to handle some odd behavior.
+ For one thing, we should usually place at least one thing next
+ to a left float, even when that float takes up all the width on a line.
+ see bug 22496
+ */
+
+ // Process the child frames reflow status. There are 5 cases:
+ // complete, not-complete, break-before, break-after-complete,
+ // break-after-not-complete. There are two situations: we are a
+ // block or we are an inline. This makes a total of 10 cases
+ // (fortunately, there is some overlap).
+ aLine->ClearForcedLineBreak();
+ if (frameReflowStatus.IsInlineBreak() ||
+ aState.mTrailingClearFromPIF != StyleClear::None) {
+ // Always abort the line reflow (because a line break is the
+ // minimal amount of break we do).
+ *aLineReflowStatus = LineReflowStatus::Stop;
+
+ // XXX what should aLine's break-type be set to in all these cases?
+ if (frameReflowStatus.IsInlineBreakBefore()) {
+ // Break-before cases.
+ if (aFrame == aLine->mFirstChild) {
+ // If we break before the first frame on the line then we must
+ // be trying to place content where there's no room (e.g. on a
+ // line with wide floats). Inform the caller to reflow the
+ // line after skipping past a float.
+ *aLineReflowStatus = LineReflowStatus::RedoNextBand;
+ } else {
+ // It's not the first child on this line so go ahead and split
+ // the line. We will see the frame again on the next-line.
+ SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
+
+ // If we're splitting the line because the frame didn't fit and it
+ // was pushed, then mark the line as having word wrapped. We need to
+ // know that if we're shrink wrapping our width
+ if (pushedFrame) {
+ aLine->SetLineWrapped(true);
+ }
+ }
+ } else {
+ MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() ||
+ aState.mTrailingClearFromPIF != StyleClear::None,
+ "We should've handled inline break-before in the if-branch!");
+
+ // If a float split and its prev-in-flow was followed by a <BR>, then
+ // combine the <BR>'s float clear type with the inline's float clear type
+ // (the inline will be the very next frame after the split float).
+ StyleClear clearType = frameReflowStatus.FloatClearType();
+ if (aState.mTrailingClearFromPIF != StyleClear::None) {
+ clearType = nsLayoutUtils::CombineClearType(
+ clearType, aState.mTrailingClearFromPIF);
+ aState.mTrailingClearFromPIF = StyleClear::None;
+ }
+ // Break-after cases
+ if (clearType != StyleClear::None || aLineLayout.GetLineEndsInBR()) {
+ aLine->SetForcedLineBreakAfter(clearType);
+ }
+ if (frameReflowStatus.IsComplete()) {
+ // Split line, but after the frame just reflowed
+ SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
+ aLineReflowStatus);
+
+ if (frameReflowStatus.IsInlineBreakAfter() &&
+ !aLineLayout.GetLineEndsInBR()) {
+ aLineLayout.SetDirtyNextLine();
+ }
+ }
+ }
+ }
+
+ if (!frameReflowStatus.IsFullyComplete()) {
+ // Create a continuation for the incomplete frame. Note that the
+ // frame may already have a continuation.
+ CreateContinuationFor(aState, aLine, aFrame);
+
+ // Remember that the line has wrapped
+ if (!aLineLayout.GetLineEndsInBR()) {
+ aLine->SetLineWrapped(true);
+ }
+
+ // If we just ended a first-letter frame or reflowed a placeholder then
+ // don't split the line and don't stop the line reflow...
+ // But if we are going to stop anyways we'd better split the line.
+ if ((!frameReflowStatus.FirstLetterComplete() &&
+ !aFrame->IsPlaceholderFrame()) ||
+ *aLineReflowStatus == LineReflowStatus::Stop) {
+ // Split line after the current frame
+ *aLineReflowStatus = LineReflowStatus::Stop;
+ SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
+ aLineReflowStatus);
+ }
+ }
+}
+
+bool nsBlockFrame::CreateContinuationFor(BlockReflowState& aState,
+ nsLineBox* aLine, nsIFrame* aFrame) {
+ nsIFrame* newFrame = nullptr;
+
+ if (!aFrame->GetNextInFlow()) {
+ newFrame =
+ PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this);
+
+ mFrames.InsertFrame(nullptr, aFrame, newFrame);
+
+ if (aLine) {
+ aLine->NoteFrameAdded(newFrame);
+ }
+ }
+#ifdef DEBUG
+ VerifyLines(false);
+#endif
+ return !!newFrame;
+}
+
+void nsBlockFrame::SplitFloat(BlockReflowState& aState, nsIFrame* aFloat,
+ const nsReflowStatus& aFloatStatus) {
+ MOZ_ASSERT(!aFloatStatus.IsFullyComplete(),
+ "why split the frame if it's fully complete?");
+ MOZ_ASSERT(aState.mBlock == this);
+
+ nsIFrame* nextInFlow = aFloat->GetNextInFlow();
+ if (nextInFlow) {
+ nsContainerFrame* oldParent = nextInFlow->GetParent();
+ oldParent->StealFrame(nextInFlow);
+ if (oldParent != this) {
+ ReparentFrame(nextInFlow, oldParent, this);
+ }
+ if (!aFloatStatus.IsOverflowIncomplete()) {
+ nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ }
+ } else {
+ nextInFlow =
+ PresShell()->FrameConstructor()->CreateContinuingFrame(aFloat, this);
+ }
+ if (aFloatStatus.IsOverflowIncomplete()) {
+ nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ }
+
+ StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
+ if (floatStyle == StyleFloat::Left) {
+ aState.FloatManager()->SetSplitLeftFloatAcrossBreak();
+ } else {
+ MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!");
+ aState.FloatManager()->SetSplitRightFloatAcrossBreak();
+ }
+
+ aState.AppendPushedFloatChain(nextInFlow);
+ if (MOZ_LIKELY(!HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) ||
+ MOZ_UNLIKELY(IsTrueOverflowContainer())) {
+ aState.mReflowStatus.SetOverflowIncomplete();
+ } else {
+ aState.mReflowStatus.SetIncomplete();
+ }
+}
+
+static bool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine,
+ nsIFrame* aFloat) {
+ if (!aFloat) {
+ return true;
+ }
+ NS_ASSERTION(!aFloat->GetPrevContinuation(),
+ "float in a line should never be a continuation");
+ NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
+ "float in a line should never be a pushed float");
+ nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame();
+ for (nsIFrame* f = ph; f; f = f->GetParent()) {
+ if (f->GetParent() == aBlock) {
+ return aLine->Contains(f);
+ }
+ }
+ NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!");
+ return true;
+}
+
+void nsBlockFrame::SplitLine(BlockReflowState& aState,
+ nsLineLayout& aLineLayout, LineIterator aLine,
+ nsIFrame* aFrame,
+ LineReflowStatus* aLineReflowStatus) {
+ MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line");
+
+ int32_t pushCount =
+ aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
+ MOZ_ASSERT(pushCount >= 0, "bad push count");
+
+#ifdef DEBUG
+ if (gNoisyReflow) {
+ nsIFrame::IndentBy(stdout, gNoiseIndent);
+ printf("split line: from line=%p pushCount=%d aFrame=",
+ static_cast<void*>(aLine.get()), pushCount);
+ if (aFrame) {
+ aFrame->ListTag(stdout);
+ } else {
+ printf("(null)");
+ }
+ printf("\n");
+ if (gReallyNoisyReflow) {
+ aLine->List(stdout, gNoiseIndent + 1);
+ }
+ }
+#endif
+
+ if (0 != pushCount) {
+ MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
+ MOZ_ASSERT(nullptr != aFrame, "whoops");
+#ifdef DEBUG
+ {
+ nsIFrame* f = aFrame;
+ int32_t count = pushCount;
+ while (f && count > 0) {
+ f = f->GetNextSibling();
+ --count;
+ }
+ NS_ASSERTION(count == 0, "Not enough frames to push");
+ }
+#endif
+
+ // Put frames being split out into their own line
+ nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
+ mLines.after_insert(aLine, newLine);
+#ifdef DEBUG
+ if (gReallyNoisyReflow) {
+ newLine->List(stdout, gNoiseIndent + 1);
+ }
+#endif
+
+ // Let line layout know that some frames are no longer part of its
+ // state.
+ aLineLayout.SplitLineTo(aLine->GetChildCount());
+
+ // If floats have been placed whose placeholders have been pushed to the new
+ // line, we need to reflow the old line again. We don't want to look at the
+ // frames in the new line, because as a large paragraph is laid out the
+ // we'd get O(N^2) performance. So instead we just check that the last
+ // float and the last below-current-line float are still in aLine.
+ if (!CheckPlaceholderInLine(
+ this, aLine,
+ aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) ||
+ !CheckPlaceholderInLine(
+ this, aLine,
+ aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) {
+ *aLineReflowStatus = LineReflowStatus::RedoNoPull;
+ }
+
+#ifdef DEBUG
+ VerifyLines(true);
+#endif
+ }
+}
+
+bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) {
+ while (++aLine != LinesEnd()) {
+ // There is another line
+ if (0 != aLine->GetChildCount()) {
+ // If the next line is a block line then this line is the last in a
+ // group of inline lines.
+ return aLine->IsBlock();
+ }
+ // The next line is empty, try the next one
+ }
+
+ // Try our next-in-flows lines to answer the question
+ nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow();
+ while (nullptr != nextInFlow) {
+ for (const auto& line : nextInFlow->Lines()) {
+ if (0 != line.GetChildCount()) {
+ return line.IsBlock();
+ }
+ }
+ nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow();
+ }
+
+ // This is the last line - so don't allow justification
+ return true;
+}
+
+bool nsBlockFrame::PlaceLine(BlockReflowState& aState,
+ nsLineLayout& aLineLayout, LineIterator aLine,
+ nsFloatManager::SavedState* aFloatStateBeforeLine,
+ nsFlowAreaRect& aFlowArea,
+ nscoord& aAvailableSpaceBSize,
+ bool* aKeepReflowGoing) {
+ // Try to position the floats in a nowrap context.
+ aLineLayout.FlushNoWrapFloats();
+
+ // Trim extra white-space from the line before placing the frames
+ aLineLayout.TrimTrailingWhiteSpace();
+
+ // Vertically align the frames on this line.
+ //
+ // According to the CSS2 spec, section 12.6.1, the "marker" box
+ // participates in the height calculation of the list-item box's
+ // first line box.
+ //
+ // There are exactly two places a ::marker can be placed: near the
+ // first or second line. It's only placed on the second line in a
+ // rare case: when the first line is empty.
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+ bool addedMarker = false;
+ if (HasOutsideMarker() &&
+ ((aLine == mLines.front() &&
+ (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) ||
+ (mLines.front() != mLines.back() && 0 == mLines.front()->BSize() &&
+ aLine == mLines.begin().next()))) {
+ ReflowOutput metrics(aState.mReflowInput);
+ nsIFrame* marker = GetOutsideMarker();
+ ReflowOutsideMarker(marker, aState, metrics, aState.mBCoord);
+ NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
+ "empty ::marker frame took up space");
+ aLineLayout.AddMarkerFrame(marker, metrics);
+ addedMarker = true;
+ }
+ aLineLayout.VerticalAlignLine();
+
+ // We want to consider the floats in the current line when determining
+ // whether the float available space is shrunk. If mLineBSize doesn't
+ // exist, we are in the first pass trying to place the line. Calling
+ // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat()
+ // for UpdateBand().
+
+ // floatAvailableSpaceWithOldLineBSize is the float available space with
+ // the old BSize, but including the floats that were added in this line.
+ LogicalRect floatAvailableSpaceWithOldLineBSize =
+ aState.mLineBSize.isNothing()
+ ? aState.GetFloatAvailableSpace(aLine->BStart()).mRect
+ : aState
+ .GetFloatAvailableSpaceForBSize(
+ aLine->BStart(), aState.mLineBSize.value(), nullptr)
+ .mRect;
+
+ // As we redo for floats, we can't reduce the amount of BSize we're
+ // checking.
+ aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
+ LogicalRect floatAvailableSpaceWithLineBSize =
+ aState
+ .GetFloatAvailableSpaceForBSize(aLine->BStart(), aAvailableSpaceBSize,
+ nullptr)
+ .mRect;
+
+ // If the available space between the floats is smaller now that we
+ // know the BSize, return false (and cause another pass with
+ // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize
+ // never decreases, which means that we can't reduce the set of floats
+ // we intersect, which means that the available space cannot grow.
+ if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize,
+ floatAvailableSpaceWithLineBSize, false)) {
+ // Prepare data for redoing the line.
+ aState.mLineBSize = Some(aLine->BSize());
+
+ // Since we want to redo the line, we update aFlowArea by using the
+ // aFloatStateBeforeLine, which is the float manager's state before the
+ // line is placed.
+ LogicalRect oldFloatAvailableSpace(aFlowArea.mRect);
+ aFlowArea = aState.GetFloatAvailableSpaceForBSize(
+ aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine);
+
+ NS_ASSERTION(
+ aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm),
+ "yikes");
+ // Restore the BSize to the position of the next band.
+ aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
+
+ // Enforce both IStart() and IEnd() never move outwards to prevent
+ // infinite grow-shrink loops.
+ const nscoord iStartDiff =
+ aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm);
+ const nscoord iEndDiff =
+ aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm);
+ if (iStartDiff < 0) {
+ aFlowArea.mRect.IStart(wm) -= iStartDiff;
+ aFlowArea.mRect.ISize(wm) += iStartDiff;
+ }
+ if (iEndDiff > 0) {
+ aFlowArea.mRect.ISize(wm) -= iEndDiff;
+ }
+
+ return false;
+ }
+
+#ifdef DEBUG
+ if (!GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ static nscoord lastHeight = 0;
+ if (ABSURD_SIZE(aLine->BStart())) {
+ lastHeight = aLine->BStart();
+ if (abs(aLine->BStart() - lastHeight) > ABSURD_COORD / 10) {
+ nsIFrame::ListTag(stdout);
+ printf(": line=%p y=%d line.bounds.height=%d\n",
+ static_cast<void*>(aLine.get()), aLine->BStart(),
+ aLine->BSize());
+ }
+ } else {
+ lastHeight = 0;
+ }
+ }
+#endif
+
+ // Only block frames horizontally align their children because
+ // inline frames "shrink-wrap" around their children (therefore
+ // there is no extra horizontal space).
+ const nsStyleText* styleText = StyleText();
+
+ /**
+ * We don't care checking for IsLastLine properly if we don't care (if it
+ * can't change the used text-align value for the line).
+ *
+ * In other words, isLastLine really means isLastLineAndWeCare.
+ */
+ const bool isLastLine =
+ !IsInSVGTextSubtree() &&
+ styleText->TextAlignForLastLine() != styleText->mTextAlign &&
+ (aLineLayout.GetLineEndsInBR() || IsLastLine(aState, aLine));
+
+ aLineLayout.TextAlignLine(aLine, isLastLine);
+
+ // From here on, pfd->mBounds rectangles are incorrect because bidi
+ // might have moved frames around!
+ OverflowAreas overflowAreas;
+ aLineLayout.RelativePositionFrames(overflowAreas);
+ aLine->SetOverflowAreas(overflowAreas);
+ if (addedMarker) {
+ aLineLayout.RemoveMarkerFrame(GetOutsideMarker());
+ }
+
+ // Inline lines do not have margins themselves; however they are
+ // impacted by prior block margins. If this line ends up having some
+ // height then we zero out the previous block-end margin value that was
+ // already applied to the line's starting Y coordinate. Otherwise we
+ // leave it be so that the previous blocks block-end margin can be
+ // collapsed with a block that follows.
+ nscoord newBCoord;
+
+ if (!aLine->CachedIsEmpty()) {
+ // This line has some height. Therefore the application of the
+ // previous-bottom-margin should stick.
+ aState.mPrevBEndMargin.Zero();
+ newBCoord = aLine->BEnd();
+ } else {
+ // Don't let the previous-bottom-margin value affect the newBCoord
+ // coordinate (it was applied in ReflowInlineFrames speculatively)
+ // since the line is empty.
+ // We already called |ShouldApplyBStartMargin|, and if we applied it
+ // then mShouldApplyBStartMargin is set.
+ nscoord dy = aState.mFlags.mShouldApplyBStartMargin
+ ? -aState.mPrevBEndMargin.get()
+ : 0;
+ newBCoord = aState.mBCoord + dy;
+ }
+
+ if (!aState.mReflowStatus.IsFullyComplete() &&
+ ShouldAvoidBreakInside(aState.mReflowInput)) {
+ aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
+ SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
+ return true;
+ }
+
+ // See if the line fit (our first line always does).
+ if (mLines.front() != aLine &&
+ aState.ContentBSize() != NS_UNCONSTRAINEDSIZE &&
+ newBCoord > aState.ContentBEnd()) {
+ NS_ASSERTION(aState.mCurrentLine == aLine, "oops");
+ if (ShouldAvoidBreakInside(aState.mReflowInput)) {
+ // All our content doesn't fit, start on the next page.
+ SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
+ } else {
+ // Push aLine and all of its children and anything else that
+ // follows to our next-in-flow.
+ PushTruncatedLine(aState, aLine, aKeepReflowGoing);
+ }
+ return true;
+ }
+
+ // Note that any early return before this update of aState.mBCoord
+ // must either (a) return false or (b) set aKeepReflowGoing to false.
+ // Otherwise we'll keep reflowing later lines at an incorrect
+ // position, and we might not come back and clean up the damage later.
+ aState.mBCoord = newBCoord;
+
+ // Add the already placed current-line floats to the line
+ aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
+
+ // Any below current line floats to place?
+ if (!aState.mBelowCurrentLineFloats.IsEmpty()) {
+ // Reflow the below-current-line floats, which places on the line's
+ // float list.
+ aState.PlaceBelowCurrentLineFloats(aLine);
+ }
+
+ // When a line has floats, factor them into the overflow areas computations.
+ if (aLine->HasFloats()) {
+ // Union the float overflow areas (stored in aState) and the value computed
+ // by the line layout code.
+ OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas;
+ lineOverflowAreas.UnionWith(aLine->GetOverflowAreas());
+ aLine->SetOverflowAreas(lineOverflowAreas);
+
+#ifdef NOISY_OVERFLOW_AREAS
+ printf("%s: Line %p, InkOverflowRect=%s, ScrollableOverflowRect=%s\n",
+ ListTag().get(), aLine.get(),
+ ToString(aLine->InkOverflowRect()).c_str(),
+ ToString(aLine->ScrollableOverflowRect()).c_str());
+#endif
+ }
+
+ // Apply break-after clearing if necessary
+ // This must stay in sync with |ReflowDirtyLines|.
+ if (aLine->HasFloatClearTypeAfter()) {
+ std::tie(aState.mBCoord, std::ignore) =
+ aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter());
+ }
+ return true;
+}
+
+void nsBlockFrame::PushLines(BlockReflowState& aState,
+ nsLineList::iterator aLineBefore) {
+ // NOTE: aLineBefore is always a normal line, not an overflow line.
+ // The following expression will assert otherwise.
+ DebugOnly<bool> check = aLineBefore == mLines.begin();
+
+ nsLineList::iterator overBegin(aLineBefore.next());
+
+ // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
+ bool firstLine = overBegin == LinesBegin();
+
+ if (overBegin != LinesEnd()) {
+ // Remove floats in the lines from mFloats
+ nsFrameList floats;
+ CollectFloats(overBegin->mFirstChild, floats, true);
+
+ if (floats.NotEmpty()) {
+#ifdef DEBUG
+ for (nsIFrame* f : floats) {
+ MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
+ "CollectFloats should've removed that bit");
+ }
+#endif
+ // Push the floats onto the front of the overflow out-of-flows list
+ nsAutoOOFFrameList oofs(this);
+ oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats));
+ }
+
+ // overflow lines can already exist in some cases, in particular,
+ // when shrinkwrapping and we discover that the shrinkwap causes
+ // the height of some child block to grow which creates additional
+ // overflowing content. In such cases we must prepend the new
+ // overflow to the existing overflow.
+ FrameLines* overflowLines = RemoveOverflowLines();
+ if (!overflowLines) {
+ // XXXldb use presshell arena!
+ overflowLines = new FrameLines();
+ }
+ if (overflowLines) {
+ nsIFrame* lineBeforeLastFrame;
+ if (firstLine) {
+ lineBeforeLastFrame = nullptr; // removes all frames
+ } else {
+ nsIFrame* f = overBegin->mFirstChild;
+ lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
+ NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(),
+ "unexpected line frames");
+ }
+ nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame);
+ overflowLines->mFrames.InsertFrames(nullptr, nullptr,
+ std::move(pushedFrames));
+
+ overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
+ overBegin, LinesEnd());
+ NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty");
+ // this takes ownership but it won't delete it immediately so we
+ // can keep using it.
+ SetOverflowLines(overflowLines);
+
+ // Mark all the overflow lines dirty so that they get reflowed when
+ // they are pulled up by our next-in-flow.
+
+ // XXXldb Can this get called O(N) times making the whole thing O(N^2)?
+ for (LineIterator line = overflowLines->mLines.begin(),
+ line_end = overflowLines->mLines.end();
+ line != line_end; ++line) {
+ line->MarkDirty();
+ line->MarkPreviousMarginDirty();
+ line->SetMovedFragments();
+ line->SetBoundsEmpty();
+ if (line->HasFloats()) {
+ line->ClearFloats();
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG
+ VerifyOverflowSituation();
+#endif
+}
+
+// The overflowLines property is stored as a pointer to a line list,
+// which must be deleted. However, the following functions all maintain
+// the invariant that the property is never set if the list is empty.
+
+bool nsBlockFrame::DrainOverflowLines() {
+#ifdef DEBUG
+ VerifyOverflowSituation();
+#endif
+
+ // Steal the prev-in-flow's overflow lines and prepend them.
+ bool didFindOverflow = false;
+ nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
+ if (prevBlock) {
+ prevBlock->ClearLineCursors();
+ FrameLines* overflowLines = prevBlock->RemoveOverflowLines();
+ if (overflowLines) {
+ // Make all the frames on the overflow line list mine.
+ ReparentFrames(overflowLines->mFrames, prevBlock, this);
+
+ // Collect overflow containers from our OverflowContainers list that are
+ // continuations from the frames we picked up from our prev-in-flow, then
+ // prepend those to ExcessOverflowContainers to ensure the continuations
+ // are ordered.
+ if (GetOverflowContainers()) {
+ nsFrameList ocContinuations;
+ for (auto* f : overflowLines->mFrames) {
+ auto* cont = f;
+ bool done = false;
+ while (!done && (cont = cont->GetNextContinuation()) &&
+ cont->GetParent() == this) {
+ bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling();
+ if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
+ TryRemoveFrame(OverflowContainersProperty(), cont)) {
+ ocContinuations.AppendFrame(nullptr, cont);
+ done = onlyChild;
+ continue;
+ }
+ break;
+ }
+ if (done) {
+ break;
+ }
+ }
+ if (!ocContinuations.IsEmpty()) {
+ if (nsFrameList* eoc = GetExcessOverflowContainers()) {
+ eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations));
+ } else {
+ SetExcessOverflowContainers(std::move(ocContinuations));
+ }
+ }
+ }
+
+ // Make the overflow out-of-flow frames mine too.
+ nsAutoOOFFrameList oofs(prevBlock);
+ if (oofs.mList.NotEmpty()) {
+ // In case we own any next-in-flows of any of the drained frames, then
+ // move those to the PushedFloat list.
+ nsFrameList pushedFloats;
+ for (nsIFrame* f : oofs.mList) {
+ nsIFrame* nif = f->GetNextInFlow();
+ for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
+ MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT));
+ RemoveFloat(nif);
+ pushedFloats.AppendFrame(nullptr, nif);
+ }
+ }
+ ReparentFrames(oofs.mList, prevBlock, this);
+ mFloats.InsertFrames(nullptr, nullptr, std::move(oofs.mList));
+ if (!pushedFloats.IsEmpty()) {
+ nsFrameList* pf = EnsurePushedFloats();
+ pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats));
+ }
+ }
+
+ if (!mLines.empty()) {
+ // Remember to recompute the margins on the first line. This will
+ // also recompute the correct deltaBCoord if necessary.
+ mLines.front()->MarkPreviousMarginDirty();
+ }
+ // The overflow lines have already been marked dirty and their previous
+ // margins marked dirty also.
+
+ // Prepend the overflow frames/lines to our principal list.
+ mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames));
+ mLines.splice(mLines.begin(), overflowLines->mLines);
+ NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list");
+ delete overflowLines;
+ didFindOverflow = true;
+ }
+ }
+
+ // Now append our own overflow lines.
+ return DrainSelfOverflowList() || didFindOverflow;
+}
+
+bool nsBlockFrame::DrainSelfOverflowList() {
+ UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines());
+ if (!ourOverflowLines) {
+ return false;
+ }
+
+ // No need to reparent frames in our own overflow lines/oofs, because they're
+ // already ours. But we should put overflow floats back in mFloats.
+ // (explicit scope to remove the OOF list before VerifyOverflowSituation)
+ {
+ nsAutoOOFFrameList oofs(this);
+ if (oofs.mList.NotEmpty()) {
+#ifdef DEBUG
+ for (nsIFrame* f : oofs.mList) {
+ MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
+ "CollectFloats should've removed that bit");
+ }
+#endif
+ // The overflow floats go after our regular floats.
+ mFloats.AppendFrames(nullptr, std::move(oofs).mList);
+ }
+ }
+ if (!ourOverflowLines->mLines.empty()) {
+ mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames));
+ mLines.splice(mLines.end(), ourOverflowLines->mLines);
+ }
+
+#ifdef DEBUG
+ VerifyOverflowSituation();
+#endif
+ return true;
+}
+
+/**
+ * Pushed floats are floats whose placeholders are in a previous
+ * continuation. They might themselves be next-continuations of a float
+ * that partially fit in an earlier continuation, or they might be the
+ * first continuation of a float that couldn't be placed at all.
+ *
+ * Pushed floats live permanently at the beginning of a block's float
+ * list, where they must live *before* any floats whose placeholders are
+ * in that block.
+ *
+ * Temporarily, during reflow, they also live on the pushed floats list,
+ * which only holds them between (a) when one continuation pushes them to
+ * its pushed floats list because they don't fit and (b) when the next
+ * continuation pulls them onto the beginning of its float list.
+ *
+ * DrainPushedFloats sets up pushed floats the way we need them at the
+ * start of reflow; they are then reflowed by ReflowPushedFloats (which
+ * might push some of them on). Floats with placeholders in this block
+ * are reflowed by (BlockReflowState/nsLineLayout)::AddFloat, which
+ * also maintains these invariants.
+ *
+ * DrainSelfPushedFloats moves any pushed floats from this block's own
+ * PushedFloats list back into mFloats. DrainPushedFloats additionally
+ * moves frames from its prev-in-flow's PushedFloats list into mFloats.
+ */
+void nsBlockFrame::DrainSelfPushedFloats() {
+ // If we're getting reflowed multiple times without our
+ // next-continuation being reflowed, we might need to pull back floats
+ // that we just put in the list to be pushed to our next-in-flow.
+ // We don't want to pull back any next-in-flows of floats on our own
+ // float list, and we only need to pull back first-in-flows whose
+ // placeholders were in earlier blocks (since first-in-flows whose
+ // placeholders are in this block will get pulled appropriately by
+ // AddFloat, and will then be more likely to be in the correct order).
+ mozilla::PresShell* presShell = PresShell();
+ nsFrameList* ourPushedFloats = GetPushedFloats();
+ if (ourPushedFloats) {
+ // When we pull back floats, we want to put them with the pushed
+ // floats, which must live at the start of our float list, but we
+ // want them at the end of those pushed floats.
+ // FIXME: This isn't quite right! What if they're all pushed floats?
+ nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */
+ for (nsIFrame* f = mFloats.FirstChild();
+ f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT);
+ f = f->GetNextSibling()) {
+ insertionPrevSibling = f;
+ }
+
+ nsIFrame* f = ourPushedFloats->LastChild();
+ while (f) {
+ nsIFrame* prevSibling = f->GetPrevSibling();
+
+ nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame();
+ nsIFrame* floatOriginalParent =
+ presShell->FrameConstructor()->GetFloatContainingBlock(placeholder);
+ if (floatOriginalParent != this) {
+ // This is a first continuation that was pushed from one of our
+ // previous continuations. Take it out of the pushed floats
+ // list and put it in our floats list, before any of our
+ // floats, but after other pushed floats.
+ ourPushedFloats->RemoveFrame(f);
+ mFloats.InsertFrame(nullptr, insertionPrevSibling, f);
+ }
+
+ f = prevSibling;
+ }
+
+ if (ourPushedFloats->IsEmpty()) {
+ RemovePushedFloats()->Delete(presShell);
+ }
+ }
+}
+
+void nsBlockFrame::DrainPushedFloats() {
+ DrainSelfPushedFloats();
+
+ // After our prev-in-flow has completed reflow, it may have a pushed
+ // floats list, containing floats that we need to own. Take these.
+ nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
+ if (prevBlock) {
+ AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats());
+ if (list && list->NotEmpty()) {
+ mFloats.InsertFrames(this, nullptr, std::move(*list));
+ }
+ }
+}
+
+nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const {
+ if (!HasOverflowLines()) {
+ return nullptr;
+ }
+ FrameLines* prop = GetProperty(OverflowLinesProperty());
+ NS_ASSERTION(
+ prop && !prop->mLines.empty() &&
+ prop->mLines.front()->GetChildCount() == 0
+ ? prop->mFrames.IsEmpty()
+ : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
+ "value should always be stored and non-empty when state set");
+ return prop;
+}
+
+nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() {
+ if (!HasOverflowLines()) {
+ return nullptr;
+ }
+ FrameLines* prop = TakeProperty(OverflowLinesProperty());
+ NS_ASSERTION(
+ prop && !prop->mLines.empty() &&
+ prop->mLines.front()->GetChildCount() == 0
+ ? prop->mFrames.IsEmpty()
+ : prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
+ "value should always be stored and non-empty when state set");
+ RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
+ return prop;
+}
+
+void nsBlockFrame::DestroyOverflowLines() {
+ NS_ASSERTION(HasOverflowLines(), "huh?");
+ FrameLines* prop = TakeProperty(OverflowLinesProperty());
+ NS_ASSERTION(prop && prop->mLines.empty(),
+ "value should always be stored but empty when destroying");
+ RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
+ delete prop;
+}
+
+// This takes ownership of aOverflowLines.
+// XXX We should allocate overflowLines from presShell arena!
+void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) {
+ NS_ASSERTION(aOverflowLines, "null lines");
+ NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
+ NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
+ aOverflowLines->mFrames.FirstChild(),
+ "invalid overflow lines / frames");
+ NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES),
+ "Overwriting existing overflow lines");
+
+ // Verify that we won't overwrite an existing overflow list
+ NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
+ SetProperty(OverflowLinesProperty(), aOverflowLines);
+ AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
+}
+
+nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const {
+ if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
+ return nullptr;
+ }
+ nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty());
+ NS_ASSERTION(result, "value should always be non-empty when state set");
+ return result;
+}
+
+void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList,
+ nsFrameList* aPropValue) {
+ MOZ_ASSERT(
+ HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue,
+ "state does not match value");
+
+ if (aList.IsEmpty()) {
+ if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
+ return;
+ }
+ nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty());
+ NS_ASSERTION(aPropValue == list, "prop value mismatch");
+ list->Clear();
+ list->Delete(PresShell());
+ RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
+ } else if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
+ NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()),
+ "prop value mismatch");
+ *aPropValue = std::move(aList);
+ } else {
+ SetProperty(OverflowOutOfFlowsProperty(),
+ new (PresShell()) nsFrameList(std::move(aList)));
+ AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
+ }
+}
+
+nsIFrame* nsBlockFrame::GetInsideMarker() const {
+ if (!HasInsideMarker()) {
+ return nullptr;
+ }
+ NS_ASSERTION(!HasOutsideMarker(), "invalid marker state");
+ nsIFrame* frame = GetProperty(InsideMarkerProperty());
+ NS_ASSERTION(frame, "bogus inside ::marker frame");
+ return frame;
+}
+
+nsIFrame* nsBlockFrame::GetOutsideMarker() const {
+ nsFrameList* list = GetOutsideMarkerList();
+ return list ? list->FirstChild() : nullptr;
+}
+
+nsFrameList* nsBlockFrame::GetOutsideMarkerList() const {
+ if (!HasOutsideMarker()) {
+ return nullptr;
+ }
+ NS_ASSERTION(!HasInsideMarker(), "invalid marker state");
+ nsFrameList* list = GetProperty(OutsideMarkerProperty());
+ NS_ASSERTION(list && list->GetLength() == 1, "bogus outside ::marker list");
+ return list;
+}
+
+nsFrameList* nsBlockFrame::GetPushedFloats() const {
+ if (!HasPushedFloats()) {
+ return nullptr;
+ }
+ nsFrameList* result = GetProperty(PushedFloatProperty());
+ NS_ASSERTION(result, "value should always be non-empty when state set");
+ return result;
+}
+
+nsFrameList* nsBlockFrame::EnsurePushedFloats() {
+ nsFrameList* result = GetPushedFloats();
+ if (result) {
+ return result;
+ }
+
+ result = new (PresShell()) nsFrameList;
+ SetProperty(PushedFloatProperty(), result);
+ AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
+
+ return result;
+}
+
+nsFrameList* nsBlockFrame::RemovePushedFloats() {
+ if (!HasPushedFloats()) {
+ return nullptr;
+ }
+ nsFrameList* result = TakeProperty(PushedFloatProperty());
+ RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
+ NS_ASSERTION(result, "value should always be non-empty when state set");
+ return result;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Frame list manipulation routines
+
+void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
+ if (aFrameList.IsEmpty()) {
+ return;
+ }
+ if (aListID != FrameChildListID::Principal) {
+ if (FrameChildListID::Float == aListID) {
+ DrainSelfPushedFloats(); // ensure the last frame is in mFloats
+ mFloats.AppendFrames(nullptr, std::move(aFrameList));
+ return;
+ }
+ MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
+ "unexpected child list");
+ }
+
+ // Find the proper last-child for where the append should go
+ nsIFrame* lastKid = mFrames.LastChild();
+ NS_ASSERTION(
+ (mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid,
+ "out-of-sync mLines / mFrames");
+
+#ifdef NOISY_REFLOW_REASON
+ ListTag(stdout);
+ printf(": append ");
+ for (nsIFrame* frame : aFrameList) {
+ frame->ListTag(stdout);
+ }
+ if (lastKid) {
+ printf(" after ");
+ lastKid->ListTag(stdout);
+ }
+ printf("\n");
+#endif
+
+ if (IsInSVGTextSubtree()) {
+ MOZ_ASSERT(GetParent()->IsSVGTextFrame(),
+ "unexpected block frame in SVG text");
+ // Workaround for bug 1399425 in case this bit has been removed from the
+ // SVGTextFrame just before the parser adds more descendant nodes.
+ GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
+ }
+
+ AddFrames(std::move(aFrameList), lastKid, nullptr);
+ if (aListID != FrameChildListID::NoReflowPrincipal) {
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
+ }
+}
+
+void nsBlockFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+
+ if (aListID != FrameChildListID::Principal) {
+ if (FrameChildListID::Float == aListID) {
+ DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats
+ mFloats.InsertFrames(this, aPrevFrame, std::move(aFrameList));
+ return;
+ }
+ MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID,
+ "unexpected child list");
+ }
+
+#ifdef NOISY_REFLOW_REASON
+ ListTag(stdout);
+ printf(": insert ");
+ for (nsIFrame* frame : aFrameList) {
+ frame->ListTag(stdout);
+ }
+ if (aPrevFrame) {
+ printf(" after ");
+ aPrevFrame->ListTag(stdout);
+ }
+ printf("\n");
+#endif
+
+ AddFrames(std::move(aFrameList), aPrevFrame, aPrevFrameLine);
+ if (aListID != FrameChildListID::NoReflowPrincipal) {
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
+ }
+}
+
+void nsBlockFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
+ nsIFrame* aOldFrame) {
+#ifdef NOISY_REFLOW_REASON
+ ListTag(stdout);
+ printf(": remove ");
+ aOldFrame->ListTag(stdout);
+ printf("\n");
+#endif
+
+ if (aListID == FrameChildListID::Principal) {
+ bool hasFloats = BlockHasAnyFloats(aOldFrame);
+ DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
+ if (hasFloats) {
+ MarkSameFloatManagerLinesDirty(this);
+ }
+ } else if (FrameChildListID::Float == aListID) {
+ // Make sure to mark affected lines dirty for the float frame
+ // we are removing; this way is a bit messy, but so is the rest of the code.
+ // See bug 390762.
+ NS_ASSERTION(!aOldFrame->GetPrevContinuation(),
+ "RemoveFrame should not be called on pushed floats.");
+ for (nsIFrame* f = aOldFrame;
+ f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ f = f->GetNextContinuation()) {
+ MarkSameFloatManagerLinesDirty(
+ static_cast<nsBlockFrame*>(f->GetParent()));
+ }
+ DoRemoveOutOfFlowFrame(aContext, aOldFrame);
+ } else if (FrameChildListID::NoReflowPrincipal == aListID) {
+ // Skip the call to |FrameNeedsReflow| below by returning now.
+ DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS);
+ return;
+ } else {
+ MOZ_CRASH("unexpected child list");
+ }
+
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
+}
+
+static bool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) {
+ LayoutFrameType type = aLastFrame->Type();
+ if (type == LayoutFrameType::Br) {
+ return true;
+ }
+ // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910.
+ if (type == LayoutFrameType::Text &&
+ !aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) {
+ return aLastFrame->HasSignificantTerminalNewline();
+ }
+ return false;
+}
+
+void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling,
+ const nsLineList::iterator* aPrevSiblingLine) {
+ // Clear our line cursor, since our lines may change.
+ ClearLineCursors();
+
+ if (aFrameList.IsEmpty()) {
+ return;
+ }
+
+ // Attempt to find the line that contains the previous sibling
+ nsLineList* lineList = &mLines;
+ nsFrameList* frames = &mFrames;
+ nsLineList::iterator prevSibLine;
+ int32_t prevSiblingIndex;
+ if (aPrevSiblingLine) {
+ MOZ_ASSERT(aPrevSibling);
+ prevSibLine = *aPrevSiblingLine;
+ FrameLines* overflowLines = GetOverflowLines();
+ MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) ||
+ (overflowLines &&
+ prevSibLine.IsInSameList(overflowLines->mLines.begin())),
+ "must be one of our line lists");
+ if (overflowLines) {
+ // We need to find out which list it's actually in. Assume that
+ // *if* we have overflow lines, that our primary lines aren't
+ // huge, but our overflow lines might be.
+ nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end();
+ while (line != lineEnd) {
+ if (line == prevSibLine) {
+ break;
+ }
+ ++line;
+ }
+ if (line == lineEnd) {
+ // By elimination, the line must be in our overflow lines.
+ lineList = &overflowLines->mLines;
+ frames = &overflowLines->mFrames;
+ }
+ }
+
+ nsLineList::iterator nextLine = prevSibLine.next();
+ nsIFrame* lastFrameInLine = nextLine == lineList->end()
+ ? frames->LastChild()
+ : nextLine->mFirstChild->GetPrevSibling();
+ prevSiblingIndex = prevSibLine->RIndexOf(aPrevSibling, lastFrameInLine);
+ MOZ_ASSERT(prevSiblingIndex >= 0,
+ "aPrevSibling must be in aPrevSiblingLine");
+ } else {
+ prevSibLine = lineList->end();
+ prevSiblingIndex = -1;
+ if (aPrevSibling) {
+ // XXX_perf This is technically O(N^2) in some cases, but by using
+ // RFind instead of Find, we make it O(N) in the most common case,
+ // which is appending content.
+
+ // Find the line that contains the previous sibling
+ if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
+ prevSibLine, mFrames.LastChild(),
+ &prevSiblingIndex)) {
+ // Not in mLines - try overflow lines.
+ FrameLines* overflowLines = GetOverflowLines();
+ bool found = false;
+ if (overflowLines) {
+ prevSibLine = overflowLines->mLines.end();
+ prevSiblingIndex = -1;
+ found = nsLineBox::RFindLineContaining(
+ aPrevSibling, overflowLines->mLines.begin(), prevSibLine,
+ overflowLines->mFrames.LastChild(), &prevSiblingIndex);
+ }
+ if (MOZ_LIKELY(found)) {
+ lineList = &overflowLines->mLines;
+ frames = &overflowLines->mFrames;
+ } else {
+ // Note: defensive code! RFindLineContaining must not return
+ // false in this case, so if it does...
+ MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
+ aPrevSibling = nullptr;
+ prevSibLine = lineList->end();
+ }
+ }
+ }
+ }
+
+ // Find the frame following aPrevSibling so that we can join up the
+ // two lists of frames.
+ if (aPrevSibling) {
+ // Split line containing aPrevSibling in two if the insertion
+ // point is somewhere in the middle of the line.
+ int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
+ if (rem) {
+ // Split the line in two where the frame(s) are being inserted.
+ nsLineBox* line =
+ NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
+ lineList->after_insert(prevSibLine, line);
+ // Mark prevSibLine dirty and as needing textrun invalidation, since
+ // we may be breaking up text in the line. Its previous line may also
+ // need to be invalidated because it may be able to pull some text up.
+ MarkLineDirty(prevSibLine, lineList);
+ // The new line will also need its textruns recomputed because of the
+ // frame changes.
+ line->MarkDirty();
+ line->SetInvalidateTextRuns(true);
+ }
+ } else if (!lineList->empty()) {
+ lineList->front()->MarkDirty();
+ lineList->front()->SetInvalidateTextRuns(true);
+ }
+ const nsFrameList::Slice& newFrames =
+ frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList));
+
+ // Walk through the new frames being added and update the line data
+ // structures to fit.
+ for (nsIFrame* newFrame : newFrames) {
+ NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
+ "Unexpected aPrevSibling");
+ NS_ASSERTION(
+ !newFrame->IsPlaceholderFrame() ||
+ (!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()),
+ "Placeholders should not float or be positioned");
+
+ bool isBlock = newFrame->IsBlockOutside();
+
+ // If the frame is a block frame, or if there is no previous line or if the
+ // previous line is a block line we need to make a new line. We also make
+ // a new line, as an optimization, in the two cases we know we'll need it:
+ // if the previous line ended with a <br>, or if it has significant
+ // whitespace and ended in a newline.
+ if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
+ (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
+ // Create a new line for the frame and add its line to the line
+ // list.
+ nsLineBox* line = NewLineBox(newFrame, isBlock);
+ if (prevSibLine != lineList->end()) {
+ // Append new line after prevSibLine
+ lineList->after_insert(prevSibLine, line);
+ ++prevSibLine;
+ } else {
+ // New line is going before the other lines
+ lineList->push_front(line);
+ prevSibLine = lineList->begin();
+ }
+ } else {
+ prevSibLine->NoteFrameAdded(newFrame);
+ // We're adding inline content to prevSibLine, so we need to mark it
+ // dirty, ensure its textruns are recomputed, and possibly do the same
+ // to its previous line since that line may be able to pull content up.
+ MarkLineDirty(prevSibLine, lineList);
+ }
+
+ aPrevSibling = newFrame;
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(aFrameList.IsEmpty());
+ VerifyLines(true);
+#endif
+}
+
+nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() {
+ auto* firstChild = PrincipalChildList().FirstChild();
+ if (firstChild && firstChild->IsRubyFrame() &&
+ firstChild->Style()->GetPseudoType() ==
+ mozilla::PseudoStyleType::blockRubyContent) {
+ return static_cast<nsContainerFrame*>(firstChild);
+ }
+ return nullptr;
+}
+
+nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() {
+ // 'display:block ruby' use the inner (Ruby) frame for insertions.
+ if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
+ return rubyContentPseudoFrame;
+ }
+ return this;
+}
+
+void nsBlockFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
+ aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame));
+ }
+}
+
+void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) {
+ // Find which line contains the float, so we can update
+ // the float cache.
+ for (auto& line : Lines()) {
+ if (line.IsInline() && line.RemoveFloat(aFloat)) {
+ break;
+ }
+ }
+}
+
+void nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
+#ifdef DEBUG
+ // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows
+ // frame list properties.
+ if (!mFloats.ContainsFrame(aFloat)) {
+ MOZ_ASSERT(
+ (GetOverflowOutOfFlows() &&
+ GetOverflowOutOfFlows()->ContainsFrame(aFloat)) ||
+ (GetPushedFloats() && GetPushedFloats()->ContainsFrame(aFloat)),
+ "aFloat is not our child or on an unexpected frame list");
+ }
+#endif
+
+ if (mFloats.StartRemoveFrame(aFloat)) {
+ return;
+ }
+
+ nsFrameList* list = GetPushedFloats();
+ if (list && list->ContinueRemoveFrame(aFloat)) {
+#if 0
+ // XXXmats not yet - need to investigate BlockReflowState::mPushedFloats
+ // first so we don't leave it pointing to a deleted list.
+ if (list->IsEmpty()) {
+ delete RemovePushedFloats();
+ }
+#endif
+ return;
+ }
+
+ {
+ nsAutoOOFFrameList oofs(this);
+ if (oofs.mList.ContinueRemoveFrame(aFloat)) {
+ return;
+ }
+ }
+}
+
+void nsBlockFrame::DoRemoveOutOfFlowFrame(DestroyContext& aContext,
+ nsIFrame* aFrame) {
+ // The containing block is always the parent of aFrame.
+ nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent();
+
+ // Remove aFrame from the appropriate list.
+ if (aFrame->IsAbsolutelyPositioned()) {
+ // This also deletes the next-in-flows
+ block->GetAbsoluteContainingBlock()->RemoveFrame(
+ aContext, FrameChildListID::Absolute, aFrame);
+ } else {
+ // First remove aFrame's next-in-flows.
+ if (nsIFrame* nif = aFrame->GetNextInFlow()) {
+ nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
+ }
+ // Now remove aFrame from its child list and Destroy it.
+ block->RemoveFloatFromFloatCache(aFrame);
+ block->RemoveFloat(aFrame);
+ aFrame->Destroy(aContext);
+ }
+}
+
+/**
+ * This helps us iterate over the list of all normal + overflow lines
+ */
+void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
+ nsLineList::iterator* aStartIterator,
+ nsLineList::iterator* aEndIterator,
+ bool* aInOverflowLines,
+ FrameLines** aOverflowLines) {
+ if (*aIterator == *aEndIterator) {
+ if (!*aInOverflowLines) {
+ // Try the overflow lines
+ *aInOverflowLines = true;
+ FrameLines* lines = GetOverflowLines();
+ if (lines) {
+ *aStartIterator = lines->mLines.begin();
+ *aIterator = *aStartIterator;
+ *aEndIterator = lines->mLines.end();
+ *aOverflowLines = lines;
+ }
+ }
+ }
+}
+
+nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
+ LineIterator aLine)
+ : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) {
+ // This will assert if aLine isn't in mLines of aFrame:
+ DebugOnly<bool> check = aLine == mFrame->LinesBegin();
+}
+
+nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
+ LineIterator aLine,
+ bool aInOverflow)
+ : mFrame(aFrame),
+ mLine(aLine),
+ mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines
+ : &aFrame->mLines) {}
+
+nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
+ bool* aFoundValidLine)
+ : mFrame(aFrame), mLineList(&aFrame->mLines) {
+ mLine = aFrame->LinesBegin();
+ *aFoundValidLine = FindValidLine();
+}
+
+static bool StyleEstablishesBFC(const ComputedStyle* aStyle) {
+ // paint/layout containment boxes and multi-column containers establish an
+ // independent formatting context.
+ // https://drafts.csswg.org/css-contain/#containment-paint
+ // https://drafts.csswg.org/css-contain/#containment-layout
+ // https://drafts.csswg.org/css-multicol/#columns
+ return aStyle->StyleDisplay()->IsContainPaint() ||
+ aStyle->StyleDisplay()->IsContainLayout() ||
+ aStyle->GetPseudoType() == PseudoStyleType::columnContent;
+}
+
+void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ nsContainerFrame::DidSetComputedStyle(aOldStyle);
+ if (!aOldStyle) {
+ return;
+ }
+
+ // If NS_BLOCK_STATIC_BFC flag was set when the frame was initialized, it
+ // remains set during the lifetime of the frame and always forces it to be
+ // treated as a BFC, independently of the value of NS_BLOCK_DYNAMIC_BFC.
+ // Consequently, we don't bother invalidating or updating that latter flag.
+ if (HasAnyStateBits(NS_BLOCK_STATIC_BFC)) {
+ return;
+ }
+
+ bool isBFC = StyleEstablishesBFC(Style());
+ if (StyleEstablishesBFC(aOldStyle) != isBFC) {
+ if (MaybeHasFloats()) {
+ // If the frame contains floats, this update may change their float
+ // manager. Be safe by dirtying all descendant lines of the nearest
+ // ancestor's float manager.
+ RemoveStateBits(NS_BLOCK_DYNAMIC_BFC);
+ MarkSameFloatManagerLinesDirty(this);
+ }
+ AddOrRemoveStateBits(NS_BLOCK_DYNAMIC_BFC, isBFC);
+ }
+}
+
+void nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState) {
+ nsIFrame* letterFrame = GetFirstLetter();
+ if (!letterFrame) {
+ return;
+ }
+
+ // Figure out what the right style parent is. This needs to match
+ // nsCSSFrameConstructor::CreateLetterFrame.
+ nsIFrame* inFlowFrame = letterFrame;
+ if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ inFlowFrame = inFlowFrame->GetPlaceholderFrame();
+ }
+ nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(),
+ PseudoStyleType::firstLetter);
+ ComputedStyle* parentStyle = styleParent->Style();
+ RefPtr<ComputedStyle> firstLetterStyle =
+ aRestyleState.StyleSet().ResolvePseudoElementStyle(
+ *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr,
+ parentStyle);
+ // Note that we don't need to worry about changehints for the continuation
+ // styles: those will be handled by the styleParent already.
+ RefPtr<ComputedStyle> continuationStyle =
+ aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(
+ parentStyle);
+ UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
+ Some(continuationStyle.get()));
+
+ // We also want to update the style on the textframe inside the first-letter.
+ // We don't need to compute a changehint for this, though, since any changes
+ // to it are handled by the first-letter anyway.
+ nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild();
+ RefPtr<ComputedStyle> firstTextStyle =
+ aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(),
+ firstLetterStyle);
+ textFrame->SetComputedStyle(firstTextStyle);
+
+ // We don't need to update style for textFrame's continuations: it's already
+ // set up to inherit from parentStyle, which is what we want.
+}
+
+static nsIFrame* FindChildContaining(nsBlockFrame* aFrame,
+ nsIFrame* aFindFrame) {
+ NS_ASSERTION(aFrame, "must have frame");
+ nsIFrame* child;
+ while (true) {
+ nsIFrame* block = aFrame;
+ do {
+ child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame);
+ if (child) {
+ break;
+ }
+ block = block->GetNextContinuation();
+ } while (block);
+ if (!child) {
+ return nullptr;
+ }
+ if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ break;
+ }
+ aFindFrame = child->GetPlaceholderFrame();
+ }
+
+ return child;
+}
+
+nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
+ nsIFrame* aFindFrame,
+ bool* aFoundValidLine)
+ : mFrame(aFrame), mLineList(&aFrame->mLines) {
+ *aFoundValidLine = false;
+
+ nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
+ if (!child) {
+ return;
+ }
+
+ LineIterator line_end = aFrame->LinesEnd();
+ mLine = aFrame->LinesBegin();
+ if (mLine != line_end && mLine.next() == line_end &&
+ !aFrame->HasOverflowLines()) {
+ // The block has a single line - that must be it!
+ *aFoundValidLine = true;
+ return;
+ }
+
+ // Try to use the cursor if it exists, otherwise fall back to the first line
+ if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) {
+ mLine = line_end;
+ // Perform a simultaneous forward and reverse search starting from the
+ // line cursor.
+ nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor);
+ nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor);
+ nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd();
+ // rline is positioned on the line containing 'cursor', so it's not
+ // rline_end. So we can safely increment it (i.e. move it to one line
+ // earlier) to start searching there.
+ ++rline;
+ while (line != line_end || rline != rline_end) {
+ if (line != line_end) {
+ if (line->Contains(child)) {
+ mLine = line;
+ break;
+ }
+ ++line;
+ }
+ if (rline != rline_end) {
+ if (rline->Contains(child)) {
+ mLine = rline;
+ break;
+ }
+ ++rline;
+ }
+ }
+ if (mLine != line_end) {
+ *aFoundValidLine = true;
+ if (mLine != cursor) {
+ aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine);
+ }
+ return;
+ }
+ } else {
+ for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) {
+ if (mLine->Contains(child)) {
+ *aFoundValidLine = true;
+ return;
+ }
+ }
+ }
+ // Didn't find the line
+ MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point");
+
+ // If we reach here, it means that we have not been able to find the
+ // desired frame in our in-flow lines. So we should start looking at
+ // our overflow lines. In order to do that, we set mLine to the end
+ // iterator so that FindValidLine starts to look at overflow lines,
+ // if any.
+
+ if (!FindValidLine()) {
+ return;
+ }
+
+ do {
+ if (mLine->Contains(child)) {
+ *aFoundValidLine = true;
+ return;
+ }
+ } while (Next());
+}
+
+nsBlockFrame::LineIterator nsBlockInFlowLineIterator::End() {
+ return mLineList->end();
+}
+
+bool nsBlockInFlowLineIterator::IsLastLineInList() {
+ LineIterator end = End();
+ return mLine != end && mLine.next() == end;
+}
+
+bool nsBlockInFlowLineIterator::Next() {
+ ++mLine;
+ return FindValidLine();
+}
+
+bool nsBlockInFlowLineIterator::Prev() {
+ LineIterator begin = mLineList->begin();
+ if (mLine != begin) {
+ --mLine;
+ return true;
+ }
+ bool currentlyInOverflowLines = GetInOverflow();
+ while (true) {
+ if (currentlyInOverflowLines) {
+ mLineList = &mFrame->mLines;
+ mLine = mLineList->end();
+ if (mLine != mLineList->begin()) {
+ --mLine;
+ return true;
+ }
+ } else {
+ mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
+ if (!mFrame) {
+ return false;
+ }
+ nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
+ if (overflowLines) {
+ mLineList = &overflowLines->mLines;
+ mLine = mLineList->end();
+ NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?");
+ --mLine;
+ return true;
+ }
+ }
+ currentlyInOverflowLines = !currentlyInOverflowLines;
+ }
+}
+
+bool nsBlockInFlowLineIterator::FindValidLine() {
+ LineIterator end = mLineList->end();
+ if (mLine != end) {
+ return true;
+ }
+ bool currentlyInOverflowLines = GetInOverflow();
+ while (true) {
+ if (currentlyInOverflowLines) {
+ mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
+ if (!mFrame) {
+ return false;
+ }
+ mLineList = &mFrame->mLines;
+ mLine = mLineList->begin();
+ if (mLine != mLineList->end()) {
+ return true;
+ }
+ } else {
+ nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
+ if (overflowLines) {
+ mLineList = &overflowLines->mLines;
+ mLine = mLineList->begin();
+ NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?");
+ return true;
+ }
+ }
+ currentlyInOverflowLines = !currentlyInOverflowLines;
+ }
+}
+
+// This function removes aDeletedFrame and all its continuations. It
+// is optimized for deleting a whole series of frames. The easy
+// implementation would invoke itself recursively on
+// aDeletedFrame->GetNextContinuation, then locate the line containing
+// aDeletedFrame and remove aDeletedFrame from that line. But here we
+// start by locating aDeletedFrame and then scanning from that point
+// on looking for continuations.
+void nsBlockFrame::DoRemoveFrame(DestroyContext& aContext,
+ nsIFrame* aDeletedFrame, uint32_t aFlags) {
+ // Clear our line cursor, since our lines may change.
+ ClearLineCursors();
+
+ if (aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
+ NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ if (!aDeletedFrame->GetPrevInFlow()) {
+ NS_ASSERTION(aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "Expected out-of-flow frame");
+ DoRemoveOutOfFlowFrame(aContext, aDeletedFrame);
+ } else {
+ // FIXME(emilio): aContext is lost here, maybe it's not a big deal?
+ nsContainerFrame::DeleteNextInFlowChild(aContext, aDeletedFrame,
+ (aFlags & FRAMES_ARE_EMPTY) != 0);
+ }
+ return;
+ }
+
+ // Find the line that contains deletedFrame
+ nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end();
+ nsLineList::iterator line = line_start;
+ FrameLines* overflowLines = nullptr;
+ bool searchingOverflowList = false;
+ // Make sure we look in the overflow lines even if the normal line
+ // list is empty
+ TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+ &overflowLines);
+ while (line != line_end) {
+ if (line->Contains(aDeletedFrame)) {
+ break;
+ }
+ ++line;
+ TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+ &overflowLines);
+ }
+
+ if (line == line_end) {
+ NS_ERROR("can't find deleted frame in lines");
+ return;
+ }
+
+ if (!(aFlags & FRAMES_ARE_EMPTY)) {
+ if (line != line_start) {
+ line.prev()->MarkDirty();
+ line.prev()->SetInvalidateTextRuns(true);
+ } else if (searchingOverflowList && !mLines.empty()) {
+ mLines.back()->MarkDirty();
+ mLines.back()->SetInvalidateTextRuns(true);
+ }
+ }
+
+ while (line != line_end && aDeletedFrame) {
+ MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code");
+ MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line");
+
+ if (!(aFlags & FRAMES_ARE_EMPTY)) {
+ line->MarkDirty();
+ line->SetInvalidateTextRuns(true);
+ }
+
+ // If the frame being deleted is the last one on the line then
+ // optimize away the line->Contains(next-in-flow) call below.
+ bool isLastFrameOnLine = 1 == line->GetChildCount();
+ if (!isLastFrameOnLine) {
+ LineIterator next = line.next();
+ nsIFrame* lastFrame =
+ next != line_end
+ ? next->mFirstChild->GetPrevSibling()
+ : (searchingOverflowList ? overflowLines->mFrames.LastChild()
+ : mFrames.LastChild());
+ NS_ASSERTION(next == line_end || lastFrame == line->LastChild(),
+ "unexpected line frames");
+ isLastFrameOnLine = lastFrame == aDeletedFrame;
+ }
+
+ // Remove aDeletedFrame from the line
+ if (line->mFirstChild == aDeletedFrame) {
+ // We should be setting this to null if aDeletedFrame
+ // is the only frame on the line. HOWEVER in that case
+ // we will be removing the line anyway, see below.
+ line->mFirstChild = aDeletedFrame->GetNextSibling();
+ }
+
+ // Hmm, this won't do anything if we're removing a frame in the first
+ // overflow line... Hopefully doesn't matter
+ --line;
+ if (line != line_end && !line->IsBlock()) {
+ // Since we just removed a frame that follows some inline
+ // frames, we need to reflow the previous line.
+ line->MarkDirty();
+ }
+ ++line;
+
+ // Take aDeletedFrame out of the sibling list. Note that
+ // prevSibling will only be nullptr when we are deleting the very
+ // first frame in the main or overflow list.
+ if (searchingOverflowList) {
+ overflowLines->mFrames.RemoveFrame(aDeletedFrame);
+ } else {
+ mFrames.RemoveFrame(aDeletedFrame);
+ }
+
+ // Update the child count of the line to be accurate
+ line->NoteFrameRemoved(aDeletedFrame);
+
+ // Destroy frame; capture its next continuation first in case we need
+ // to destroy that too.
+ nsIFrame* deletedNextContinuation =
+ (aFlags & REMOVE_FIXED_CONTINUATIONS)
+ ? aDeletedFrame->GetNextContinuation()
+ : aDeletedFrame->GetNextInFlow();
+#ifdef NOISY_REMOVE_FRAME
+ printf("DoRemoveFrame: %s line=%p frame=",
+ searchingOverflowList ? "overflow" : "normal", line.get());
+ aDeletedFrame->ListTag(stdout);
+ printf(" prevSibling=%p deletedNextContinuation=%p\n",
+ aDeletedFrame->GetPrevSibling(), deletedNextContinuation);
+#endif
+
+ // If next-in-flow is an overflow container, must remove it first.
+ // FIXME: Can we do this unconditionally?
+ if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits(
+ NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ deletedNextContinuation->GetParent()->DeleteNextInFlowChild(
+ aContext, deletedNextContinuation, false);
+ deletedNextContinuation = nullptr;
+ }
+
+ aDeletedFrame->Destroy(aContext);
+ aDeletedFrame = deletedNextContinuation;
+
+ bool haveAdvancedToNextLine = false;
+ // If line is empty, remove it now.
+ if (0 == line->GetChildCount()) {
+#ifdef NOISY_REMOVE_FRAME
+ printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
+ searchingOverflowList ? "overflow" : "normal", line.get());
+#endif
+ nsLineBox* cur = line;
+ if (!searchingOverflowList) {
+ line = mLines.erase(line);
+ // Invalidate the space taken up by the line.
+ // XXX We need to do this if we're removing a frame as a result of
+ // a call to RemoveFrame(), but we may not need to do this in all
+ // cases...
+#ifdef NOISY_BLOCK_INVALIDATE
+ nsRect inkOverflow(cur->InkOverflowRect());
+ printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x,
+ inkOverflow.y, inkOverflow.width, inkOverflow.height);
+#endif
+ } else {
+ line = overflowLines->mLines.erase(line);
+ if (overflowLines->mLines.empty()) {
+ DestroyOverflowLines();
+ overflowLines = nullptr;
+ // We just invalidated our iterators. Since we were in
+ // the overflow lines list, which is now empty, set them
+ // so we're at the end of the regular line list.
+ line_start = mLines.begin();
+ line_end = mLines.end();
+ line = line_end;
+ }
+ }
+ FreeLineBox(cur);
+
+ // If we're removing a line, ReflowDirtyLines isn't going to
+ // know that it needs to slide lines unless something is marked
+ // dirty. So mark the previous margin of the next line dirty if
+ // there is one.
+ if (line != line_end) {
+ line->MarkPreviousMarginDirty();
+ }
+ haveAdvancedToNextLine = true;
+ } else {
+ // Make the line that just lost a frame dirty, and advance to
+ // the next line.
+ if (!deletedNextContinuation || isLastFrameOnLine ||
+ !line->Contains(deletedNextContinuation)) {
+ line->MarkDirty();
+ ++line;
+ haveAdvancedToNextLine = true;
+ }
+ }
+
+ if (deletedNextContinuation) {
+ // See if we should keep looking in the current flow's line list.
+ if (deletedNextContinuation->GetParent() != this) {
+ // The deceased frames continuation is not a child of the
+ // current block. So break out of the loop so that we advance
+ // to the next parent.
+ //
+ // If we have a continuation in a different block then all bets are
+ // off regarding whether we are deleting frames without actual content,
+ // so don't propagate FRAMES_ARE_EMPTY any further.
+ aFlags &= ~FRAMES_ARE_EMPTY;
+ break;
+ }
+
+ // If we advanced to the next line then check if we should switch to the
+ // overflow line list.
+ if (haveAdvancedToNextLine) {
+ if (line != line_end && !searchingOverflowList &&
+ !line->Contains(deletedNextContinuation)) {
+ // We have advanced to the next *normal* line but the next-in-flow
+ // is not there - force a switch to the overflow line list.
+ line = line_end;
+ }
+
+ TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+ &overflowLines);
+#ifdef NOISY_REMOVE_FRAME
+ printf("DoRemoveFrame: now on %s line=%p\n",
+ searchingOverflowList ? "overflow" : "normal", line.get());
+#endif
+ }
+ }
+ }
+
+ if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) {
+ line.next()->MarkDirty();
+ line.next()->SetInvalidateTextRuns(true);
+ }
+
+#ifdef DEBUG
+ VerifyLines(true);
+ VerifyOverflowSituation();
+#endif
+
+ // Advance to next flow block if the frame has more continuations.
+ if (!aDeletedFrame) {
+ return;
+ }
+ nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent());
+ NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?");
+ uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS);
+ nextBlock->DoRemoveFrame(aContext, aDeletedFrame, flags);
+}
+
+static bool FindBlockLineFor(nsIFrame* aChild, nsLineList::iterator aBegin,
+ nsLineList::iterator aEnd,
+ nsLineList::iterator* aResult) {
+ MOZ_ASSERT(aChild->IsBlockOutside());
+ for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
+ MOZ_ASSERT(line->GetChildCount() > 0);
+ if (line->IsBlock() && line->mFirstChild == aChild) {
+ MOZ_ASSERT(line->GetChildCount() == 1);
+ *aResult = line;
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool FindInlineLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
+ nsLineList::iterator aBegin,
+ nsLineList::iterator aEnd,
+ nsLineList::iterator* aResult) {
+ MOZ_ASSERT(!aChild->IsBlockOutside());
+ for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
+ MOZ_ASSERT(line->GetChildCount() > 0);
+ if (!line->IsBlock()) {
+ // Optimize by comparing the line's last child first.
+ nsLineList::iterator next = line.next();
+ if (aChild == (next == aEnd ? aFrameList.LastChild()
+ : next->mFirstChild->GetPrevSibling()) ||
+ line->Contains(aChild)) {
+ *aResult = line;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool FindLineFor(nsIFrame* aChild, const nsFrameList& aFrameList,
+ nsLineList::iterator aBegin, nsLineList::iterator aEnd,
+ nsLineList::iterator* aResult) {
+ return aChild->IsBlockOutside()
+ ? FindBlockLineFor(aChild, aBegin, aEnd, aResult)
+ : FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult);
+}
+
+void nsBlockFrame::StealFrame(nsIFrame* aChild) {
+ MOZ_ASSERT(aChild->GetParent() == this);
+
+ if (aChild->IsFloating()) {
+ RemoveFloat(aChild);
+ return;
+ }
+
+ if (MaybeStealOverflowContainerFrame(aChild)) {
+ return;
+ }
+
+ MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+
+ nsLineList::iterator line;
+ if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) {
+ RemoveFrameFromLine(aChild, line, mFrames, mLines);
+ } else {
+ FrameLines* overflowLines = GetOverflowLines();
+ DebugOnly<bool> found;
+ found = FindLineFor(aChild, overflowLines->mFrames,
+ overflowLines->mLines.begin(),
+ overflowLines->mLines.end(), &line);
+ MOZ_ASSERT(found, "Why can't we find aChild in our overflow lines?");
+ RemoveFrameFromLine(aChild, line, overflowLines->mFrames,
+ overflowLines->mLines);
+ if (overflowLines->mLines.empty()) {
+ DestroyOverflowLines();
+ }
+ }
+}
+
+void nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild,
+ nsLineList::iterator aLine,
+ nsFrameList& aFrameList,
+ nsLineList& aLineList) {
+ aFrameList.RemoveFrame(aChild);
+ if (aChild == aLine->mFirstChild) {
+ aLine->mFirstChild = aChild->GetNextSibling();
+ }
+ aLine->NoteFrameRemoved(aChild);
+ if (aLine->GetChildCount() > 0) {
+ aLine->MarkDirty();
+ } else {
+ // The line became empty - destroy it.
+ nsLineBox* lineBox = aLine;
+ aLine = aLineList.erase(aLine);
+ if (aLine != aLineList.end()) {
+ aLine->MarkPreviousMarginDirty();
+ }
+ FreeLineBox(lineBox);
+ }
+}
+
+void nsBlockFrame::DeleteNextInFlowChild(DestroyContext& aContext,
+ nsIFrame* aNextInFlow,
+ bool aDeletingEmptyFrames) {
+ MOZ_ASSERT(aNextInFlow->GetPrevInFlow(), "bad next-in-flow");
+
+ if (aNextInFlow->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
+ NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ nsContainerFrame::DeleteNextInFlowChild(aContext, aNextInFlow,
+ aDeletingEmptyFrames);
+ } else {
+#ifdef DEBUG
+ if (aDeletingEmptyFrames) {
+ nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
+ }
+#endif
+ DoRemoveFrame(aContext, aNextInFlow,
+ aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0);
+ }
+}
+
+const nsStyleText* nsBlockFrame::StyleTextForLineLayout() {
+ // Return the pointer to an unmodified style text
+ return StyleText();
+}
+
+void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
+ nsIFrame* aFloat,
+ nsReflowStatus& aReflowStatus) {
+ MOZ_ASSERT(aReflowStatus.IsEmpty(),
+ "Caller should pass a fresh reflow status!");
+ MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "aFloat must be an out-of-flow frame");
+
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+
+ // Setup a block reflow context to reflow the float.
+ nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
+
+ nsIFrame* clearanceFrame = nullptr;
+ do {
+ nsCollapsingMargin margin;
+ bool mayNeedRetry = false;
+ aFloatRI.mDiscoveredClearance = nullptr;
+ // Only first in flow gets a block-start margin.
+ if (!aFloat->GetPrevInFlow()) {
+ brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame,
+ &mayNeedRetry);
+
+ if (mayNeedRetry && !clearanceFrame) {
+ aFloatRI.mDiscoveredClearance = &clearanceFrame;
+ // We don't need to push the float manager state because the the block
+ // has its own float manager that will be destroyed and recreated
+ }
+ }
+
+ // When reflowing a float, aSpace argument doesn't matter because we pass
+ // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock()
+ // later.
+ brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI,
+ aReflowStatus, aState);
+ } while (clearanceFrame);
+
+ if (aFloat->IsLetterFrame()) {
+ // We never split floating first letters; an incomplete status for such
+ // frames simply means that there is more content to be reflowed on the
+ // line.
+ if (aReflowStatus.IsIncomplete()) {
+ aReflowStatus.Reset();
+ }
+ }
+
+ NS_ASSERTION(aReflowStatus.IsFullyComplete() ||
+ aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
+ "The status can only be incomplete or overflow-incomplete if "
+ "the available block-size is constrained!");
+
+ if (aReflowStatus.NextInFlowNeedsReflow()) {
+ aState.mReflowStatus.SetNextInFlowNeedsReflow();
+ }
+
+ const ReflowOutput& metrics = brc.GetMetrics();
+
+ // Set the rect, make sure the view is properly sized and positioned,
+ // and tell the frame we're done reflowing it
+ // XXXldb This seems like the wrong place to be doing this -- shouldn't
+ // we be doing this in BlockReflowState::FlowAndPlaceFloat after
+ // we've positioned the float, and shouldn't we be doing the equivalent
+ // of |PlaceFrameView| here?
+ WritingMode metricsWM = metrics.GetWritingMode();
+ aFloat->SetSize(metricsWM, metrics.Size(metricsWM));
+ if (aFloat->HasView()) {
+ nsContainerFrame::SyncFrameViewAfterReflow(
+ aState.mPresContext, aFloat, aFloat->GetView(), metrics.InkOverflow(),
+ ReflowChildFlags::NoMoveView);
+ }
+ aFloat->DidReflow(aState.mPresContext, &aFloatRI);
+}
+
+StyleClear nsBlockFrame::FindTrailingClear() {
+ for (nsBlockFrame* b = this; b;
+ b = static_cast<nsBlockFrame*>(b->GetPrevInFlow())) {
+ auto endLine = b->LinesRBegin();
+ if (endLine != b->LinesREnd()) {
+ return endLine->FloatClearTypeAfter();
+ }
+ }
+ return StyleClear::None;
+}
+
+void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState,
+ OverflowAreas& aOverflowAreas) {
+ // Pushed floats live at the start of our float list; see comment
+ // above nsBlockFrame::DrainPushedFloats.
+ nsIFrame* f = mFloats.FirstChild();
+ nsIFrame* prev = nullptr;
+ while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
+ MOZ_ASSERT(prev == f->GetPrevSibling());
+ // When we push a first-continuation float in a non-initial reflow,
+ // it's possible that we end up with two continuations with the same
+ // parent. This happens if, on the previous reflow of the block or
+ // a previous reflow of the line containing the block, the float was
+ // split between continuations A and B of the parent, but on the
+ // current reflow, none of the float can fit in A.
+ //
+ // When this happens, we might even have the two continuations
+ // out-of-order due to the management of the pushed floats. In
+ // particular, if the float's placeholder was in a pushed line that
+ // we reflowed before it was pushed, and we split the float during
+ // that reflow, we might have the continuation of the float before
+ // the float itself. (In the general case, however, it's correct
+ // for floats in the pushed floats list to come before floats
+ // anchored in pushed lines; however, in this case it's wrong. We
+ // should probably find a way to fix it somehow, since it leads to
+ // incorrect layout in some cases.)
+ //
+ // When we have these out-of-order continuations, we might hit the
+ // next-continuation before the previous-continuation. When that
+ // happens, just push it. When we reflow the next continuation,
+ // we'll either pull all of its content back and destroy it (by
+ // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will
+ // pull it out of its current position and push it again (and
+ // potentially repeat this cycle for the next continuation, although
+ // hopefully then they'll be in the right order).
+ //
+ // We should also need this code for the in-order case if the first
+ // continuation of a float gets moved across more than one
+ // continuation of the containing block. In this case we'd manage
+ // to push the second continuation without this check, but not the
+ // third and later.
+ nsIFrame* prevContinuation = f->GetPrevContinuation();
+ if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
+ mFloats.RemoveFrame(f);
+ aState.AppendPushedFloatChain(f);
+ f = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
+ continue;
+ }
+
+ // Always call FlowAndPlaceFloat; we might need to place this float if it
+ // didn't belong to this block the last time it was reflowed. Note that if
+ // the float doesn't get placed, we don't consider its overflow areas.
+ // (Not-getting-placed means it didn't fit and we pushed it instead of
+ // placing it, and its position could be stale.)
+ if (aState.FlowAndPlaceFloat(f) ==
+ BlockReflowState::PlaceFloatResult::Placed) {
+ ConsiderChildOverflow(aOverflowAreas, f);
+ }
+
+ nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
+ if (next == f) {
+ // We didn't push |f| so its next-sibling is next.
+ next = f->GetNextSibling();
+ prev = f;
+ } // else: we did push |f| so |prev|'s new next-sibling is next.
+ f = next;
+ }
+
+ // If there are pushed or split floats, then we may need to continue BR
+ // clearance
+ if (auto [bCoord, result] = aState.ClearFloats(0, StyleClear::Both);
+ result != ClearFloatsResult::BCoordNoChange) {
+ Unused << bCoord;
+ if (auto* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow())) {
+ aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear();
+ }
+ }
+}
+
+void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM,
+ const nsSize& aContainerSize) {
+ // Recover our own floats
+ nsIFrame* stop = nullptr; // Stop before we reach pushed floats that
+ // belong to our next-in-flow
+ for (nsIFrame* f = mFloats.FirstChild(); f && f != stop;
+ f = f->GetNextSibling()) {
+ LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize);
+ aFloatManager.AddFloat(f, region, aWM, aContainerSize);
+ if (!stop && f->GetNextInFlow()) {
+ stop = f->GetNextInFlow();
+ }
+ }
+
+ // Recurse into our overflow container children
+ for (nsIFrame* oc =
+ GetChildList(FrameChildListID::OverflowContainers).FirstChild();
+ oc; oc = oc->GetNextSibling()) {
+ RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize);
+ }
+
+ // Recurse into our normal children
+ for (const auto& line : Lines()) {
+ if (line.IsBlock()) {
+ RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize);
+ }
+ }
+}
+
+void nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame,
+ nsFloatManager& aFloatManager,
+ WritingMode aWM,
+ const nsSize& aContainerSize) {
+ MOZ_ASSERT(aFrame, "null frame");
+
+ // Only blocks have floats
+ nsBlockFrame* block = do_QueryFrame(aFrame);
+ // Don't recover any state inside a block that has its own float manager
+ // (we don't currently have any blocks like this, though, thanks to our
+ // use of extra frames for 'overflow')
+ if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) {
+ // If the element is relatively positioned, then adjust x and y
+ // accordingly so that we consider relatively positioned frames
+ // at their original position.
+
+ const LogicalRect rect = block->GetLogicalNormalRect(aWM, aContainerSize);
+ nscoord lineLeft = rect.LineLeft(aWM, aContainerSize);
+ nscoord blockStart = rect.BStart(aWM);
+ aFloatManager.Translate(lineLeft, blockStart);
+ block->RecoverFloats(aFloatManager, aWM, aContainerSize);
+ aFloatManager.Translate(-lineLeft, -blockStart);
+ }
+}
+
+bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const {
+ if (!mFloats.IsEmpty()) {
+ // If we have pushed floats, then they should be at the beginning of our
+ // float list.
+ if (mFloats.FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
+ return true;
+ }
+ }
+
+#ifdef DEBUG
+ // Double-check the above assertion that pushed floats should be at the
+ // beginning of our floats list.
+ for (nsIFrame* f : mFloats) {
+ NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT),
+ "pushed floats must be at the beginning of the float list");
+ }
+#endif
+
+ // We may have a pending push of pushed floats too:
+ if (HasPushedFloats()) {
+ // XXX we can return 'true' here once we make HasPushedFloats
+ // not lie. (see nsBlockFrame::RemoveFloat)
+ auto* pushedFloats = GetPushedFloats();
+ return pushedFloats && !pushedFloats->IsEmpty();
+ }
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Painting, event handling
+
+#ifdef DEBUG
+static void ComputeInkOverflowArea(nsLineList& aLines, nscoord aWidth,
+ nscoord aHeight, nsRect& aResult) {
+ nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
+ for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
+ line != line_end; ++line) {
+ // Compute min and max x/y values for the reflowed frame's
+ // combined areas
+ nsRect inkOverflow(line->InkOverflowRect());
+ nscoord x = inkOverflow.x;
+ nscoord y = inkOverflow.y;
+ nscoord xmost = x + inkOverflow.width;
+ nscoord ymost = y + inkOverflow.height;
+ if (x < xa) {
+ xa = x;
+ }
+ if (xmost > xb) {
+ xb = xmost;
+ }
+ if (y < ya) {
+ ya = y;
+ }
+ if (ymost > yb) {
+ yb = ymost;
+ }
+ }
+
+ aResult.x = xa;
+ aResult.y = ya;
+ aResult.width = xb - xa;
+ aResult.height = yb - ya;
+}
+#endif
+
+#ifdef DEBUG
+static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) {
+ if (nsBlockFrame::gNoisyDamageRepair) {
+ nsIFrame::IndentBy(stdout, aDepth + 1);
+ nsRect lineArea = aLine->InkOverflowRect();
+ printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
+ aDrawn ? "draw" : "skip", static_cast<void*>(aLine), aLine->IStart(),
+ aLine->BStart(), aLine->ISize(), aLine->BSize(), lineArea.x,
+ lineArea.y, lineArea.width, lineArea.height);
+ }
+}
+#endif
+
+static void DisplayLine(nsDisplayListBuilder* aBuilder,
+ nsBlockFrame::LineIterator& aLine,
+ const bool aLineInLine, const nsDisplayListSet& aLists,
+ nsBlockFrame* aFrame, TextOverflow* aTextOverflow,
+ uint32_t aLineNumberForTextOverflow, int32_t aDepth,
+ int32_t& aDrawnLines) {
+#ifdef DEBUG
+ if (nsBlockFrame::gLamePaintMetrics) {
+ aDrawnLines++;
+ }
+ const bool intersect =
+ aLine->InkOverflowRect().Intersects(aBuilder->GetDirtyRect());
+ DebugOutputDrawLine(aDepth, aLine.get(), intersect);
+#endif
+
+ // Collect our line's display items in a temporary nsDisplayListCollection,
+ // so that we can apply any "text-overflow" clipping to the entire collection
+ // without affecting previous lines.
+ nsDisplayListCollection collection(aBuilder);
+
+ // Block-level child backgrounds go on the blockBorderBackgrounds list ...
+ // Inline-level child backgrounds go on the regular child content list.
+ nsDisplayListSet childLists(
+ collection,
+ aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds());
+
+ auto flags =
+ aLineInLine
+ ? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline)
+ : nsIFrame::DisplayChildFlags();
+
+ nsIFrame* kid = aLine->mFirstChild;
+ int32_t n = aLine->GetChildCount();
+ while (--n >= 0) {
+ aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags);
+ kid = kid->GetNextSibling();
+ }
+
+ if (aTextOverflow && aLineInLine) {
+ aTextOverflow->ProcessLine(collection, aLine.get(),
+ aLineNumberForTextOverflow);
+ }
+
+ collection.MoveTo(aLists);
+}
+
+void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ int32_t drawnLines; // Will only be used if set (gLamePaintMetrics).
+ int32_t depth = 0;
+#ifdef DEBUG
+ if (gNoisyDamageRepair) {
+ nsRect dirty = aBuilder->GetDirtyRect();
+ depth = GetDepth();
+ nsRect ca;
+ ::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca);
+ nsIFrame::IndentBy(stdout, depth);
+ ListTag(stdout);
+ printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
+ mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
+ dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height);
+ }
+ PRTime start = 0; // Initialize these variables to silence the compiler.
+ if (gLamePaintMetrics) {
+ start = PR_Now();
+ drawnLines = 0;
+ }
+#endif
+
+ // TODO(heycam): Should we boost the load priority of any shape-outside
+ // images using CATEGORY_DISPLAY, now that this block is being displayed?
+ // We don't have a float manager here.
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ if (GetPrevInFlow()) {
+ DisplayOverflowContainers(aBuilder, aLists);
+ for (nsIFrame* f : mFloats) {
+ if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
+ BuildDisplayListForChild(aBuilder, f, aLists);
+ }
+ }
+ }
+
+ aBuilder->MarkFramesForDisplayList(this, mFloats);
+
+ if (HasOutsideMarker()) {
+ // Display outside ::marker manually.
+ BuildDisplayListForChild(aBuilder, GetOutsideMarker(), aLists);
+ }
+
+ // Prepare for text-overflow processing.
+ Maybe<TextOverflow> textOverflow =
+ TextOverflow::WillProcessLines(aBuilder, this);
+
+ const bool hasDescendantPlaceHolders =
+ HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
+ ForceDescendIntoIfVisible() || aBuilder->GetIncludeAllOutOfFlows();
+
+ const auto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool {
+ // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with
+ // some frame trees, building display list for child lines can change it.
+ // See bug 1552789.
+ const bool descendAlways =
+ HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
+ aBuilder->GetIncludeAllOutOfFlows();
+
+ return descendAlways || aLineArea.Intersects(aBuilder->GetDirtyRect()) ||
+ (ForceDescendIntoIfVisible() &&
+ aLineArea.Intersects(aBuilder->GetVisibleRect()));
+ };
+
+ Maybe<nscolor> backplateColor;
+
+ // We'll try to draw an accessibility backplate behind text (to ensure it's
+ // readable over any possible background-images), if all of the following
+ // hold:
+ // (A) the backplate feature is preffed on
+ // (B) we are not honoring the document colors
+ // (C) the force color adjust property is set to auto
+ if (StaticPrefs::browser_display_permit_backplate() &&
+ PresContext()->ForcingColors() && !IsComboboxControlFrame() &&
+ StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) {
+ backplateColor.emplace(GetBackplateColor(this));
+ }
+
+ // Don't use the line cursor if we might have a descendant placeholder ...
+ // it might skip lines that contain placeholders but don't themselves
+ // intersect with the dirty area.
+ // In particular, we really want to check ShouldDescendIntoFrame()
+ // on all our child frames, but that might be expensive. So we
+ // approximate it by checking it on |this|; if it's true for any
+ // frame in our child list, it's also true for |this|.
+ // Also skip the cursor if we're creating text overflow markers,
+ // since we need to know what line number we're up to in order
+ // to generate unique display item keys.
+ // Lastly, the cursor should be skipped if we're drawing
+ // backplates behind text. When backplating we consider consecutive
+ // runs of text as a whole, which requires we iterate through all lines
+ // to find our backplate size.
+ nsLineBox* cursor =
+ (hasDescendantPlaceHolders || textOverflow.isSome() || backplateColor)
+ ? nullptr
+ : GetFirstLineContaining(aBuilder->GetDirtyRect().y);
+ LineIterator line_end = LinesEnd();
+
+ TextOverflow* textOverflowPtr = textOverflow.ptrOr(nullptr);
+
+ if (cursor) {
+ for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) {
+ const nsRect lineArea = line->InkOverflowRect();
+ if (!lineArea.IsEmpty()) {
+ // Because we have a cursor, the combinedArea.ys are non-decreasing.
+ // Once we've passed aDirtyRect.YMost(), we can never see it again.
+ if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) {
+ break;
+ }
+ MOZ_ASSERT(textOverflow.isNothing());
+
+ if (ShouldDescendIntoLine(lineArea)) {
+ DisplayLine(aBuilder, line, line->IsInline(), aLists, this, nullptr,
+ 0, depth, drawnLines);
+ }
+ }
+ }
+ } else {
+ bool nonDecreasingYs = true;
+ uint32_t lineCount = 0;
+ nscoord lastY = INT32_MIN;
+ nscoord lastYMost = INT32_MIN;
+
+ // A frame's display list cannot contain more than one copy of a
+ // given display item unless the items are uniquely identifiable.
+ // Because backplate occasionally requires multiple
+ // SolidColor items, we use an index (backplateIndex) to maintain
+ // uniqueness among them. Note this is a mapping of index to
+ // item, and the mapping is stable even if the dirty rect changes.
+ uint16_t backplateIndex = 0;
+ nsRect curBackplateArea;
+
+ auto AddBackplate = [&]() {
+ aLists.BorderBackground()->AppendNewToTopWithIndex<nsDisplaySolidColor>(
+ aBuilder, this, backplateIndex, curBackplateArea,
+ backplateColor.value());
+ };
+
+ for (LineIterator line = LinesBegin(); line != line_end; ++line) {
+ const nsRect lineArea = line->InkOverflowRect();
+ const bool lineInLine = line->IsInline();
+
+ if ((lineInLine && textOverflowPtr) || ShouldDescendIntoLine(lineArea)) {
+ DisplayLine(aBuilder, line, lineInLine, aLists, this, textOverflowPtr,
+ lineCount, depth, drawnLines);
+ }
+
+ if (!lineInLine && !curBackplateArea.IsEmpty()) {
+ // If we have encountered a non-inline line but were previously
+ // forming a backplate, we should add the backplate to the display
+ // list as-is and render future backplates disjointly.
+ MOZ_ASSERT(backplateColor,
+ "if this master switch is off, curBackplateArea "
+ "must be empty and we shouldn't get here");
+ AddBackplate();
+ backplateIndex++;
+ curBackplateArea = nsRect();
+ }
+
+ if (!lineArea.IsEmpty()) {
+ if (lineArea.y < lastY || lineArea.YMost() < lastYMost) {
+ nonDecreasingYs = false;
+ }
+ lastY = lineArea.y;
+ lastYMost = lineArea.YMost();
+ if (lineInLine && backplateColor && LineHasVisibleInlineContent(line)) {
+ nsRect lineBackplate = GetLineTextArea(line, aBuilder) +
+ aBuilder->ToReferenceFrame(this);
+ if (curBackplateArea.IsEmpty()) {
+ curBackplateArea = lineBackplate;
+ } else {
+ curBackplateArea.OrWith(lineBackplate);
+ }
+ }
+ }
+ lineCount++;
+ }
+
+ if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
+ SetupLineCursorForDisplay();
+ }
+
+ if (!curBackplateArea.IsEmpty()) {
+ AddBackplate();
+ }
+ }
+
+ if (textOverflow.isSome()) {
+ // Put any text-overflow:ellipsis markers on top of the non-positioned
+ // content of the block's lines. (If we ever start sorting the Content()
+ // list this will end up in the wrong place.)
+ aLists.Content()->AppendToTop(&textOverflow->GetMarkers());
+ }
+
+#ifdef DEBUG
+ if (gLamePaintMetrics) {
+ PRTime end = PR_Now();
+
+ int32_t numLines = mLines.size();
+ if (!numLines) {
+ numLines = 1;
+ }
+ PRTime lines, deltaPerLine, delta;
+ lines = int64_t(numLines);
+ delta = end - start;
+ deltaPerLine = delta / lines;
+
+ ListTag(stdout);
+ char buf[400];
+ SprintfLiteral(buf,
+ ": %" PRId64 " elapsed (%" PRId64
+ " per line) lines=%d drawn=%d skip=%d",
+ delta, deltaPerLine, numLines, drawnLines,
+ numLines - drawnLines);
+ printf("%s\n", buf);
+ }
+#endif
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsBlockFrame::AccessibleType() {
+ if (IsTableCaption()) {
+ return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
+ }
+
+ // block frame may be for <hr>
+ if (mContent->IsHTMLElement(nsGkAtoms::hr)) {
+ return a11y::eHTMLHRType;
+ }
+
+ if (!HasMarker() || !PresContext()) {
+ // XXXsmaug What if we're in the shadow dom?
+ if (!mContent->GetParent()) {
+ // Don't create accessible objects for the root content node, they are
+ // redundant with the nsDocAccessible object created with the document
+ // node
+ return a11y::eNoType;
+ }
+
+ if (mContent == mContent->OwnerDoc()->GetBody()) {
+ // Don't create accessible objects for the body, they are redundant with
+ // the nsDocAccessible object created with the document node
+ return a11y::eNoType;
+ }
+
+ // Not a list item with a ::marker, treat as normal HTML container.
+ return a11y::eHyperTextType;
+ }
+
+ // Create special list item accessible since we have a ::marker.
+ return a11y::eHTMLLiType;
+}
+#endif
+
+void nsBlockFrame::SetupLineCursorForDisplay() {
+ if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) {
+ return;
+ }
+
+ SetProperty(LineCursorPropertyDisplay(), mLines.front());
+ AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
+}
+
+void nsBlockFrame::SetupLineCursorForQuery() {
+ if (mLines.empty() || HasProperty(LineCursorPropertyQuery())) {
+ return;
+ }
+
+ SetProperty(LineCursorPropertyQuery(), mLines.front());
+ AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
+}
+
+nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) {
+ // Although this looks like a "querying" method, it is used by the
+ // display-list building code, so uses the Display cursor.
+ nsLineBox* property = GetLineCursorForDisplay();
+ if (!property) {
+ return nullptr;
+ }
+ LineIterator cursor = mLines.begin(property);
+ nsRect cursorArea = cursor->InkOverflowRect();
+
+ while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) &&
+ cursor != mLines.front()) {
+ cursor = cursor.prev();
+ cursorArea = cursor->InkOverflowRect();
+ }
+ while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) &&
+ cursor != mLines.back()) {
+ cursor = cursor.next();
+ cursorArea = cursor->InkOverflowRect();
+ }
+
+ if (cursor.get() != property) {
+ SetProperty(LineCursorPropertyDisplay(), cursor.get());
+ }
+
+ return cursor.get();
+}
+
+/* virtual */
+void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) {
+ // See if the child is absolutely positioned
+ if (aChild->IsAbsolutelyPositioned()) {
+ // do nothing
+ } else if (aChild == GetOutsideMarker()) {
+ // The ::marker lives in the first line, unless the first line has
+ // height 0 and there is a second line, in which case it lives
+ // in the second line.
+ LineIterator markerLine = LinesBegin();
+ if (markerLine != LinesEnd() && markerLine->BSize() == 0 &&
+ markerLine != mLines.back()) {
+ markerLine = markerLine.next();
+ }
+
+ if (markerLine != LinesEnd()) {
+ MarkLineDirty(markerLine, &mLines);
+ }
+ // otherwise we have an empty line list, and ReflowDirtyLines
+ // will handle reflowing the ::marker.
+ } else {
+ // Note that we should go through our children to mark lines dirty
+ // before the next reflow. Doing it now could make things O(N^2)
+ // since finding the right line is O(N).
+ // We don't need to worry about marking lines on the overflow list
+ // as dirty; we're guaranteed to reflow them if we take them off the
+ // overflow list.
+ // However, we might have gotten a float, in which case we need to
+ // reflow the line containing its placeholder. So find the
+ // ancestor-or-self of the placeholder that's a child of the block,
+ // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark
+ // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
+ // We need to take some care to handle the case where a float is in
+ // a different continuation than its placeholder, including marking
+ // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
+ if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
+ } else {
+ NS_ASSERTION(aChild->IsFloating(), "should be a float");
+ nsIFrame* thisFC = FirstContinuation();
+ nsIFrame* placeholderPath = aChild->GetPlaceholderFrame();
+ // SVG code sometimes sends FrameNeedsReflow notifications during
+ // frame destruction, leading to null placeholders, but we're safe
+ // ignoring those.
+ if (placeholderPath) {
+ for (;;) {
+ nsIFrame* parent = placeholderPath->GetParent();
+ if (parent->GetContent() == mContent &&
+ parent->FirstContinuation() == thisFC) {
+ parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
+ break;
+ }
+ placeholderPath = parent;
+ }
+ placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ }
+ }
+
+ nsContainerFrame::ChildIsDirty(aChild);
+}
+
+static bool AlwaysEstablishesBFC(const nsBlockFrame* aFrame) {
+ switch (aFrame->Type()) {
+ case LayoutFrameType::ColumnSetWrapper:
+ // CSS Multi-column level 1 section 2: A multi-column container
+ // establishes a new block formatting context, as per CSS 2.1 section
+ // 9.4.1.
+ case LayoutFrameType::ComboboxControl:
+ return true;
+ case LayoutFrameType::Block:
+ return static_cast<const nsFileControlFrame*>(do_QueryFrame(aFrame)) ||
+ // Ensure that the options inside the select aren't expanded by
+ // right floats outside the select.
+ static_cast<const nsSelectsAreaFrame*>(do_QueryFrame(aFrame)) ||
+ // See bug 1373767 and bug 353894.
+ static_cast<const nsMathMLmathBlockFrame*>(do_QueryFrame(aFrame));
+ default:
+ return false;
+ }
+}
+
+void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ // These are all the block specific frame bits, they are copied from
+ // the prev-in-flow to a newly created next-in-flow, except for the
+ // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
+ constexpr nsFrameState NS_BLOCK_FLAGS_MASK =
+ NS_BLOCK_BFC_STATE_BITS | NS_BLOCK_CLIP_PAGINATED_OVERFLOW |
+ NS_BLOCK_HAS_FIRST_LETTER_STYLE | NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER |
+ NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
+
+ // This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited
+ // by default. They should only be set on the first-in-flow.
+ constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK =
+ NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER | NS_BLOCK_HAS_FIRST_LETTER_CHILD |
+ NS_BLOCK_FRAME_HAS_INSIDE_MARKER;
+
+ if (aPrevInFlow) {
+ // Copy over the inherited block frame bits from the prev-in-flow.
+ RemoveStateBits(NS_BLOCK_FLAGS_MASK);
+ AddStateBits(aPrevInFlow->GetStateBits() &
+ (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
+ }
+
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (!aPrevInFlow ||
+ aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
+ AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ }
+
+ // A display:flow-root box establishes a block formatting context.
+ //
+ // If a box has a different writing-mode value than its containing block:
+ // ...
+ // If the box is a block container, then it establishes a new block
+ // formatting context.
+ // (https://drafts.csswg.org/css-writing-modes/#block-flow)
+ //
+ // If the box has contain: paint or contain:layout (or contain:strict),
+ // then it should also establish a formatting context.
+ //
+ // Per spec, a column-span always establishes a new block formatting context.
+ //
+ // Other more specific frame types also always establish a BFC.
+ //
+ if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot ||
+ (GetParent() &&
+ (GetWritingMode().GetBlockDir() !=
+ GetParent()->GetWritingMode().GetBlockDir() ||
+ GetWritingMode().IsVerticalSideways() !=
+ GetParent()->GetWritingMode().IsVerticalSideways())) ||
+ IsColumnSpan() || AlwaysEstablishesBFC(this)) {
+ AddStateBits(NS_BLOCK_STATIC_BFC);
+ }
+
+ if (StyleEstablishesBFC(Style())) {
+ AddStateBits(NS_BLOCK_DYNAMIC_BFC);
+ }
+
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER) &&
+ HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+}
+
+void nsBlockFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ if (FrameChildListID::Float == aListID) {
+ mFloats = std::move(aChildList);
+ } else if (FrameChildListID::Principal == aListID) {
+#ifdef DEBUG
+ // The only times a block that is an anonymous box is allowed to have a
+ // first-letter frame are when it's the block inside a non-anonymous cell,
+ // the block inside a fieldset, button or column set, or a scrolled content
+ // block, except for <select>. Note that this means that blocks which are
+ // the anonymous block in {ib} splits do NOT get first-letter frames.
+ // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
+ // of the block.
+ auto pseudo = Style()->GetPseudoType();
+ bool haveFirstLetterStyle =
+ (pseudo == PseudoStyleType::NotPseudo ||
+ (pseudo == PseudoStyleType::cellContent &&
+ !GetParent()->Style()->IsPseudoOrAnonBox()) ||
+ pseudo == PseudoStyleType::fieldsetContent ||
+ pseudo == PseudoStyleType::buttonContent ||
+ pseudo == PseudoStyleType::columnContent ||
+ (pseudo == PseudoStyleType::scrolledContent &&
+ !GetParent()->IsListControlFrame()) ||
+ pseudo == PseudoStyleType::mozSVGText) &&
+ !IsComboboxControlFrame() && !IsMathMLFrame() &&
+ !IsColumnSetWrapperFrame() &&
+ RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr;
+ NS_ASSERTION(haveFirstLetterStyle ==
+ HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE),
+ "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
+#endif
+
+ AddFrames(std::move(aChildList), nullptr, nullptr);
+ } else {
+ nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
+ }
+}
+
+void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) {
+ MOZ_ASSERT(aMarkerFrame);
+ MOZ_ASSERT(!HasAnyStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER |
+ NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER),
+ "How can we have a ::marker frame already?");
+
+ if (StyleList()->mListStylePosition == StyleListStylePosition::Inside) {
+ SetProperty(InsideMarkerProperty(), aMarkerFrame);
+ AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER);
+ } else {
+ if (nsBlockFrame* marker = do_QueryFrame(aMarkerFrame)) {
+ // An outside ::marker needs to be an independent formatting context
+ // to avoid being influenced by the float manager etc.
+ marker->AddStateBits(NS_BLOCK_STATIC_BFC);
+ }
+ SetProperty(OutsideMarkerProperty(),
+ new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame));
+ AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
+ }
+}
+
+bool nsBlockFrame::MarkerIsEmpty() const {
+ NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() &&
+ HasOutsideMarker(),
+ "should only care when we have an outside ::marker");
+ nsIFrame* marker = GetMarker();
+ const nsStyleList* list = marker->StyleList();
+ return marker->StyleContent()->mContent.IsNone() ||
+ (list->mCounterStyle.IsNone() && list->mListStyleImage.IsNone() &&
+ marker->StyleContent()->ContentCount() == 0);
+}
+
+void nsBlockFrame::ReflowOutsideMarker(nsIFrame* aMarkerFrame,
+ BlockReflowState& aState,
+ ReflowOutput& aMetrics,
+ nscoord aLineTop) {
+ const ReflowInput& ri = aState.mReflowInput;
+
+ WritingMode markerWM = aMarkerFrame->GetWritingMode();
+ LogicalSize availSize(markerWM);
+ // Make up an inline-size since it doesn't really matter (XXX).
+ availSize.ISize(markerWM) = aState.ContentISize();
+ availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE;
+
+ ReflowInput reflowInput(aState.mPresContext, ri, aMarkerFrame, availSize,
+ Nothing(), {}, {}, {ComputeSizeFlag::ShrinkWrap});
+ nsReflowStatus status;
+ aMarkerFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status);
+
+ // Get the float available space using our saved state from before we
+ // started reflowing the block, so that we ignore any floats inside
+ // the block.
+ // FIXME: aLineTop isn't actually set correctly by some callers, since
+ // they reposition the line.
+ LogicalRect floatAvailSpace =
+ aState
+ .GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside,
+ &aState.mFloatManagerStateBefore)
+ .mRect;
+ // FIXME (bug 25888): need to check the entire region that the first
+ // line overlaps, not just the top pixel.
+
+ // Place the ::marker now. We want to place the ::marker relative to the
+ // border-box of the associated block (using the right/left margin of
+ // the ::marker frame as separation). However, if a line box would be
+ // displaced by floats that are *outside* the associated block, we
+ // want to displace it by the same amount. That is, we act as though
+ // the edge of the floats is the content-edge of the block, and place
+ // the ::marker at a position offset from there by the block's padding,
+ // the block's border, and the ::marker frame's margin.
+
+ // IStart from floatAvailSpace gives us the content/float start edge
+ // in the current writing mode. Then we subtract out the start
+ // border/padding and the ::marker's width and margin to offset the position.
+ WritingMode wm = ri.GetWritingMode();
+ // Get the ::marker's margin, converted to our writing mode so that we can
+ // combine it with other logical values here.
+ LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm);
+ nscoord iStart = floatAvailSpace.IStart(wm) -
+ ri.ComputedLogicalBorderPadding(wm).IStart(wm) -
+ markerMargin.IEnd(wm) - aMetrics.ISize(wm);
+
+ // Approximate the ::marker's position; vertical alignment will provide
+ // the final vertical location. We pass our writing-mode here, because
+ // it may be different from the ::marker frame's mode.
+ nscoord bStart = floatAvailSpace.BStart(wm);
+ aMarkerFrame->SetRect(
+ wm,
+ LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)),
+ aState.ContainerSize());
+ aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput);
+}
+
+// This is used to scan frames for any float placeholders, add their
+// floats to the list represented by aList, and remove the
+// floats from whatever list they might be in. We don't search descendants
+// that are float containing blocks. Floats that or not children of 'this'
+// are ignored (they are not added to aList).
+void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList,
+ bool aCollectSiblings) {
+ while (aFrame) {
+ // Don't descend into float containing blocks.
+ if (!aFrame->IsFloatContainingBlock()) {
+ nsIFrame* outOfFlowFrame =
+ aFrame->IsPlaceholderFrame()
+ ? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
+ : nullptr;
+ while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) {
+ RemoveFloat(outOfFlowFrame);
+ // Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from
+ // the PushedFloats list.
+ outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
+ aList.AppendFrame(nullptr, outOfFlowFrame);
+ outOfFlowFrame = outOfFlowFrame->GetNextInFlow();
+ // FIXME: By not pulling floats whose parent is one of our
+ // later siblings, are we risking the pushed floats getting
+ // out-of-order?
+ // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
+ }
+
+ DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true);
+ DoCollectFloats(
+ aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(), aList,
+ true);
+ }
+ if (!aCollectSiblings) {
+ break;
+ }
+ aFrame = aFrame->GetNextSibling();
+ }
+}
+
+void nsBlockFrame::CheckFloats(BlockReflowState& aState) {
+#ifdef DEBUG
+ // If any line is still dirty, that must mean we're going to reflow this
+ // block again soon (e.g. because we bailed out after noticing that
+ // clearance was imposed), so don't worry if the floats are out of sync.
+ bool anyLineDirty = false;
+
+ // Check that the float list is what we would have built
+ AutoTArray<nsIFrame*, 8> lineFloats;
+ for (auto& line : Lines()) {
+ if (line.HasFloats()) {
+ lineFloats.AppendElements(line.Floats());
+ }
+ if (line.IsDirty()) {
+ anyLineDirty = true;
+ }
+ }
+
+ AutoTArray<nsIFrame*, 8> storedFloats;
+ bool equal = true;
+ bool hasHiddenFloats = false;
+ uint32_t i = 0;
+ for (nsIFrame* f : mFloats) {
+ if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
+ continue;
+ }
+ // There are chances that the float children won't be added to lines,
+ // because in nsBlockFrame::ReflowLine, it skips reflow line if the first
+ // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout.
+ // There are also chances that the floats in line are out of date, for
+ // instance, lines could reflow if
+ // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is
+ // off, the reflow of lines could be skipped, but the floats are still in
+ // there. Here we can't know whether the floats hidden by c-v are included
+ // in the lines or not. So we use hasHiddenFloats to skip the float length
+ // checking.
+ if (!hasHiddenFloats &&
+ f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ hasHiddenFloats = true;
+ }
+ storedFloats.AppendElement(f);
+ if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
+ equal = false;
+ }
+ ++i;
+ }
+
+ if ((!equal || lineFloats.Length() != storedFloats.Length()) &&
+ !anyLineDirty && !hasHiddenFloats) {
+ NS_ERROR(
+ "nsBlockFrame::CheckFloats: Explicit float list is out of sync with "
+ "float cache");
+# if defined(DEBUG_roc)
+ nsIFrame::RootFrameList(PresContext(), stdout, 0);
+ for (i = 0; i < lineFloats.Length(); ++i) {
+ printf("Line float: %p\n", lineFloats.ElementAt(i));
+ }
+ for (i = 0; i < storedFloats.Length(); ++i) {
+ printf("Stored float: %p\n", storedFloats.ElementAt(i));
+ }
+# endif
+ }
+#endif
+
+ const nsFrameList* oofs = GetOverflowOutOfFlows();
+ if (oofs && oofs->NotEmpty()) {
+ // Floats that were pushed should be removed from our float
+ // manager. Otherwise the float manager's YMost or XMost might
+ // be larger than necessary, causing this block to get an
+ // incorrect desired height (or width). Some of these floats
+ // may not actually have been added to the float manager because
+ // they weren't reflowed before being pushed; that's OK,
+ // RemoveRegions will ignore them. It is safe to do this here
+ // because we know from here on the float manager will only be
+ // used for its XMost and YMost, not to place new floats and
+ // lines.
+ aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild());
+ }
+}
+
+void nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot,
+ bool* aBEndMarginRoot) {
+ nsIFrame* parent = GetParent();
+ if (!HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ if (!parent || parent->IsFloatContainingBlock()) {
+ *aBStartMarginRoot = false;
+ *aBEndMarginRoot = false;
+ return;
+ }
+ }
+
+ if (parent && parent->IsColumnSetFrame()) {
+ // The first column is a start margin root and the last column is an end
+ // margin root. (If the column-set is split by a column-span:all box then
+ // the first and last column in each column-set fragment are margin roots.)
+ *aBStartMarginRoot = GetPrevInFlow() == nullptr;
+ *aBEndMarginRoot = GetNextInFlow() == nullptr;
+ return;
+ }
+
+ *aBStartMarginRoot = true;
+ *aBEndMarginRoot = true;
+}
+
+/* static */
+bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) {
+ MOZ_ASSERT(aBlock, "Must have a frame");
+ NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block");
+
+ nsIFrame* parent = aBlock->GetParent();
+ return aBlock->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS) ||
+ (parent && !parent->IsFloatContainingBlock());
+}
+
+/* static */
+bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) {
+ return aFrame->IsBlockFrameOrSubclass() && !aFrame->IsReplaced() &&
+ !aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS);
+}
+
+// Note that this width can vary based on the vertical position.
+// However, the cases where it varies are the cases where the width fits
+// in the available space given, which means that variation shouldn't
+// matter.
+/* static */
+nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats(
+ const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace,
+ nsIFrame* aFloatAvoidingBlock) {
+ nscoord inlineStartOffset, inlineEndOffset;
+ WritingMode wm = aState.mReflowInput.GetWritingMode();
+
+ FloatAvoidingISizeToClear result;
+ aState.ComputeFloatAvoidingOffsets(aFloatAvoidingBlock, aFloatAvailableSpace,
+ inlineStartOffset, inlineEndOffset);
+ nscoord availISize =
+ aState.mContentArea.ISize(wm) - inlineStartOffset - inlineEndOffset;
+
+ // We actually don't want the min width here; see bug 427782; we only
+ // want to displace if the width won't compute to a value small enough
+ // to fit.
+ // All we really need here is the result of ComputeSize, and we
+ // could *almost* get that from an SizeComputationInput, except for the
+ // last argument.
+ WritingMode frWM = aFloatAvoidingBlock->GetWritingMode();
+ LogicalSize availSpace =
+ LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm);
+ ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
+ aFloatAvoidingBlock, availSpace);
+ result.borderBoxISize =
+ reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm);
+
+ // Use the margins from sizingInput rather than reflowInput so that
+ // they aren't reduced by ignoring margins in overconstrained cases.
+ SizeComputationInput sizingInput(aFloatAvoidingBlock,
+ aState.mReflowInput.mRenderingContext, wm,
+ aState.mContentArea.ISize(wm));
+ const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm);
+
+ nscoord marginISize = computedMargin.IStartEnd(wm);
+ const auto& iSize = reflowInput.mStylePosition->ISize(wm);
+ if (marginISize < 0 && (iSize.IsAuto() || iSize.IsMozAvailable())) {
+ // If we get here, floatAvoidingBlock has a negative amount of inline-axis
+ // margin and an 'auto' (or ~equivalently, -moz-available) inline
+ // size. Under these circumstances, we use the margin to establish a
+ // (positive) minimum size for the border-box, in order to satisfy the
+ // equation in CSS2 10.3.3. That equation essentially simplifies to the
+ // following:
+ //
+ // iSize of margins + iSize of borderBox = iSize of containingBlock
+ //
+ // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's
+ // inline-axis components of border, padding, and {width,height}.
+ //
+ // Right now, in the above equation, "iSize of margins" is the only term
+ // that we know for sure. (And we also know that it's negative, since we
+ // got here.) The other terms are as-yet unresolved, since the frame has an
+ // 'auto' iSize, and since we aren't yet sure if we'll clear this frame
+ // beyond floats or place it alongside them.
+ //
+ // However: we *do* know that the equation's "iSize of containingBlock"
+ // term *must* be non-negative, since boxes' widths and heights generally
+ // can't be negative in CSS. To satisfy that requirement, we can then
+ // infer that the equation's "iSize of borderBox" term *must* be large
+ // enough to cancel out the (known-to-be-negative) "iSize of margins"
+ // term. Therefore, marginISize value (negated to make it positive)
+ // establishes a lower-bound for how much inline-axis space our border-box
+ // will really require in order to fit alongside any floats.
+ //
+ // XXXdholbert This explanation is admittedly a bit hand-wavy and may not
+ // precisely match what any particular spec requires. It's the best
+ // reasoning I could come up with to explain engines' behavior. Also, our
+ // behavior with -moz-available doesn't seem particularly correct here, per
+ // bug 1767217, though that's probably due to a bug elsewhere in our float
+ // handling code...
+ result.borderBoxISize = std::max(result.borderBoxISize, -marginISize);
+ }
+
+ result.marginIStart = computedMargin.IStart(wm);
+ return result;
+}
+
+/* static */
+nsBlockFrame* nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) {
+ nsBlockFrame* block = nullptr;
+ while (aCandidate) {
+ block = do_QueryFrame(aCandidate);
+ if (block) {
+ // yay, candidate is a block!
+ return block;
+ }
+ // Not a block. Check its parent next.
+ aCandidate = aCandidate->GetParent();
+ }
+ MOZ_ASSERT_UNREACHABLE("Fell off frame tree looking for ancestor block!");
+ return nullptr;
+}
+
+nscoord nsBlockFrame::ComputeFinalBSize(BlockReflowState& aState,
+ nscoord aBEndEdgeOfChildren) {
+ const WritingMode wm = aState.mReflowInput.GetWritingMode();
+
+ const nscoord effectiveContentBoxBSize =
+ GetEffectiveComputedBSize(aState.mReflowInput, aState.mConsumedBSize);
+ const nscoord blockStartBP = aState.BorderPadding().BStart(wm);
+ const nscoord blockEndBP = aState.BorderPadding().BEnd(wm);
+
+ NS_ASSERTION(
+ !IsTrueOverflowContainer() || (effectiveContentBoxBSize == 0 &&
+ blockStartBP == 0 && blockEndBP == 0),
+ "An overflow container's effective content-box block-size, block-start "
+ "BP, and block-end BP should all be zero!");
+
+ const nscoord effectiveContentBoxBSizeWithBStartBP =
+ NSCoordSaturatingAdd(blockStartBP, effectiveContentBoxBSize);
+ const nscoord effectiveBorderBoxBSize =
+ NSCoordSaturatingAdd(effectiveContentBoxBSizeWithBStartBP, blockEndBP);
+
+ if (HasColumnSpanSiblings()) {
+ MOZ_ASSERT(LastInFlow()->GetNextContinuation(),
+ "Frame constructor should've created column-span siblings!");
+
+ // If a block is split by any column-spans, we calculate the final
+ // block-size by shrinkwrapping our children's block-size for all the
+ // fragments except for those after the final column-span, but we should
+ // take no more than our effective border-box block-size. If there's any
+ // leftover block-size, our next continuations will take up rest.
+ //
+ // We don't need to adjust aBri.mReflowStatus because our children's status
+ // is the same as ours.
+ return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren);
+ }
+
+ const nscoord availBSize = aState.mReflowInput.AvailableBSize();
+ if (availBSize == NS_UNCONSTRAINEDSIZE) {
+ return effectiveBorderBoxBSize;
+ }
+
+ // Save our children's reflow status.
+ const bool isChildStatusComplete = aState.mReflowStatus.IsComplete();
+ if (isChildStatusComplete && effectiveContentBoxBSize > 0 &&
+ effectiveBorderBoxBSize > availBSize &&
+ ShouldAvoidBreakInside(aState.mReflowInput)) {
+ aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
+ return effectiveBorderBoxBSize;
+ }
+
+ const bool isBDBClone =
+ aState.mReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone;
+
+ // The maximum value our content-box block-size can take within the given
+ // available block-size.
+ const nscoord maxContentBoxBSize = aState.ContentBSize();
+
+ // The block-end edge of our content-box (relative to this frame's origin) if
+ // we consumed the maximum block-size available to us (maxContentBoxBSize).
+ const nscoord maxContentBoxBEnd = aState.ContentBEnd();
+
+ // These variables are uninitialized intentionally so that the compiler can
+ // check they are assigned in every if-else branch below.
+ nscoord finalContentBoxBSizeWithBStartBP;
+ bool isOurStatusComplete;
+
+ if (effectiveBorderBoxBSize <= availBSize) {
+ // Our effective border-box block-size can fit in the available block-size,
+ // so we are complete.
+ finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
+ isOurStatusComplete = true;
+ } else if (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) {
+ // Note: The following assertion should generally hold because, for
+ // box-decoration-break:clone, this "else if" branch is mathematically
+ // equivalent to the initial "if".
+ NS_ASSERTION(!isBDBClone,
+ "This else-if branch is handling a situation that's specific "
+ "to box-decoration-break:slice, i.e. a case when we can skip "
+ "our block-end border and padding!");
+
+ // Our effective content-box block-size plus the block-start border and
+ // padding can fit in the available block-size, but it cannot fit after
+ // adding the block-end border and padding. Thus, we need a continuation
+ // (unless we already weren't asking for any block-size, in which case we
+ // stay complete to avoid looping forever).
+ finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
+ isOurStatusComplete = effectiveContentBoxBSize == 0;
+ } else {
+ // We aren't going to be able to fit our content-box in the space available
+ // to it, which means we'll probably call ourselves incomplete to request a
+ // continuation. But before making that decision, we check for certain
+ // conditions which would force us to overflow beyond the available space --
+ // these might result in us actually being complete if we're forced to
+ // overflow far enough.
+ if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone &&
+ maxContentBoxBSize <= 0 &&
+ aBEndEdgeOfChildren == blockStartBP)) {
+ // In this rare case, we are at the top of page/column, we have
+ // box-decoration-break:clone and zero available block-size for our
+ // content-box (e.g. our own block-start border and padding already exceed
+ // the available block-size), and we didn't lay out any child to consume
+ // our content-box block-size. To ensure we make progress (avoid looping
+ // forever), use 1px as our content-box block-size regardless of our
+ // effective content-box block-size, in the spirit of
+ // https://drafts.csswg.org/css-break/#breaking-rules.
+ finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel();
+ isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel();
+ } else if (aBEndEdgeOfChildren > maxContentBoxBEnd) {
+ // We have a unbreakable child whose block-end edge exceeds the available
+ // block-size for children.
+ if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) {
+ // The unbreakable child's block-end edge forces us to consume all of
+ // our effective content-box block-size.
+ finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
+
+ // Even though we've consumed all of our effective content-box
+ // block-size, we may still need to report an incomplete status in order
+ // to get another continuation, which will be responsible for laying out
+ // & drawing our block-end border & padding. But if we have no such
+ // border & padding, or if we're forced to apply that border & padding
+ // on this frame due to box-decoration-break:clone, then we don't need
+ // to bother with that additional continuation.
+ isOurStatusComplete = (isBDBClone || blockEndBP == 0);
+ } else {
+ // The unbreakable child's block-end edge doesn't force us to consume
+ // all of our effective content-box block-size.
+ finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren;
+ isOurStatusComplete = false;
+ }
+ } else {
+ // The children's block-end edge can fit in the content-box space that we
+ // have available for it. Consume all the space that is available so that
+ // our inline-start/inline-end borders extend all the way to the block-end
+ // edge of column/page.
+ finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd;
+ isOurStatusComplete = false;
+ }
+ }
+
+ nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP;
+ if (isOurStatusComplete) {
+ finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
+ if (isChildStatusComplete) {
+ // We want to use children's reflow status as ours, which can be overflow
+ // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here.
+ } else {
+ aState.mReflowStatus.SetOverflowIncomplete();
+ }
+ } else {
+ NS_ASSERTION(!IsTrueOverflowContainer(),
+ "An overflow container should always be complete because of "
+ "its zero border-box block-size!");
+ if (isBDBClone) {
+ finalBorderBoxBSize =
+ NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
+ }
+ aState.mReflowStatus.SetIncomplete();
+ if (!GetNextInFlow()) {
+ aState.mReflowStatus.SetNextInFlowNeedsReflow();
+ }
+ }
+
+ return finalBorderBoxBSize;
+}
+
+nsresult nsBlockFrame::ResolveBidi() {
+ NS_ASSERTION(!GetPrevInFlow(),
+ "ResolveBidi called on non-first continuation");
+ MOZ_ASSERT(PresContext()->BidiEnabled());
+ return nsBidiPresUtils::Resolve(this);
+}
+
+void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) {
+ // first-letter needs to be updated before first-line, because first-line can
+ // change the style of the first-letter.
+ if (HasFirstLetterChild()) {
+ UpdateFirstLetterStyle(aRestyleState);
+ }
+
+ if (nsIFrame* firstLineFrame = GetFirstLineFrame()) {
+ nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(),
+ PseudoStyleType::firstLine);
+
+ ComputedStyle* parentStyle = styleParent->Style();
+ RefPtr<ComputedStyle> firstLineStyle =
+ aRestyleState.StyleSet().ResolvePseudoElementStyle(
+ *mContent->AsElement(), PseudoStyleType::firstLine, nullptr,
+ parentStyle);
+
+ // FIXME(bz): Can we make first-line continuations be non-inheriting anon
+ // boxes?
+ RefPtr<ComputedStyle> continuationStyle =
+ aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::mozLineFrame, parentStyle);
+
+ UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState,
+ Some(continuationStyle.get()));
+
+ // We also want to update the styles of the first-line's descendants. We
+ // don't need to compute a changehint for this, though, since any changes to
+ // them are handled by the first-line anyway.
+ RestyleManager* manager = PresContext()->RestyleManager();
+ for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
+ manager->ReparentComputedStyleForFirstLine(kid);
+ }
+ }
+}
+
+nsIFrame* nsBlockFrame::GetFirstLetter() const {
+ if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
+ // Certainly no first-letter frame.
+ return nullptr;
+ }
+
+ return GetProperty(FirstLetterProperty());
+}
+
+nsIFrame* nsBlockFrame::GetFirstLineFrame() const {
+ nsIFrame* maybeFirstLine = PrincipalChildList().FirstChild();
+ if (maybeFirstLine && maybeFirstLine->IsLineFrame()) {
+ return maybeFirstLine;
+ }
+
+ return nullptr;
+}
+
+#ifdef DEBUG
+void nsBlockFrame::VerifyLines(bool aFinalCheckOK) {
+ if (!gVerifyLines) {
+ return;
+ }
+ if (mLines.empty()) {
+ return;
+ }
+
+ nsLineBox* cursor = GetLineCursorForQuery();
+
+ // Add up the counts on each line. Also validate that IsFirstLine is
+ // set properly.
+ int32_t count = 0;
+ for (const auto& line : Lines()) {
+ if (&line == cursor) {
+ cursor = nullptr;
+ }
+ if (aFinalCheckOK) {
+ MOZ_ASSERT(line.GetChildCount(), "empty line");
+ if (line.IsBlock()) {
+ NS_ASSERTION(1 == line.GetChildCount(), "bad first line");
+ }
+ }
+ count += line.GetChildCount();
+ }
+
+ // Then count the frames
+ int32_t frameCount = 0;
+ nsIFrame* frame = mLines.front()->mFirstChild;
+ while (frame) {
+ frameCount++;
+ frame = frame->GetNextSibling();
+ }
+ NS_ASSERTION(count == frameCount, "bad line list");
+
+ // Next: test that each line has right number of frames on it
+ for (LineIterator line = LinesBegin(), line_end = LinesEnd();
+ line != line_end;) {
+ count = line->GetChildCount();
+ frame = line->mFirstChild;
+ while (--count >= 0) {
+ frame = frame->GetNextSibling();
+ }
+ ++line;
+ if ((line != line_end) && (0 != line->GetChildCount())) {
+ NS_ASSERTION(frame == line->mFirstChild, "bad line list");
+ }
+ }
+
+ if (cursor) {
+ FrameLines* overflowLines = GetOverflowLines();
+ if (overflowLines) {
+ LineIterator line = overflowLines->mLines.begin();
+ LineIterator line_end = overflowLines->mLines.end();
+ for (; line != line_end; ++line) {
+ if (line == cursor) {
+ cursor = nullptr;
+ break;
+ }
+ }
+ }
+ }
+ NS_ASSERTION(!cursor, "stale LineCursorProperty");
+}
+
+void nsBlockFrame::VerifyOverflowSituation() {
+ // Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames.
+ nsFrameList* oofs = GetOverflowOutOfFlows();
+ if (oofs) {
+ for (nsIFrame* f : *oofs) {
+ nsIFrame* nif = f->GetNextInFlow();
+ MOZ_ASSERT(!nif ||
+ (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
+ }
+ }
+
+ // Pushed floats must not have a next-in-flow in mFloats or mFrames.
+ oofs = GetPushedFloats();
+ if (oofs) {
+ for (nsIFrame* f : *oofs) {
+ nsIFrame* nif = f->GetNextInFlow();
+ MOZ_ASSERT(!nif ||
+ (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
+ }
+ }
+
+ // A child float next-in-flow's parent must be |this| or a next-in-flow of
+ // |this|. Later next-in-flows must have the same or later parents.
+ ChildListID childLists[] = {FrameChildListID::Float,
+ FrameChildListID::PushedFloats};
+ for (size_t i = 0; i < ArrayLength(childLists); ++i) {
+ const nsFrameList& children = GetChildList(childLists[i]);
+ for (nsIFrame* f : children) {
+ nsIFrame* parent = this;
+ nsIFrame* nif = f->GetNextInFlow();
+ for (; nif; nif = nif->GetNextInFlow()) {
+ bool found = false;
+ for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) {
+ if (nif->GetParent() == p) {
+ parent = p;
+ found = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(
+ found,
+ "next-in-flow is a child of parent earlier in the frame tree?");
+ }
+ }
+ }
+
+ nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow());
+ while (flow) {
+ FrameLines* overflowLines = flow->GetOverflowLines();
+ if (overflowLines) {
+ NS_ASSERTION(!overflowLines->mLines.empty(),
+ "should not be empty if present");
+ NS_ASSERTION(overflowLines->mLines.front()->mFirstChild,
+ "bad overflow lines");
+ NS_ASSERTION(overflowLines->mLines.front()->mFirstChild ==
+ overflowLines->mFrames.FirstChild(),
+ "bad overflow frames / lines");
+ }
+ auto checkCursor = [&](nsLineBox* cursor) -> bool {
+ if (!cursor) {
+ return true;
+ }
+ LineIterator line = flow->LinesBegin();
+ LineIterator line_end = flow->LinesEnd();
+ for (; line != line_end && line != cursor; ++line)
+ ;
+ if (line == line_end && overflowLines) {
+ line = overflowLines->mLines.begin();
+ line_end = overflowLines->mLines.end();
+ for (; line != line_end && line != cursor; ++line)
+ ;
+ }
+ return line != line_end;
+ };
+ MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()),
+ "stale LineCursorPropertyDisplay");
+ MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()),
+ "stale LineCursorPropertyQuery");
+ flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow());
+ }
+}
+
+int32_t nsBlockFrame::GetDepth() const {
+ int32_t depth = 0;
+ nsIFrame* parent = GetParent();
+ while (parent) {
+ parent = parent->GetParent();
+ depth++;
+ }
+ return depth;
+}
+
+already_AddRefed<ComputedStyle> nsBlockFrame::GetFirstLetterStyle(
+ nsPresContext* aPresContext) {
+ return aPresContext->StyleSet()->ProbePseudoElementStyle(
+ *mContent->AsElement(), PseudoStyleType::firstLetter, nullptr, Style());
+}
+#endif
diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h
new file mode 100644
index 0000000000..15dd4c3278
--- /dev/null
+++ b/layout/generic/nsBlockFrame.h
@@ -0,0 +1,1118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * rendering object for CSS display:block, inline-block, and list-item
+ * boxes, also used for various anonymous boxes
+ */
+
+#ifndef nsBlockFrame_h___
+#define nsBlockFrame_h___
+
+#include "nsContainerFrame.h"
+#include "nsHTMLParts.h"
+#include "nsLineBox.h"
+#include "nsCSSPseudoElements.h"
+#include "nsFloatManager.h"
+
+enum class LineReflowStatus {
+ // The line was completely reflowed and fit in available width, and we should
+ // try to pull up content from the next line if possible.
+ OK,
+ // The line was completely reflowed and fit in available width, but we should
+ // not try to pull up content from the next line.
+ Stop,
+ // We need to reflow the line again at its current vertical position. The
+ // new reflow should not try to pull up any frames from the next line.
+ RedoNoPull,
+ // We need to reflow the line again using the floats from its height
+ // this reflow, since its height made it hit floats that were not
+ // adjacent to its top.
+ RedoMoreFloats,
+ // We need to reflow the line again at a lower vertical postion where there
+ // may be more horizontal space due to different float configuration.
+ RedoNextBand,
+ // The line did not fit in the available vertical space. Try pushing it to
+ // the next page or column if it's not the first line on the current
+ // page/column.
+ Truncated
+};
+
+class nsBlockInFlowLineIterator;
+namespace mozilla {
+class BlockReflowState;
+class PresShell;
+class ServoRestyleState;
+class ServoStyleSet;
+} // namespace mozilla
+
+/**
+ * Some invariants:
+ * -- The overflow out-of-flows list contains the out-of-
+ * flow frames whose placeholders are in the overflow list.
+ * -- A given piece of content has at most one placeholder
+ * frame in a block's normal child list.
+ * -- While a block is being reflowed, and from then until
+ * its next-in-flow is reflowed it may have a
+ * PushedFloatProperty frame property that points to
+ * an nsFrameList. This list contains continuations for
+ * floats whose prev-in-flow is in the block's regular float
+ * list and first-in-flows of floats that did not fit, but
+ * whose placeholders are in the block or one of its
+ * prev-in-flows.
+ * -- In all these frame lists, if there are two frames for
+ * the same content appearing in the list, then the frames
+ * appear with the prev-in-flow before the next-in-flow.
+ * -- While reflowing a block, its overflow line list
+ * will usually be empty but in some cases will have lines
+ * (while we reflow the block at its shrink-wrap width).
+ * In this case any new overflowing content must be
+ * prepended to the overflow lines.
+ */
+
+/*
+ * Base class for block and inline frames.
+ * The block frame has an additional child list, FrameChildListID::Absolute,
+ * which contains the absolutely positioned frames.
+ */
+class nsBlockFrame : public nsContainerFrame {
+ using BlockReflowState = mozilla::BlockReflowState;
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsBlockFrame)
+
+ typedef nsLineList::iterator LineIterator;
+ typedef nsLineList::const_iterator ConstLineIterator;
+ typedef nsLineList::reverse_iterator ReverseLineIterator;
+ typedef nsLineList::const_reverse_iterator ConstReverseLineIterator;
+
+ LineIterator LinesBegin() { return mLines.begin(); }
+ LineIterator LinesEnd() { return mLines.end(); }
+ ConstLineIterator LinesBegin() const { return mLines.begin(); }
+ ConstLineIterator LinesEnd() const { return mLines.end(); }
+ ReverseLineIterator LinesRBegin() { return mLines.rbegin(); }
+ ReverseLineIterator LinesREnd() { return mLines.rend(); }
+ ConstReverseLineIterator LinesRBegin() const { return mLines.rbegin(); }
+ ConstReverseLineIterator LinesREnd() const { return mLines.rend(); }
+ LineIterator LinesBeginFrom(nsLineBox* aList) { return mLines.begin(aList); }
+ ReverseLineIterator LinesRBeginFrom(nsLineBox* aList) {
+ return mLines.rbegin(aList);
+ }
+
+ // Methods declared to be used in 'range-based-for-loop'
+ nsLineList& Lines() { return mLines; }
+ const nsLineList& Lines() const { return mLines; }
+
+ friend nsBlockFrame* NS_NewBlockFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ // nsQueryFrame
+ NS_DECL_QUERYFRAME
+
+ // nsIFrame
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame* aOldFrame) override;
+ nsContainerFrame* GetContentInsertionFrame() override;
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+ const nsFrameList& GetChildList(ChildListID aListID) const override;
+ void GetChildLists(nsTArray<ChildList>* aLists) const override;
+ nscoord SynthesizeFallbackBaseline(
+ mozilla::WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup) const override;
+ BaselineSharingGroup GetDefaultBaselineSharingGroup() const override {
+ return BaselineSharingGroup::Last;
+ }
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const override;
+ nscoord GetCaretBaseline() const override;
+ void Destroy(DestroyContext&) override;
+
+ bool IsFloatContainingBlock() const override;
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ void InvalidateFrame(uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true) override;
+ void InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ void List(FILE* out = stderr, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const override;
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+#ifdef DEBUG
+ const char* LineReflowStatusToString(
+ LineReflowStatus aLineReflowStatus) const;
+#endif
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ // Line cursor methods to speed up line searching in which one query result
+ // is expected to be close to the next in general. This is mainly for
+ // searching line(s) containing a point. It is also used as a cache for local
+ // computation. The basic idea for the former is that we set the cursor
+ // property if the lines' overflowArea.InkOverflow().ys and
+ // overflowArea.InkOverflow().yMosts are non-decreasing
+ // (considering only non-empty overflowArea.InkOverflow()s; empty
+ // overflowArea.InkOverflow()s never participate in event handling
+ // or painting), and the block has sufficient number of lines. The
+ // cursor property points to a "recently used" line. If we get a
+ // series of requests that work on lines
+ // "near" the cursor, then we can find those nearby lines quickly by
+ // starting our search at the cursor.
+
+ // We have two independent line cursors, one used for display-list building
+ // and the other for a11y or other frame queries. Either or both may be
+ // present at any given time. When we reflow or otherwise munge the lines,
+ // both cursors will be cleared.
+ // The display cursor is only created and used if the lines satisfy the non-
+ // decreasing y-coordinate condition (see SetupLineCursorForDisplay comment
+ // below), whereas the query cursor may be created for any block. The two
+ // are separated so creating a cursor for a11y queries (eg GetRenderedText)
+ // does not risk confusing the display-list building code.
+
+ // Clear out line cursors because we're disturbing the lines (i.e., Reflow)
+ void ClearLineCursors() {
+ if (MaybeHasLineCursor()) {
+ ClearLineCursorForDisplay();
+ ClearLineCursorForQuery();
+ RemoveStateBits(NS_BLOCK_HAS_LINE_CURSOR);
+ }
+ ClearLineIterator();
+ }
+ void ClearLineCursorForDisplay() {
+ RemoveProperty(LineCursorPropertyDisplay());
+ }
+ void ClearLineCursorForQuery() { RemoveProperty(LineCursorPropertyQuery()); }
+
+ // Clear just the line-iterator property; this is used if we need to get a
+ // LineIterator temporarily during reflow, when using a persisted iterator
+ // would be invalid. So we clear the stored property immediately after use.
+ void ClearLineIterator() { RemoveProperty(LineIteratorProperty()); }
+
+ // Get the first line that might contain y-coord 'y', or nullptr if you must
+ // search all lines. If nonnull is returned then we guarantee that the lines'
+ // combinedArea.ys and combinedArea.yMosts are non-decreasing.
+ // The actual line returned might not contain 'y', but if not, it is
+ // guaranteed to be before any line which does contain 'y'.
+ nsLineBox* GetFirstLineContaining(nscoord y);
+
+ // Ensure the frame has a display-list line cursor, initializing it to the
+ // first line if it is not already present. (If there's an existing cursor,
+ // it is left untouched.) Only call this if you guarantee that the lines'
+ // combinedArea.ys and combinedArea.yMosts are non-decreasing.
+ void SetupLineCursorForDisplay();
+
+ // Ensure the frame has a query line cursor, initializing it to the first
+ // line if it is not already present. (If there's an existing cursor, it is
+ // left untouched.)
+ void SetupLineCursorForQuery();
+
+ void ChildIsDirty(nsIFrame* aChild) override;
+
+ bool IsEmpty() override;
+ bool CachedIsEmpty() override;
+ bool IsSelfEmpty() override;
+
+ // Given that we have a ::marker frame, does it actually draw something, i.e.,
+ // do we have either a 'list-style-type' or 'list-style-image' that is
+ // not 'none', and no 'content'?
+ bool MarkerIsEmpty() const;
+
+ /**
+ * Return true if this frame has a ::marker frame.
+ */
+ bool HasMarker() const { return HasOutsideMarker() || HasInsideMarker(); }
+
+ /**
+ * @return true if this frame has an inside ::marker frame.
+ */
+ bool HasInsideMarker() const {
+ return HasAnyStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER);
+ }
+
+ /**
+ * @return true if this frame has an outside ::marker frame.
+ */
+ bool HasOutsideMarker() const {
+ return HasAnyStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
+ }
+
+ /**
+ * @return the ::marker frame or nullptr if we don't have one.
+ */
+ nsIFrame* GetMarker() const {
+ nsIFrame* outside = GetOutsideMarker();
+ return outside ? outside : GetInsideMarker();
+ }
+
+ /**
+ * @return the first-letter frame or nullptr if we don't have one.
+ */
+ nsIFrame* GetFirstLetter() const;
+
+ /**
+ * @return the ::first-line frame or nullptr if we don't have one.
+ */
+ nsIFrame* GetFirstLineFrame() const;
+
+ void MarkIntrinsicISizesDirty() override;
+
+ private:
+ // Whether CSS text-indent should be applied to the given line.
+ bool TextIndentAppliesTo(const LineIterator& aLine) const;
+
+ void CheckIntrinsicCacheAgainstShrinkWrapState();
+
+ template <typename LineIteratorType>
+ Maybe<nscoord> GetBaselineBOffset(LineIteratorType aStart,
+ LineIteratorType aEnd,
+ mozilla::WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const;
+
+ public:
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const override;
+
+ nsresult GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
+ nscoord* aXMost) override;
+
+ /**
+ * Compute the final block size of this frame.
+ *
+ * @param aState BlockReflowState passed from parent during reflow.
+ * Note: aState.mReflowStatus is mostly an "input" parameter. When this
+ * method is called, it should represent what our status would be as if
+ * we were shrinkwrapping our children's block-size. This method will
+ * then adjust it before returning if our status is different in light
+ * of our actual final block-size and current page/column's available
+ * block-size.
+ * @param aBEndEdgeOfChildren The distance between this frame's block-start
+ * border-edge and the block-end edge of our last child's border-box.
+ * This is effectively our block-start border-padding plus the
+ * block-size of our children, precomputed outside of this function.
+ * @return our final block-size with respect to aReflowInput's writing-mode.
+ */
+ nscoord ComputeFinalBSize(BlockReflowState& aState,
+ nscoord aBEndEdgeOfChildren);
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ /**
+ * Move any frames on our overflow list to the end of our principal list.
+ * @return true if there were any overflow frames
+ */
+ bool DrainSelfOverflowList() override;
+
+ void StealFrame(nsIFrame* aChild) override;
+
+ void DeleteNextInFlowChild(DestroyContext&, nsIFrame* aNextInFlow,
+ bool aDeletingEmptyFrames) override;
+
+ /**
+ * This is a special method that allows a child class of nsBlockFrame to
+ * return a special, customized nsStyleText object to the nsLineLayout
+ * constructor. It is used when the nsBlockFrame child needs to specify its
+ * custom rendering style.
+ */
+ virtual const nsStyleText* StyleTextForLineLayout();
+
+ /**
+ * Determines whether the collapsed margin carried out of the last
+ * line includes the margin-top of a line with clearance (in which
+ * case we must avoid collapsing that margin with our bottom margin)
+ */
+ bool CheckForCollapsedBEndMarginFromClearanceLine();
+
+ static nsresult GetCurrentLine(BlockReflowState* aState,
+ nsLineBox** aOutCurrentLine);
+
+ /**
+ * Determine if this block is a margin root at the top/bottom edges.
+ */
+ void IsMarginRoot(bool* aBStartMarginRoot, bool* aBEndMarginRoot);
+
+ static bool BlockNeedsFloatManager(nsIFrame* aBlock);
+
+ /**
+ * Returns whether aFrame is a block frame that will wrap its contents
+ * around floats intruding on it from the outside. (aFrame need not
+ * be a block frame, but if it's not, the result will be false.)
+ *
+ * Note: We often use the term "float-avoiding block" to refer to
+ * block-level frames for whom this function returns false.
+ */
+ static bool BlockCanIntersectFloats(nsIFrame* aFrame);
+
+ /**
+ * Returns the inline size that needs to be cleared past floats for
+ * blocks that avoid (i.e. cannot intersect) floats. aState must already
+ * have GetFloatAvailableSpace called on it for the block-dir position that
+ * we care about (which need not be its current mBCoord)
+ */
+ struct FloatAvoidingISizeToClear {
+ // Note that we care about the inline-start margin but can ignore
+ // the inline-end margin.
+ nscoord marginIStart, borderBoxISize;
+ };
+ static FloatAvoidingISizeToClear ISizeToClearPastFloats(
+ const BlockReflowState& aState,
+ const mozilla::LogicalRect& aFloatAvailableSpace,
+ nsIFrame* aFloatAvoidingBlock);
+
+ /**
+ * Creates a contination for aFloat and adds it to the list of overflow
+ * floats. Also updates aState.mReflowStatus to include the float's
+ * incompleteness. Must only be called while this block frame is in reflow.
+ * aFloatStatus must be the float's true, unmodified reflow status.
+ */
+ void SplitFloat(BlockReflowState& aState, nsIFrame* aFloat,
+ const nsReflowStatus& aFloatStatus);
+
+ /**
+ * Walks up the frame tree, starting with aCandidate, and returns the first
+ * block frame that it encounters.
+ */
+ static nsBlockFrame* GetNearestAncestorBlock(nsIFrame* aCandidate);
+
+ struct FrameLines {
+ nsLineList mLines;
+ nsFrameList mFrames;
+ };
+
+ /**
+ * Update the styles of our various pseudo-elements (marker, first-line,
+ * etc, but _not_ first-letter).
+ */
+ void UpdatePseudoElementStyles(mozilla::ServoRestyleState& aRestyleState);
+
+ // Update our first-letter styles during stylo post-traversal. This needs to
+ // be done at a slightly different time than our other pseudo-elements.
+ void UpdateFirstLetterStyle(mozilla::ServoRestyleState& aRestyleState);
+
+ protected:
+ explicit nsBlockFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID = kClassID)
+ : nsContainerFrame(aStyle, aPresContext, aID) {
+#ifdef DEBUG
+ InitDebugFlags();
+#endif
+ }
+
+ virtual ~nsBlockFrame();
+
+ void DidSetComputedStyle(ComputedStyle* aOldStyle) override;
+
+#ifdef DEBUG
+ already_AddRefed<ComputedStyle> GetFirstLetterStyle(
+ nsPresContext* aPresContext);
+#endif
+
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(LineCursorPropertyDisplay, nsLineBox)
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(LineCursorPropertyQuery, nsLineBox)
+ // Note that the NS_BLOCK_HAS_LINE_CURSOR flag does not necessarily mean the
+ // cursor is present, as it covers both the "display" and "query" cursors,
+ // but may remain set if they have been separately deleted. In such a case,
+ // the Get* accessors will be slightly more expensive, but will still safely
+ // return null if the cursor is absent.
+ bool MaybeHasLineCursor() {
+ return HasAnyStateBits(NS_BLOCK_HAS_LINE_CURSOR);
+ }
+ nsLineBox* GetLineCursorForDisplay() {
+ return MaybeHasLineCursor() ? GetProperty(LineCursorPropertyDisplay())
+ : nullptr;
+ }
+ nsLineBox* GetLineCursorForQuery() {
+ return MaybeHasLineCursor() ? GetProperty(LineCursorPropertyQuery())
+ : nullptr;
+ }
+
+ nsLineBox* NewLineBox(nsIFrame* aFrame, bool aIsBlock) {
+ return NS_NewLineBox(PresShell(), aFrame, aIsBlock);
+ }
+ nsLineBox* NewLineBox(nsLineBox* aFromLine, nsIFrame* aFrame,
+ int32_t aCount) {
+ return NS_NewLineBox(PresShell(), aFromLine, aFrame, aCount);
+ }
+ void FreeLineBox(nsLineBox* aLine) {
+ if (aLine == GetLineCursorForDisplay()) {
+ ClearLineCursorForDisplay();
+ }
+ if (aLine == GetLineCursorForQuery()) {
+ ClearLineCursorForQuery();
+ }
+ aLine->Destroy(PresShell());
+ }
+ /**
+ * Helper method for StealFrame.
+ */
+ void RemoveFrameFromLine(nsIFrame* aChild, nsLineList::iterator aLine,
+ nsFrameList& aFrameList, nsLineList& aLineList);
+
+ void TryAllLines(nsLineList::iterator* aIterator,
+ nsLineList::iterator* aStartIterator,
+ nsLineList::iterator* aEndIterator, bool* aInOverflowLines,
+ FrameLines** aOverflowLines);
+
+ /** move the frames contained by aLine by aDeltaBCoord
+ * if aLine is a block, its child floats are added to the state manager
+ */
+ void SlideLine(BlockReflowState& aState, nsLineBox* aLine,
+ nscoord aDeltaBCoord);
+
+ void UpdateLineContainerSize(nsLineBox* aLine,
+ const nsSize& aNewContainerSize);
+
+ // helper for SlideLine and UpdateLineContainerSize
+ void MoveChildFramesOfLine(nsLineBox* aLine, nscoord aDeltaBCoord);
+
+ // Returns block-end edge of children.
+ nscoord ComputeFinalSize(const ReflowInput& aReflowInput,
+ BlockReflowState& aState, ReflowOutput& aMetrics);
+
+ /**
+ * Helper method for Reflow(). Computes the overflow areas created by our
+ * children, and includes them into aOverflowAreas.
+ */
+ void ComputeOverflowAreas(mozilla::OverflowAreas& aOverflowAreas,
+ nscoord aBEndEdgeOfChildren,
+ const nsStyleDisplay* aDisplay) const;
+
+ /**
+ * Helper method for ComputeOverflowAreas(). Incorporates aBEndEdgeOfChildren
+ * into the aOverflowAreas.
+ */
+ void ConsiderBlockEndEdgeOfChildren(mozilla::OverflowAreas& aOverflowAreas,
+ nscoord aBEndEdgeOfChildren,
+ const nsStyleDisplay* aDisplay) const;
+
+ /**
+ * Add the frames in aFrameList to this block after aPrevSibling.
+ * This block thinks in terms of lines, but the frame construction code
+ * knows nothing about lines at all so we need to find the line that
+ * contains aPrevSibling and add aFrameList after aPrevSibling on that line.
+ * New lines are created as necessary to handle block data in aFrameList.
+ * This function will clear aFrameList.
+ *
+ * aPrevSiblingLine, if present, must be the line containing aPrevSibling.
+ * Providing it will make this function faster.
+ */
+ void AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling,
+ const nsLineList::iterator* aPrevSiblingLine);
+
+ // Return the :-moz-block-ruby-content child frame, if any.
+ // (It's non-null only if this block frame is for 'display:block ruby'.)
+ nsContainerFrame* GetRubyContentPseudoFrame();
+
+ /**
+ * Perform Bidi resolution on this frame
+ */
+ nsresult ResolveBidi();
+
+ /**
+ * Test whether the frame is a form control in a visual Bidi page.
+ * This is necessary for backwards-compatibility, because most visual
+ * pages use logical order for form controls so that they will
+ * display correctly on native widgets in OSs with Bidi support
+ * @param aPresContext the pres context
+ * @return whether the frame is a BIDI form control
+ */
+ bool IsVisualFormControl(nsPresContext* aPresContext);
+
+ /**
+ * For text-wrap:balance, we iteratively try reflowing with adjusted inline
+ * size to find the "best" result (the tightest size that can be applied
+ * without increasing the total line count of the block).
+ * This record is used to manage the state of these "trial reflows", and
+ * return results from the final trial.
+ */
+ struct TrialReflowState {
+ // Values pre-computed at start of Reflow(), constant across trials.
+ const nscoord mConsumedBSize;
+ const nscoord mEffectiveContentBoxBSize;
+ bool mNeedFloatManager;
+ // [out] Whether reflowing resulted in use of an overflow-wrap break.
+ bool mUsedOverflowWrap = false;
+ // Settings for the current trial.
+ bool mBalancing = false;
+ nscoord mInset = 0;
+ // Results computed during the trial reflow. Values from the final trial
+ // will be used by the remainder of Reflow().
+ mozilla::OverflowAreas mOcBounds;
+ mozilla::OverflowAreas mFcBounds;
+ nscoord mBlockEndEdgeOfChildren = 0;
+ nscoord mContainerWidth = 0;
+
+ // Initialize for the initial trial reflow, with zero inset.
+ TrialReflowState(nscoord aConsumedBSize, nscoord aEffectiveContentBoxBSize,
+ bool aNeedFloatManager)
+ : mConsumedBSize(aConsumedBSize),
+ mEffectiveContentBoxBSize(aEffectiveContentBoxBSize),
+ mNeedFloatManager(aNeedFloatManager) {}
+
+ // Adjust the inset amount, and reset state for a new trial.
+ void ResetForBalance(nscoord aInsetDelta) {
+ // Tells the reflow-lines loop we must consider all lines "dirty" (as we
+ // are modifying the effective inline-size to be used).
+ mBalancing = true;
+ // Adjust inset to apply.
+ mInset += aInsetDelta;
+ // Re-initialize state that the reflow loop will compute.
+ mOcBounds.Clear();
+ mFcBounds.Clear();
+ mBlockEndEdgeOfChildren = 0;
+ mContainerWidth = 0;
+ mUsedOverflowWrap = false;
+ }
+ };
+
+ /**
+ * Internal helper for Reflow(); may be called repeatedly during a single
+ * Reflow() in order to implement text-wrap:balance.
+ * This method applies aTrialState.mInset during line-breaking to reduce
+ * the effective available inline-size (without affecting alignment).
+ */
+ nsReflowStatus TrialReflow(nsPresContext* aPresContext,
+ ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ TrialReflowState& aTrialState);
+
+ public:
+ /**
+ * Helper function for the frame ctor to register a ::marker frame.
+ */
+ void SetMarkerFrameForListItem(nsIFrame* aMarkerFrame);
+
+ /**
+ * Does all the real work for removing aDeletedFrame
+ * -- finds the line containing aDeletedFrame
+ * -- removes all aDeletedFrame next-in-flows (or all continuations,
+ * if REMOVE_FIXED_CONTINUATIONS is given)
+ * -- marks lines dirty as needed
+ * -- marks textruns dirty (unless FRAMES_ARE_EMPTY is given, in which
+ * case textruns do not need to be dirtied)
+ * -- destroys all removed frames
+ */
+ enum { REMOVE_FIXED_CONTINUATIONS = 0x02, FRAMES_ARE_EMPTY = 0x04 };
+ void DoRemoveFrame(DestroyContext&, nsIFrame* aDeletedFrame, uint32_t aFlags);
+
+ void ReparentFloats(nsIFrame* aFirstFrame, nsBlockFrame* aOldParent,
+ bool aReparentSiblings);
+
+ bool ComputeCustomOverflow(mozilla::OverflowAreas&) override;
+
+ void UnionChildOverflow(mozilla::OverflowAreas&) override;
+
+ /**
+ * Load all of aFrame's floats into the float manager iff aFrame is not a
+ * block formatting context. Handles all necessary float manager translations;
+ * assumes float manager is in aFrame's parent's coord system.
+ *
+ * Safe to call on non-blocks (does nothing).
+ */
+ static void RecoverFloatsFor(nsIFrame* aFrame, nsFloatManager& aFloatManager,
+ mozilla::WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ /**
+ * Determine if we have any pushed floats from a previous continuation.
+ *
+ * @returns true, if any of the floats at the beginning of our mFloats list
+ * have the NS_FRAME_IS_PUSHED_FLOAT bit set; false otherwise.
+ */
+ bool HasPushedFloatsFromPrevContinuation() const;
+
+ // @see nsIFrame::AddSizeOfExcludingThisForTree
+ void AddSizeOfExcludingThisForTree(nsWindowSizes&) const override;
+
+ /**
+ * Clears any -webkit-line-clamp ellipsis on a line in this block or one
+ * of its descendants.
+ */
+ void ClearLineClampEllipsis();
+
+ /**
+ * Returns whether this block is in a -webkit-line-clamp context. That is,
+ * whether this block is in a block formatting-context whose root block has
+ * -webkit-line-clamp: <n>.
+ */
+ bool IsInLineClampContext() const;
+
+ /**
+ * @return false iff this block does not have a float on any child list.
+ * This function is O(1).
+ */
+ bool MaybeHasFloats() const {
+ if (!mFloats.IsEmpty()) {
+ return true;
+ }
+ // XXX this could be replaced with HasPushedFloats() if we enforced
+ // removing the property when the frame list becomes empty.
+ nsFrameList* list = GetPushedFloats();
+ if (list && !list->IsEmpty()) {
+ return true;
+ }
+ // For the OverflowOutOfFlowsProperty I think we do enforce that, but it's
+ // a mix of out-of-flow frames, so that's why the method name has "Maybe".
+ return HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
+ }
+
+ protected:
+ /** grab overflow lines from this block's prevInFlow, and make them
+ * part of this block's mLines list.
+ * @return true if any lines were drained.
+ */
+ bool DrainOverflowLines();
+
+ /**
+ * Moves frames from our PushedFloats list back into our mFloats list.
+ */
+ void DrainSelfPushedFloats();
+
+ /**
+ * First calls DrainSelfPushedFloats() then grabs pushed floats from this
+ * block's prev-in-flow, and splice them into this block's mFloats list too.
+ */
+ void DrainPushedFloats();
+
+ /** Load all our floats into the float manager (without reflowing them).
+ * Assumes float manager is in our own coordinate system.
+ */
+ void RecoverFloats(nsFloatManager& aFloatManager, mozilla::WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ /** Reflow pushed floats
+ */
+ void ReflowPushedFloats(BlockReflowState& aState,
+ mozilla::OverflowAreas& aOverflowAreas);
+
+ /**
+ * Find any trailing BR clear from the last line of this block (or from its
+ * prev-in-flows).
+ */
+ mozilla::StyleClear FindTrailingClear();
+
+ /**
+ * Remove a float from our float list.
+ */
+ void RemoveFloat(nsIFrame* aFloat);
+ /**
+ * Remove a float from the float cache for the line its placeholder is on.
+ */
+ void RemoveFloatFromFloatCache(nsIFrame* aFloat);
+
+ void CollectFloats(nsIFrame* aFrame, nsFrameList& aList,
+ bool aCollectFromSiblings) {
+ if (MaybeHasFloats()) {
+ DoCollectFloats(aFrame, aList, aCollectFromSiblings);
+ }
+ }
+ void DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList,
+ bool aCollectFromSiblings);
+
+ // Remove a float, abs, rel positioned frame from the appropriate block's list
+ static void DoRemoveOutOfFlowFrame(DestroyContext&, nsIFrame*);
+
+ /** set up the conditions necessary for an resize reflow
+ * the primary task is to mark the minimumly sufficient lines dirty.
+ */
+ void PrepareResizeReflow(BlockReflowState& aState);
+
+ /**
+ * Reflow all lines that have been marked dirty.
+ * Returns whether an overflow-wrap break was used anywhere.
+ */
+ bool ReflowDirtyLines(BlockReflowState& aState);
+
+ /** Mark a given line dirty due to reflow being interrupted on or before it */
+ void MarkLineDirtyForInterrupt(nsLineBox* aLine);
+
+ //----------------------------------------
+ // Methods for line reflow
+ /**
+ * Reflow a line.
+ *
+ * @param aState
+ * the current reflow input
+ * @param aLine
+ * the line to reflow. can contain a single block frame or contain 1 or
+ * more inline frames.
+ * @param aKeepReflowGoing [OUT]
+ * indicates whether the caller should continue to reflow more lines
+ * @returns
+ * whether an overflow-wrap breakpoint was used
+ */
+ bool ReflowLine(BlockReflowState& aState, LineIterator aLine,
+ bool* aKeepReflowGoing);
+
+ // Return false if it needs another reflow because of reduced space
+ // between floats that are next to it (but not next to its top), and
+ // return true otherwise.
+ bool PlaceLine(BlockReflowState& aState, nsLineLayout& aLineLayout,
+ LineIterator aLine,
+ nsFloatManager::SavedState* aFloatStateBeforeLine,
+ nsFlowAreaRect& aFlowArea, // in-out
+ nscoord& aAvailableSpaceBSize, // in-out
+ bool* aKeepReflowGoing);
+
+ /**
+ * If NS_BLOCK_LOOK_FOR_DIRTY_FRAMES is set, call MarkLineDirty
+ * on any line with a child frame that is dirty.
+ */
+ void LazyMarkLinesDirty();
+
+ /**
+ * Mark |aLine| dirty, and, if necessary because of possible
+ * pull-up, mark the previous line dirty as well. Also invalidates textruns
+ * on those lines because the text in the lines might have changed due to
+ * addition/removal of frames.
+ * @param aLine the line to mark dirty
+ * @param aLineList the line list containing that line
+ */
+ void MarkLineDirty(LineIterator aLine, const nsLineList* aLineList);
+
+ // XXX where to go
+ bool IsLastLine(BlockReflowState& aState, LineIterator aLine);
+
+ void DeleteLine(BlockReflowState& aState, nsLineList::iterator aLine,
+ nsLineList::iterator aLineEnd);
+
+ //----------------------------------------
+ // Methods for individual frame reflow
+
+ bool ShouldApplyBStartMargin(BlockReflowState& aState, nsLineBox* aLine);
+
+ void ReflowBlockFrame(BlockReflowState& aState, LineIterator aLine,
+ bool* aKeepGoing);
+
+ // Returns whether an overflow-wrap break was used.
+ bool ReflowInlineFrames(BlockReflowState& aState, LineIterator aLine,
+ bool* aKeepLineGoing);
+
+ void DoReflowInlineFrames(
+ BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine,
+ nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize,
+ nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing,
+ LineReflowStatus* aLineReflowStatus, bool aAllowPullUp);
+
+ void ReflowInlineFrame(BlockReflowState& aState, nsLineLayout& aLineLayout,
+ LineIterator aLine, nsIFrame* aFrame,
+ LineReflowStatus* aLineReflowStatus);
+
+ // @param aReflowStatus an incomplete status indicates the float should be
+ // split but only if the available block-size is constrained.
+ void ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
+ nsIFrame* aFloat, nsReflowStatus& aReflowStatus);
+
+ //----------------------------------------
+ // Methods for pushing/pulling lines/frames
+
+ /**
+ * Create a next-in-flow, if necessary, for aFrame. If a new frame is
+ * created, place it in aLine if aLine is not null.
+ * @param aState the block reflow state
+ * @param aLine where to put a new frame
+ * @param aFrame the frame
+ * @return true if a new frame was created, false if not
+ */
+ bool CreateContinuationFor(BlockReflowState& aState, nsLineBox* aLine,
+ nsIFrame* aFrame);
+
+ /**
+ * Set line-break-before status in aState.mReflowStatus because aLine cannot
+ * be placed on this page/column and we don't want to break within ourselves.
+ * Also, mark the aLine dirty, and set aKeepReflowGoing to false;
+ */
+ void SetBreakBeforeStatusBeforeLine(BlockReflowState& aState,
+ LineIterator aLine,
+ bool* aKeepReflowGoing);
+
+ /**
+ * Push aLine (and any after it), since it cannot be placed on this
+ * page/column. Set aKeepReflowGoing to false and set
+ * flag aState.mReflowStatus as incomplete.
+ */
+ void PushTruncatedLine(BlockReflowState& aState, LineIterator aLine,
+ bool* aKeepReflowGoing);
+
+ void SplitLine(BlockReflowState& aState, nsLineLayout& aLineLayout,
+ LineIterator aLine, nsIFrame* aFrame,
+ LineReflowStatus* aLineReflowStatus);
+
+ /**
+ * Pull a frame from the next available location (one of our lines or
+ * one of our next-in-flows lines).
+ * @return the pulled frame or nullptr
+ */
+ nsIFrame* PullFrame(BlockReflowState& aState, LineIterator aLine);
+
+ /**
+ * Try to pull a frame out of a line pointed at by aFromLine.
+ *
+ * Note: pulling a frame from a line that is a place-holder frame
+ * doesn't automatically remove the corresponding float from the
+ * line's float array. This happens indirectly: either the line gets
+ * emptied (and destroyed) or the line gets reflowed (because we mark
+ * it dirty) and the code at the top of ReflowLine empties the
+ * array. So eventually, it will be removed, just not right away.
+ *
+ * @return the pulled frame or nullptr
+ */
+ nsIFrame* PullFrameFrom(nsLineBox* aLine, nsBlockFrame* aFromContainer,
+ nsLineList::iterator aFromLine);
+
+ /**
+ * Push the line after aLineBefore to the overflow line list.
+ * @param aLineBefore a line in 'mLines' (or LinesBegin() when
+ * pushing the first line)
+ */
+ void PushLines(BlockReflowState& aState, nsLineList::iterator aLineBefore);
+
+ void PropagateFloatDamage(BlockReflowState& aState, nsLineBox* aLine,
+ nscoord aDeltaBCoord);
+
+ void CheckFloats(BlockReflowState& aState);
+
+ //----------------------------------------
+ // List handling kludge
+
+ void ReflowOutsideMarker(nsIFrame* aMarkerFrame, BlockReflowState& aState,
+ ReflowOutput& aMetrics, nscoord aLineTop);
+
+ //----------------------------------------
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(LineIteratorProperty, nsLineIterator);
+
+ bool CanProvideLineIterator() const final { return true; }
+ nsILineIterator* GetLineIterator() final;
+
+ public:
+ bool HasOverflowLines() const {
+ return HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
+ }
+ FrameLines* GetOverflowLines() const;
+
+ protected:
+ FrameLines* RemoveOverflowLines();
+ void SetOverflowLines(FrameLines* aOverflowLines);
+ void DestroyOverflowLines();
+
+ /**
+ * This class is useful for efficiently modifying the out of flow
+ * overflow list. It gives the client direct writable access to
+ * the frame list temporarily but ensures that property is only
+ * written back if absolutely necessary.
+ */
+ struct nsAutoOOFFrameList {
+ nsFrameList mList;
+
+ explicit nsAutoOOFFrameList(nsBlockFrame* aBlock)
+ : mPropValue(aBlock->GetOverflowOutOfFlows()), mBlock(aBlock) {
+ if (mPropValue) {
+ mList = std::move(*mPropValue);
+ }
+ }
+ ~nsAutoOOFFrameList() {
+ mBlock->SetOverflowOutOfFlows(std::move(mList), mPropValue);
+ }
+
+ protected:
+ nsFrameList* const mPropValue;
+ nsBlockFrame* const mBlock;
+ };
+ friend struct nsAutoOOFFrameList;
+
+ nsFrameList* GetOverflowOutOfFlows() const;
+
+ // This takes ownership of the frames in aList.
+ void SetOverflowOutOfFlows(nsFrameList&& aList, nsFrameList* aPropValue);
+
+ /**
+ * @return the inside ::marker frame or nullptr if we don't have one.
+ */
+ nsIFrame* GetInsideMarker() const;
+
+ /**
+ * @return the outside ::marker frame or nullptr if we don't have one.
+ */
+ nsIFrame* GetOutsideMarker() const;
+
+ /**
+ * @return the outside ::marker frame list frame property.
+ */
+ nsFrameList* GetOutsideMarkerList() const;
+
+ /**
+ * @return true if this frame has pushed floats.
+ */
+ bool HasPushedFloats() const {
+ return HasAnyStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
+ }
+
+ // Get the pushed floats list, which is used for *temporary* storage
+ // of floats during reflow, between when we decide they don't fit in
+ // this block until our next continuation takes them.
+ nsFrameList* GetPushedFloats() const;
+ // Get the pushed floats list, or if there is not currently one,
+ // make a new empty one.
+ nsFrameList* EnsurePushedFloats();
+ // Remove and return the pushed floats list.
+ nsFrameList* RemovePushedFloats();
+
+#ifdef DEBUG
+ void VerifyLines(bool aFinalCheckOK);
+ void VerifyOverflowSituation();
+ int32_t GetDepth() const;
+#endif
+
+ nscoord mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ nscoord mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+
+ nsLineList mLines;
+
+ // List of all floats in this block
+ // XXXmats blocks rarely have floats, make it a frame property
+ nsFrameList mFloats;
+
+ friend class mozilla::BlockReflowState;
+ friend class nsBlockInFlowLineIterator;
+
+#ifdef DEBUG
+ public:
+ static bool gLamePaintMetrics;
+ static bool gLameReflowMetrics;
+ static bool gNoisy;
+ static bool gNoisyDamageRepair;
+ static bool gNoisyIntrinsic;
+ static bool gNoisyReflow;
+ static bool gReallyNoisyReflow;
+ static bool gNoisyFloatManager;
+ static bool gVerifyLines;
+ static bool gDisableResizeOpt;
+
+ static int32_t gNoiseIndent;
+
+ static const char* kReflowCommandType[];
+
+ protected:
+ static void InitDebugFlags();
+#endif
+};
+
+#ifdef DEBUG
+class AutoNoisyIndenter {
+ public:
+ explicit AutoNoisyIndenter(bool aDoIndent) : mIndented(aDoIndent) {
+ if (mIndented) {
+ nsBlockFrame::gNoiseIndent++;
+ }
+ }
+ ~AutoNoisyIndenter() {
+ if (mIndented) {
+ nsBlockFrame::gNoiseIndent--;
+ }
+ }
+
+ private:
+ bool mIndented;
+};
+#endif
+
+/**
+ * Iterates over all lines in the prev-in-flows/next-in-flows of this block.
+ */
+class nsBlockInFlowLineIterator {
+ public:
+ typedef nsBlockFrame::LineIterator LineIterator;
+ /**
+ * Set up the iterator to point to aLine which must be a normal line
+ * in aFrame (not an overflow line).
+ */
+ nsBlockInFlowLineIterator(nsBlockFrame* aFrame, LineIterator aLine);
+ /**
+ * Set up the iterator to point to the first line found starting from
+ * aFrame. Sets aFoundValidLine to false if there is no such line.
+ * After aFoundValidLine has returned false, don't call any methods on this
+ * object again.
+ */
+ nsBlockInFlowLineIterator(nsBlockFrame* aFrame, bool* aFoundValidLine);
+ /**
+ * Set up the iterator to point to the line that contains aFindFrame (either
+ * directly or indirectly). If aFrame is out of flow, or contained in an
+ * out-of-flow, finds the line containing the out-of-flow's placeholder. If
+ * the frame is not found, sets aFoundValidLine to false. After
+ * aFoundValidLine has returned false, don't call any methods on this
+ * object again.
+ */
+ nsBlockInFlowLineIterator(nsBlockFrame* aFrame, nsIFrame* aFindFrame,
+ bool* aFoundValidLine);
+
+ // Allow to be uninitialized (and then assigned from another object).
+ nsBlockInFlowLineIterator() : mFrame(nullptr) {}
+
+ LineIterator GetLine() { return mLine; }
+ bool IsLastLineInList();
+ nsBlockFrame* GetContainer() { return mFrame; }
+ bool GetInOverflow() { return mLineList != &mFrame->mLines; }
+
+ /**
+ * Returns the current line list we're iterating, null means
+ * we're iterating |mLines| of the container.
+ */
+ nsLineList* GetLineList() { return mLineList; }
+
+ /**
+ * Returns the end-iterator of whatever line list we're in.
+ */
+ LineIterator End();
+
+ /**
+ * Returns false if there are no more lines. After this has returned false,
+ * don't call any methods on this object again.
+ */
+ bool Next();
+ /**
+ * Returns false if there are no more lines. After this has returned false,
+ * don't call any methods on this object again.
+ */
+ bool Prev();
+
+ // XXX nsBlockFrame uses this internally in one place. Try to remove it.
+ // XXX uhm, and nsBidiPresUtils::Resolve too.
+ nsBlockInFlowLineIterator(nsBlockFrame* aFrame, LineIterator aLine,
+ bool aInOverflow);
+
+ private:
+ nsBlockFrame* mFrame;
+ LineIterator mLine;
+ nsLineList* mLineList; // the line list mLine is in
+
+ /**
+ * Moves iterator to next valid line reachable from the current block.
+ * Returns false if there are no valid lines.
+ */
+ bool FindValidLine();
+};
+
+#endif /* nsBlockFrame_h___ */
diff --git a/layout/generic/nsBlockReflowContext.cpp b/layout/generic/nsBlockReflowContext.cpp
new file mode 100644
index 0000000000..ceae9f4849
--- /dev/null
+++ b/layout/generic/nsBlockReflowContext.cpp
@@ -0,0 +1,440 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* class that a parent frame uses to reflow a block frame */
+
+#include "nsBlockReflowContext.h"
+#include "BlockReflowState.h"
+#include "nsFloatManager.h"
+#include "nsColumnSetFrame.h"
+#include "nsContainerFrame.h"
+#include "nsBlockFrame.h"
+#include "nsLineBox.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+
+#ifdef DEBUG
+# include "nsBlockDebugFlags.h" // For NOISY_BLOCK_DIR_MARGINS
+#endif
+
+nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext,
+ const ReflowInput& aParentRI)
+ : mPresContext(aPresContext),
+ mOuterReflowInput(aParentRI),
+ mFrame(nullptr),
+ mSpace(aParentRI.GetWritingMode()),
+ mICoord(0),
+ mBCoord(0),
+ mMetrics(aParentRI) {}
+
+static nsIFrame* DescendIntoBlockLevelFrame(nsIFrame* aFrame) {
+ LayoutFrameType type = aFrame->Type();
+ if (type == LayoutFrameType::ColumnSet) {
+ static_cast<nsColumnSetFrame*>(aFrame)->DrainOverflowColumns();
+ nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
+ if (child) {
+ return DescendIntoBlockLevelFrame(child);
+ }
+ }
+ return aFrame;
+}
+
+bool nsBlockReflowContext::ComputeCollapsedBStartMargin(
+ const ReflowInput& aRI, nsCollapsingMargin* aMargin,
+ nsIFrame* aClearanceFrame, bool* aMayNeedRetry, bool* aBlockIsEmpty) {
+ WritingMode wm = aRI.GetWritingMode();
+ WritingMode parentWM = mMetrics.GetWritingMode();
+
+ // Include block-start element of frame's margin
+ aMargin->Include(aRI.ComputedLogicalMargin(parentWM).BStart(parentWM));
+
+ // The inclusion of the block-end margin when empty is done by the caller
+ // since it doesn't need to be done by the top-level (non-recursive)
+ // caller.
+
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ aRI.mFrame->ListTag(stdout);
+ printf(": %d => %d\n", aRI.ComputedLogicalMargin(wm).BStart(wm),
+ aMargin->get());
+#endif
+
+ bool dirtiedLine = false;
+ bool setBlockIsEmpty = false;
+
+ // Calculate the frame's generational block-start-margin from its child
+ // blocks. Note that if the frame has a non-zero block-start-border or
+ // block-start-padding then this step is skipped because it will be a margin
+ // root. It is also skipped if the frame is a margin root for other
+ // reasons.
+ nsIFrame* frame = DescendIntoBlockLevelFrame(aRI.mFrame);
+ nsPresContext* prescontext = frame->PresContext();
+ nsBlockFrame* block = nullptr;
+ if (0 == aRI.ComputedLogicalBorderPadding(wm).BStart(wm)) {
+ block = do_QueryFrame(frame);
+ if (block) {
+ bool bStartMarginRoot, unused;
+ block->IsMarginRoot(&bStartMarginRoot, &unused);
+ if (bStartMarginRoot) {
+ block = nullptr;
+ }
+ }
+ }
+
+ // iterate not just through the lines of 'block' but also its
+ // overflow lines and the normal and overflow lines of its next in
+ // flows. Note that this will traverse some frames more than once:
+ // for example, if A contains B and A->nextinflow contains
+ // B->nextinflow, we'll traverse B->nextinflow twice. But this is
+ // OK because our traversal is idempotent.
+ for (; block; block = static_cast<nsBlockFrame*>(block->GetNextInFlow())) {
+ for (int overflowLines = 0; overflowLines <= 1; ++overflowLines) {
+ nsBlockFrame::LineIterator line;
+ nsBlockFrame::LineIterator line_end;
+ bool anyLines = true;
+ if (overflowLines) {
+ nsBlockFrame::FrameLines* frames = block->GetOverflowLines();
+ nsLineList* lines = frames ? &frames->mLines : nullptr;
+ if (!lines) {
+ anyLines = false;
+ } else {
+ line = lines->begin();
+ line_end = lines->end();
+ }
+ } else {
+ line = block->LinesBegin();
+ line_end = block->LinesEnd();
+ }
+ for (; anyLines && line != line_end; ++line) {
+ if (!aClearanceFrame && line->HasClearance()) {
+ // If we don't have a clearance frame, then we're computing
+ // the collapsed margin in the first pass, assuming that all
+ // lines have no clearance. So clear their clearance flags.
+ line->ClearHasClearance();
+ line->MarkDirty();
+ dirtiedLine = true;
+ }
+
+ bool isEmpty;
+ if (line->IsInline()) {
+ isEmpty = line->IsEmpty();
+ } else {
+ nsIFrame* kid = line->mFirstChild;
+ if (kid == aClearanceFrame) {
+ line->SetHasClearance();
+ line->MarkDirty();
+ dirtiedLine = true;
+ if (!setBlockIsEmpty && aBlockIsEmpty) {
+ setBlockIsEmpty = true;
+ *aBlockIsEmpty = false;
+ }
+ goto done;
+ }
+ // Here is where we recur. Now that we have determined that a
+ // generational collapse is required we need to compute the
+ // child blocks margin and so in so that we can look into
+ // it. For its margins to be computed we need to have a reflow
+ // input for it.
+
+ // We may have to construct an extra reflow input here if
+ // we drilled down through a block wrapper. At the moment
+ // we can only drill down one level so we only have to support
+ // one extra reflow input.
+ const ReflowInput* outerReflowInput = &aRI;
+ if (frame != aRI.mFrame) {
+ NS_ASSERTION(frame->GetParent() == aRI.mFrame,
+ "Can only drill through one level of block wrapper");
+ LogicalSize availSpace = aRI.ComputedSize(frame->GetWritingMode());
+ outerReflowInput =
+ new ReflowInput(prescontext, aRI, frame, availSpace);
+ }
+ {
+ LogicalSize availSpace =
+ outerReflowInput->ComputedSize(kid->GetWritingMode());
+ ReflowInput innerReflowInput(prescontext, *outerReflowInput, kid,
+ availSpace);
+ // Record that we're being optimistic by assuming the kid
+ // has no clearance
+ if (kid->StyleDisplay()->mClear != StyleClear::None ||
+ !nsBlockFrame::BlockCanIntersectFloats(kid)) {
+ *aMayNeedRetry = true;
+ }
+ if (ComputeCollapsedBStartMargin(innerReflowInput, aMargin,
+ aClearanceFrame, aMayNeedRetry,
+ &isEmpty)) {
+ line->MarkDirty();
+ dirtiedLine = true;
+ }
+ if (isEmpty) {
+ LogicalMargin innerMargin =
+ innerReflowInput.ComputedLogicalMargin(parentWM);
+ aMargin->Include(innerMargin.BEnd(parentWM));
+ }
+ }
+ if (outerReflowInput != &aRI) {
+ delete const_cast<ReflowInput*>(outerReflowInput);
+ }
+ }
+ if (!isEmpty) {
+ if (!setBlockIsEmpty && aBlockIsEmpty) {
+ setBlockIsEmpty = true;
+ *aBlockIsEmpty = false;
+ }
+ goto done;
+ }
+ }
+ if (!setBlockIsEmpty && aBlockIsEmpty) {
+ // The first time we reach here is when this is the first block
+ // and we have processed all its normal lines.
+ setBlockIsEmpty = true;
+ // All lines are empty, or we wouldn't be here!
+ *aBlockIsEmpty = aRI.mFrame->IsSelfEmpty();
+ }
+ }
+ }
+done:
+
+ if (!setBlockIsEmpty && aBlockIsEmpty) {
+ *aBlockIsEmpty = aRI.mFrame->IsEmpty();
+ }
+
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ aRI.mFrame->ListTag(stdout);
+ printf(": => %d\n", aMargin->get());
+#endif
+
+ return dirtiedLine;
+}
+
+void nsBlockReflowContext::ReflowBlock(const LogicalRect& aSpace,
+ bool aApplyBStartMargin,
+ nsCollapsingMargin& aPrevMargin,
+ nscoord aClearance, nsLineBox* aLine,
+ ReflowInput& aFrameRI,
+ nsReflowStatus& aFrameReflowStatus,
+ BlockReflowState& aState) {
+ mFrame = aFrameRI.mFrame;
+ mWritingMode = aState.mReflowInput.GetWritingMode();
+ mContainerSize = aState.ContainerSize();
+ mSpace = aSpace;
+
+ if (!aState.IsAdjacentWithBStart()) {
+ aFrameRI.mFlags.mIsTopOfPage = false; // make sure this is cleared
+ }
+
+ if (aApplyBStartMargin) {
+ mBStartMargin = aPrevMargin;
+
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ mOuterReflowInput.mFrame->ListTag(stdout);
+ printf(": reflowing ");
+ mFrame->ListTag(stdout);
+ printf(" margin => %d, clearance => %d\n", mBStartMargin.get(), aClearance);
+#endif
+
+ // Adjust the available size if it's constrained so that the
+ // child frame doesn't think it can reflow into its margin area.
+ if (mWritingMode.IsOrthogonalTo(mFrame->GetWritingMode())) {
+ if (NS_UNCONSTRAINEDSIZE != aFrameRI.AvailableISize()) {
+ aFrameRI.SetAvailableISize(std::max(
+ 0, aFrameRI.AvailableISize() - mBStartMargin.get() - aClearance));
+ }
+ } else {
+ if (NS_UNCONSTRAINEDSIZE != aFrameRI.AvailableBSize()) {
+ aFrameRI.SetAvailableBSize(std::max(
+ 0, aFrameRI.AvailableBSize() - mBStartMargin.get() - aClearance));
+ }
+ }
+ } else {
+ // nsBlockFrame::ReflowBlock might call us multiple times with
+ // *different* values of aApplyBStartMargin.
+ mBStartMargin.Zero();
+ }
+
+ nscoord tI = 0, tB = 0;
+ // The values of x and y do not matter for floats, so don't bother
+ // calculating them. Floats are guaranteed to have their own float
+ // manager, so tI and tB don't matter. mICoord and mBCoord don't
+ // matter becacuse they are only used in PlaceBlock, which is not used
+ // for floats.
+ if (aLine) {
+ // Compute inline/block coordinate where reflow will begin. Use the
+ // rules from 10.3.3 to determine what to apply. At this point in the
+ // reflow auto inline-start/end margins will have a zero value.
+ LogicalMargin usedMargin = aFrameRI.ComputedLogicalMargin(mWritingMode);
+ mICoord = mSpace.IStart(mWritingMode) + usedMargin.IStart(mWritingMode);
+ mBCoord = mSpace.BStart(mWritingMode) + mBStartMargin.get() + aClearance;
+
+ LogicalRect space(
+ mWritingMode, mICoord, mBCoord,
+ mSpace.ISize(mWritingMode) - usedMargin.IStartEnd(mWritingMode),
+ mSpace.BSize(mWritingMode) - usedMargin.BStartEnd(mWritingMode));
+ tI = space.LineLeft(mWritingMode, mContainerSize);
+ tB = mBCoord;
+
+ if (!mFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ aFrameRI.mBlockDelta =
+ mOuterReflowInput.mBlockDelta + mBCoord - aLine->BStart();
+ }
+ }
+
+#ifdef DEBUG
+ mMetrics.ISize(mWritingMode) = nscoord(0xdeadbeef);
+ mMetrics.BSize(mWritingMode) = nscoord(0xdeadbeef);
+#endif
+
+ mOuterReflowInput.mFloatManager->Translate(tI, tB);
+ mFrame->Reflow(mPresContext, mMetrics, aFrameRI, aFrameReflowStatus);
+ mOuterReflowInput.mFloatManager->Translate(-tI, -tB);
+
+#ifdef DEBUG
+ if (!aFrameReflowStatus.IsInlineBreakBefore()) {
+ if ((ABSURD_SIZE(mMetrics.ISize(mWritingMode)) ||
+ ABSURD_SIZE(mMetrics.BSize(mWritingMode))) &&
+ !mFrame->GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ printf("nsBlockReflowContext: ");
+ mFrame->ListTag(stdout);
+ printf(" metrics=%d,%d!\n", mMetrics.ISize(mWritingMode),
+ mMetrics.BSize(mWritingMode));
+ }
+ if ((mMetrics.ISize(mWritingMode) == nscoord(0xdeadbeef)) ||
+ (mMetrics.BSize(mWritingMode) == nscoord(0xdeadbeef))) {
+ printf("nsBlockReflowContext: ");
+ mFrame->ListTag(stdout);
+ printf(" didn't set i/b %d,%d!\n", mMetrics.ISize(mWritingMode),
+ mMetrics.BSize(mWritingMode));
+ }
+ }
+#endif
+
+ if (!mFrame->HasOverflowAreas()) {
+ mMetrics.SetOverflowAreasToDesiredBounds();
+ }
+
+ if (!aFrameReflowStatus.IsInlineBreakBefore() &&
+ !aFrameRI.WillReflowAgainForClearance() &&
+ aFrameReflowStatus.IsFullyComplete()) {
+ // If mFrame is fully-complete and has a next-in-flow, we need to delete
+ // them now. Do not do this when a break-before is signaled or when a
+ // clearance frame is discovered in mFrame's subtree because mFrame is going
+ // to get reflowed again (whether the frame is (in)complete is undefined in
+ // that case anyway).
+ if (nsIFrame* kidNextInFlow = mFrame->GetNextInFlow()) {
+ // Remove all of the childs next-in-flows. Make sure that we ask
+ // the right parent to do the removal (it's possible that the
+ // parent is not this because we are executing pullup code).
+ // Floats will eventually be removed via nsBlockFrame::RemoveFloat
+ // which detaches the placeholder from the float.
+ nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker,
+ mFrame);
+ nsIFrame::DestroyContext context(mPresContext->PresShell());
+ kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, kidNextInFlow,
+ true);
+ }
+ }
+}
+
+/**
+ * Attempt to place the block frame within the available space. If
+ * it fits, apply inline-dir ("horizontal") positioning (CSS 10.3.3),
+ * collapse margins (CSS2 8.3.1). Also apply relative positioning.
+ */
+bool nsBlockReflowContext::PlaceBlock(const ReflowInput& aReflowInput,
+ bool aForceFit, nsLineBox* aLine,
+ nsCollapsingMargin& aBEndMarginResult,
+ OverflowAreas& aOverflowAreas,
+ const nsReflowStatus& aReflowStatus) {
+ // Compute collapsed block-end margin value.
+ WritingMode parentWM = mMetrics.GetWritingMode();
+
+ // Don't apply the block-end margin if the block has a *later* sibling across
+ // column-span split.
+ if (aReflowStatus.IsComplete() && !mFrame->HasColumnSpanSiblings()) {
+ aBEndMarginResult = mMetrics.mCarriedOutBEndMargin;
+ aBEndMarginResult.Include(
+ aReflowInput.ComputedLogicalMargin(parentWM).BEnd(parentWM));
+ } else {
+ // The used block-end-margin is set to zero before a break.
+ aBEndMarginResult.Zero();
+ }
+
+ nscoord backupContainingBlockAdvance = 0;
+
+ // Check whether the block's block-end margin collapses with its block-start
+ // margin. See CSS 2.1 section 8.3.1; those rules seem to match
+ // nsBlockFrame::IsEmpty(). Any such block must have zero block-size so
+ // check that first. Note that a block can have clearance and still
+ // have adjoining block-start/end margins, because the clearance goes
+ // above the block-start margin.
+ // Mark the frame as non-dirty; it has been reflowed (or we wouldn't
+ // be here), and we don't want to assert in CachedIsEmpty()
+ mFrame->RemoveStateBits(NS_FRAME_IS_DIRTY);
+ bool empty = 0 == mMetrics.BSize(parentWM) && aLine->CachedIsEmpty();
+ if (empty) {
+ // Collapse the block-end margin with the block-start margin that was
+ // already applied.
+ aBEndMarginResult.Include(mBStartMargin);
+
+#ifdef NOISY_BLOCK_DIR_MARGINS
+ printf(" ");
+ mOuterReflowInput.mFrame->ListTag(stdout);
+ printf(": ");
+ mFrame->ListTag(stdout);
+ printf(
+ " -- collapsing block start & end margin together; BStart=%d "
+ "spaceBStart=%d\n",
+ mBCoord, mSpace.BStart(mWritingMode));
+#endif
+ // Section 8.3.1 of CSS 2.1 says that blocks with adjoining
+ // "top/bottom" (i.e. block-start/end) margins whose top margin collapses
+ // with their parent's top margin should have their top border-edge at the
+ // top border-edge of their parent. We actually don't have to do
+ // anything special to make this happen. In that situation,
+ // nsBlockFrame::ShouldApplyBStartMargin will have returned false,
+ // and mBStartMargin and aClearance will have been zero in
+ // ReflowBlock.
+
+ // If we did apply our block-start margin, but now we're collapsing it
+ // into the block-end margin, we need to back up the containing
+ // block's bCoord-advance by our block-start margin so that it doesn't get
+ // counted twice. Note that here we're allowing the line's bounds
+ // to become different from the block's position; we do this
+ // because the containing block will place the next line at the
+ // line's BEnd, and it must place the next line at a different
+ // point from where this empty block will be.
+ backupContainingBlockAdvance = mBStartMargin.get();
+ }
+
+ // See if the frame fit. If it's the first frame or empty then it
+ // always fits. If the block-size is unconstrained then it always fits,
+ // even if there's some sort of integer overflow that makes bCoord +
+ // mMetrics.BSize() appear to go beyond the available block size.
+ if (!empty && !aForceFit &&
+ mSpace.BSize(mWritingMode) != NS_UNCONSTRAINEDSIZE) {
+ nscoord bEnd =
+ mBCoord - backupContainingBlockAdvance + mMetrics.BSize(mWritingMode);
+ if (bEnd > mSpace.BEnd(mWritingMode)) {
+ // didn't fit, we must acquit.
+ mFrame->DidReflow(mPresContext, &aReflowInput);
+ return false;
+ }
+ }
+
+ aLine->SetBounds(mWritingMode, mICoord,
+ mBCoord - backupContainingBlockAdvance,
+ mMetrics.ISize(mWritingMode), mMetrics.BSize(mWritingMode),
+ mContainerSize);
+
+ // Now place the frame and complete the reflow process
+ nsContainerFrame::FinishReflowChild(
+ mFrame, mPresContext, mMetrics, &aReflowInput, mWritingMode,
+ LogicalPoint(mWritingMode, mICoord, mBCoord), mContainerSize,
+ nsIFrame::ReflowChildFlags::ApplyRelativePositioning);
+
+ aOverflowAreas = mMetrics.mOverflowAreas + mFrame->GetPosition();
+
+ return true;
+}
diff --git a/layout/generic/nsBlockReflowContext.h b/layout/generic/nsBlockReflowContext.h
new file mode 100644
index 0000000000..a573e644ee
--- /dev/null
+++ b/layout/generic/nsBlockReflowContext.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* class that a parent frame uses to reflow a block frame */
+
+#ifndef nsBlockReflowContext_h___
+#define nsBlockReflowContext_h___
+
+#include "mozilla/ReflowOutput.h"
+
+class nsIFrame;
+class nsLineBox;
+class nsPresContext;
+class nsReflowStatus;
+namespace mozilla {
+class BlockReflowState;
+} // namespace mozilla
+
+/**
+ * An encapsulation of the state and algorithm for reflowing block frames.
+ */
+class nsBlockReflowContext {
+ using BlockReflowState = mozilla::BlockReflowState;
+ using ReflowInput = mozilla::ReflowInput;
+ using ReflowOutput = mozilla::ReflowOutput;
+
+ public:
+ nsBlockReflowContext(nsPresContext* aPresContext,
+ const ReflowInput& aParentRI);
+ ~nsBlockReflowContext() = default;
+
+ void ReflowBlock(const mozilla::LogicalRect& aSpace, bool aApplyBStartMargin,
+ nsCollapsingMargin& aPrevMargin, nscoord aClearance,
+ nsLineBox* aLine, ReflowInput& aReflowInput,
+ nsReflowStatus& aReflowStatus, BlockReflowState& aState);
+
+ bool PlaceBlock(const ReflowInput& aReflowInput, bool aForceFit,
+ nsLineBox* aLine,
+ nsCollapsingMargin& aBEndMarginResult /* out */,
+ mozilla::OverflowAreas& aOverflowAreas,
+ const nsReflowStatus& aReflowStatus);
+
+ nsCollapsingMargin& GetCarriedOutBEndMargin() {
+ return mMetrics.mCarriedOutBEndMargin;
+ }
+
+ const ReflowOutput& GetMetrics() const { return mMetrics; }
+
+ /**
+ * Computes the collapsed block-start margin (in the context's parent's
+ * writing mode) for a block whose reflow input is in aRI.
+ * The computed margin is added into aMargin, whose writing mode is the
+ * parent's mode as found in mMetrics.GetWritingMode(); note this may not be
+ * the block's own writing mode as found in aRI.
+ * If aClearanceFrame is null then this is the first optimistic pass which
+ * shall assume that no frames have clearance, and we clear the HasClearance
+ * on all frames encountered.
+ * If non-null, this is the second pass and the caller has decided
+ * aClearanceFrame needs clearance (and we will therefore stop collapsing
+ * there); also, this function is responsible for marking it with
+ * SetHasClearance.
+ * If in the optimistic pass any frame is encountered that might possibly
+ * need clearance (i.e., if we really needed the optimism assumption) then
+ * we set aMayNeedRetry to true.
+ * We return true if we changed the clearance state of any line and marked it
+ * dirty.
+ */
+ bool ComputeCollapsedBStartMargin(const ReflowInput& aRI,
+ nsCollapsingMargin* aMargin,
+ nsIFrame* aClearanceFrame,
+ bool* aMayNeedRetry,
+ bool* aIsEmpty = nullptr);
+
+ protected:
+ nsPresContext* mPresContext;
+ const ReflowInput& mOuterReflowInput;
+
+ nsIFrame* mFrame;
+ mozilla::LogicalRect mSpace;
+
+ nscoord mICoord, mBCoord;
+ nsSize mContainerSize;
+ mozilla::WritingMode mWritingMode;
+ ReflowOutput mMetrics;
+ nsCollapsingMargin mBStartMargin;
+};
+
+#endif /* nsBlockReflowContext_h___ */
diff --git a/layout/generic/nsCanvasFrame.cpp b/layout/generic/nsCanvasFrame.cpp
new file mode 100644
index 0000000000..64c56b1c00
--- /dev/null
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -0,0 +1,835 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object that goes directly inside the document's scrollbars */
+
+#include "nsCanvasFrame.h"
+
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/dom/AnonymousContent.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsContainerFrame.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSRendering.h"
+#include "nsDisplayList.h"
+#include "nsFrameManager.h"
+#include "nsGkAtoms.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+nsCanvasFrame* NS_NewCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsCanvasFrame(aStyle, aPresShell->GetPresContext());
+}
+
+nsIPopupContainer* nsIPopupContainer::GetPopupContainer(PresShell* aPresShell) {
+ return aPresShell ? aPresShell->GetCanvasFrame() : nullptr;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsCanvasFrame)
+
+NS_QUERYFRAME_HEAD(nsCanvasFrame)
+ NS_QUERYFRAME_ENTRY(nsCanvasFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+ NS_QUERYFRAME_ENTRY(nsIPopupContainer)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+void nsCanvasFrame::ShowCustomContentContainer() {
+ if (mCustomContentContainer) {
+ mCustomContentContainer->UnsetAttr(kNameSpaceID_None, nsGkAtoms::hidden,
+ true);
+ }
+}
+
+void nsCanvasFrame::HideCustomContentContainer() {
+ if (mCustomContentContainer) {
+ mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden,
+ u"true"_ns, true);
+ }
+}
+
+// Do this off a script-runner because some anon content might load CSS which we
+// don't want to deal with while doing frame construction.
+void InsertAnonymousContentInContainer(Document& aDoc, Element& aContainer) {
+ if (!aContainer.IsInComposedDoc() || aDoc.GetAnonymousContents().IsEmpty()) {
+ return;
+ }
+ for (RefPtr<AnonymousContent>& anonContent : aDoc.GetAnonymousContents()) {
+ if (nsCOMPtr<nsINode> parent = anonContent->Host()->GetParentNode()) {
+ // Parent had better be an old custom content container already
+ // removed from a reframe. Forget about it since we're about to get
+ // inserted in a new one.
+ //
+ // TODO(emilio): Maybe we should extend PostDestroyData and do this
+ // stuff there instead, or something...
+ MOZ_ASSERT(parent != &aContainer);
+ MOZ_ASSERT(parent->IsElement());
+ MOZ_ASSERT(parent->AsElement()->IsRootOfNativeAnonymousSubtree());
+ MOZ_ASSERT(!parent->IsInComposedDoc());
+ MOZ_ASSERT(!parent->GetParentNode());
+
+ parent->RemoveChildNode(anonContent->Host(), true);
+ }
+ aContainer.AppendChildTo(anonContent->Host(), true, IgnoreErrors());
+ }
+ // Flush frames now. This is really sadly needed, but otherwise stylesheets
+ // inserted by the above DOM changes might not be processed in time for layout
+ // to run.
+ // FIXME(emilio): This is because we have a script-running checkpoint just
+ // after ProcessPendingRestyles but before DoReflow. That seems wrong! Ideally
+ // the whole layout / styling pass should be atomic.
+ aDoc.FlushPendingNotifications(FlushType::Frames);
+}
+
+nsresult nsCanvasFrame::CreateAnonymousContent(
+ nsTArray<ContentInfo>& aElements) {
+ MOZ_ASSERT(!mCustomContentContainer);
+
+ if (!mContent) {
+ return NS_OK;
+ }
+
+ Document* doc = mContent->OwnerDoc();
+
+ // Create the custom content container.
+ mCustomContentContainer = doc->CreateHTMLElement(nsGkAtoms::div);
+#ifdef DEBUG
+ // We restyle our mCustomContentContainer, even though it's root anonymous
+ // content. Normally that's not OK because the frame constructor doesn't know
+ // how to order the frame tree in such cases, but we make this work for this
+ // particular case, so it's OK.
+ mCustomContentContainer->SetProperty(nsGkAtoms::restylableAnonymousNode,
+ reinterpret_cast<void*>(true));
+#endif // DEBUG
+
+ mCustomContentContainer->SetProperty(
+ nsGkAtoms::docLevelNativeAnonymousContent, reinterpret_cast<void*>(true));
+
+ // This will usually be done by the caller, but in this case we do it here,
+ // since we reuse the document's AnoymousContent list, and those survive
+ // across reframes and thus may already be flagged as being in an anonymous
+ // subtree. We don't really want to have this semi-broken state where
+ // anonymous nodes have a non-anonymous.
+ mCustomContentContainer->SetIsNativeAnonymousRoot();
+
+ aElements.AppendElement(mCustomContentContainer);
+
+ // Do not create an accessible object for the container.
+ mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::role,
+ u"presentation"_ns, false);
+
+ mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+ u"moz-custom-content-container"_ns, false);
+
+ // Only create a frame for mCustomContentContainer if it has some children.
+ if (doc->GetAnonymousContents().IsEmpty()) {
+ HideCustomContentContainer();
+ } else {
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "InsertAnonymousContentInContainer",
+ [doc = RefPtr{doc}, container = RefPtr{mCustomContentContainer.get()}] {
+ InsertAnonymousContentInContainer(*doc, *container);
+ }));
+ }
+
+ // Create a default tooltip element for system privileged documents.
+ if (XRE_IsParentProcess() && doc->NodePrincipal()->IsSystemPrincipal()) {
+ nsNodeInfoManager* nodeInfoManager = doc->NodeInfoManager();
+ RefPtr<NodeInfo> nodeInfo = nodeInfoManager->GetNodeInfo(
+ nsGkAtoms::tooltip, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
+
+ nsresult rv = NS_NewXULElement(getter_AddRefs(mTooltipContent),
+ nodeInfo.forget(), dom::NOT_FROM_PARSER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_default, u"true"_ns,
+ false);
+ // Set the page attribute so XULTooltipElement::PostHandleEvent will find
+ // the text for the tooltip from the currently hovered element.
+ mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::page, u"true"_ns,
+ false);
+
+ mTooltipContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
+ reinterpret_cast<void*>(true));
+
+ aElements.AppendElement(mTooltipContent);
+ }
+
+#ifdef DEBUG
+ for (auto& element : aElements) {
+ MOZ_ASSERT(element.mContent->GetProperty(
+ nsGkAtoms::docLevelNativeAnonymousContent),
+ "NAC from the canvas frame needs to be document-level, otherwise"
+ " it (1) inherits from the document which is unexpected, and (2)"
+ " StyleChildrenIterator won't be able to find it properly");
+ }
+#endif
+ return NS_OK;
+}
+
+void nsCanvasFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) {
+ if (mCustomContentContainer) {
+ aElements.AppendElement(mCustomContentContainer);
+ }
+ if (mTooltipContent) {
+ aElements.AppendElement(mTooltipContent);
+ }
+}
+
+void nsCanvasFrame::Destroy(DestroyContext& aContext) {
+ nsIScrollableFrame* sf = PresShell()->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ sf->RemoveScrollPositionListener(this);
+ }
+
+ aContext.AddAnonymousContent(mCustomContentContainer.forget());
+ if (mTooltipContent) {
+ aContext.AddAnonymousContent(mTooltipContent.forget());
+ }
+ nsContainerFrame::Destroy(aContext);
+}
+
+void nsCanvasFrame::ScrollPositionWillChange(nscoord aX, nscoord aY) {
+ if (mDoPaintFocus) {
+ mDoPaintFocus = false;
+ PresShell()->GetRootFrame()->InvalidateFrameSubtree();
+ }
+}
+
+NS_IMETHODIMP
+nsCanvasFrame::SetHasFocus(bool aHasFocus) {
+ if (mDoPaintFocus != aHasFocus) {
+ mDoPaintFocus = aHasFocus;
+ PresShell()->GetRootFrame()->InvalidateFrameSubtree();
+
+ if (!mAddedScrollPositionListener) {
+ nsIScrollableFrame* sf = PresShell()->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ sf->AddScrollPositionListener(this);
+ mAddedScrollPositionListener = true;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void nsCanvasFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ NS_ASSERTION(aListID != FrameChildListID::Principal || aChildList.IsEmpty() ||
+ aChildList.OnlyChild(),
+ "Primary child list can have at most one frame in it");
+ nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
+}
+
+void nsCanvasFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+#ifdef DEBUG
+ MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
+ if (!mFrames.IsEmpty()) {
+ for (nsIFrame* f : aFrameList) {
+ // We only allow native anonymous child frames to be in principal child
+ // list in canvas frame.
+ MOZ_ASSERT(f->GetContent()->IsInNativeAnonymousSubtree(),
+ "invalid child list");
+ }
+ }
+ nsIFrame::VerifyDirtyBitSet(aFrameList);
+#endif
+ nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
+}
+
+void nsCanvasFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ // Because we only support a single child frame inserting is the same
+ // as appending
+ MOZ_ASSERT(!aPrevFrame, "unexpected previous sibling frame");
+ AppendFrames(aListID, std::move(aFrameList));
+}
+
+#ifdef DEBUG
+void nsCanvasFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
+ nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
+}
+#endif
+
+nsRect nsCanvasFrame::CanvasArea() const {
+ // Not clear which overflow rect we want here, but it probably doesn't
+ // matter.
+ nsRect result(InkOverflowRect());
+
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(GetParent());
+ if (scrollableFrame) {
+ nsRect portRect = scrollableFrame->GetScrollPortRect();
+ result.UnionRect(result, nsRect(nsPoint(0, 0), portRect.Size()));
+ }
+ return result;
+}
+
+Element* nsCanvasFrame::GetDefaultTooltip() { return mTooltipContent; }
+
+void nsDisplayCanvasBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ nsPoint offset = ToReferenceFrame();
+ nsRect bgClipRect = frame->CanvasArea() + offset;
+ if (NS_GET_A(mColor) > 0) {
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Rect devPxRect =
+ NSRectToSnappedRect(bgClipRect, appUnitsPerDevPixel, *drawTarget);
+ drawTarget->FillRect(devPxRect, ColorPattern(ToDeviceColor(mColor)));
+ }
+}
+
+bool nsDisplayCanvasBackgroundColor::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ nsPoint offset = ToReferenceFrame();
+ nsRect bgClipRect = frame->CanvasArea() + offset;
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ LayoutDeviceRect rect =
+ LayoutDeviceRect::FromAppUnits(bgClipRect, appUnitsPerDevPixel);
+
+ wr::LayoutRect r = wr::ToLayoutRect(rect);
+ aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false,
+ wr::ToColorF(ToDeviceColor(mColor)));
+ return true;
+}
+
+void nsDisplayCanvasBackgroundColor::WriteDebugInfo(
+ std::stringstream& aStream) {
+ aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor)
+ << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor)
+ << ")";
+}
+
+void nsDisplayCanvasBackgroundImage::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ nsPoint offset = ToReferenceFrame();
+ nsRect bgClipRect = frame->CanvasArea() + offset;
+
+ PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &bgClipRect);
+}
+
+bool nsDisplayCanvasBackgroundImage::IsSingleFixedPositionImage(
+ nsDisplayListBuilder* aBuilder, const nsRect& aClipRect,
+ gfxRect* aDestRect) {
+ if (!mBackgroundStyle) return false;
+
+ if (mBackgroundStyle->StyleBackground()->mImage.mLayers.Length() != 1)
+ return false;
+
+ nsPresContext* presContext = mFrame->PresContext();
+ uint32_t flags = aBuilder->GetBackgroundPaintFlags();
+ nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ const nsStyleImageLayers::Layer& layer =
+ mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
+
+ if (layer.mAttachment != StyleImageLayerAttachment::Fixed) return false;
+
+ nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
+ presContext, mFrame, flags, borderArea, aClipRect, layer);
+
+ // We only care about images here, not gradients.
+ if (!mIsRasterImage) return false;
+
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ *aDestRect =
+ nsLayoutUtils::RectToGfxRect(state.mFillArea, appUnitsPerDevPixel);
+
+ return true;
+}
+
+void nsDisplayCanvasThemedBackground::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ nsPoint offset = ToReferenceFrame();
+ nsRect bgClipRect = frame->CanvasArea() + offset;
+
+ PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &bgClipRect);
+}
+
+/**
+ * A display item to paint the focus ring for the document.
+ *
+ * The only reason this can't use nsDisplayGeneric is overriding GetBounds.
+ */
+class nsDisplayCanvasFocus : public nsPaintedDisplayItem {
+ public:
+ nsDisplayCanvasFocus(nsDisplayListBuilder* aBuilder, nsCanvasFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayCanvasFocus);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCanvasFocus)
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ // This is an overestimate, but that's not a problem.
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ return frame->CanvasArea() + ToReferenceFrame();
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) override {
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ frame->PaintFocus(aCtx->GetDrawTarget(), ToReferenceFrame());
+ }
+
+ NS_DISPLAY_DECL_NAME("CanvasFocus", TYPE_CANVAS_FOCUS)
+};
+
+void nsCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (GetPrevInFlow()) {
+ DisplayOverflowContainers(aBuilder, aLists);
+ }
+
+ // Force a background to be shown. We may have a background propagated to us,
+ // in which case StyleBackground wouldn't have the right background
+ // and the code in nsIFrame::DisplayBorderBackgroundOutline might not give us
+ // a background.
+ // We don't have any border or outline, and our background draws over
+ // the overflow area, so just add nsDisplayCanvasBackground instead of
+ // calling DisplayBorderBackgroundOutline.
+ if (IsVisibleForPainting()) {
+ ComputedStyle* bg = nullptr;
+ nsIFrame* dependentFrame = nullptr;
+ bool isThemed = IsThemed();
+ if (!isThemed) {
+ dependentFrame = nsCSSRendering::FindBackgroundFrame(this);
+ if (dependentFrame) {
+ bg = dependentFrame->Style();
+ if (dependentFrame == this) {
+ dependentFrame = nullptr;
+ }
+ }
+ }
+
+ if (isThemed) {
+ aLists.BorderBackground()
+ ->AppendNewToTop<nsDisplayCanvasThemedBackground>(aBuilder, this);
+ return;
+ }
+
+ if (!bg) {
+ return;
+ }
+
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+
+ bool needBlendContainer = false;
+ nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
+
+ const bool suppressBackgroundImage = [&] {
+ // Handle print settings.
+ if (!ComputeShouldPaintBackground().mImage) {
+ return true;
+ }
+ // In high-contrast-mode, we suppress background-image on the canvas frame
+ // (even when backplating), because users expect site backgrounds to
+ // conform to their HCM background color when a solid color is rendered,
+ // and some websites use solid-color images instead of an overwritable
+ // background color.
+ if (PresContext()->ForcingColors() &&
+ StaticPrefs::
+ browser_display_suppress_canvas_background_image_on_forced_colors()) {
+ return true;
+ }
+ return false;
+ }();
+
+ nsDisplayList layerItems(aBuilder);
+
+ // Create separate items for each background layer.
+ const nsStyleImageLayers& layers = bg->StyleBackground()->mImage;
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) {
+ if (layers.mLayers[i].mImage.IsNone() || suppressBackgroundImage) {
+ continue;
+ }
+ if (layers.mLayers[i].mBlendMode != StyleBlend::Normal) {
+ needBlendContainer = true;
+ }
+
+ nsRect bgRect =
+ GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
+
+ const ActiveScrolledRoot* thisItemASR = asr;
+ nsDisplayList thisItemList(aBuilder);
+ nsDisplayBackgroundImage::InitData bgData =
+ nsDisplayBackgroundImage::GetInitData(aBuilder, this, i, bgRect, bg);
+
+ if (bgData.shouldFixToViewport) {
+ auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData();
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
+ aBuilder, this, aBuilder->GetVisibleRect(),
+ aBuilder->GetDirtyRect());
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
+ aBuilder);
+ if (displayData) {
+ const nsPoint offset = GetOffsetTo(PresShell()->GetRootFrame());
+ aBuilder->SetVisibleRect(displayData->mVisibleRect + offset);
+ aBuilder->SetDirtyRect(displayData->mDirtyRect + offset);
+
+ clipState.SetClipChainForContainingBlockDescendants(
+ displayData->mContainingBlockClipChain);
+ asrSetter.SetCurrentActiveScrolledRoot(
+ displayData->mContainingBlockActiveScrolledRoot);
+ asrSetter.SetCurrentScrollParentId(displayData->mScrollParentId);
+ thisItemASR = displayData->mContainingBlockActiveScrolledRoot;
+ }
+ nsDisplayCanvasBackgroundImage* bgItem = nullptr;
+ {
+ DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder);
+ bgImageClip.Clear();
+ bgItem = MakeDisplayItemWithIndex<nsDisplayCanvasBackgroundImage>(
+ aBuilder, this, /* aIndex = */ i, bgData);
+ if (bgItem) {
+ bgItem->SetDependentFrame(aBuilder, dependentFrame);
+ }
+ }
+ if (bgItem) {
+ thisItemList.AppendToTop(
+ nsDisplayFixedPosition::CreateForFixedBackground(
+ aBuilder, this, nullptr, bgItem, i, asr));
+ }
+
+ } else {
+ nsDisplayCanvasBackgroundImage* bgItem =
+ MakeDisplayItemWithIndex<nsDisplayCanvasBackgroundImage>(
+ aBuilder, this, /* aIndex = */ i, bgData);
+ if (bgItem) {
+ bgItem->SetDependentFrame(aBuilder, dependentFrame);
+ thisItemList.AppendToTop(bgItem);
+ }
+ }
+
+ if (layers.mLayers[i].mBlendMode != StyleBlend::Normal) {
+ DisplayListClipState::AutoSaveRestore blendClip(aBuilder);
+ thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>(
+ aBuilder, this, i + 1, &thisItemList, layers.mLayers[i].mBlendMode,
+ thisItemASR, true);
+ }
+ layerItems.AppendToTop(&thisItemList);
+ }
+
+ bool hasFixedBottomLayer =
+ layers.mImageCount > 0 &&
+ layers.mLayers[0].mAttachment == StyleImageLayerAttachment::Fixed;
+
+ if (!hasFixedBottomLayer || needBlendContainer) {
+ // Put a scrolled background color item in place, at the bottom of the
+ // list. The color of this item will be filled in during
+ // PresShell::AddCanvasBackgroundColorItem.
+ // Do not add this item if there's a fixed background image at the bottom
+ // (unless we have to, for correct blending); with a fixed background,
+ // it's better to allow the fixed background image to combine itself with
+ // a non-scrolled background color directly underneath, rather than
+ // interleaving the two with a scrolled background color.
+ // PresShell::AddCanvasBackgroundColorItem makes sure there always is a
+ // non-scrolled background color item at the bottom.
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayCanvasBackgroundColor>(
+ aBuilder, this);
+ }
+
+ aLists.BorderBackground()->AppendToTop(&layerItems);
+
+ if (needBlendContainer) {
+ const ActiveScrolledRoot* containerASR = contASRTracker.GetContainerASR();
+ DisplayListClipState::AutoSaveRestore blendContainerClip(aBuilder);
+ aLists.BorderBackground()->AppendToTop(
+ nsDisplayBlendContainer::CreateForBackgroundBlendMode(
+ aBuilder, this, nullptr, aLists.BorderBackground(),
+ containerASR));
+ }
+ }
+
+ for (nsIFrame* kid : PrincipalChildList()) {
+ // Put our child into its own pseudo-stack.
+ BuildDisplayListForChild(aBuilder, kid, aLists);
+ }
+
+ if (!mDoPaintFocus) return;
+ // Only paint the focus if we're visible
+ if (!StyleVisibility()->IsVisible()) return;
+
+ aLists.Outlines()->AppendNewToTop<nsDisplayCanvasFocus>(aBuilder, this);
+}
+
+void nsCanvasFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt) {
+ nsRect focusRect(aPt, GetSize());
+
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(GetParent());
+ if (scrollableFrame) {
+ nsRect portRect = scrollableFrame->GetScrollPortRect();
+ focusRect.width = portRect.width;
+ focusRect.height = portRect.height;
+ focusRect.MoveBy(scrollableFrame->GetScrollPosition());
+ }
+
+ // XXX use the root frame foreground color, but should we find BODY frame
+ // for HTML documents?
+ nsIFrame* root = mFrames.FirstChild();
+ const auto* text = root ? root->StyleText() : StyleText();
+ nsCSSRendering::PaintFocus(PresContext(), aDrawTarget, focusRect,
+ text->mColor.ToColor());
+}
+
+/* virtual */
+nscoord nsCanvasFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ if (mFrames.IsEmpty())
+ result = 0;
+ else
+ result = mFrames.FirstChild()->GetMinISize(aRenderingContext);
+ return result;
+}
+
+/* virtual */
+nscoord nsCanvasFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ if (mFrames.IsEmpty())
+ result = 0;
+ else
+ result = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
+ return result;
+}
+
+void nsCanvasFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsCanvasFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE_REFLOW_IN("nsCanvasFrame::Reflow");
+
+ nsCanvasFrame* prevCanvasFrame = static_cast<nsCanvasFrame*>(GetPrevInFlow());
+ if (prevCanvasFrame) {
+ AutoFrameListPtr overflow(aPresContext,
+ prevCanvasFrame->StealOverflowFrames());
+ if (overflow) {
+ NS_ASSERTION(overflow->OnlyChild(),
+ "must have doc root as canvas frame's only child");
+ nsContainerFrame::ReparentFrameViewList(*overflow, prevCanvasFrame, this);
+ // Prepend overflow to the our child list. There may already be
+ // children placeholders for fixed-pos elements, which don't get
+ // reflowed but must not be lost until the canvas frame is destroyed.
+ mFrames.InsertFrames(this, nullptr, std::move(*overflow));
+ }
+ }
+
+ // Set our size up front, since some parts of reflow depend on it
+ // being already set. Note that the computed height may be
+ // unconstrained; that's ok. Consumers should watch out for that.
+ SetSize(aReflowInput.ComputedPhysicalSize());
+
+ // Reflow our children. Typically, we only have one child - the root
+ // element's frame or a placeholder for that frame, if the root element
+ // is abs-pos or fixed-pos. Note that this child might be missing though
+ // if that frame was Complete in one of our earlier continuations. This
+ // happens when we create additional pages purely to make room for painting
+ // overflow (painted by BuildPreviousPageOverflow in nsPageFrame.cpp).
+ // We may have additional children which are placeholders for continuations
+ // of fixed-pos content, see nsCSSFrameConstructor::ReplicateFixedFrames.
+ const WritingMode wm = aReflowInput.GetWritingMode();
+ aDesiredSize.SetSize(wm, aReflowInput.ComputedSize());
+ if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
+ // Set the block-size to zero for now in case we don't have any non-
+ // placeholder children that would update the size in the loop below.
+ aDesiredSize.BSize(wm) = nscoord(0);
+ }
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ nsIFrame* nextKid = nullptr;
+ for (auto* kidFrame = mFrames.FirstChild(); kidFrame; kidFrame = nextKid) {
+ nextKid = kidFrame->GetNextSibling();
+ ReflowOutput kidDesiredSize(aReflowInput);
+ bool kidDirty = kidFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
+ WritingMode kidWM = kidFrame->GetWritingMode();
+ auto availableSize = aReflowInput.AvailableSize(kidWM);
+ nscoord bOffset = 0;
+ nscoord canvasBSizeSum = 0;
+ if (prevCanvasFrame && availableSize.BSize(kidWM) != NS_UNCONSTRAINEDSIZE &&
+ !kidFrame->IsPlaceholderFrame() &&
+ StaticPrefs::layout_display_list_improve_fragmentation()) {
+ for (auto* pif = prevCanvasFrame; pif;
+ pif = static_cast<nsCanvasFrame*>(pif->GetPrevInFlow())) {
+ canvasBSizeSum += pif->BSize(kidWM);
+ auto* pifChild = pif->PrincipalChildList().FirstChild();
+ if (pifChild) {
+ nscoord layoutOverflow = pifChild->BSize(kidWM) - canvasBSizeSum;
+ // A negative value means that the :root frame does not fill
+ // the canvas. In this case we can't determine the offset exactly
+ // so we use the end edge of the scrollable overflow as the offset
+ // instead. This will likely push down the content below where it
+ // should be placed, creating a gap. That's preferred over making
+ // content overlap which would otherwise occur.
+ // See layout/reftests/pagination/inline-block-slice-7.html for an
+ // example of this.
+ if (layoutOverflow < 0) {
+ LogicalRect so(kidWM, pifChild->ScrollableOverflowRect(),
+ pifChild->GetSize());
+ layoutOverflow = so.BEnd(kidWM) - canvasBSizeSum;
+ }
+ bOffset = std::max(bOffset, layoutOverflow);
+ }
+ }
+ availableSize.BSize(kidWM) -= bOffset;
+ }
+
+ if (MOZ_LIKELY(availableSize.BSize(kidWM) > 0)) {
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame,
+ availableSize);
+
+ if (aReflowInput.IsBResizeForWM(kidReflowInput.GetWritingMode()) &&
+ kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ // Tell our kid it's being block-dir resized too. Bit of a
+ // hack for framesets.
+ kidReflowInput.SetBResize(true);
+ }
+
+ nsSize containerSize = aReflowInput.ComputedPhysicalSize();
+ LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(kidWM);
+ LogicalPoint kidPt(kidWM, margin.IStart(kidWM), margin.BStart(kidWM));
+ (kidWM.IsOrthogonalTo(wm) ? kidPt.I(kidWM) : kidPt.B(kidWM)) += bOffset;
+
+ nsReflowStatus kidStatus;
+ ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, kidWM,
+ kidPt, containerSize, ReflowChildFlags::Default, kidStatus);
+
+ FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, &kidReflowInput,
+ kidWM, kidPt, containerSize,
+ ReflowChildFlags::ApplyRelativePositioning);
+
+ if (!kidStatus.IsFullyComplete()) {
+ nsIFrame* nextFrame = kidFrame->GetNextInFlow();
+ NS_ASSERTION(nextFrame || kidStatus.NextInFlowNeedsReflow(),
+ "If it's incomplete and has no nif yet, it must flag a "
+ "nif reflow.");
+ if (!nextFrame) {
+ nextFrame = aPresContext->PresShell()
+ ->FrameConstructor()
+ ->CreateContinuingFrame(kidFrame, this);
+ SetOverflowFrames(nsFrameList(nextFrame, nextFrame));
+ // Root overflow containers will be normal children of
+ // the canvas frame, but that's ok because there
+ // aren't any other frames we need to isolate them from
+ // during reflow.
+ }
+ if (kidStatus.IsOverflowIncomplete()) {
+ nextFrame->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ }
+ }
+ aStatus.MergeCompletionStatusFrom(kidStatus);
+
+ // If the child frame was just inserted, then we're responsible for making
+ // sure it repaints
+ if (kidDirty) {
+ // But we have a new child, which will affect our background, so
+ // invalidate our whole rect.
+ // Note: Even though we request to be sized to our child's size, our
+ // scroll frame ensures that we are always the size of the viewport.
+ // Also note: GetPosition() on a CanvasFrame is always going to return
+ // (0, 0). We only want to invalidate GetRect() since Get*OverflowRect()
+ // could also include overflow to our top and left (out of the viewport)
+ // which doesn't need to be painted.
+ nsIFrame* viewport = PresShell()->GetRootFrame();
+ viewport->InvalidateFrame();
+ }
+
+ // Return our desired size. Normally it's what we're told, but sometimes
+ // we can be given an unconstrained block-size (when a window is
+ // sizing-to-content), and we should compute our desired block-size. This
+ // is done by PresShell::ResizeReflow, when given the BSizeLimit flag.
+ //
+ // We do this here rather than at the viewport frame, because the canvas
+ // is what draws the background, so it can extend a little bit more than
+ // the real content without visual glitches, realistically.
+ if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE &&
+ !kidFrame->IsPlaceholderFrame()) {
+ LogicalSize finalSize = aReflowInput.ComputedSize();
+ finalSize.BSize(wm) = nsPresContext::RoundUpAppUnitsToCSSPixel(
+ kidFrame->GetLogicalSize(wm).BSize(wm) +
+ kidReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm));
+ aDesiredSize.SetSize(wm, finalSize);
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ }
+ aDesiredSize.mOverflowAreas.UnionWith(kidDesiredSize.mOverflowAreas +
+ kidFrame->GetPosition());
+ } else if (kidFrame->IsPlaceholderFrame()) {
+ // Placeholders always fit even if there's no available block-size left.
+ } else {
+ // This only occurs in paginated mode. There is no available space on
+ // this page due to reserving space for overflow from a previous page,
+ // so we push our child to the next page. Note that we can have some
+ // placeholders for fixed pos. frames in mFrames too, so we need to be
+ // careful to only push `kidFrame`.
+ mFrames.RemoveFrame(kidFrame);
+ SetOverflowFrames(nsFrameList(kidFrame, kidFrame));
+ aStatus.SetIncomplete();
+ }
+ }
+
+ if (prevCanvasFrame) {
+ ReflowOverflowContainerChildren(aPresContext, aReflowInput,
+ aDesiredSize.mOverflowAreas,
+ ReflowChildFlags::Default, aStatus);
+ }
+
+ FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
+ aStatus);
+
+ NS_FRAME_TRACE_REFLOW_OUT("nsCanvasFrame::Reflow", aStatus);
+}
+
+nsresult nsCanvasFrame::GetContentForEvent(const WidgetEvent* aEvent,
+ nsIContent** aContent) {
+ NS_ENSURE_ARG_POINTER(aContent);
+ nsresult rv = nsIFrame::GetContentForEvent(aEvent, aContent);
+ if (NS_FAILED(rv) || !*aContent) {
+ nsIFrame* kid = mFrames.FirstChild();
+ if (kid) {
+ rv = kid->GetContentForEvent(aEvent, aContent);
+ }
+ }
+
+ return rv;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsCanvasFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Canvas"_ns, aResult);
+}
+#endif
diff --git a/layout/generic/nsCanvasFrame.h b/layout/generic/nsCanvasFrame.h
new file mode 100644
index 0000000000..6922428a1d
--- /dev/null
+++ b/layout/generic/nsCanvasFrame.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object that goes directly inside the document's scrollbars */
+
+#ifndef nsCanvasFrame_h___
+#define nsCanvasFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsContainerFrame.h"
+#include "nsDisplayList.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIPopupContainer.h"
+#include "nsIScrollPositionListener.h"
+
+class nsPresContext;
+class gfxContext;
+
+/**
+ * Root frame class.
+ *
+ * The root frame is the parent frame for the document element's frame.
+ * It only supports having a single child frame which must be an area
+ * frame.
+ * @note nsCanvasFrame keeps overflow container continuations of its child
+ * frame in the main child list.
+ */
+class nsCanvasFrame final : public nsContainerFrame,
+ public nsIScrollPositionListener,
+ public nsIAnonymousContentCreator,
+ public nsIPopupContainer {
+ using Element = mozilla::dom::Element;
+
+ public:
+ explicit nsCanvasFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID),
+ mDoPaintFocus(false),
+ mAddedScrollPositionListener(false) {}
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsCanvasFrame)
+
+ Element* GetDefaultTooltip() override;
+
+ void Destroy(DestroyContext&) override;
+
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+#ifdef DEBUG
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override;
+#endif
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ // nsIAnonymousContentCreator
+ nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ Element* GetCustomContentContainer() const { return mCustomContentContainer; }
+
+ /**
+ * Unhide the CustomContentContainer. This call only has an effect if
+ * mCustomContentContainer is non-null.
+ */
+ void ShowCustomContentContainer();
+
+ /**
+ * Hide the CustomContentContainer. This call only has an effect if
+ * mCustomContentContainer is non-null.
+ */
+ void HideCustomContentContainer();
+
+ /** SetHasFocus tells the CanvasFrame to draw with focus ring
+ * @param aHasFocus true to show focus ring, false to hide it
+ */
+ NS_IMETHOD SetHasFocus(bool aHasFocus);
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ void PaintFocus(mozilla::gfx::DrawTarget* aRenderingContext, nsPoint aPt);
+
+ // nsIScrollPositionListener
+ void ScrollPositionWillChange(nscoord aX, nscoord aY) override;
+ void ScrollPositionDidChange(nscoord aX, nscoord aY) override {}
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+ nsresult GetContentForEvent(const mozilla::WidgetEvent* aEvent,
+ nsIContent** aContent) override;
+
+ nsRect CanvasArea() const;
+
+ protected:
+ // Data members
+ bool mDoPaintFocus;
+ bool mAddedScrollPositionListener;
+
+ nsCOMPtr<Element> mCustomContentContainer;
+ nsCOMPtr<Element> mTooltipContent;
+};
+
+namespace mozilla {
+/**
+ * Override nsDisplayBackground methods so that we pass aBGClipRect to
+ * PaintBackground, covering the whole overflow area.
+ * We can also paint an "extra background color" behind the normal
+ * background.
+ */
+class nsDisplayCanvasBackgroundColor final : public nsDisplaySolidColorBase {
+ public:
+ nsDisplayCanvasBackgroundColor(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+ : nsDisplaySolidColorBase(aBuilder, aFrame, NS_RGBA(0, 0, 0, 0)) {}
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
+ *aSnap = true;
+ return frame->CanvasArea() + ToReferenceFrame();
+ }
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override {
+ // We need to override so we don't consider border-radius.
+ aOutFrames->AppendElement(mFrame);
+ }
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ void SetExtraBackgroundColor(nscolor aColor) { mColor = aColor; }
+
+ NS_DISPLAY_DECL_NAME("CanvasBackgroundColor", TYPE_CANVAS_BACKGROUND_COLOR)
+
+ void WriteDebugInfo(std::stringstream& aStream) override;
+};
+
+class nsDisplayCanvasBackgroundImage : public nsDisplayBackgroundImage {
+ public:
+ explicit nsDisplayCanvasBackgroundImage(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ const InitData& aInitData)
+ : nsDisplayBackgroundImage(aBuilder, aFrame, aInitData) {}
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ // We still need to paint a background color as well as an image for this
+ // item, so we can't support this yet.
+ bool SupportsOptimizingToImage() const override { return false; }
+
+ bool IsSingleFixedPositionImage(nsDisplayListBuilder* aBuilder,
+ const nsRect& aClipRect, gfxRect* aDestRect);
+
+ NS_DISPLAY_DECL_NAME("CanvasBackgroundImage", TYPE_CANVAS_BACKGROUND_IMAGE)
+};
+
+class nsDisplayCanvasThemedBackground : public nsDisplayThemedBackground {
+ public:
+ nsDisplayCanvasThemedBackground(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+ : nsDisplayThemedBackground(aBuilder, aFrame,
+ aFrame->GetRectRelativeToSelf() +
+ aBuilder->ToReferenceFrame(aFrame)) {
+ nsDisplayThemedBackground::Init(aBuilder);
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ NS_DISPLAY_DECL_NAME("CanvasThemedBackground", TYPE_CANVAS_THEMED_BACKGROUND)
+};
+
+} // namespace mozilla
+
+#endif /* nsCanvasFrame_h___ */
diff --git a/layout/generic/nsColumnSetFrame.cpp b/layout/generic/nsColumnSetFrame.cpp
new file mode 100644
index 0000000000..cd97519a98
--- /dev/null
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -0,0 +1,1355 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for css3 multi-column layout */
+
+#include "nsColumnSetFrame.h"
+
+#include "mozilla/ColumnUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/ToString.h"
+#include "nsCSSRendering.h"
+#include "nsDisplayList.h"
+#include "nsIFrameInlines.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+// To see this log, use $ MOZ_LOG=ColumnSet:4 ./mach run
+static LazyLogModule sColumnSetLog("ColumnSet");
+#define COLUMN_SET_LOG(msg, ...) \
+ MOZ_LOG(sColumnSetLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+class nsDisplayColumnRule : public nsPaintedDisplayItem {
+ public:
+ nsDisplayColumnRule(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayColumnRule);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayColumnRule)
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
+ *aSnap = false;
+ // We just return the frame's ink-overflow rect, which is guaranteed to
+ // contain all the column-rule areas. It's not worth calculating the exact
+ // union of those areas since it would only lead to performance improvements
+ // during painting in rare edge cases.
+ return mFrame->InkOverflowRect() + ToReferenceFrame();
+ }
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ NS_DISPLAY_DECL_NAME("ColumnRule", TYPE_COLUMN_RULE);
+
+ private:
+ nsTArray<nsCSSBorderRenderer> mBorderRenderers;
+};
+
+void nsDisplayColumnRule::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
+ mBorderRenderers, aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame());
+
+ for (auto iter = mBorderRenderers.begin(); iter != mBorderRenderers.end();
+ iter++) {
+ iter->DrawBorders();
+ }
+}
+
+bool nsDisplayColumnRule::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ RefPtr dt = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ if (!dt || !dt->IsValid()) {
+ return false;
+ }
+ gfxContext screenRefCtx(dt);
+
+ bool dummy;
+ static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
+ mBorderRenderers, &screenRefCtx, GetBounds(aDisplayListBuilder, &dummy),
+ ToReferenceFrame());
+
+ if (mBorderRenderers.IsEmpty()) {
+ return true;
+ }
+
+ for (auto& renderer : mBorderRenderers) {
+ renderer.CreateWebRenderCommands(this, aBuilder, aResources, aSc);
+ }
+
+ return true;
+}
+
+/**
+ * Tracking issues:
+ *
+ * XXX cursor movement around the top and bottom of colums seems to make the
+ * editor lose the caret.
+ *
+ * XXX should we support CSS columns applied to table elements?
+ */
+nsContainerFrame* NS_NewColumnSetFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle,
+ nsFrameState aStateFlags) {
+ nsColumnSetFrame* it =
+ new (aPresShell) nsColumnSetFrame(aStyle, aPresShell->GetPresContext());
+ it->AddStateBits(aStateFlags);
+ return it;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)
+
+nsColumnSetFrame::nsColumnSetFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID),
+ mLastBalanceBSize(NS_UNCONSTRAINEDSIZE) {}
+
+void nsColumnSetFrame::ForEachColumnRule(
+ const std::function<void(const nsRect& lineRect)>& aSetLineRect,
+ const nsPoint& aPt) const {
+ nsIFrame* child = mFrames.FirstChild();
+ if (!child) return; // no columns
+
+ nsIFrame* nextSibling = child->GetNextSibling();
+ if (!nextSibling) return; // 1 column only - this means no gap to draw on
+
+ const nsStyleColumn* colStyle = StyleColumn();
+ nscoord ruleWidth = colStyle->GetColumnRuleWidth();
+ if (!ruleWidth) return;
+
+ WritingMode wm = GetWritingMode();
+ bool isVertical = wm.IsVertical();
+ bool isRTL = wm.IsBidiRTL();
+
+ nsRect contentRect = GetContentRectRelativeToSelf() + aPt;
+ nsSize ruleSize = isVertical ? nsSize(contentRect.width, ruleWidth)
+ : nsSize(ruleWidth, contentRect.height);
+
+ while (nextSibling) {
+ // The frame tree goes RTL in RTL.
+ // The |prevFrame| and |nextFrame| frames here are the visually preceding
+ // (left/above) and following (right/below) frames, not in logical writing-
+ // mode direction.
+ nsIFrame* prevFrame = isRTL ? nextSibling : child;
+ nsIFrame* nextFrame = isRTL ? child : nextSibling;
+
+ // Each child frame's position coordinates is actually relative to this
+ // nsColumnSetFrame.
+ // linePt will be at the top-left edge to paint the line.
+ nsPoint linePt;
+ if (isVertical) {
+ nscoord edgeOfPrev = prevFrame->GetRect().YMost() + aPt.y;
+ nscoord edgeOfNext = nextFrame->GetRect().Y() + aPt.y;
+ linePt = nsPoint(contentRect.x,
+ (edgeOfPrev + edgeOfNext - ruleSize.height) / 2);
+ } else {
+ nscoord edgeOfPrev = prevFrame->GetRect().XMost() + aPt.x;
+ nscoord edgeOfNext = nextFrame->GetRect().X() + aPt.x;
+ linePt = nsPoint((edgeOfPrev + edgeOfNext - ruleSize.width) / 2,
+ contentRect.y);
+ }
+
+ aSetLineRect(nsRect(linePt, ruleSize));
+
+ child = nextSibling;
+ nextSibling = nextSibling->GetNextSibling();
+ }
+}
+
+void nsColumnSetFrame::CreateBorderRenderers(
+ nsTArray<nsCSSBorderRenderer>& aBorderRenderers, gfxContext* aCtx,
+ const nsRect& aDirtyRect, const nsPoint& aPt) {
+ WritingMode wm = GetWritingMode();
+ bool isVertical = wm.IsVertical();
+ const nsStyleColumn* colStyle = StyleColumn();
+ StyleBorderStyle ruleStyle;
+
+ // Per spec, inset => ridge and outset => groove
+ if (colStyle->mColumnRuleStyle == StyleBorderStyle::Inset) {
+ ruleStyle = StyleBorderStyle::Ridge;
+ } else if (colStyle->mColumnRuleStyle == StyleBorderStyle::Outset) {
+ ruleStyle = StyleBorderStyle::Groove;
+ } else {
+ ruleStyle = colStyle->mColumnRuleStyle;
+ }
+
+ nscoord ruleWidth = colStyle->GetColumnRuleWidth();
+ if (!ruleWidth) {
+ return;
+ }
+
+ aBorderRenderers.Clear();
+ nscolor ruleColor =
+ GetVisitedDependentColor(&nsStyleColumn::mColumnRuleColor);
+
+ nsPresContext* pc = PresContext();
+ // In order to re-use a large amount of code, we treat the column rule as a
+ // border. We create a new border style object and fill in all the details of
+ // the column rule as the left border. PaintBorder() does all the rendering
+ // for us, so we not only save an enormous amount of code but we'll support
+ // all the line styles that we support on borders!
+ nsStyleBorder border;
+ Sides skipSides;
+ if (isVertical) {
+ border.SetBorderWidth(eSideTop, ruleWidth, pc->AppUnitsPerDevPixel());
+ border.SetBorderStyle(eSideTop, ruleStyle);
+ border.mBorderTopColor = StyleColor::FromColor(ruleColor);
+ skipSides |= mozilla::SideBits::eLeftRight;
+ skipSides |= mozilla::SideBits::eBottom;
+ } else {
+ border.SetBorderWidth(eSideLeft, ruleWidth, pc->AppUnitsPerDevPixel());
+ border.SetBorderStyle(eSideLeft, ruleStyle);
+ border.mBorderLeftColor = StyleColor::FromColor(ruleColor);
+ skipSides |= mozilla::SideBits::eTopBottom;
+ skipSides |= mozilla::SideBits::eRight;
+ }
+ // If we use box-decoration-break: slice (the default), the border
+ // renderers will require clipping if we have continuations (see the
+ // aNeedsClip parameter to ConstructBorderRenderer in nsCSSRendering).
+ //
+ // Since it doesn't matter which box-decoration-break we use since
+ // we're only drawing borders (and not border-images), use 'clone'.
+ border.mBoxDecorationBreak = StyleBoxDecorationBreak::Clone;
+
+ ForEachColumnRule(
+ [&](const nsRect& aLineRect) {
+ // Assert that we're not drawing a border-image here; if we were, we
+ // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
+ // returns.
+ MOZ_ASSERT(border.mBorderImageSource.IsNone());
+
+ gfx::DrawTarget* dt = aCtx ? aCtx->GetDrawTarget() : nullptr;
+ bool borderIsEmpty = false;
+ Maybe<nsCSSBorderRenderer> br =
+ nsCSSRendering::CreateBorderRendererWithStyleBorder(
+ pc, dt, this, aDirtyRect, aLineRect, border, Style(),
+ &borderIsEmpty, skipSides);
+ if (br.isSome()) {
+ MOZ_ASSERT(!borderIsEmpty);
+ aBorderRenderers.AppendElement(br.value());
+ }
+ },
+ aPt);
+}
+
+static uint32_t ColumnBalancingDepth(const ReflowInput& aReflowInput,
+ uint32_t aMaxDepth) {
+ uint32_t depth = 0;
+ for (const ReflowInput* ri = aReflowInput.mParentReflowInput;
+ ri && depth < aMaxDepth; ri = ri->mParentReflowInput) {
+ if (ri->mFlags.mIsColumnBalancing) {
+ ++depth;
+ }
+ }
+ return depth;
+}
+
+nsColumnSetFrame::ReflowConfig nsColumnSetFrame::ChooseColumnStrategy(
+ const ReflowInput& aReflowInput, bool aForceAuto = false) const {
+ const nsStyleColumn* colStyle = StyleColumn();
+ nscoord availContentISize = aReflowInput.AvailableISize();
+ if (aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE) {
+ availContentISize = aReflowInput.ComputedISize();
+ }
+
+ nscoord colBSize = aReflowInput.AvailableBSize();
+ nscoord colGap =
+ ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize());
+ int32_t numColumns = colStyle->mColumnCount;
+
+ // If column-fill is set to 'balance' or we have a column-span sibling, then
+ // we want to balance the columns.
+ bool isBalancing = (colStyle->mColumnFill == StyleColumnFill::Balance ||
+ HasColumnSpanSiblings()) &&
+ !aForceAuto;
+ if (isBalancing) {
+ const uint32_t kMaxNestedColumnBalancingDepth = 2;
+ const uint32_t balancingDepth =
+ ColumnBalancingDepth(aReflowInput, kMaxNestedColumnBalancingDepth);
+ if (balancingDepth == kMaxNestedColumnBalancingDepth) {
+ isBalancing = false;
+ numColumns = 1;
+ }
+ }
+
+ nscoord colISize;
+ // In vertical writing-mode, "column-width" (inline size) will actually be
+ // physical height, but its CSS name is still column-width.
+ if (colStyle->mColumnWidth.IsLength()) {
+ colISize =
+ ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
+ NS_ASSERTION(colISize >= 0, "negative column width");
+ // Reduce column count if necessary to make columns fit in the
+ // available width. Compute max number of columns that fit in
+ // availContentISize, satisfying colGap*(maxColumns - 1) +
+ // colISize*maxColumns <= availContentISize
+ if (availContentISize != NS_UNCONSTRAINEDSIZE && colGap + colISize > 0 &&
+ numColumns > 0) {
+ // This expression uses truncated rounding, which is what we
+ // want
+ int32_t maxColumns =
+ std::min(nscoord(nsStyleColumn::kMaxColumnCount),
+ (availContentISize + colGap) / (colGap + colISize));
+ numColumns = std::max(1, std::min(numColumns, maxColumns));
+ }
+ } else if (numColumns > 0 && availContentISize != NS_UNCONSTRAINEDSIZE) {
+ nscoord iSizeMinusGaps = availContentISize - colGap * (numColumns - 1);
+ colISize = iSizeMinusGaps / numColumns;
+ } else {
+ colISize = NS_UNCONSTRAINEDSIZE;
+ }
+ // Take care of the situation where there's only one column but it's
+ // still too wide
+ colISize = std::max(1, std::min(colISize, availContentISize));
+
+ nscoord expectedISizeLeftOver = 0;
+
+ if (colISize != NS_UNCONSTRAINEDSIZE &&
+ availContentISize != NS_UNCONSTRAINEDSIZE) {
+ // distribute leftover space
+
+ // First, determine how many columns will be showing if the column
+ // count is auto
+ if (numColumns <= 0) {
+ // choose so that colGap*(nominalColumnCount - 1) +
+ // colISize*nominalColumnCount is nearly availContentISize
+ // make sure to round down
+ if (colGap + colISize > 0) {
+ numColumns = (availContentISize + colGap) / (colGap + colISize);
+ // The number of columns should never exceed kMaxColumnCount.
+ numColumns =
+ std::min(nscoord(nsStyleColumn::kMaxColumnCount), numColumns);
+ }
+ if (numColumns <= 0) {
+ numColumns = 1;
+ }
+ }
+
+ // Compute extra space and divide it among the columns
+ nscoord extraSpace =
+ std::max(0, availContentISize -
+ (colISize * numColumns + colGap * (numColumns - 1)));
+ nscoord extraToColumns = extraSpace / numColumns;
+ colISize += extraToColumns;
+ expectedISizeLeftOver = extraSpace - (extraToColumns * numColumns);
+ }
+
+ if (isBalancing) {
+ if (numColumns <= 0) {
+ // Hmm, auto column count, column width or available width is unknown,
+ // and balancing is required. Let's just use one column then.
+ numColumns = 1;
+ }
+ colBSize = std::min(mLastBalanceBSize, colBSize);
+ } else {
+ // CSS Fragmentation spec says, "To guarantee progress, fragmentainers are
+ // assumed to have a minimum block size of 1px regardless of their used
+ // size." https://drafts.csswg.org/css-break/#breaking-rules
+ //
+ // Note: we don't enforce the minimum block-size during balancing because
+ // this affects the result. If a balancing column container or its
+ // next-in-flows has zero block-size, it eventually gives up balancing, and
+ // ends up here.
+ colBSize = std::max(colBSize, nsPresContext::CSSPixelsToAppUnits(1));
+ }
+
+ ReflowConfig config;
+ config.mUsedColCount = numColumns;
+ config.mColISize = colISize;
+ config.mExpectedISizeLeftOver = expectedISizeLeftOver;
+ config.mColGap = colGap;
+ config.mColBSize = colBSize;
+ config.mIsBalancing = isBalancing;
+ config.mForceAuto = aForceAuto;
+ config.mKnownFeasibleBSize = NS_UNCONSTRAINEDSIZE;
+ config.mKnownInfeasibleBSize = 0;
+
+ COLUMN_SET_LOG(
+ "%s: this=%p, mUsedColCount=%d, mColISize=%d, "
+ "mExpectedISizeLeftOver=%d, mColGap=%d, mColBSize=%d, mIsBalancing=%d",
+ __func__, this, config.mUsedColCount, config.mColISize,
+ config.mExpectedISizeLeftOver, config.mColGap, config.mColBSize,
+ config.mIsBalancing);
+
+ return config;
+}
+
+static void MarkPrincipalChildrenDirty(nsIFrame* aFrame) {
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ childFrame->MarkSubtreeDirty();
+ }
+}
+
+static void MoveChildTo(nsIFrame* aChild, LogicalPoint aOrigin, WritingMode aWM,
+ const nsSize& aContainerSize) {
+ if (aChild->GetLogicalPosition(aWM, aContainerSize) == aOrigin) {
+ return;
+ }
+
+ aChild->SetPosition(aWM, aOrigin, aContainerSize);
+ nsContainerFrame::PlaceFrameView(aChild);
+}
+
+nscoord nsColumnSetFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord iSize = 0;
+ DISPLAY_MIN_INLINE_SIZE(this, iSize);
+
+ if (mFrames.FirstChild()) {
+ // We want to ignore this in the case that we're size contained
+ // because our children should not contribute to our
+ // intrinsic size.
+ iSize = mFrames.FirstChild()->GetMinISize(aRenderingContext);
+ }
+ const nsStyleColumn* colStyle = StyleColumn();
+ if (colStyle->mColumnWidth.IsLength()) {
+ nscoord colISize =
+ ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
+ // As available width reduces to zero, we reduce our number of columns
+ // to one, and don't enforce the column width, so just return the min
+ // of the child's min-width with any specified column width.
+ iSize = std::min(iSize, colISize);
+ } else {
+ NS_ASSERTION(colStyle->mColumnCount > 0,
+ "column-count and column-width can't both be auto");
+ // As available width reduces to zero, we still have mColumnCount columns,
+ // so compute our minimum size based on the number of columns and their gaps
+ // and minimum per-column size.
+ nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
+ iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount, colGap, iSize);
+ }
+ // XXX count forced column breaks here? Maybe we should return the child's
+ // min-width times the minimum number of columns.
+ return iSize;
+}
+
+nscoord nsColumnSetFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ // Our preferred width is our desired column width, if specified, otherwise
+ // the child's preferred width, times the number of columns, plus the width
+ // of any required column gaps
+ // XXX what about forced column breaks here?
+ nscoord result = 0;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ const nsStyleColumn* colStyle = StyleColumn();
+
+ nscoord colISize;
+ if (colStyle->mColumnWidth.IsLength()) {
+ colISize =
+ ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
+ } else if (mFrames.FirstChild()) {
+ // We want to ignore this in the case that we're size contained
+ // because our children should not contribute to our
+ // intrinsic size.
+ colISize = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
+ } else {
+ colISize = 0;
+ }
+
+ // If column-count is auto, assume one column.
+ uint32_t numColumns =
+ colStyle->mColumnCount == nsStyleColumn::kColumnCountAuto
+ ? 1
+ : colStyle->mColumnCount;
+ nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
+ result = ColumnUtils::IntrinsicISize(numColumns, colGap, colISize);
+ return result;
+}
+
+nsColumnSetFrame::ColumnBalanceData nsColumnSetFrame::ReflowColumns(
+ ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus, const ReflowConfig& aConfig,
+ bool aUnboundedLastColumn) {
+ ColumnBalanceData colData;
+ bool allFit = true;
+ WritingMode wm = GetWritingMode();
+ const bool isRTL = wm.IsBidiRTL();
+ const bool shrinkingBSize = mLastBalanceBSize > aConfig.mColBSize;
+ const bool changingBSize = mLastBalanceBSize != aConfig.mColBSize;
+
+ COLUMN_SET_LOG(
+ "%s: Doing column reflow pass: mLastBalanceBSize=%d,"
+ " mColBSize=%d, RTL=%d, mUsedColCount=%d,"
+ " mColISize=%d, mColGap=%d",
+ __func__, mLastBalanceBSize, aConfig.mColBSize, isRTL,
+ aConfig.mUsedColCount, aConfig.mColISize, aConfig.mColGap);
+
+ DrainOverflowColumns();
+
+ if (changingBSize) {
+ mLastBalanceBSize = aConfig.mColBSize;
+ // XXX Seems like this could fire if incremental reflow pushed the column
+ // set down so we reflow incrementally with a different available height.
+ // We need a way to do an incremental reflow and be sure availableHeight
+ // changes are taken account of! Right now I think block frames with
+ // absolute children might exit early.
+ /*
+ NS_ASSERTION(
+ aKidReason != eReflowReason_Incremental,
+ "incremental reflow should not have changed the balance height");
+ */
+ }
+
+ nsRect contentRect(0, 0, 0, 0);
+ OverflowAreas overflowRects;
+
+ nsIFrame* child = mFrames.FirstChild();
+ LogicalPoint childOrigin(wm, 0, 0);
+
+ // In vertical-rl mode, columns will not be correctly placed if the
+ // reflowInput's ComputedWidth() is UNCONSTRAINED (in which case we'll get
+ // a containerSize.width of zero here). In that case, the column positions
+ // will be adjusted later, after our correct contentSize is known.
+ //
+ // When column-span is enabled, containerSize.width is always constrained.
+ // However, for RTL, we need to adjust the column positions as well after our
+ // correct containerSize is known.
+ nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
+
+ const nscoord computedBSize =
+ aReflowInput.mParentReflowInput->ComputedBSize();
+ nscoord contentBEnd = 0;
+ bool reflowNext = false;
+
+ while (child) {
+ const bool reflowLastColumnWithUnconstrainedAvailBSize =
+ aUnboundedLastColumn && colData.mColCount == aConfig.mUsedColCount &&
+ aConfig.mIsBalancing;
+
+ // We need to reflow the child (column) ...
+ bool reflowChild =
+ // if we are told to do so;
+ aReflowInput.ShouldReflowAllKids() ||
+ // if the child is dirty;
+ child->IsSubtreeDirty() ||
+ // if it's the last child because we need to obtain the block-end
+ // margin;
+ !child->GetNextSibling() ||
+ // if the next column is dirty, because the next column's first line(s)
+ // might be pullable back to this column;
+ child->GetNextSibling()->IsSubtreeDirty() ||
+ // if this is the last column and we are supposed to assign unbounded
+ // block-size to it, because that could change the available block-size
+ // from the last time we reflowed it and we should try to pull all the
+ // content from its next sibling (Note that it might be the last column,
+ // but not be the last child because the desired number of columns has
+ // changed.)
+ reflowLastColumnWithUnconstrainedAvailBSize;
+
+ // If column-fill is auto (not the default), then we might need to
+ // move content between columns for any change in column block-size.
+ //
+ // The same is true if we have a non-'auto' computed block-size.
+ //
+ // FIXME: It's not clear to me why it's *ever* valid to have
+ // reflowChild be false when changingBSize is true, since it
+ // seems like a child broken over multiple columns might need to
+ // change the size of the fragment in each column.
+ if (!reflowChild && changingBSize &&
+ (StyleColumn()->mColumnFill == StyleColumnFill::Auto ||
+ computedBSize != NS_UNCONSTRAINEDSIZE)) {
+ reflowChild = true;
+ }
+ // If we need to pull up content from the prev-in-flow then this is not just
+ // a block-size shrink. The prev in flow will have set the dirty bit.
+ // Check the overflow rect YMost instead of just the child's content
+ // block-size. The child may have overflowing content that cares about the
+ // available block-size boundary. (It may also have overflowing content that
+ // doesn't care about the available block-size boundary, but if so, too bad,
+ // this optimization is defeated.) We want scrollable overflow here since
+ // this is a calculation that affects layout.
+ if (!reflowChild && shrinkingBSize) {
+ switch (wm.GetBlockDir()) {
+ case WritingMode::eBlockTB:
+ if (child->ScrollableOverflowRect().YMost() > aConfig.mColBSize) {
+ reflowChild = true;
+ }
+ break;
+ case WritingMode::eBlockLR:
+ if (child->ScrollableOverflowRect().XMost() > aConfig.mColBSize) {
+ reflowChild = true;
+ }
+ break;
+ case WritingMode::eBlockRL:
+ // XXX not sure how to handle this, so for now just don't attempt
+ // the optimization
+ reflowChild = true;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown block direction");
+ break;
+ }
+ }
+
+ nscoord childContentBEnd = 0;
+ if (!reflowNext && !reflowChild) {
+ // This child does not need to be reflowed, but we may need to move it
+ MoveChildTo(child, childOrigin, wm, containerSize);
+
+ // If this is the last frame then make sure we get the right status
+ nsIFrame* kidNext = child->GetNextSibling();
+ if (kidNext) {
+ aStatus.Reset();
+ if (kidNext->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ aStatus.SetOverflowIncomplete();
+ } else {
+ aStatus.SetIncomplete();
+ }
+ } else {
+ aStatus = mLastFrameStatus;
+ }
+ childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
+
+ COLUMN_SET_LOG("%s: Skipping child #%d %p: status=%s", __func__,
+ colData.mColCount, child, ToString(aStatus).c_str());
+ } else {
+ LogicalSize availSize(wm, aConfig.mColISize, aConfig.mColBSize);
+ if (reflowLastColumnWithUnconstrainedAvailBSize) {
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+
+ COLUMN_SET_LOG(
+ "%s: Reflowing last column with unconstrained block-size. Change "
+ "available block-size from %d to %d",
+ __func__, aConfig.mColBSize, availSize.BSize(wm));
+ }
+
+ if (reflowNext) {
+ child->MarkSubtreeDirty();
+ }
+
+ LogicalSize kidCBSize(wm, availSize.ISize(wm), computedBSize);
+ ReflowInput kidReflowInput(PresContext(), aReflowInput, child, availSize,
+ Some(kidCBSize));
+ kidReflowInput.mFlags.mIsTopOfPage = [&]() {
+ const bool isNestedMulticolOrPaginated =
+ aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits(
+ NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
+ PresContext()->IsPaginated();
+ if (isNestedMulticolOrPaginated) {
+ if (aConfig.mForceAuto) {
+ // If we are forced to fill columns sequentially, force fit the
+ // content whether we are at top of page or not.
+ return true;
+ }
+ if (aReflowInput.mFlags.mIsTopOfPage) {
+ // If this is the last balancing reflow, we want to force fit
+ // content to avoid infinite loops.
+ return !aConfig.mIsBalancing || aConfig.mIsLastBalancingReflow;
+ }
+ // If we are a not at the top of page, we shouldn't force fit content.
+ // This is because our ColumnSetWrapperFrame can be pushed to the next
+ // column or page and reflowed again with a potentially larger
+ // available block-size.
+ return false;
+ }
+ // We are a top-level multicol in non-paginated context. Force fit the
+ // content only if we are not balancing columns.
+ return !aConfig.mIsBalancing;
+ }();
+ kidReflowInput.mFlags.mTableIsSplittable = false;
+ kidReflowInput.mFlags.mIsColumnBalancing = aConfig.mIsBalancing;
+ kidReflowInput.mFlags.mIsInLastColumnBalancingReflow =
+ aConfig.mIsLastBalancingReflow;
+ kidReflowInput.mBreakType = ReflowInput::BreakType::Column;
+
+ // We need to reflow any float placeholders, even if our column block-size
+ // hasn't changed.
+ kidReflowInput.mFlags.mMustReflowPlaceholders = !changingBSize;
+
+ COLUMN_SET_LOG(
+ "%s: Reflowing child #%d %p: availSize=(%d,%d), kidCBSize=(%d,%d), "
+ "child's mIsTopOfPage=%d",
+ __func__, colData.mColCount, child, availSize.ISize(wm),
+ availSize.BSize(wm), kidCBSize.ISize(wm), kidCBSize.BSize(wm),
+ kidReflowInput.mFlags.mIsTopOfPage);
+
+ // Note if the column's next in flow is not being changed by this
+ // incremental reflow. This may allow the current column to avoid trying
+ // to pull lines from the next column.
+ if (child->GetNextSibling() && !HasAnyStateBits(NS_FRAME_IS_DIRTY) &&
+ !child->GetNextSibling()->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ kidReflowInput.mFlags.mNextInFlowUntouched = true;
+ }
+
+ ReflowOutput kidDesiredSize(wm);
+
+ // XXX it would be cool to consult the float manager for the
+ // previous block to figure out the region of floats from the
+ // previous column that extend into this column, and subtract
+ // that region from the new float manager. So you could stick a
+ // really big float in the first column and text in following
+ // columns would flow around it.
+
+ MOZ_ASSERT(kidReflowInput.ComputedLogicalMargin(wm).IsAllZero(),
+ "-moz-column-content has no margin!");
+ aStatus.Reset();
+ ReflowChild(child, PresContext(), kidDesiredSize, kidReflowInput, wm,
+ childOrigin, containerSize, ReflowChildFlags::Default,
+ aStatus);
+
+ if (colData.mColCount == 1 && aStatus.IsInlineBreakBefore()) {
+ COLUMN_SET_LOG("%s: Content in the first column reports break-before!",
+ __func__);
+ allFit = false;
+ break;
+ }
+
+ reflowNext = aStatus.NextInFlowNeedsReflow();
+
+ // The carried-out block-end margin of column content might be non-zero
+ // when we try to find the best column balancing block size, but it should
+ // never affect the size column set nor be further carried out. Set it to
+ // zero.
+ //
+ // FIXME: For some types of fragmentation, we should carry the margin into
+ // the next column. Also see
+ // https://drafts.csswg.org/css-break-4/#break-margins
+ //
+ // FIXME: This should never happen for the last column, since it should be
+ // a margin root; see nsBlockFrame::IsMarginRoot(). However, sometimes the
+ // last column has an empty continuation while searching for the best
+ // column balancing bsize, which prevents the last column from being a
+ // margin root.
+ kidDesiredSize.mCarriedOutBEndMargin.Zero();
+
+ NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
+
+ FinishReflowChild(child, PresContext(), kidDesiredSize, &kidReflowInput,
+ wm, childOrigin, containerSize,
+ ReflowChildFlags::Default);
+
+ childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
+ if (childContentBEnd > aConfig.mColBSize) {
+ allFit = false;
+ }
+ if (childContentBEnd > availSize.BSize(wm)) {
+ colData.mMaxOverflowingBSize =
+ std::max(childContentBEnd, colData.mMaxOverflowingBSize);
+ }
+
+ COLUMN_SET_LOG(
+ "%s: Reflowed child #%d %p: status=%s, desiredSize=(%d,%d), "
+ "childContentBEnd=%d, CarriedOutBEndMargin=%d (ignored)",
+ __func__, colData.mColCount, child, ToString(aStatus).c_str(),
+ kidDesiredSize.ISize(wm), kidDesiredSize.BSize(wm), childContentBEnd,
+ kidDesiredSize.mCarriedOutBEndMargin.get());
+ }
+
+ contentRect.UnionRect(contentRect, child->GetRect());
+
+ ConsiderChildOverflow(overflowRects, child);
+ contentBEnd = std::max(contentBEnd, childContentBEnd);
+ colData.mLastBSize = childContentBEnd;
+ colData.mSumBSize += childContentBEnd;
+
+ // Build a continuation column if necessary
+ nsIFrame* kidNextInFlow = child->GetNextInFlow();
+
+ if (aStatus.IsFullyComplete()) {
+ NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
+ child = nullptr;
+ break;
+ }
+
+ // Make sure that the column has a next-in-flow. If not, we must
+ // create one to hold the overflowing stuff, even if we're just
+ // going to put it on our overflow list and let *our*
+ // next in flow handle it.
+ if (!kidNextInFlow) {
+ NS_ASSERTION(aStatus.NextInFlowNeedsReflow(),
+ "We have to create a continuation, but the block doesn't "
+ "want us to reflow it?");
+
+ // We need to create a continuing column
+ kidNextInFlow = CreateNextInFlow(child);
+ }
+
+ // Make sure we reflow a next-in-flow when it switches between being
+ // normal or overflow container
+ if (aStatus.IsOverflowIncomplete()) {
+ if (!kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ aStatus.SetNextInFlowNeedsReflow();
+ reflowNext = true;
+ kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ }
+ } else if (kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ aStatus.SetNextInFlowNeedsReflow();
+ reflowNext = true;
+ kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ }
+
+ // We have reached the maximum number of columns. If we are balancing, stop
+ // this reflow and continue finding the optimal balancing block-size.
+ //
+ // Otherwise, i.e. we are not balancing, stop this reflow and let the parent
+ // of our multicol container create a next-in-flow if all of the following
+ // conditions are met.
+ //
+ // 1) We fill columns sequentially by the request of the style, not by our
+ // internal needs, i.e. aConfig.mForceAuto is false.
+ //
+ // We don't want to stop this reflow when we force fill the columns
+ // sequentially. We usually go into this mode when giving up balancing, and
+ // this is the last resort to fit all our children by creating overflow
+ // columns.
+ //
+ // 2) In a fragmented context, our multicol container still has block-size
+ // left for its next-in-flow, i.e.
+ // aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft is false.
+ //
+ // Note that in a continuous context, i.e. our multicol container's
+ // available block-size is unconstrained, if it has a fixed block-size
+ // mColumnSetWrapperHasNoBSizeLeft is always true because nothing stops it
+ // from applying all its block-size in the first-in-flow. Otherwise, i.e.
+ // our multicol container has an unconstrained block-size, we shouldn't be
+ // here because all our children should fit in the very first column even if
+ // mColumnSetWrapperHasNoBSizeLeft is false.
+ //
+ // According to the definition of mColumnSetWrapperHasNoBSizeLeft, if the
+ // bit is *not* set, either our multicol container has unconstrained
+ // block-size, or it has a constrained block-size and has block-size left
+ // for its next-in-flow. In either cases, the parent of our multicol
+ // container can create a next-in-flow for the container that guaranteed to
+ // have non-zero block-size for the container's children.
+ //
+ // Put simply, if either one of the above conditions is not met, we are
+ // going to create more overflow columns until all our children are fit.
+ if (colData.mColCount >= aConfig.mUsedColCount &&
+ (aConfig.mIsBalancing ||
+ (!aConfig.mForceAuto &&
+ !aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft))) {
+ NS_ASSERTION(aConfig.mIsBalancing ||
+ aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
+ "Why are we here if we have unlimited block-size to fill "
+ "columns sequentially.");
+
+ // No more columns allowed here. Stop.
+ aStatus.SetNextInFlowNeedsReflow();
+ kidNextInFlow->MarkSubtreeDirty();
+ // Move any of our leftover columns to our overflow list. Our
+ // next-in-flow will eventually pick them up.
+ nsFrameList continuationColumns = mFrames.TakeFramesAfter(child);
+ if (continuationColumns.NotEmpty()) {
+ SetOverflowFrames(std::move(continuationColumns));
+ }
+ child = nullptr;
+
+ COLUMN_SET_LOG("%s: We are not going to create overflow columns.",
+ __func__);
+ break;
+ }
+
+ if (PresContext()->HasPendingInterrupt()) {
+ // Stop the loop now while |child| still points to the frame that bailed
+ // out. We could keep going here and condition a bunch of the code in
+ // this loop on whether there's an interrupt, or even just keep going and
+ // trying to reflow the blocks (even though we know they'll interrupt
+ // right after their first line), but stopping now is conceptually the
+ // simplest (and probably fastest) thing.
+ break;
+ }
+
+ // Advance to the next column
+ child = child->GetNextSibling();
+ ++colData.mColCount;
+
+ if (child) {
+ childOrigin.I(wm) += aConfig.mColISize + aConfig.mColGap;
+
+ COLUMN_SET_LOG("%s: Next childOrigin.iCoord=%d", __func__,
+ childOrigin.I(wm));
+ }
+ }
+
+ if (PresContext()->CheckForInterrupt(this) &&
+ HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ // Mark all our kids starting with |child| dirty
+
+ // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
+ // because we might have interrupted while reflowing |child|, and since
+ // we're about to add a dirty bit to |child| we need to make sure that
+ // |this| is scheduled to have dirty bits marked on it and its ancestors.
+ // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
+ // bail out immediately, since it'll already have a dirty bit.
+ for (; child; child = child->GetNextSibling()) {
+ child->MarkSubtreeDirty();
+ }
+ }
+
+ colData.mMaxBSize = contentBEnd;
+ LogicalSize contentSize = LogicalSize(wm, contentRect.Size());
+ contentSize.BSize(wm) = std::max(contentSize.BSize(wm), contentBEnd);
+ mLastFrameStatus = aStatus;
+
+ if (computedBSize != NS_UNCONSTRAINEDSIZE && !HasColumnSpanSiblings()) {
+ NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
+ "Available block-size should be constrained because it's "
+ "restricted by the computed block-size when our reflow "
+ "input is created in nsBlockFrame::ReflowBlockFrame()!");
+
+ // If a) our parent ColumnSetWrapper has constrained block-size
+ // (nsBlockFrame::ReflowBlockFrame() applies the block-size constraint
+ // when creating a ReflowInput for ColumnSetFrame child); and b) we are the
+ // sole ColumnSet or the last ColumnSet continuation split by column-spans
+ // in a ColumnSetWrapper, extend our block-size to consume the available
+ // block-size so that the column-rules are drawn to the content block-end
+ // edge of the multicol container.
+ contentSize.BSize(wm) =
+ std::max(contentSize.BSize(wm), aReflowInput.AvailableBSize());
+ }
+
+ aDesiredSize.SetSize(wm, contentSize);
+ aDesiredSize.mOverflowAreas = overflowRects;
+ aDesiredSize.UnionOverflowAreasWithDesiredBounds();
+
+ // In vertical-rl mode, make a second pass if necessary to reposition the
+ // columns with the correct container width. (In other writing modes,
+ // correct containerSize was not required for column positioning so we don't
+ // need this fixup.)
+ //
+ // RTL column positions also depend on ColumnSet's actual contentSize. We need
+ // this fixup, too.
+ if ((wm.IsVerticalRL() || isRTL) &&
+ containerSize.width != contentSize.Width(wm)) {
+ const nsSize finalContainerSize = aDesiredSize.PhysicalSize();
+ OverflowAreas overflowRects;
+ for (nsIFrame* child : mFrames) {
+ // Get the logical position as set previously using a provisional or
+ // dummy containerSize, and reset with the correct container size.
+ child->SetPosition(wm, child->GetLogicalPosition(wm, containerSize),
+ finalContainerSize);
+ ConsiderChildOverflow(overflowRects, child);
+ }
+ aDesiredSize.mOverflowAreas = overflowRects;
+ aDesiredSize.UnionOverflowAreasWithDesiredBounds();
+ }
+
+ colData.mFeasible = allFit && aStatus.IsFullyComplete();
+
+ COLUMN_SET_LOG(
+ "%s: Done column reflow pass: %s, mMaxBSize=%d, mSumBSize=%d, "
+ "mLastBSize=%d, mMaxOverflowingBSize=%d",
+ __func__, colData.mFeasible ? "Feasible :)" : "Infeasible :(",
+ colData.mMaxBSize, colData.mSumBSize, colData.mLastBSize,
+ colData.mMaxOverflowingBSize);
+
+ return colData;
+}
+
+void nsColumnSetFrame::DrainOverflowColumns() {
+ // First grab the prev-in-flows overflows and reparent them to this
+ // frame.
+ nsPresContext* presContext = PresContext();
+ nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
+ if (prev) {
+ AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames());
+ if (overflows) {
+ nsContainerFrame::ReparentFrameViewList(*overflows, prev, this);
+
+ mFrames.InsertFrames(this, nullptr, std::move(*overflows));
+ }
+ }
+
+ // Now pull back our own overflows and append them to our children.
+ // We don't need to reparent them since we're already their parent.
+ AutoFrameListPtr overflows(presContext, StealOverflowFrames());
+ if (overflows) {
+ // We're already the parent for these frames, so no need to set
+ // their parent again.
+ mFrames.AppendFrames(nullptr, std::move(*overflows));
+ }
+}
+
+void nsColumnSetFrame::FindBestBalanceBSize(const ReflowInput& aReflowInput,
+ nsPresContext* aPresContext,
+ ReflowConfig& aConfig,
+ ColumnBalanceData aColData,
+ ReflowOutput& aDesiredSize,
+ bool aUnboundedLastColumn,
+ nsReflowStatus& aStatus) {
+ MOZ_ASSERT(aConfig.mIsBalancing,
+ "Why are we here if we are not balancing columns?");
+
+ const nscoord availableContentBSize = aReflowInput.AvailableBSize();
+
+ // Termination of the algorithm below is guaranteed because
+ // aConfig.knownFeasibleBSize - aConfig.knownInfeasibleBSize decreases in
+ // every iteration.
+ int32_t iterationCount = 1;
+
+ // We set this flag when we detect that we may contain a frame
+ // that can break anywhere (thus foiling the linear decrease-by-one
+ // search)
+ bool maybeContinuousBreakingDetected = false;
+ bool possibleOptimalBSizeDetected = false;
+
+ // This is the extra block-size added to the optimal column block-size
+ // estimation which is calculated in the while-loop by dividing
+ // aColData.mSumBSize into N columns.
+ //
+ // The constant is arbitrary. We use a half of line-height first. In case a
+ // column container uses *zero* (or a very small) line-height, use a half of
+ // default line-height 1140/2 = 570 app units as the minimum value. Otherwise
+ // we might take more than necessary iterations before finding a feasible
+ // block-size.
+ nscoord extraBlockSize = std::max(570, aReflowInput.GetLineHeight() / 2);
+
+ // We use divide-by-N to estimate the optimal column block-size only if the
+ // last column's available block-size is unbounded.
+ bool foundFeasibleBSizeCloserToBest = !aUnboundedLastColumn;
+
+ // Stop the binary search when the difference of the feasible and infeasible
+ // block-size is within this gap. Here we use one device pixel.
+ const int32_t gapToStop = aPresContext->DevPixelsToAppUnits(1);
+
+ while (!aPresContext->HasPendingInterrupt()) {
+ nscoord lastKnownFeasibleBSize = aConfig.mKnownFeasibleBSize;
+
+ // Record what we learned from the last reflow
+ if (aColData.mFeasible) {
+ // mMaxBSize is feasible. Also, mLastBalanceBSize is feasible.
+ aConfig.mKnownFeasibleBSize =
+ std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
+ aConfig.mKnownFeasibleBSize =
+ std::min(aConfig.mKnownFeasibleBSize, mLastBalanceBSize);
+
+ // Furthermore, no block-size less than the block-size of the last
+ // column can ever be feasible. (We might be able to reduce the
+ // block-size of a non-last column by moving content to a later column,
+ // but we can't do that with the last column.)
+ if (aColData.mColCount == aConfig.mUsedColCount) {
+ aConfig.mKnownInfeasibleBSize =
+ std::max(aConfig.mKnownInfeasibleBSize, aColData.mLastBSize - 1);
+ }
+ } else {
+ aConfig.mKnownInfeasibleBSize =
+ std::max(aConfig.mKnownInfeasibleBSize, mLastBalanceBSize);
+
+ // If a column didn't fit in its available block-size, then its current
+ // block-size must be the minimum block-size for unbreakable content in
+ // the column, and therefore no smaller block-size can be feasible.
+ aConfig.mKnownInfeasibleBSize = std::max(
+ aConfig.mKnownInfeasibleBSize, aColData.mMaxOverflowingBSize - 1);
+
+ if (aUnboundedLastColumn) {
+ // The last column is unbounded, so all content got reflowed, so the
+ // mMaxBSize is feasible.
+ aConfig.mKnownFeasibleBSize =
+ std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
+
+ NS_ASSERTION(mLastFrameStatus.IsComplete(),
+ "Last column should be complete if the available "
+ "block-size is unconstrained!");
+ }
+ }
+
+ COLUMN_SET_LOG(
+ "%s: this=%p, mKnownInfeasibleBSize=%d, mKnownFeasibleBSize=%d",
+ __func__, this, aConfig.mKnownInfeasibleBSize,
+ aConfig.mKnownFeasibleBSize);
+
+ if (aConfig.mKnownInfeasibleBSize >= aConfig.mKnownFeasibleBSize - 1) {
+ // aConfig.mKnownFeasibleBSize is where we want to be. This can happen in
+ // the very first iteration when a column container solely has a tall
+ // unbreakable child that overflows the container.
+ break;
+ }
+
+ if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
+ // There's no feasible block-size to fit our contents. We may need to
+ // reflow one more time after this loop.
+ break;
+ }
+
+ const nscoord gap =
+ aConfig.mKnownFeasibleBSize - aConfig.mKnownInfeasibleBSize;
+ if (gap <= gapToStop && possibleOptimalBSizeDetected) {
+ // We detected a possible optimal block-size in the last iteration. If it
+ // is infeasible, we may need to reflow one more time after this loop.
+ break;
+ }
+
+ if (lastKnownFeasibleBSize - aConfig.mKnownFeasibleBSize == 1) {
+ // We decreased the feasible block-size by one twip only. This could
+ // indicate that there is a continuously breakable child frame
+ // that we are crawling through.
+ maybeContinuousBreakingDetected = true;
+ }
+
+ nscoord nextGuess = aConfig.mKnownInfeasibleBSize + gap / 2;
+ if (aConfig.mKnownFeasibleBSize - nextGuess < extraBlockSize &&
+ !maybeContinuousBreakingDetected) {
+ // We're close to our target, so just try shrinking just the
+ // minimum amount that will cause one of our columns to break
+ // differently.
+ nextGuess = aConfig.mKnownFeasibleBSize - 1;
+ } else if (!foundFeasibleBSizeCloserToBest) {
+ // Make a guess by dividing mSumBSize into N columns and adding
+ // extraBlockSize to try to make it on the feasible side.
+ nextGuess = aColData.mSumBSize / aConfig.mUsedColCount + extraBlockSize;
+ // Sanitize it
+ nextGuess = clamped(nextGuess, aConfig.mKnownInfeasibleBSize + 1,
+ aConfig.mKnownFeasibleBSize - 1);
+ // We keep doubling extraBlockSize in every iteration until we find a
+ // feasible guess.
+ extraBlockSize *= 2;
+ } else if (aConfig.mKnownFeasibleBSize == NS_UNCONSTRAINEDSIZE) {
+ // This can happen when we had a next-in-flow so we didn't
+ // want to do an unbounded block-size measuring step. Let's just increase
+ // from the infeasible block-size by some reasonable amount.
+ nextGuess = aConfig.mKnownInfeasibleBSize * 2 + extraBlockSize;
+ } else if (gap <= gapToStop) {
+ // Floor nextGuess to the greatest multiple of gapToStop below or equal to
+ // mKnownFeasibleBSize.
+ nextGuess = aConfig.mKnownFeasibleBSize / gapToStop * gapToStop;
+ possibleOptimalBSizeDetected = true;
+ }
+
+ // Don't bother guessing more than our block-size constraint.
+ nextGuess = std::min(availableContentBSize, nextGuess);
+
+ COLUMN_SET_LOG("%s: Choosing next guess=%d, iteration=%d", __func__,
+ nextGuess, iterationCount);
+ ++iterationCount;
+
+ aConfig.mColBSize = nextGuess;
+
+ aUnboundedLastColumn = false;
+ MarkPrincipalChildrenDirty(this);
+ aColData =
+ ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig, false);
+
+ if (!foundFeasibleBSizeCloserToBest && aColData.mFeasible) {
+ foundFeasibleBSizeCloserToBest = true;
+ }
+ }
+
+ if (!aColData.mFeasible && !aPresContext->HasPendingInterrupt()) {
+ // We need to reflow one more time at the feasible block-size to
+ // get a valid layout.
+ if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
+ aConfig.mColBSize = availableContentBSize;
+ if (mLastBalanceBSize == availableContentBSize) {
+ // If we end up here, we have a constrained available content
+ // block-size, and our last column's block-size exceeds it. Also, if
+ // this is the first balancing iteration, the last column is given
+ // unconstrained available block-size, so it has a fully complete
+ // reflow status. Therefore, we always want to reflow again at the
+ // available content block-size to get a valid layout and a correct
+ // reflow status (likely an *incomplete* status) so that our column
+ // container can be fragmented if needed.
+
+ if (aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft) {
+ // If our column container has a constrained block-size (either in a
+ // paginated context or in a nested column container), and is going
+ // to consume all its computed block-size in this fragment, then our
+ // column container has no block-size left to contain our
+ // next-in-flows. We have to give up balancing, and create our
+ // own overflow columns.
+ //
+ // We don't want to create overflow columns immediately when our
+ // content doesn't fit since this changes our reflow status from
+ // incomplete to complete. Valid reasons include 1) the outer column
+ // container might do column balancing, and it can enlarge the
+ // available content block-size so that the nested one could fit its
+ // content in next balancing iteration; or 2) the outer column
+ // container is filling columns sequentially, and may have more
+ // inline-size to create more column boxes for the nested column
+ // container's next-in-flows.
+ aConfig = ChooseColumnStrategy(aReflowInput, true);
+ }
+ }
+ } else {
+ aConfig.mColBSize = aConfig.mKnownFeasibleBSize;
+ }
+
+ // This is our last attempt to reflow. If our column container's available
+ // block-size is unconstrained, make sure that the last column is
+ // allowed to have arbitrary block-size here, even though we were
+ // balancing. Otherwise we'd have to split, and it's not clear what we'd
+ // do with that.
+ COLUMN_SET_LOG("%s: Last attempt to call ReflowColumns", __func__);
+ aConfig.mIsLastBalancingReflow = true;
+ const bool forceUnboundedLastColumn =
+ aReflowInput.mParentReflowInput->AvailableBSize() ==
+ NS_UNCONSTRAINEDSIZE;
+ MarkPrincipalChildrenDirty(this);
+ ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig,
+ forceUnboundedLastColumn);
+ }
+}
+
+void nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ // Don't support interruption in columns
+ nsPresContext::InterruptPreventer noInterrupts(aPresContext);
+
+ DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ MOZ_ASSERT(aReflowInput.mCBReflowInput->mFrame->StyleColumn()
+ ->IsColumnContainerStyle(),
+ "The column container should have relevant column styles!");
+ MOZ_ASSERT(aReflowInput.mParentReflowInput->mFrame->IsColumnSetWrapperFrame(),
+ "The column container should be ColumnSetWrapperFrame!");
+ MOZ_ASSERT(
+ aReflowInput.ComputedLogicalBorderPadding(aReflowInput.GetWritingMode())
+ .IsAllZero(),
+ "Only the column container can have border and padding!");
+ MOZ_ASSERT(
+ GetChildList(FrameChildListID::OverflowContainers).IsEmpty() &&
+ GetChildList(FrameChildListID::ExcessOverflowContainers).IsEmpty(),
+ "ColumnSetFrame should store overflow containers in principal "
+ "child list!");
+
+ //------------ Handle Incremental Reflow -----------------
+
+ COLUMN_SET_LOG("%s: Begin Reflow: this=%p, is nested multicol=%d", __func__,
+ this,
+ aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits(
+ NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR));
+
+ // If inline size is unconstrained, set aForceAuto to true to allow
+ // the columns to expand in the inline direction. (This typically
+ // happens in orthogonal flows where the inline direction is the
+ // container's block direction).
+ ReflowConfig config = ChooseColumnStrategy(
+ aReflowInput, aReflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE);
+
+ // If balancing, then we allow the last column to grow to unbounded
+ // block-size during the first reflow. This gives us a way to estimate
+ // what the average column block-size should be, because we can measure
+ // the block-size of all the columns and sum them up. But don't do this
+ // if we have a next in flow because we don't want to suck all its
+ // content back here and then have to push it out again!
+ nsIFrame* nextInFlow = GetNextInFlow();
+ bool unboundedLastColumn = config.mIsBalancing && !nextInFlow;
+ const ColumnBalanceData colData = ReflowColumns(
+ aDesiredSize, aReflowInput, aStatus, config, unboundedLastColumn);
+
+ // If we're not balancing, then we're already done, since we should have
+ // reflown all of our children, and there is no need for a binary search to
+ // determine proper column block-size.
+ if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) {
+ FindBestBalanceBSize(aReflowInput, aPresContext, config, colData,
+ aDesiredSize, unboundedLastColumn, aStatus);
+ }
+
+ if (aPresContext->HasPendingInterrupt() &&
+ aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
+ // In this situation, we might be lying about our reflow status, because
+ // our last kid (the one that got interrupted) was incomplete. Fix that.
+ aStatus.Reset();
+ }
+
+ NS_ASSERTION(aStatus.IsFullyComplete() ||
+ aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
+ "Column set should be complete if the available block-size is "
+ "unconstrained");
+
+ MOZ_ASSERT(!HasAbsolutelyPositionedChildren(),
+ "ColumnSetWrapperFrame should be the abs.pos container!");
+ FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
+
+ COLUMN_SET_LOG("%s: End Reflow: this=%p", __func__, this);
+}
+
+void nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ if (IsVisibleForPainting()) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayColumnRule>(aBuilder,
+ this);
+ }
+
+ // Our children won't have backgrounds so it doesn't matter where we put them.
+ for (nsIFrame* f : mFrames) {
+ BuildDisplayListForChild(aBuilder, f, aLists);
+ }
+}
+
+void nsColumnSetFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ // Everything in mFrames is continuations of the first thing in mFrames.
+ nsIFrame* column = mFrames.FirstChild();
+
+ // We might not have any columns, apparently?
+ if (!column) {
+ return;
+ }
+
+ MOZ_ASSERT(column->Style()->GetPseudoType() == PseudoStyleType::columnContent,
+ "What sort of child is this?");
+ aResult.AppendElement(OwnedAnonBox(column));
+}
+
+Maybe<nscoord> nsColumnSetFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ Maybe<nscoord> result;
+ for (const auto* kid : mFrames) {
+ auto kidBaseline =
+ kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext);
+ if (!kidBaseline) {
+ continue;
+ }
+ // The kid frame may not necessarily be aligned with the columnset frame.
+ LogicalRect kidRect{aWM, kid->GetLogicalNormalPosition(aWM, GetSize()),
+ kid->GetLogicalSize(aWM)};
+ if (aBaselineGroup == BaselineSharingGroup::First) {
+ *kidBaseline += kidRect.BStart(aWM);
+ } else {
+ *kidBaseline += (GetLogicalSize().BSize(aWM) - kidRect.BEnd(aWM));
+ }
+ // Take the smallest of the baselines (i.e. Closest to border-block-start
+ // for `BaselineSharingGroup::First`, border-block-end for
+ // `BaselineSharingGroup::Last`)
+ if (!result || *kidBaseline < *result) {
+ result = kidBaseline;
+ }
+ }
+ return result;
+}
+
+#ifdef DEBUG
+void nsColumnSetFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ MOZ_ASSERT(aListID != FrameChildListID::Principal || aChildList.OnlyChild(),
+ "initial principal child list must have exactly one child");
+ nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
+}
+
+void nsColumnSetFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ MOZ_CRASH("unsupported operation");
+}
+
+void nsColumnSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ MOZ_CRASH("unsupported operation");
+}
+
+void nsColumnSetFrame::RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) {
+ MOZ_CRASH("unsupported operation");
+}
+#endif
diff --git a/layout/generic/nsColumnSetFrame.h b/layout/generic/nsColumnSetFrame.h
new file mode 100644
index 0000000000..20df72a07d
--- /dev/null
+++ b/layout/generic/nsColumnSetFrame.h
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsColumnSetFrame_h___
+#define nsColumnSetFrame_h___
+
+/* rendering object for css3 multi-column layout */
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+
+class nsCSSBorderRenderer;
+
+/**
+ * nsColumnSetFrame implements CSS multi-column layout.
+ * @note nsColumnSetFrame keeps true overflow containers in the normal flow
+ * child lists (i.e. the principal and overflow lists).
+ */
+class nsColumnSetFrame final : public nsContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsColumnSetFrame)
+
+ explicit nsColumnSetFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+#ifdef DEBUG
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override;
+#endif
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ nsContainerFrame* GetContentInsertionFrame() override {
+ nsIFrame* frame = PrincipalChildList().FirstChild();
+
+ // if no children return nullptr
+ if (!frame) return nullptr;
+
+ return frame->GetContentInsertionFrame();
+ }
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ /**
+ * Similar to nsBlockFrame::DrainOverflowLines. Locate any columns not
+ * handled by our prev-in-flow, and any columns sitting on our own
+ * overflow list, and put them in our primary child list for reflowing.
+ */
+ void DrainOverflowColumns();
+
+ // Return the column-content frame.
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"ColumnSet"_ns, aResult);
+ }
+#endif
+
+ void CreateBorderRenderers(nsTArray<nsCSSBorderRenderer>& aBorderRenderers,
+ gfxContext* aCtx, const nsRect& aDirtyRect,
+ const nsPoint& aPt);
+
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const override;
+
+ protected:
+ nscoord mLastBalanceBSize;
+ nsReflowStatus mLastFrameStatus;
+
+ /**
+ * These are the parameters that control the layout of columns.
+ */
+ struct ReflowConfig {
+ // The optimal number of columns that we want to use. This is computed from
+ // column-count, column-width, available inline-size, etc.
+ int32_t mUsedColCount = INT32_MAX;
+
+ // The inline-size of each individual column.
+ nscoord mColISize = NS_UNCONSTRAINEDSIZE;
+
+ // The amount of inline-size that is expected to be left over after all the
+ // columns and column gaps are laid out.
+ nscoord mExpectedISizeLeftOver = 0;
+
+ // The width (inline-size) of each column gap.
+ nscoord mColGap = NS_UNCONSTRAINEDSIZE;
+
+ // The available block-size of each individual column. This parameter is set
+ // during each iteration of the binary search for the best column
+ // block-size.
+ nscoord mColBSize = NS_UNCONSTRAINEDSIZE;
+
+ // A boolean controlling whether or not we are balancing.
+ bool mIsBalancing = false;
+
+ // A boolean controlling whether or not we are forced to fill columns
+ // sequentially.
+ bool mForceAuto = false;
+
+ // A boolean indicates whether or not we are in the last attempt to reflow
+ // columns. We set it to true at the end of FindBestBalanceBSize().
+ bool mIsLastBalancingReflow = false;
+
+ // The last known column block-size that was 'feasible'. A column bSize is
+ // feasible if all child content fits within the specified bSize.
+ nscoord mKnownFeasibleBSize = NS_UNCONSTRAINEDSIZE;
+
+ // The last known block-size that was 'infeasible'. A column bSize is
+ // infeasible if not all child content fits within the specified bSize.
+ nscoord mKnownInfeasibleBSize = 0;
+ };
+
+ // Collect various block-size data calculated in ReflowChildren(), which are
+ // mainly used for column balancing. This is the output of ReflowChildren()
+ // and ReflowColumns().
+ struct ColumnBalanceData {
+ // The maximum "content block-size" of any column
+ nscoord mMaxBSize = 0;
+
+ // The sum of the "content block-size" for all columns
+ nscoord mSumBSize = 0;
+
+ // The "content block-size" of the last column
+ nscoord mLastBSize = 0;
+
+ // The maximum "content block-size" of all columns that overflowed
+ // their available block-size
+ nscoord mMaxOverflowingBSize = 0;
+
+ // The number of columns (starting from 1 because we have at least one
+ // column). It can be less than ReflowConfig::mUsedColCount.
+ int32_t mColCount = 1;
+
+ // This flag indicates the content that was reflowed fits into the
+ // mColMaxBSize in ReflowConfig.
+ bool mFeasible = false;
+ };
+
+ ColumnBalanceData ReflowColumns(ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ const ReflowConfig& aConfig,
+ bool aUnboundedLastColumn);
+
+ /**
+ * The basic reflow strategy is to call this function repeatedly to
+ * obtain specific parameters that determine the layout of the
+ * columns. This function will compute those parameters from the CSS
+ * style. This function will also be responsible for implementing
+ * the state machine that controls column balancing.
+ */
+ ReflowConfig ChooseColumnStrategy(const ReflowInput& aReflowInput,
+ bool aForceAuto) const;
+
+ /**
+ * Perform the binary search for the best balance block-size for this column
+ * set.
+ *
+ * @param aReflowInput The input parameters for the current reflow iteration.
+ * @param aPresContext The presentation context in which the current reflow
+ * iteration is occurring.
+ * @param aConfig The ReflowConfig object associated with this column set
+ * frame, generated by ChooseColumnStrategy().
+ * @param aColData A data structure used to keep track of data needed between
+ * successive iterations of the balancing process.
+ * @param aDesiredSize The final output size of the column set frame (output
+ * of reflow procedure).
+ * @param aUnboundedLastColumn A boolean value indicating that the last column
+ * can be of any block-size. Used during the first iteration of the
+ * balancing procedure to measure the block-size of all content in
+ * descendant frames of the column set.
+ * @param aStatus A final reflow status of the column set frame, passed in as
+ * an output parameter.
+ */
+ void FindBestBalanceBSize(const ReflowInput& aReflowInput,
+ nsPresContext* aPresContext, ReflowConfig& aConfig,
+ ColumnBalanceData aColData,
+ ReflowOutput& aDesiredSize,
+ bool aUnboundedLastColumn, nsReflowStatus& aStatus);
+
+ void ForEachColumnRule(
+ const std::function<void(const nsRect& lineRect)>& aSetLineRect,
+ const nsPoint& aPt) const;
+};
+
+#endif // nsColumnSetFrame_h___
diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp
new file mode 100644
index 0000000000..9629905968
--- /dev/null
+++ b/layout/generic/nsContainerFrame.cpp
@@ -0,0 +1,3041 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class #1 for rendering objects that have child lists */
+
+#include "nsContainerFrame.h"
+#include "mozilla/widget/InitData.h"
+#include "nsContainerFrameInlines.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsFlexContainerFrame.h"
+#include "nsFrameSelection.h"
+#include "mozilla/dom/Document.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsPoint.h"
+#include "nsStyleConsts.h"
+#include "nsView.h"
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+#include "nsViewManager.h"
+#include "nsIWidget.h"
+#include "nsCanvasFrame.h"
+#include "nsCSSRendering.h"
+#include "nsError.h"
+#include "nsDisplayList.h"
+#include "nsIBaseWindow.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsBlockFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "mozilla/AutoRestore.h"
+#include "nsIFrameInlines.h"
+#include "nsPrintfCString.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+
+using mozilla::gfx::ColorPattern;
+using mozilla::gfx::DeviceColor;
+using mozilla::gfx::DrawTarget;
+using mozilla::gfx::Rect;
+using mozilla::gfx::sRGBColor;
+using mozilla::gfx::ToDeviceColor;
+
+nsContainerFrame::~nsContainerFrame() = default;
+
+NS_QUERYFRAME_HEAD(nsContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsSplittableFrame)
+
+void nsContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsSplittableFrame::Init(aContent, aParent, aPrevInFlow);
+ if (aPrevInFlow) {
+ // Make sure we copy bits from our prev-in-flow that will affect
+ // us. A continuation for a container frame needs to know if it
+ // has a child with a view so that we'll properly reposition it.
+ if (aPrevInFlow->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
+ AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
+ }
+ }
+}
+
+void nsContainerFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+#ifdef DEBUG
+ nsIFrame::VerifyDirtyBitSet(aChildList);
+ for (nsIFrame* f : aChildList) {
+ MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
+ }
+#endif
+ if (aListID == FrameChildListID::Principal) {
+ MOZ_ASSERT(mFrames.IsEmpty(),
+ "unexpected second call to SetInitialChildList");
+ mFrames = std::move(aChildList);
+ } else if (aListID == FrameChildListID::Backdrop) {
+ MOZ_ASSERT(StyleDisplay()->mTopLayer != StyleTopLayer::None,
+ "Only top layer frames should have backdrop");
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "Top layer frames should be out-of-flow");
+ MOZ_ASSERT(!GetProperty(BackdropProperty()),
+ "We shouldn't have setup backdrop frame list before");
+#ifdef DEBUG
+ {
+ nsIFrame* placeholder = aChildList.FirstChild();
+ MOZ_ASSERT(aChildList.OnlyChild(), "Should have only one backdrop");
+ MOZ_ASSERT(placeholder->IsPlaceholderFrame(),
+ "The frame to be stored should be a placeholder");
+ MOZ_ASSERT(static_cast<nsPlaceholderFrame*>(placeholder)
+ ->GetOutOfFlowFrame()
+ ->IsBackdropFrame(),
+ "The placeholder should points to a backdrop frame");
+ }
+#endif
+ nsFrameList* list = new (PresShell()) nsFrameList(std::move(aChildList));
+ SetProperty(BackdropProperty(), list);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected child list");
+ }
+}
+
+void nsContainerFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal ||
+ aListID == FrameChildListID::NoReflowPrincipal,
+ "unexpected child list");
+
+ if (MOZ_UNLIKELY(aFrameList.IsEmpty())) {
+ return;
+ }
+
+ DrainSelfOverflowList(); // ensure the last frame is in mFrames
+ mFrames.AppendFrames(this, std::move(aFrameList));
+
+ if (aListID != FrameChildListID::NoReflowPrincipal) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+}
+
+void nsContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal ||
+ aListID == FrameChildListID::NoReflowPrincipal,
+ "unexpected child list");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+
+ if (MOZ_UNLIKELY(aFrameList.IsEmpty())) {
+ return;
+ }
+
+ DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
+ mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList));
+
+ if (aListID != FrameChildListID::NoReflowPrincipal) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+}
+
+void nsContainerFrame::RemoveFrame(DestroyContext& aContext,
+ ChildListID aListID, nsIFrame* aOldFrame) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal ||
+ aListID == FrameChildListID::NoReflowPrincipal,
+ "unexpected child list");
+
+ AutoTArray<nsIFrame*, 10> continuations;
+ {
+ nsIFrame* continuation = aOldFrame;
+ while (continuation) {
+ continuations.AppendElement(continuation);
+ continuation = continuation->GetNextContinuation();
+ }
+ }
+
+ mozilla::PresShell* presShell = PresShell();
+ nsContainerFrame* lastParent = nullptr;
+
+ // Loop and destroy aOldFrame and all of its continuations.
+ //
+ // Request a reflow on the parent frames involved unless we were explicitly
+ // told not to (FrameChildListID::NoReflowPrincipal).
+ const bool generateReflowCommand =
+ aListID != FrameChildListID::NoReflowPrincipal;
+ for (nsIFrame* continuation : Reversed(continuations)) {
+ nsContainerFrame* parent = continuation->GetParent();
+
+ // Please note that 'parent' may not actually be where 'continuation' lives.
+ // We really MUST use StealFrame() and nothing else here.
+ // @see nsInlineFrame::StealFrame for details.
+ parent->StealFrame(continuation);
+ continuation->Destroy(aContext);
+ if (generateReflowCommand && parent != lastParent) {
+ presShell->FrameNeedsReflow(parent, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ lastParent = parent;
+ }
+ }
+}
+
+void nsContainerFrame::DestroyAbsoluteFrames(DestroyContext& aContext) {
+ if (IsAbsoluteContainer()) {
+ GetAbsoluteContainingBlock()->DestroyFrames(aContext);
+ MarkAsNotAbsoluteContainingBlock();
+ }
+}
+
+void nsContainerFrame::SafelyDestroyFrameListProp(
+ DestroyContext& aContext, mozilla::PresShell* aPresShell,
+ FrameListPropertyDescriptor aProp) {
+ // Note that the last frame can be removed through another route and thus
+ // delete the property -- that's why we fetch the property again before
+ // removing each frame rather than fetching it once and iterating the list.
+ while (nsFrameList* frameList = GetProperty(aProp)) {
+ nsIFrame* frame = frameList->RemoveFirstChild();
+ if (MOZ_LIKELY(frame)) {
+ frame->Destroy(aContext);
+ } else {
+ Unused << TakeProperty(aProp);
+ frameList->Delete(aPresShell);
+ return;
+ }
+ }
+}
+
+void nsContainerFrame::Destroy(DestroyContext& aContext) {
+ // Prevent event dispatch during destruction.
+ if (HasView()) {
+ GetView()->SetFrame(nullptr);
+ }
+
+ DestroyAbsoluteFrames(aContext);
+
+ // Destroy frames on the principal child list.
+ mFrames.DestroyFrames(aContext);
+
+ // If we have any IB split siblings, clear their references to us.
+ if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // Delete previous sibling's reference to me.
+ if (nsIFrame* prevSib = GetProperty(nsIFrame::IBSplitPrevSibling())) {
+ NS_WARNING_ASSERTION(
+ this == prevSib->GetProperty(nsIFrame::IBSplitSibling()),
+ "IB sibling chain is inconsistent");
+ prevSib->RemoveProperty(nsIFrame::IBSplitSibling());
+ }
+
+ // Delete next sibling's reference to me.
+ if (nsIFrame* nextSib = GetProperty(nsIFrame::IBSplitSibling())) {
+ NS_WARNING_ASSERTION(
+ this == nextSib->GetProperty(nsIFrame::IBSplitPrevSibling()),
+ "IB sibling chain is inconsistent");
+ nextSib->RemoveProperty(nsIFrame::IBSplitPrevSibling());
+ }
+
+#ifdef DEBUG
+ // This is just so we can assert it's not set in nsIFrame::DestroyFrom.
+ RemoveStateBits(NS_FRAME_PART_OF_IBSPLIT);
+#endif
+ }
+
+ if (MOZ_UNLIKELY(!mProperties.IsEmpty())) {
+ using T = mozilla::FrameProperties::UntypedDescriptor;
+ bool hasO = false, hasOC = false, hasEOC = false, hasBackdrop = false;
+ mProperties.ForEach([&](const T& aProp, uint64_t) {
+ if (aProp == OverflowProperty()) {
+ hasO = true;
+ } else if (aProp == OverflowContainersProperty()) {
+ hasOC = true;
+ } else if (aProp == ExcessOverflowContainersProperty()) {
+ hasEOC = true;
+ } else if (aProp == BackdropProperty()) {
+ hasBackdrop = true;
+ }
+ return true;
+ });
+
+ // Destroy frames on the auxiliary frame lists and delete the lists.
+ mozilla::PresShell* presShell = PresShell();
+ if (hasO) {
+ SafelyDestroyFrameListProp(aContext, presShell, OverflowProperty());
+ }
+
+ MOZ_ASSERT(CanContainOverflowContainers() || !(hasOC || hasEOC),
+ "this type of frame shouldn't have overflow containers");
+ if (hasOC) {
+ SafelyDestroyFrameListProp(aContext, presShell,
+ OverflowContainersProperty());
+ }
+ if (hasEOC) {
+ SafelyDestroyFrameListProp(aContext, presShell,
+ ExcessOverflowContainersProperty());
+ }
+
+ MOZ_ASSERT(!GetProperty(BackdropProperty()) ||
+ StyleDisplay()->mTopLayer != StyleTopLayer::None,
+ "only top layer frame may have backdrop");
+ if (hasBackdrop) {
+ SafelyDestroyFrameListProp(aContext, presShell, BackdropProperty());
+ }
+ }
+
+ nsSplittableFrame::Destroy(aContext);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Child frame enumeration
+
+const nsFrameList& nsContainerFrame::GetChildList(ChildListID aListID) const {
+ // We only know about the principal child list, the overflow lists,
+ // and the backdrop list.
+ switch (aListID) {
+ case FrameChildListID::Principal:
+ return mFrames;
+ case FrameChildListID::Overflow: {
+ nsFrameList* list = GetOverflowFrames();
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ case FrameChildListID::OverflowContainers: {
+ nsFrameList* list = GetOverflowContainers();
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ case FrameChildListID::ExcessOverflowContainers: {
+ nsFrameList* list = GetExcessOverflowContainers();
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ case FrameChildListID::Backdrop: {
+ nsFrameList* list = GetProperty(BackdropProperty());
+ return list ? *list : nsFrameList::EmptyList();
+ }
+ default:
+ return nsSplittableFrame::GetChildList(aListID);
+ }
+}
+
+void nsContainerFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
+ mFrames.AppendIfNonempty(aLists, FrameChildListID::Principal);
+
+ using T = mozilla::FrameProperties::UntypedDescriptor;
+ mProperties.ForEach([this, aLists](const T& aProp, uint64_t aValue) {
+ typedef const nsFrameList* L;
+ if (aProp == OverflowProperty()) {
+ reinterpret_cast<L>(aValue)->AppendIfNonempty(aLists,
+ FrameChildListID::Overflow);
+ } else if (aProp == OverflowContainersProperty()) {
+ MOZ_ASSERT(CanContainOverflowContainers(),
+ "found unexpected OverflowContainersProperty");
+ Unused << this; // silence clang -Wunused-lambda-capture in opt builds
+ reinterpret_cast<L>(aValue)->AppendIfNonempty(
+ aLists, FrameChildListID::OverflowContainers);
+ } else if (aProp == ExcessOverflowContainersProperty()) {
+ MOZ_ASSERT(CanContainOverflowContainers(),
+ "found unexpected ExcessOverflowContainersProperty");
+ Unused << this; // silence clang -Wunused-lambda-capture in opt builds
+ reinterpret_cast<L>(aValue)->AppendIfNonempty(
+ aLists, FrameChildListID::ExcessOverflowContainers);
+ } else if (aProp == BackdropProperty()) {
+ reinterpret_cast<L>(aValue)->AppendIfNonempty(aLists,
+ FrameChildListID::Backdrop);
+ }
+ return true;
+ });
+
+ nsSplittableFrame::GetChildLists(aLists);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Painting/Events
+
+void nsContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+ BuildDisplayListForNonBlockChildren(aBuilder, aLists);
+}
+
+void nsContainerFrame::BuildDisplayListForNonBlockChildren(
+ nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists,
+ DisplayChildFlags aFlags) {
+ nsIFrame* kid = mFrames.FirstChild();
+ // Put each child's background directly onto the content list
+ nsDisplayListSet set(aLists, aLists.Content());
+ // The children should be in content order
+ while (kid) {
+ BuildDisplayListForChild(aBuilder, kid, set, aFlags);
+ kid = kid->GetNextSibling();
+ }
+}
+
+class nsDisplaySelectionOverlay : public nsPaintedDisplayItem {
+ public:
+ /**
+ * @param aSelectionValue nsISelectionController::getDisplaySelection.
+ */
+ nsDisplaySelectionOverlay(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ int16_t aSelectionValue)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mSelectionValue(aSelectionValue) {
+ MOZ_COUNT_CTOR(nsDisplaySelectionOverlay);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySelectionOverlay)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ NS_DISPLAY_DECL_NAME("SelectionOverlay", TYPE_SELECTION_OVERLAY)
+ private:
+ DeviceColor ComputeColor() const;
+
+ static DeviceColor ComputeColorFromSelectionStyle(ComputedStyle&);
+ static DeviceColor ApplyTransparencyIfNecessary(nscolor);
+
+ // nsISelectionController::getDisplaySelection.
+ int16_t mSelectionValue;
+};
+
+DeviceColor nsDisplaySelectionOverlay::ApplyTransparencyIfNecessary(
+ nscolor aColor) {
+ // If it has already alpha, leave it like that.
+ if (NS_GET_A(aColor) != 255) {
+ return ToDeviceColor(aColor);
+ }
+
+ // NOTE(emilio): Blink and WebKit do something slightly different here, and
+ // blend the color with white instead, both for overlays and text backgrounds.
+ auto color = sRGBColor::FromABGR(aColor);
+ color.a = 0.5;
+ return ToDeviceColor(color);
+}
+
+DeviceColor nsDisplaySelectionOverlay::ComputeColorFromSelectionStyle(
+ ComputedStyle& aStyle) {
+ return ApplyTransparencyIfNecessary(
+ aStyle.GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor));
+}
+
+DeviceColor nsDisplaySelectionOverlay::ComputeColor() const {
+ LookAndFeel::ColorID colorID;
+ if (RefPtr<ComputedStyle> style =
+ mFrame->ComputeSelectionStyle(mSelectionValue)) {
+ return ComputeColorFromSelectionStyle(*style);
+ }
+ if (mSelectionValue == nsISelectionController::SELECTION_ON) {
+ colorID = LookAndFeel::ColorID::Highlight;
+ } else if (mSelectionValue == nsISelectionController::SELECTION_ATTENTION) {
+ colorID = LookAndFeel::ColorID::TextSelectAttentionBackground;
+ } else {
+ colorID = LookAndFeel::ColorID::TextSelectDisabledBackground;
+ }
+
+ return ApplyTransparencyIfNecessary(
+ LookAndFeel::Color(colorID, mFrame, NS_RGB(255, 255, 255)));
+}
+
+void nsDisplaySelectionOverlay::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
+ ColorPattern color(ComputeColor());
+
+ nsIntRect pxRect =
+ GetPaintRect(aBuilder, aCtx)
+ .ToOutsidePixels(mFrame->PresContext()->AppUnitsPerDevPixel());
+ Rect rect(pxRect.x, pxRect.y, pxRect.width, pxRect.height);
+ MaybeSnapToDevicePixels(rect, aDrawTarget, true);
+
+ aDrawTarget.FillRect(rect, color);
+}
+
+bool nsDisplaySelectionOverlay::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ wr::LayoutRect bounds = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
+ nsRect(ToReferenceFrame(), Frame()->GetSize()),
+ mFrame->PresContext()->AppUnitsPerDevPixel()));
+ aBuilder.PushRect(bounds, bounds, !BackfaceIsHidden(), false, false,
+ wr::ToColorF(ComputeColor()));
+ return true;
+}
+
+void nsContainerFrame::DisplaySelectionOverlay(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ uint16_t aContentType) {
+ if (!IsSelected() || !IsVisibleForPainting()) {
+ return;
+ }
+
+ int16_t displaySelection = PresShell()->GetSelectionFlags();
+ if (!(displaySelection & aContentType)) {
+ return;
+ }
+
+ const nsFrameSelection* frameSelection = GetConstFrameSelection();
+ int16_t selectionValue = frameSelection->GetDisplaySelection();
+
+ if (selectionValue <= nsISelectionController::SELECTION_HIDDEN) {
+ return; // selection is hidden or off
+ }
+
+ nsIContent* newContent = mContent->GetParent();
+
+ // check to see if we are anonymous content
+ // XXXbz there has GOT to be a better way of determining this!
+ int32_t offset =
+ newContent ? newContent->ComputeIndexOf_Deprecated(mContent) : 0;
+
+ // look up to see what selection(s) are on this frame
+ UniquePtr<SelectionDetails> details =
+ frameSelection->LookUpSelection(newContent, offset, 1, false);
+ if (!details) {
+ return;
+ }
+
+ bool normal = false;
+ for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
+ if (sd->mSelectionType == SelectionType::eNormal) {
+ normal = true;
+ }
+ }
+
+ if (!normal && aContentType == nsISelectionDisplay::DISPLAY_IMAGES) {
+ // Don't overlay an image if it's not in the primary selection.
+ return;
+ }
+
+ aList->AppendNewToTop<nsDisplaySelectionOverlay>(aBuilder, this,
+ selectionValue);
+}
+
+/* virtual */
+void nsContainerFrame::ChildIsDirty(nsIFrame* aChild) {
+ NS_ASSERTION(aChild->IsSubtreeDirty(), "child isn't actually dirty");
+
+ AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+nsIFrame::FrameSearchResult nsContainerFrame::PeekOffsetNoAmount(
+ bool aForward, int32_t* aOffset) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // Don't allow the caret to stay in an empty (leaf) container frame.
+ return CONTINUE_EMPTY;
+}
+
+nsIFrame::FrameSearchResult nsContainerFrame::PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // Don't allow the caret to stay in an empty (leaf) container frame.
+ return CONTINUE_EMPTY;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Helper member functions
+
+/**
+ * Position the view associated with |aKidFrame|, if there is one. A
+ * container frame should call this method after positioning a frame,
+ * but before |Reflow|.
+ */
+void nsContainerFrame::PositionFrameView(nsIFrame* aKidFrame) {
+ nsIFrame* parentFrame = aKidFrame->GetParent();
+ if (!aKidFrame->HasView() || !parentFrame) return;
+
+ nsView* view = aKidFrame->GetView();
+ nsViewManager* vm = view->GetViewManager();
+ nsPoint pt;
+ nsView* ancestorView = parentFrame->GetClosestView(&pt);
+
+ if (ancestorView != view->GetParent()) {
+ NS_ASSERTION(ancestorView == view->GetParent()->GetParent(),
+ "Allowed only one anonymous view between frames");
+ // parentFrame is responsible for positioning aKidFrame's view
+ // explicitly
+ return;
+ }
+
+ pt += aKidFrame->GetPosition();
+ vm->MoveViewTo(view, pt.x, pt.y);
+}
+
+void nsContainerFrame::ReparentFrameView(nsIFrame* aChildFrame,
+ nsIFrame* aOldParentFrame,
+ nsIFrame* aNewParentFrame) {
+#ifdef DEBUG
+ MOZ_ASSERT(aChildFrame, "null child frame pointer");
+ MOZ_ASSERT(aOldParentFrame, "null old parent frame pointer");
+ MOZ_ASSERT(aNewParentFrame, "null new parent frame pointer");
+ MOZ_ASSERT(aOldParentFrame != aNewParentFrame,
+ "same old and new parent frame");
+
+ // See if either the old parent frame or the new parent frame have a view
+ while (!aOldParentFrame->HasView() && !aNewParentFrame->HasView()) {
+ // Walk up both the old parent frame and the new parent frame nodes
+ // stopping when we either find a common parent or views for one
+ // or both of the frames.
+ //
+ // This works well in the common case where we push/pull and the old parent
+ // frame and the new parent frame are part of the same flow. They will
+ // typically be the same distance (height wise) from the
+ aOldParentFrame = aOldParentFrame->GetParent();
+ aNewParentFrame = aNewParentFrame->GetParent();
+
+ // We should never walk all the way to the root frame without finding
+ // a view
+ NS_ASSERTION(aOldParentFrame && aNewParentFrame, "didn't find view");
+
+ // See if we reached a common ancestor
+ if (aOldParentFrame == aNewParentFrame) {
+ break;
+ }
+ }
+
+ // See if we found a common parent frame
+ if (aOldParentFrame == aNewParentFrame) {
+ // We found a common parent and there are no views between the old parent
+ // and the common parent or the new parent frame and the common parent.
+ // Because neither the old parent frame nor the new parent frame have views,
+ // then any child views don't need reparenting
+ return;
+ }
+
+ // We found views for one or both of the ancestor frames before we
+ // found a common ancestor.
+ nsView* oldParentView = aOldParentFrame->GetClosestView();
+ nsView* newParentView = aNewParentFrame->GetClosestView();
+
+ // See if the old parent frame and the new parent frame are in the
+ // same view sub-hierarchy. If they are then we don't have to do
+ // anything
+ if (oldParentView != newParentView) {
+ MOZ_ASSERT_UNREACHABLE("can't move frames between views");
+ // They're not so we need to reparent any child views
+ aChildFrame->ReparentFrameViewTo(oldParentView->GetViewManager(),
+ newParentView);
+ }
+#endif
+}
+
+void nsContainerFrame::ReparentFrameViewList(const nsFrameList& aChildFrameList,
+ nsIFrame* aOldParentFrame,
+ nsIFrame* aNewParentFrame) {
+#ifdef DEBUG
+ MOZ_ASSERT(aChildFrameList.NotEmpty(), "empty child frame list");
+ MOZ_ASSERT(aOldParentFrame, "null old parent frame pointer");
+ MOZ_ASSERT(aNewParentFrame, "null new parent frame pointer");
+ MOZ_ASSERT(aOldParentFrame != aNewParentFrame,
+ "same old and new parent frame");
+
+ // See if either the old parent frame or the new parent frame have a view
+ while (!aOldParentFrame->HasView() && !aNewParentFrame->HasView()) {
+ // Walk up both the old parent frame and the new parent frame nodes
+ // stopping when we either find a common parent or views for one
+ // or both of the frames.
+ //
+ // This works well in the common case where we push/pull and the old parent
+ // frame and the new parent frame are part of the same flow. They will
+ // typically be the same distance (height wise) from the
+ aOldParentFrame = aOldParentFrame->GetParent();
+ aNewParentFrame = aNewParentFrame->GetParent();
+
+ // We should never walk all the way to the root frame without finding
+ // a view
+ NS_ASSERTION(aOldParentFrame && aNewParentFrame, "didn't find view");
+
+ // See if we reached a common ancestor
+ if (aOldParentFrame == aNewParentFrame) {
+ break;
+ }
+ }
+
+ // See if we found a common parent frame
+ if (aOldParentFrame == aNewParentFrame) {
+ // We found a common parent and there are no views between the old parent
+ // and the common parent or the new parent frame and the common parent.
+ // Because neither the old parent frame nor the new parent frame have views,
+ // then any child views don't need reparenting
+ return;
+ }
+
+ // We found views for one or both of the ancestor frames before we
+ // found a common ancestor.
+ nsView* oldParentView = aOldParentFrame->GetClosestView();
+ nsView* newParentView = aNewParentFrame->GetClosestView();
+
+ // See if the old parent frame and the new parent frame are in the
+ // same view sub-hierarchy. If they are then we don't have to do
+ // anything
+ if (oldParentView != newParentView) {
+ MOZ_ASSERT_UNREACHABLE("can't move frames between views");
+ nsViewManager* viewManager = oldParentView->GetViewManager();
+
+ // They're not so we need to reparent any child views
+ for (nsIFrame* f : aChildFrameList) {
+ f->ReparentFrameViewTo(viewManager, newParentView);
+ }
+ }
+#endif
+}
+
+void nsContainerFrame::ReparentFrame(nsIFrame* aFrame,
+ nsContainerFrame* aOldParent,
+ nsContainerFrame* aNewParent) {
+ NS_ASSERTION(aOldParent == aFrame->GetParent(),
+ "Parent not consistent with expectations");
+
+ aFrame->SetParent(aNewParent);
+
+ // When pushing and pulling frames we need to check for whether any
+ // views need to be reparented
+ ReparentFrameView(aFrame, aOldParent, aNewParent);
+}
+
+void nsContainerFrame::ReparentFrames(nsFrameList& aFrameList,
+ nsContainerFrame* aOldParent,
+ nsContainerFrame* aNewParent) {
+ for (auto* f : aFrameList) {
+ ReparentFrame(f, aOldParent, aNewParent);
+ }
+}
+
+void nsContainerFrame::SetSizeConstraints(nsPresContext* aPresContext,
+ nsIWidget* aWidget,
+ const nsSize& aMinSize,
+ const nsSize& aMaxSize) {
+ LayoutDeviceIntSize devMinSize(
+ aPresContext->AppUnitsToDevPixels(aMinSize.width),
+ aPresContext->AppUnitsToDevPixels(aMinSize.height));
+ LayoutDeviceIntSize devMaxSize(
+ aMaxSize.width == NS_UNCONSTRAINEDSIZE
+ ? NS_MAXSIZE
+ : aPresContext->AppUnitsToDevPixels(aMaxSize.width),
+ aMaxSize.height == NS_UNCONSTRAINEDSIZE
+ ? NS_MAXSIZE
+ : aPresContext->AppUnitsToDevPixels(aMaxSize.height));
+
+ // MinSize has a priority over MaxSize
+ if (devMinSize.width > devMaxSize.width) devMaxSize.width = devMinSize.width;
+ if (devMinSize.height > devMaxSize.height)
+ devMaxSize.height = devMinSize.height;
+
+ nsIWidget* rootWidget = aPresContext->GetNearestWidget();
+ DesktopToLayoutDeviceScale constraintsScale(MOZ_WIDGET_INVALID_SCALE);
+ if (rootWidget) {
+ constraintsScale = rootWidget->GetDesktopToDeviceScale();
+ }
+
+ widget::SizeConstraints constraints(devMinSize, devMaxSize, constraintsScale);
+
+ // The sizes are in inner window sizes, so convert them into outer window
+ // sizes. Use a size of (200, 200) as only the difference between the inner
+ // and outer size is needed.
+ const LayoutDeviceIntSize sizeDiff = aWidget->ClientToWindowSizeDifference();
+ if (constraints.mMinSize.width) {
+ constraints.mMinSize.width += sizeDiff.width;
+ }
+ if (constraints.mMinSize.height) {
+ constraints.mMinSize.height += sizeDiff.height;
+ }
+ if (constraints.mMaxSize.width != NS_MAXSIZE) {
+ constraints.mMaxSize.width += sizeDiff.width;
+ }
+ if (constraints.mMaxSize.height != NS_MAXSIZE) {
+ constraints.mMaxSize.height += sizeDiff.height;
+ }
+
+ aWidget->SetSizeConstraints(constraints);
+}
+
+void nsContainerFrame::SyncFrameViewAfterReflow(nsPresContext* aPresContext,
+ nsIFrame* aFrame, nsView* aView,
+ const nsRect& aInkOverflowArea,
+ ReflowChildFlags aFlags) {
+ if (!aView) {
+ return;
+ }
+
+ // Make sure the view is sized and positioned correctly
+ if (!(aFlags & ReflowChildFlags::NoMoveView)) {
+ PositionFrameView(aFrame);
+ }
+
+ if (!(aFlags & ReflowChildFlags::NoSizeView)) {
+ nsViewManager* vm = aView->GetViewManager();
+
+ vm->ResizeView(aView, aInkOverflowArea, true);
+ }
+}
+
+void nsContainerFrame::DoInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) {
+ auto handleChildren = [aRenderingContext](auto frame, auto data) {
+ for (nsIFrame* kid : frame->mFrames) {
+ kid->AddInlineMinISize(aRenderingContext, data);
+ }
+ };
+ DoInlineIntrinsicISize(aData, handleChildren);
+}
+
+void nsContainerFrame::DoInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) {
+ auto handleChildren = [aRenderingContext](auto frame, auto data) {
+ for (nsIFrame* kid : frame->mFrames) {
+ kid->AddInlinePrefISize(aRenderingContext, data);
+ }
+ };
+ DoInlineIntrinsicISize(aData, handleChildren);
+ aData->mLineIsEmpty = false;
+}
+
+/* virtual */
+LogicalSize nsContainerFrame::ComputeAutoSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
+ LogicalSize result(aWM, 0xdeadbeef, NS_UNCONSTRAINEDSIZE);
+ nscoord availBased =
+ aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
+ // replaced elements always shrink-wrap
+ if (aFlags.contains(ComputeSizeFlag::ShrinkWrap) || IsReplaced()) {
+ // Only bother computing our 'auto' ISize if the result will be used.
+ const auto& styleISize = aSizeOverrides.mStyleISize
+ ? *aSizeOverrides.mStyleISize
+ : StylePosition()->ISize(aWM);
+ if (styleISize.IsAuto()) {
+ result.ISize(aWM) =
+ ShrinkISizeToFit(aRenderingContext, availBased, aFlags);
+ }
+ } else {
+ result.ISize(aWM) = availBased;
+ }
+
+ if (IsTableCaption()) {
+ // If we're a container for font size inflation, then shrink
+ // wrapping inside of us should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(this);
+
+ WritingMode tableWM = GetParent()->GetWritingMode();
+ if (aWM.IsOrthogonalTo(tableWM)) {
+ // For an orthogonal caption on a block-dir side of the table, shrink-wrap
+ // to min-isize.
+ result.ISize(aWM) = GetMinISize(aRenderingContext);
+ } else {
+ // The outer frame constrains our available isize to the isize of
+ // the table. Grow if our min-isize is bigger than that, but not
+ // larger than the containing block isize. (It would really be nice
+ // to transmit that information another way, so we could grow up to
+ // the table's available isize, but that's harder.)
+ nscoord min = GetMinISize(aRenderingContext);
+ if (min > aCBSize.ISize(aWM)) {
+ min = aCBSize.ISize(aWM);
+ }
+ if (min > result.ISize(aWM)) {
+ result.ISize(aWM) = min;
+ }
+ }
+ }
+ return result;
+}
+
+void nsContainerFrame::ReflowChild(
+ nsIFrame* aKidFrame, nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
+ const WritingMode& aWM, const LogicalPoint& aPos,
+ const nsSize& aContainerSize, ReflowChildFlags aFlags,
+ nsReflowStatus& aStatus, nsOverflowContinuationTracker* aTracker) {
+ MOZ_ASSERT(aReflowInput.mFrame == aKidFrame, "bad reflow input");
+ if (aWM.IsPhysicalRTL()) {
+ NS_ASSERTION(aContainerSize.width != NS_UNCONSTRAINEDSIZE,
+ "ReflowChild with unconstrained container width!");
+ }
+ MOZ_ASSERT(aDesiredSize.InkOverflow() == nsRect(0, 0, 0, 0) &&
+ aDesiredSize.ScrollableOverflow() == nsRect(0, 0, 0, 0),
+ "please reset the overflow areas before calling ReflowChild");
+
+ // Position the child frame and its view if requested.
+ if (ReflowChildFlags::NoMoveFrame !=
+ (aFlags & ReflowChildFlags::NoMoveFrame)) {
+ aKidFrame->SetPosition(aWM, aPos, aContainerSize);
+ }
+
+ if (!(aFlags & ReflowChildFlags::NoMoveView)) {
+ PositionFrameView(aKidFrame);
+ PositionChildViews(aKidFrame);
+ }
+
+ // Reflow the child frame
+ aKidFrame->Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ // If the child frame is complete, delete any next-in-flows,
+ // but only if the NoDeleteNextInFlowChild flag isn't set.
+ if (!aStatus.IsInlineBreakBefore() && aStatus.IsFullyComplete() &&
+ !(aFlags & ReflowChildFlags::NoDeleteNextInFlowChild)) {
+ if (nsIFrame* kidNextInFlow = aKidFrame->GetNextInFlow()) {
+ // Remove all of the childs next-in-flows. Make sure that we ask
+ // the right parent to do the removal (it's possible that the
+ // parent is not this because we are executing pullup code)
+ nsOverflowContinuationTracker::AutoFinish fini(aTracker, aKidFrame);
+ DestroyContext context(PresShell());
+ kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, kidNextInFlow,
+ true);
+ }
+ }
+}
+
+// XXX temporary: hold on to a copy of the old physical version of
+// ReflowChild so that we can convert callers incrementally.
+void nsContainerFrame::ReflowChild(nsIFrame* aKidFrame,
+ nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput, nscoord aX,
+ nscoord aY, ReflowChildFlags aFlags,
+ nsReflowStatus& aStatus,
+ nsOverflowContinuationTracker* aTracker) {
+ MOZ_ASSERT(aReflowInput.mFrame == aKidFrame, "bad reflow input");
+
+ // Position the child frame and its view if requested.
+ if (ReflowChildFlags::NoMoveFrame !=
+ (aFlags & ReflowChildFlags::NoMoveFrame)) {
+ aKidFrame->SetPosition(nsPoint(aX, aY));
+ }
+
+ if (!(aFlags & ReflowChildFlags::NoMoveView)) {
+ PositionFrameView(aKidFrame);
+ PositionChildViews(aKidFrame);
+ }
+
+ // Reflow the child frame
+ aKidFrame->Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ // If the child frame is complete, delete any next-in-flows,
+ // but only if the NoDeleteNextInFlowChild flag isn't set.
+ if (aStatus.IsFullyComplete() &&
+ !(aFlags & ReflowChildFlags::NoDeleteNextInFlowChild)) {
+ if (nsIFrame* kidNextInFlow = aKidFrame->GetNextInFlow()) {
+ // Remove all of the childs next-in-flows. Make sure that we ask
+ // the right parent to do the removal (it's possible that the
+ // parent is not this because we are executing pullup code)
+ nsOverflowContinuationTracker::AutoFinish fini(aTracker, aKidFrame);
+ DestroyContext context(PresShell());
+ kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, kidNextInFlow,
+ true);
+ }
+ }
+}
+
+/**
+ * Position the views of |aFrame|'s descendants. A container frame
+ * should call this method if it moves a frame after |Reflow|.
+ */
+void nsContainerFrame::PositionChildViews(nsIFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
+ return;
+ }
+
+ // Recursively walk aFrame's child frames.
+ // Process the additional child lists, but skip the popup list as the view for
+ // popups is managed by the parent.
+ // Currently only nsMenuFrame has a popupList and during layout will adjust
+ // the view manually to position the popup.
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ if (listID == FrameChildListID::Popup) {
+ continue;
+ }
+ for (nsIFrame* childFrame : list) {
+ // Position the frame's view (if it has one) otherwise recursively
+ // process its children
+ if (childFrame->HasView()) {
+ PositionFrameView(childFrame);
+ } else {
+ PositionChildViews(childFrame);
+ }
+ }
+ }
+}
+
+void nsContainerFrame::FinishReflowChild(
+ nsIFrame* aKidFrame, nsPresContext* aPresContext,
+ const ReflowOutput& aDesiredSize, const ReflowInput* aReflowInput,
+ const WritingMode& aWM, const LogicalPoint& aPos,
+ const nsSize& aContainerSize, nsIFrame::ReflowChildFlags aFlags) {
+ MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == aKidFrame);
+ MOZ_ASSERT(aReflowInput || aKidFrame->IsMathMLFrame() ||
+ aKidFrame->IsTableCellFrame(),
+ "aReflowInput should be passed in almost all cases");
+
+ if (aWM.IsPhysicalRTL()) {
+ NS_ASSERTION(aContainerSize.width != NS_UNCONSTRAINEDSIZE,
+ "FinishReflowChild with unconstrained container width!");
+ }
+
+ nsPoint curOrigin = aKidFrame->GetPosition();
+ const LogicalSize convertedSize = aDesiredSize.Size(aWM);
+ LogicalPoint pos(aPos);
+
+ if (aFlags & ReflowChildFlags::ApplyRelativePositioning) {
+ MOZ_ASSERT(aReflowInput, "caller must have passed reflow input");
+ // ApplyRelativePositioning in right-to-left writing modes needs to know
+ // the updated frame width to set the normal position correctly.
+ aKidFrame->SetSize(aWM, convertedSize);
+
+ const LogicalMargin offsets = aReflowInput->ComputedLogicalOffsets(aWM);
+ ReflowInput::ApplyRelativePositioning(aKidFrame, aWM, offsets, &pos,
+ aContainerSize);
+ }
+
+ if (ReflowChildFlags::NoMoveFrame !=
+ (aFlags & ReflowChildFlags::NoMoveFrame)) {
+ aKidFrame->SetRect(aWM, LogicalRect(aWM, pos, convertedSize),
+ aContainerSize);
+ } else {
+ aKidFrame->SetSize(aWM, convertedSize);
+ }
+
+ if (aKidFrame->HasView()) {
+ nsView* view = aKidFrame->GetView();
+ // Make sure the frame's view is properly sized and positioned and has
+ // things like opacity correct
+ SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
+ aDesiredSize.InkOverflow(), aFlags);
+ }
+
+ nsPoint newOrigin = aKidFrame->GetPosition();
+ if (!(aFlags & ReflowChildFlags::NoMoveView) && curOrigin != newOrigin) {
+ if (!aKidFrame->HasView()) {
+ // If the frame has moved, then we need to make sure any child views are
+ // correctly positioned
+ PositionChildViews(aKidFrame);
+ }
+ }
+
+ aKidFrame->DidReflow(aPresContext, aReflowInput);
+}
+#if defined(_MSC_VER) && !defined(__clang__) && defined(_M_AMD64)
+# pragma optimize("", on)
+#endif
+
+// XXX temporary: hold on to a copy of the old physical version of
+// FinishReflowChild so that we can convert callers incrementally.
+void nsContainerFrame::FinishReflowChild(nsIFrame* aKidFrame,
+ nsPresContext* aPresContext,
+ const ReflowOutput& aDesiredSize,
+ const ReflowInput* aReflowInput,
+ nscoord aX, nscoord aY,
+ ReflowChildFlags aFlags) {
+ MOZ_ASSERT(!(aFlags & ReflowChildFlags::ApplyRelativePositioning),
+ "only the logical version supports ApplyRelativePositioning "
+ "since ApplyRelativePositioning requires the container size");
+
+ nsPoint curOrigin = aKidFrame->GetPosition();
+ nsPoint pos(aX, aY);
+ nsSize size(aDesiredSize.PhysicalSize());
+
+ if (ReflowChildFlags::NoMoveFrame !=
+ (aFlags & ReflowChildFlags::NoMoveFrame)) {
+ aKidFrame->SetRect(nsRect(pos, size));
+ } else {
+ aKidFrame->SetSize(size);
+ }
+
+ if (aKidFrame->HasView()) {
+ nsView* view = aKidFrame->GetView();
+ // Make sure the frame's view is properly sized and positioned and has
+ // things like opacity correct
+ SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
+ aDesiredSize.InkOverflow(), aFlags);
+ }
+
+ if (!(aFlags & ReflowChildFlags::NoMoveView) && curOrigin != pos) {
+ if (!aKidFrame->HasView()) {
+ // If the frame has moved, then we need to make sure any child views are
+ // correctly positioned
+ PositionChildViews(aKidFrame);
+ }
+ }
+
+ aKidFrame->DidReflow(aPresContext, aReflowInput);
+}
+
+void nsContainerFrame::ReflowOverflowContainerChildren(
+ nsPresContext* aPresContext, const ReflowInput& aReflowInput,
+ OverflowAreas& aOverflowRects, ReflowChildFlags aFlags,
+ nsReflowStatus& aStatus, ChildFrameMerger aMergeFunc,
+ Maybe<nsSize> aContainerSize) {
+ MOZ_ASSERT(aPresContext, "null pointer");
+
+ nsFrameList* overflowContainers =
+ DrainExcessOverflowContainersList(aMergeFunc);
+ if (!overflowContainers) {
+ return; // nothing to reflow
+ }
+
+ nsOverflowContinuationTracker tracker(this, false, false);
+ bool shouldReflowAllKids = aReflowInput.ShouldReflowAllKids();
+
+ for (nsIFrame* frame : *overflowContainers) {
+ if (frame->GetPrevInFlow()->GetParent() != GetPrevInFlow()) {
+ // frame's prevInFlow has moved, skip reflowing this frame;
+ // it will get reflowed once it's been placed
+ if (GetNextInFlow()) {
+ // We report OverflowIncomplete status in this case to avoid our parent
+ // deleting our next-in-flows which might destroy non-empty frames.
+ nsReflowStatus status;
+ status.SetOverflowIncomplete();
+ aStatus.MergeCompletionStatusFrom(status);
+ }
+ continue;
+ }
+
+ auto ScrollableOverflowExceedsAvailableBSize =
+ [this, &aReflowInput](nsIFrame* aFrame) {
+ if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
+ return false;
+ }
+ const auto parentWM = GetWritingMode();
+ const nscoord scrollableOverflowRectBEnd =
+ LogicalRect(parentWM,
+ aFrame->ScrollableOverflowRectRelativeToParent(),
+ GetSize())
+ .BEnd(parentWM);
+ return scrollableOverflowRectBEnd > aReflowInput.AvailableBSize();
+ };
+
+ // If the available block-size has changed, or the existing scrollable
+ // overflow's block-end exceeds it, we need to reflow even if the frame
+ // isn't dirty.
+ if (shouldReflowAllKids || frame->IsSubtreeDirty() ||
+ ScrollableOverflowExceedsAvailableBSize(frame)) {
+ // Get prev-in-flow
+ nsIFrame* prevInFlow = frame->GetPrevInFlow();
+ NS_ASSERTION(prevInFlow,
+ "overflow container frame must have a prev-in-flow");
+ NS_ASSERTION(
+ frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER),
+ "overflow container frame must have overflow container bit set");
+ WritingMode wm = frame->GetWritingMode();
+ nsSize containerSize =
+ aContainerSize ? *aContainerSize
+ : aReflowInput.AvailableSize(wm).GetPhysicalSize(wm);
+ LogicalRect prevRect = prevInFlow->GetLogicalRect(wm, containerSize);
+
+ // Initialize reflow params
+ LogicalSize availSpace(wm, prevRect.ISize(wm),
+ aReflowInput.AvailableSize(wm).BSize(wm));
+ ReflowOutput desiredSize(aReflowInput);
+
+ StyleSizeOverrides sizeOverride;
+ if (frame->IsFlexItem()) {
+ // A flex item's size is determined by the flex algorithm, not solely by
+ // its style. Thus, the following overrides are necessary.
+ //
+ // Use the overflow container flex item's prev-in-flow inline-size since
+ // this continuation's inline-size is the same.
+ sizeOverride.mStyleISize.emplace(
+ StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(
+ frame->StylePosition()->mBoxSizing == StyleBoxSizing::Border
+ ? prevRect.ISize(wm)
+ : prevInFlow->ContentISize(wm))));
+
+ // An overflow container's block-size must be 0.
+ sizeOverride.mStyleBSize.emplace(
+ StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(0)));
+ }
+ ReflowInput reflowInput(aPresContext, aReflowInput, frame, availSpace,
+ Nothing(), {}, sizeOverride);
+
+ LogicalPoint pos(wm, prevRect.IStart(wm), 0);
+ nsReflowStatus frameStatus;
+ ReflowChild(frame, aPresContext, desiredSize, reflowInput, wm, pos,
+ containerSize, aFlags, frameStatus, &tracker);
+ FinishReflowChild(frame, aPresContext, desiredSize, &reflowInput, wm, pos,
+ containerSize, aFlags);
+
+ // Handle continuations
+ if (!frameStatus.IsFullyComplete()) {
+ if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ // Abspos frames can't cause their parent to be incomplete,
+ // only overflow incomplete.
+ frameStatus.SetOverflowIncomplete();
+ } else {
+ NS_ASSERTION(frameStatus.IsComplete(),
+ "overflow container frames can't be incomplete, only "
+ "overflow-incomplete");
+ }
+
+ // Acquire a next-in-flow, creating it if necessary
+ nsIFrame* nif = frame->GetNextInFlow();
+ if (!nif) {
+ NS_ASSERTION(frameStatus.NextInFlowNeedsReflow(),
+ "Someone forgot a NextInFlowNeedsReflow flag");
+ nif = PresShell()->FrameConstructor()->CreateContinuingFrame(frame,
+ this);
+ } else if (!nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ // used to be a normal next-in-flow; steal it from the child list
+ nif->GetParent()->StealFrame(nif);
+ }
+
+ tracker.Insert(nif, frameStatus);
+ }
+ aStatus.MergeCompletionStatusFrom(frameStatus);
+ // At this point it would be nice to assert
+ // !frame->GetOverflowRect().IsEmpty(), but we have some unsplittable
+ // frames that, when taller than availableHeight will push zero-height
+ // content into a next-in-flow.
+ } else {
+ tracker.Skip(frame, aStatus);
+ if (aReflowInput.mFloatManager) {
+ nsBlockFrame::RecoverFloatsFor(frame, *aReflowInput.mFloatManager,
+ aReflowInput.GetWritingMode(),
+ aReflowInput.ComputedPhysicalSize());
+ }
+ }
+ ConsiderChildOverflow(aOverflowRects, frame);
+ }
+}
+
+void nsContainerFrame::DisplayOverflowContainers(
+ nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
+ nsFrameList* overflowconts = GetOverflowContainers();
+ if (overflowconts) {
+ for (nsIFrame* frame : *overflowconts) {
+ BuildDisplayListForChild(aBuilder, frame, aLists);
+ }
+ }
+}
+
+bool nsContainerFrame::TryRemoveFrame(FrameListPropertyDescriptor aProp,
+ nsIFrame* aChildToRemove) {
+ nsFrameList* list = GetProperty(aProp);
+ if (list && list->StartRemoveFrame(aChildToRemove)) {
+ // aChildToRemove *may* have been removed from this list.
+ if (list->IsEmpty()) {
+ Unused << TakeProperty(aProp);
+ list->Delete(PresShell());
+ }
+ return true;
+ }
+ return false;
+}
+
+bool nsContainerFrame::MaybeStealOverflowContainerFrame(nsIFrame* aChild) {
+ bool removed = false;
+ if (MOZ_UNLIKELY(aChild->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER))) {
+ // Try removing from the overflow container list.
+ removed = TryRemoveFrame(OverflowContainersProperty(), aChild);
+ if (!removed) {
+ // It might be in the excess overflow container list.
+ removed = TryRemoveFrame(ExcessOverflowContainersProperty(), aChild);
+ }
+ }
+ return removed;
+}
+
+void nsContainerFrame::StealFrame(nsIFrame* aChild) {
+#ifdef DEBUG
+ if (!mFrames.ContainsFrame(aChild)) {
+ nsFrameList* list = GetOverflowFrames();
+ if (!list || !list->ContainsFrame(aChild)) {
+ list = GetOverflowContainers();
+ if (!list || !list->ContainsFrame(aChild)) {
+ list = GetExcessOverflowContainers();
+ MOZ_ASSERT(list && list->ContainsFrame(aChild),
+ "aChild isn't our child"
+ " or on a frame list not supported by StealFrame");
+ }
+ }
+ }
+#endif
+
+ if (MaybeStealOverflowContainerFrame(aChild)) {
+ return;
+ }
+
+ // NOTE nsColumnSetFrame and nsCanvasFrame have their overflow containers
+ // on the normal lists so we might get here also if the frame bit
+ // NS_FRAME_IS_OVERFLOW_CONTAINER is set.
+ if (mFrames.StartRemoveFrame(aChild)) {
+ return;
+ }
+
+ // We didn't find the child in our principal child list.
+ // Maybe it's on the overflow list?
+ nsFrameList* frameList = GetOverflowFrames();
+ if (frameList && frameList->ContinueRemoveFrame(aChild)) {
+ if (frameList->IsEmpty()) {
+ DestroyOverflowList();
+ }
+ return;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("StealFrame: can't find aChild");
+}
+
+nsFrameList nsContainerFrame::StealFramesAfter(nsIFrame* aChild) {
+ NS_ASSERTION(
+ !aChild || !aChild->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER),
+ "StealFramesAfter doesn't handle overflow containers");
+ NS_ASSERTION(!IsBlockFrame(), "unexpected call");
+
+ if (!aChild) {
+ return std::move(mFrames);
+ }
+
+ for (nsIFrame* f : mFrames) {
+ if (f == aChild) {
+ return mFrames.TakeFramesAfter(f);
+ }
+ }
+
+ // We didn't find the child in the principal child list.
+ // Maybe it's on the overflow list?
+ if (nsFrameList* overflowFrames = GetOverflowFrames()) {
+ for (nsIFrame* f : *overflowFrames) {
+ if (f == aChild) {
+ return mFrames.TakeFramesAfter(f);
+ }
+ }
+ }
+
+ NS_ERROR("StealFramesAfter: can't find aChild");
+ return nsFrameList();
+}
+
+/*
+ * Create a next-in-flow for aFrame. Will return the newly created
+ * frame <b>if and only if</b> a new frame is created; otherwise
+ * nullptr is returned.
+ */
+nsIFrame* nsContainerFrame::CreateNextInFlow(nsIFrame* aFrame) {
+ MOZ_ASSERT(
+ !IsBlockFrame(),
+ "you should have called nsBlockFrame::CreateContinuationFor instead");
+ MOZ_ASSERT(mFrames.ContainsFrame(aFrame), "expected an in-flow child frame");
+
+ nsIFrame* nextInFlow = aFrame->GetNextInFlow();
+ if (nullptr == nextInFlow) {
+ // Create a continuation frame for the child frame and insert it
+ // into our child list.
+ nextInFlow =
+ PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this);
+ mFrames.InsertFrame(nullptr, aFrame, nextInFlow);
+
+ NS_FRAME_LOG(NS_FRAME_TRACE_NEW_FRAMES,
+ ("nsContainerFrame::CreateNextInFlow: frame=%p nextInFlow=%p",
+ aFrame, nextInFlow));
+
+ return nextInFlow;
+ }
+ return nullptr;
+}
+
+/**
+ * Remove and delete aNextInFlow and its next-in-flows. Updates the sibling and
+ * flow pointers
+ */
+void nsContainerFrame::DeleteNextInFlowChild(DestroyContext& aContext,
+ nsIFrame* aNextInFlow,
+ bool aDeletingEmptyFrames) {
+#ifdef DEBUG
+ nsIFrame* prevInFlow = aNextInFlow->GetPrevInFlow();
+#endif
+ MOZ_ASSERT(prevInFlow, "bad prev-in-flow");
+
+ // If the next-in-flow has a next-in-flow then delete it, too (and
+ // delete it first).
+ // Do this in a loop so we don't overflow the stack for frames
+ // with very many next-in-flows
+ nsIFrame* nextNextInFlow = aNextInFlow->GetNextInFlow();
+ if (nextNextInFlow) {
+ AutoTArray<nsIFrame*, 8> frames;
+ for (nsIFrame* f = nextNextInFlow; f; f = f->GetNextInFlow()) {
+ frames.AppendElement(f);
+ }
+ for (nsIFrame* delFrame : Reversed(frames)) {
+ nsContainerFrame* parent = delFrame->GetParent();
+ parent->DeleteNextInFlowChild(aContext, delFrame, aDeletingEmptyFrames);
+ }
+ }
+
+ // Take the next-in-flow out of the parent's child list
+ StealFrame(aNextInFlow);
+
+#ifdef DEBUG
+ if (aDeletingEmptyFrames) {
+ nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
+ }
+#endif
+
+ // Delete the next-in-flow frame and its descendants. This will also
+ // remove it from its next-in-flow/prev-in-flow chain.
+ aNextInFlow->Destroy(aContext);
+
+ MOZ_ASSERT(!prevInFlow->GetNextInFlow(), "non null next-in-flow");
+}
+
+void nsContainerFrame::PushChildrenToOverflow(nsIFrame* aFromChild,
+ nsIFrame* aPrevSibling) {
+ MOZ_ASSERT(aFromChild, "null pointer");
+ MOZ_ASSERT(aPrevSibling, "pushing first child");
+ MOZ_ASSERT(aPrevSibling->GetNextSibling() == aFromChild, "bad prev sibling");
+
+ // Add the frames to our overflow list (let our next in flow drain
+ // our overflow list when it is ready)
+ SetOverflowFrames(mFrames.TakeFramesAfter(aPrevSibling));
+}
+
+bool nsContainerFrame::PushIncompleteChildren(
+ const FrameHashtable& aPushedItems, const FrameHashtable& aIncompleteItems,
+ const FrameHashtable& aOverflowIncompleteItems) {
+ MOZ_ASSERT(IsFlexOrGridContainer(),
+ "Only Grid / Flex containers can call this!");
+
+ if (aPushedItems.IsEmpty() && aIncompleteItems.IsEmpty() &&
+ aOverflowIncompleteItems.IsEmpty()) {
+ return false;
+ }
+
+ // Iterate the children in normal document order and append them (or a NIF)
+ // to one of the following frame lists according to their status.
+ nsFrameList pushedList;
+ nsFrameList incompleteList;
+ nsFrameList overflowIncompleteList;
+ auto* fc = PresShell()->FrameConstructor();
+ for (nsIFrame* child = PrincipalChildList().FirstChild(); child;) {
+ MOZ_ASSERT((aPushedItems.Contains(child) ? 1 : 0) +
+ (aIncompleteItems.Contains(child) ? 1 : 0) +
+ (aOverflowIncompleteItems.Contains(child) ? 1 : 0) <=
+ 1,
+ "child should only be in one of these sets");
+ // Save the next-sibling so we can continue the loop if |child| is moved.
+ nsIFrame* next = child->GetNextSibling();
+ if (aPushedItems.Contains(child)) {
+ MOZ_ASSERT(child->GetParent() == this);
+ StealFrame(child);
+ pushedList.AppendFrame(nullptr, child);
+ } else if (aIncompleteItems.Contains(child)) {
+ nsIFrame* childNIF = child->GetNextInFlow();
+ if (!childNIF) {
+ childNIF = fc->CreateContinuingFrame(child, this);
+ incompleteList.AppendFrame(nullptr, childNIF);
+ } else {
+ auto* parent = childNIF->GetParent();
+ MOZ_ASSERT(parent != this || !mFrames.ContainsFrame(childNIF),
+ "child's NIF shouldn't be in the same principal list");
+ // If child's existing NIF is an overflow container, convert it to an
+ // actual NIF, since now |child| has non-overflow stuff to give it.
+ // Or, if it's further away then our next-in-flow, then pull it up.
+ if (childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) ||
+ (parent != this && parent != GetNextInFlow())) {
+ parent->StealFrame(childNIF);
+ childNIF->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ if (parent == this) {
+ incompleteList.AppendFrame(nullptr, childNIF);
+ } else {
+ // If childNIF already lives on the next fragment, then we
+ // don't need to reparent it, since we know it's destined to end
+ // up there anyway. Just move it to its parent's overflow list.
+ if (parent == GetNextInFlow()) {
+ nsFrameList toMove(childNIF, childNIF);
+ parent->MergeSortedOverflow(toMove);
+ } else {
+ ReparentFrame(childNIF, parent, this);
+ incompleteList.AppendFrame(nullptr, childNIF);
+ }
+ }
+ }
+ }
+ } else if (aOverflowIncompleteItems.Contains(child)) {
+ nsIFrame* childNIF = child->GetNextInFlow();
+ if (!childNIF) {
+ childNIF = fc->CreateContinuingFrame(child, this);
+ childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ overflowIncompleteList.AppendFrame(nullptr, childNIF);
+ } else {
+ DebugOnly<nsContainerFrame*> lastParent = this;
+ auto* nif = static_cast<nsContainerFrame*>(GetNextInFlow());
+ // If child has any non-overflow-container NIFs, convert them to
+ // overflow containers, since that's all |child| needs now.
+ while (childNIF &&
+ !childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ auto* parent = childNIF->GetParent();
+ parent->StealFrame(childNIF);
+ childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ if (parent == this) {
+ overflowIncompleteList.AppendFrame(nullptr, childNIF);
+ } else {
+ if (!nif || parent == nif) {
+ nsFrameList toMove(childNIF, childNIF);
+ parent->MergeSortedExcessOverflowContainers(toMove);
+ } else {
+ ReparentFrame(childNIF, parent, nif);
+ nsFrameList toMove(childNIF, childNIF);
+ nif->MergeSortedExcessOverflowContainers(toMove);
+ }
+ // We only need to reparent the first childNIF (or not at all if
+ // its parent is our NIF).
+ nif = nullptr;
+ }
+ lastParent = parent;
+ childNIF = childNIF->GetNextInFlow();
+ }
+ }
+ }
+ child = next;
+ }
+
+ // Merge the results into our respective overflow child lists.
+ if (!pushedList.IsEmpty()) {
+ MergeSortedOverflow(pushedList);
+ }
+ if (!incompleteList.IsEmpty()) {
+ MergeSortedOverflow(incompleteList);
+ }
+ if (!overflowIncompleteList.IsEmpty()) {
+ // If our next-in-flow already has overflow containers list, merge the
+ // overflowIncompleteList into that list. Otherwise, merge it into our
+ // excess overflow containers list, to be drained by our next-in-flow.
+ auto* nif = static_cast<nsContainerFrame*>(GetNextInFlow());
+ nsFrameList* oc = nif ? nif->GetOverflowContainers() : nullptr;
+ if (oc) {
+ ReparentFrames(overflowIncompleteList, this, nif);
+ MergeSortedFrameLists(*oc, overflowIncompleteList, GetContent());
+ } else {
+ MergeSortedExcessOverflowContainers(overflowIncompleteList);
+ }
+ }
+ return true;
+}
+
+void nsContainerFrame::NormalizeChildLists() {
+ MOZ_ASSERT(IsFlexOrGridContainer(),
+ "Only Flex / Grid containers can call this!");
+
+ // Note: the following description uses grid container as an example. Flex
+ // container is similar.
+ //
+ // First we gather child frames we should include in our reflow/placement,
+ // i.e. overflowed children from our prev-in-flow, and pushed first-in-flow
+ // children (that might now fit). It's important to note that these children
+ // can be in arbitrary order vis-a-vis the current children in our lists.
+ // E.g. grid items in the document order: A, B, C may be placed in the rows
+ // 3, 2, 1. Assume each row goes in a separate grid container fragment,
+ // and we reflow the second fragment. Now if C (in fragment 1) overflows,
+ // we can't just prepend it to our mFrames like we usually do because that
+ // would violate the document order invariant that other code depends on.
+ // Similarly if we pull up child A (from fragment 3) we can't just append
+ // that for the same reason. Instead, we must sort these children into
+ // our child lists. (The sorting is trivial given that both lists are
+ // already fully sorted individually - it's just a merge.)
+ //
+ // The invariants that we maintain are that each grid container child list
+ // is sorted in the normal document order at all times, but that children
+ // in different grid container continuations may be in arbitrary order.
+
+ const auto didPushItemsBit = IsFlexContainerFrame()
+ ? NS_STATE_FLEX_DID_PUSH_ITEMS
+ : NS_STATE_GRID_DID_PUSH_ITEMS;
+ const auto hasChildNifBit = IsFlexContainerFrame()
+ ? NS_STATE_FLEX_HAS_CHILD_NIFS
+ : NS_STATE_GRID_HAS_CHILD_NIFS;
+
+ auto* prevInFlow = static_cast<nsContainerFrame*>(GetPrevInFlow());
+ // Merge overflow frames from our prev-in-flow into our principal child list.
+ if (prevInFlow) {
+ AutoFrameListPtr overflow(PresContext(), prevInFlow->StealOverflowFrames());
+ if (overflow) {
+ ReparentFrames(*overflow, prevInFlow, this);
+ MergeSortedFrameLists(mFrames, *overflow, GetContent());
+
+ // Move trailing next-in-flows into our overflow list.
+ nsFrameList continuations;
+ for (nsIFrame* f = mFrames.FirstChild(); f;) {
+ nsIFrame* next = f->GetNextSibling();
+ nsIFrame* pif = f->GetPrevInFlow();
+ if (pif && pif->GetParent() == this) {
+ mFrames.RemoveFrame(f);
+ continuations.AppendFrame(nullptr, f);
+ }
+ f = next;
+ }
+ MergeSortedOverflow(continuations);
+
+ // Move prev-in-flow's excess overflow containers list into our own
+ // overflow containers list. If we already have an excess overflow
+ // containers list, any child in that list which doesn't have a
+ // prev-in-flow in this frame is also merged into our overflow container
+ // list.
+ nsFrameList* overflowContainers =
+ DrainExcessOverflowContainersList(MergeSortedFrameListsFor);
+
+ // Move trailing OC next-in-flows into our excess overflow containers
+ // list.
+ if (overflowContainers) {
+ nsFrameList moveToEOC;
+ for (nsIFrame* f = overflowContainers->FirstChild(); f;) {
+ nsIFrame* next = f->GetNextSibling();
+ nsIFrame* pif = f->GetPrevInFlow();
+ if (pif && pif->GetParent() == this) {
+ overflowContainers->RemoveFrame(f);
+ moveToEOC.AppendFrame(nullptr, f);
+ }
+ f = next;
+ }
+ if (overflowContainers->IsEmpty()) {
+ DestroyOverflowContainers();
+ }
+ MergeSortedExcessOverflowContainers(moveToEOC);
+ }
+ }
+ }
+
+ // For each item in aItems, pull up its next-in-flow (if any), and reparent it
+ // to our next-in-flow, unless its parent is already ourselves or our
+ // next-in-flow (to avoid leaving a hole there).
+ auto PullItemsNextInFlow = [this](const nsFrameList& aItems) {
+ auto* firstNIF = static_cast<nsContainerFrame*>(GetNextInFlow());
+ if (!firstNIF) {
+ return;
+ }
+ nsFrameList childNIFs;
+ nsFrameList childOCNIFs;
+ for (auto* child : aItems) {
+ if (auto* childNIF = child->GetNextInFlow()) {
+ if (auto* parent = childNIF->GetParent();
+ parent != this && parent != firstNIF) {
+ parent->StealFrame(childNIF);
+ ReparentFrame(childNIF, parent, firstNIF);
+ if (childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ childOCNIFs.AppendFrame(nullptr, childNIF);
+ } else {
+ childNIFs.AppendFrame(nullptr, childNIF);
+ }
+ }
+ }
+ }
+ // Merge aItems' NIFs into our NIF's respective overflow child lists.
+ firstNIF->MergeSortedOverflow(childNIFs);
+ firstNIF->MergeSortedExcessOverflowContainers(childOCNIFs);
+ };
+
+ // Merge our own overflow frames into our principal child list,
+ // except those that are a next-in-flow for one of our items.
+ DebugOnly<bool> foundOwnPushedChild = false;
+ {
+ nsFrameList* ourOverflow = GetOverflowFrames();
+ if (ourOverflow) {
+ nsFrameList items;
+ for (nsIFrame* f = ourOverflow->FirstChild(); f;) {
+ nsIFrame* next = f->GetNextSibling();
+ nsIFrame* pif = f->GetPrevInFlow();
+ if (!pif || pif->GetParent() != this) {
+ MOZ_ASSERT(f->GetParent() == this);
+ ourOverflow->RemoveFrame(f);
+ items.AppendFrame(nullptr, f);
+ if (!pif) {
+ foundOwnPushedChild = true;
+ }
+ }
+ f = next;
+ }
+
+ if (ourOverflow->IsEmpty()) {
+ DestroyOverflowList();
+ ourOverflow = nullptr;
+ }
+ if (items.NotEmpty()) {
+ PullItemsNextInFlow(items);
+ }
+ MergeSortedFrameLists(mFrames, items, GetContent());
+ }
+ }
+
+ // Push any child next-in-flows in our principal list to OverflowList.
+ if (HasAnyStateBits(hasChildNifBit)) {
+ nsFrameList framesToPush;
+ nsIFrame* firstChild = mFrames.FirstChild();
+ // Note that we potentially modify our mFrames list as we go.
+ for (auto* child = firstChild; child; child = child->GetNextSibling()) {
+ if (auto* childNIF = child->GetNextInFlow()) {
+ if (childNIF->GetParent() == this) {
+ for (auto* c = child->GetNextSibling(); c; c = c->GetNextSibling()) {
+ if (c == childNIF) {
+ // child's next-in-flow is in our principal child list, push it.
+ mFrames.RemoveFrame(childNIF);
+ framesToPush.AppendFrame(nullptr, childNIF);
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!framesToPush.IsEmpty()) {
+ MergeSortedOverflow(framesToPush);
+ }
+ RemoveStateBits(hasChildNifBit);
+ }
+
+ // Pull up any first-in-flow children we might have pushed.
+ if (HasAnyStateBits(didPushItemsBit)) {
+ RemoveStateBits(didPushItemsBit);
+ nsFrameList items;
+ auto* nif = static_cast<nsContainerFrame*>(GetNextInFlow());
+ DebugOnly<bool> nifNeedPushedItem = false;
+ while (nif) {
+ nsFrameList nifItems;
+ for (nsIFrame* nifChild = nif->PrincipalChildList().FirstChild();
+ nifChild;) {
+ nsIFrame* next = nifChild->GetNextSibling();
+ if (!nifChild->GetPrevInFlow()) {
+ nif->StealFrame(nifChild);
+ ReparentFrame(nifChild, nif, this);
+ nifItems.AppendFrame(nullptr, nifChild);
+ nifNeedPushedItem = false;
+ }
+ nifChild = next;
+ }
+ MergeSortedFrameLists(items, nifItems, GetContent());
+
+ if (!nif->HasAnyStateBits(didPushItemsBit)) {
+ MOZ_ASSERT(!nifNeedPushedItem || mDidPushItemsBitMayLie,
+ "The state bit stored in didPushItemsBit lied!");
+ break;
+ }
+ nifNeedPushedItem = true;
+
+ for (nsIFrame* nifChild =
+ nif->GetChildList(FrameChildListID::Overflow).FirstChild();
+ nifChild;) {
+ nsIFrame* next = nifChild->GetNextSibling();
+ if (!nifChild->GetPrevInFlow()) {
+ nif->StealFrame(nifChild);
+ ReparentFrame(nifChild, nif, this);
+ nifItems.AppendFrame(nullptr, nifChild);
+ nifNeedPushedItem = false;
+ }
+ nifChild = next;
+ }
+ MergeSortedFrameLists(items, nifItems, GetContent());
+
+ nif->RemoveStateBits(didPushItemsBit);
+ nif = static_cast<nsContainerFrame*>(nif->GetNextInFlow());
+ MOZ_ASSERT(nif || !nifNeedPushedItem || mDidPushItemsBitMayLie,
+ "The state bit stored in didPushItemsBit lied!");
+ }
+
+ if (!items.IsEmpty()) {
+ PullItemsNextInFlow(items);
+ }
+
+ MOZ_ASSERT(
+ foundOwnPushedChild || !items.IsEmpty() || mDidPushItemsBitMayLie,
+ "The state bit stored in didPushItemsBit lied!");
+ MergeSortedFrameLists(mFrames, items, GetContent());
+ }
+}
+
+void nsContainerFrame::NoteNewChildren(ChildListID aListID,
+ const nsFrameList& aFrameList) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
+ MOZ_ASSERT(IsFlexOrGridContainer(),
+ "Only Flex / Grid containers can call this!");
+
+ mozilla::PresShell* presShell = PresShell();
+ const auto didPushItemsBit = IsFlexContainerFrame()
+ ? NS_STATE_FLEX_DID_PUSH_ITEMS
+ : NS_STATE_GRID_DID_PUSH_ITEMS;
+ for (auto* pif = GetPrevInFlow(); pif; pif = pif->GetPrevInFlow()) {
+ pif->AddStateBits(didPushItemsBit);
+ presShell->FrameNeedsReflow(pif, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_IS_DIRTY);
+ }
+}
+
+bool nsContainerFrame::MoveOverflowToChildList() {
+ bool result = false;
+
+ // Check for an overflow list with our prev-in-flow
+ nsContainerFrame* prevInFlow = (nsContainerFrame*)GetPrevInFlow();
+ if (nullptr != prevInFlow) {
+ AutoFrameListPtr prevOverflowFrames(PresContext(),
+ prevInFlow->StealOverflowFrames());
+ if (prevOverflowFrames) {
+ // Tables are special; they can have repeated header/footer
+ // frames on mFrames at this point.
+ NS_ASSERTION(mFrames.IsEmpty() || IsTableFrame(), "bad overflow list");
+ // When pushing and pulling frames we need to check for whether any
+ // views need to be reparented.
+ nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow,
+ this);
+ mFrames.AppendFrames(this, std::move(*prevOverflowFrames));
+ result = true;
+ }
+ }
+
+ // It's also possible that we have an overflow list for ourselves.
+ return DrainSelfOverflowList() || result;
+}
+
+void nsContainerFrame::MergeSortedOverflow(nsFrameList& aList) {
+ if (aList.IsEmpty()) {
+ return;
+ }
+ MOZ_ASSERT(
+ !aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER),
+ "this is the wrong list to put this child frame");
+ MOZ_ASSERT(aList.FirstChild()->GetParent() == this);
+ nsFrameList* overflow = GetOverflowFrames();
+ if (overflow) {
+ MergeSortedFrameLists(*overflow, aList, GetContent());
+ } else {
+ SetOverflowFrames(std::move(aList));
+ }
+}
+
+void nsContainerFrame::MergeSortedExcessOverflowContainers(nsFrameList& aList) {
+ if (aList.IsEmpty()) {
+ return;
+ }
+ MOZ_ASSERT(
+ aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER),
+ "this is the wrong list to put this child frame");
+ MOZ_ASSERT(aList.FirstChild()->GetParent() == this);
+ if (nsFrameList* eoc = GetExcessOverflowContainers()) {
+ MergeSortedFrameLists(*eoc, aList, GetContent());
+ } else {
+ SetExcessOverflowContainers(std::move(aList));
+ }
+}
+
+nsIFrame* nsContainerFrame::GetFirstNonAnonBoxInSubtree(nsIFrame* aFrame) {
+ while (aFrame) {
+ // If aFrame isn't an anonymous container, or it's text or such, then it'll
+ // do.
+ if (!aFrame->Style()->IsAnonBox() ||
+ nsCSSAnonBoxes::IsNonElement(aFrame->Style()->GetPseudoType())) {
+ break;
+ }
+
+ // Otherwise, descend to its first child and repeat.
+
+ // SPECIAL CASE: if we're dealing with an anonymous table, then it might
+ // be wrapping something non-anonymous in its caption or col-group lists
+ // (instead of its principal child list), so we have to look there.
+ // (Note: For anonymous tables that have a non-anon cell *and* a non-anon
+ // column, we'll always return the column. This is fine; we're really just
+ // looking for a handle to *anything* with a meaningful content node inside
+ // the table, for use in DOM comparisons to things outside of the table.)
+ if (MOZ_UNLIKELY(aFrame->IsTableWrapperFrame())) {
+ nsIFrame* captionDescendant = GetFirstNonAnonBoxInSubtree(
+ aFrame->GetChildList(FrameChildListID::Caption).FirstChild());
+ if (captionDescendant) {
+ return captionDescendant;
+ }
+ } else if (MOZ_UNLIKELY(aFrame->IsTableFrame())) {
+ nsIFrame* colgroupDescendant = GetFirstNonAnonBoxInSubtree(
+ aFrame->GetChildList(FrameChildListID::ColGroup).FirstChild());
+ if (colgroupDescendant) {
+ return colgroupDescendant;
+ }
+ }
+
+ // USUAL CASE: Descend to the first child in principal list.
+ aFrame = aFrame->PrincipalChildList().FirstChild();
+ }
+ return aFrame;
+}
+
+/**
+ * Is aFrame1 a prev-continuation of aFrame2?
+ */
+static bool IsPrevContinuationOf(nsIFrame* aFrame1, nsIFrame* aFrame2) {
+ nsIFrame* prev = aFrame2;
+ while ((prev = prev->GetPrevContinuation())) {
+ if (prev == aFrame1) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsContainerFrame::MergeSortedFrameLists(nsFrameList& aDest,
+ nsFrameList& aSrc,
+ nsIContent* aCommonAncestor) {
+ // Returns a frame whose DOM node can be used for the purpose of ordering
+ // aFrame among its sibling frames by DOM position. If aFrame is
+ // non-anonymous, this just returns aFrame itself. Otherwise, this returns the
+ // first non-anonymous descendant in aFrame's continuation chain.
+ auto FrameForDOMPositionComparison = [](nsIFrame* aFrame) {
+ if (!aFrame->Style()->IsAnonBox()) {
+ // The usual case.
+ return aFrame;
+ }
+
+ // Walk the continuation chain from the start, and return the first
+ // non-anonymous descendant that we find.
+ for (nsIFrame* f = aFrame->FirstContinuation(); f;
+ f = f->GetNextContinuation()) {
+ if (nsIFrame* nonAnonBox = GetFirstNonAnonBoxInSubtree(f)) {
+ return nonAnonBox;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE(
+ "Why is there no non-anonymous descendants in the continuation chain?");
+ return aFrame;
+ };
+
+ nsIFrame* dest = aDest.FirstChild();
+ for (nsIFrame* src = aSrc.FirstChild(); src;) {
+ if (!dest) {
+ aDest.AppendFrames(nullptr, std::move(aSrc));
+ break;
+ }
+ nsIContent* srcContent = FrameForDOMPositionComparison(src)->GetContent();
+ nsIContent* destContent = FrameForDOMPositionComparison(dest)->GetContent();
+ int32_t result = nsContentUtils::CompareTreePosition<TreeKind::Flat>(
+ srcContent, destContent, aCommonAncestor);
+ if (MOZ_UNLIKELY(result == 0)) {
+ // NOTE: we get here when comparing ::before/::after for the same element.
+ if (MOZ_UNLIKELY(srcContent->IsGeneratedContentContainerForBefore())) {
+ if (MOZ_LIKELY(!destContent->IsGeneratedContentContainerForBefore()) ||
+ ::IsPrevContinuationOf(src, dest)) {
+ result = -1;
+ }
+ } else if (MOZ_UNLIKELY(
+ srcContent->IsGeneratedContentContainerForAfter())) {
+ if (MOZ_UNLIKELY(destContent->IsGeneratedContentContainerForAfter()) &&
+ ::IsPrevContinuationOf(src, dest)) {
+ result = -1;
+ }
+ } else if (::IsPrevContinuationOf(src, dest)) {
+ result = -1;
+ }
+ }
+ if (result < 0) {
+ // src should come before dest
+ nsIFrame* next = src->GetNextSibling();
+ aSrc.RemoveFrame(src);
+ aDest.InsertFrame(nullptr, dest->GetPrevSibling(), src);
+ src = next;
+ } else {
+ dest = dest->GetNextSibling();
+ }
+ }
+ MOZ_ASSERT(aSrc.IsEmpty());
+}
+
+bool nsContainerFrame::MoveInlineOverflowToChildList(nsIFrame* aLineContainer) {
+ MOZ_ASSERT(aLineContainer,
+ "Must have line container for moving inline overflows");
+
+ bool result = false;
+
+ // Check for an overflow list with our prev-in-flow
+ if (auto prevInFlow = static_cast<nsContainerFrame*>(GetPrevInFlow())) {
+ AutoFrameListPtr prevOverflowFrames(PresContext(),
+ prevInFlow->StealOverflowFrames());
+ if (prevOverflowFrames) {
+ // We may need to reparent floats from prev-in-flow to our line
+ // container if the container has prev continuation.
+ if (aLineContainer->GetPrevContinuation()) {
+ ReparentFloatsForInlineChild(aLineContainer,
+ prevOverflowFrames->FirstChild(), true);
+ }
+ // When pushing and pulling frames we need to check for whether
+ // any views need to be reparented.
+ nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow,
+ this);
+ // Prepend overflow frames to the list.
+ mFrames.InsertFrames(this, nullptr, std::move(*prevOverflowFrames));
+ result = true;
+ }
+ }
+
+ // It's also possible that we have overflow list for ourselves.
+ return DrainSelfOverflowList() || result;
+}
+
+bool nsContainerFrame::DrainSelfOverflowList() {
+ AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
+ if (overflowFrames) {
+ mFrames.AppendFrames(nullptr, std::move(*overflowFrames));
+ return true;
+ }
+ return false;
+}
+
+bool nsContainerFrame::DrainAndMergeSelfOverflowList() {
+ MOZ_ASSERT(IsFlexOrGridContainer(),
+ "Only Flex / Grid containers can call this!");
+
+ // Unlike nsContainerFrame::DrainSelfOverflowList, flex or grid containers
+ // need to merge these lists so that the resulting mFrames is in document
+ // content order.
+ // NOTE: nsContainerFrame::AppendFrames/InsertFrames calls this method and
+ // there are also direct calls from the fctor (FindAppendPrevSibling).
+ AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
+ if (overflowFrames) {
+ MergeSortedFrameLists(mFrames, *overflowFrames, GetContent());
+ // We set a frame bit to push them again in Reflow() to avoid creating
+ // multiple flex / grid items per flex / grid container fragment for the
+ // same content.
+ AddStateBits(IsFlexContainerFrame() ? NS_STATE_FLEX_HAS_CHILD_NIFS
+ : NS_STATE_GRID_HAS_CHILD_NIFS);
+ return true;
+ }
+ return false;
+}
+
+nsFrameList* nsContainerFrame::DrainExcessOverflowContainersList(
+ ChildFrameMerger aMergeFunc) {
+ nsFrameList* overflowContainers = GetOverflowContainers();
+
+ // Drain excess overflow containers from our prev-in-flow.
+ if (auto* prev = static_cast<nsContainerFrame*>(GetPrevInFlow())) {
+ AutoFrameListPtr excessFrames(PresContext(),
+ prev->StealExcessOverflowContainers());
+ if (excessFrames) {
+ excessFrames->ApplySetParent(this);
+ nsContainerFrame::ReparentFrameViewList(*excessFrames, prev, this);
+ if (overflowContainers) {
+ // The default merge function is AppendFrames, so we use excessFrames as
+ // the destination and then assign the result to overflowContainers.
+ aMergeFunc(*excessFrames, *overflowContainers, this);
+ *overflowContainers = std::move(*excessFrames);
+ } else {
+ overflowContainers = SetOverflowContainers(std::move(*excessFrames));
+ }
+ }
+ }
+
+ // Our own excess overflow containers from a previous reflow can still be
+ // present if our next-in-flow hasn't been reflown yet. Move any children
+ // from it that don't have a continuation in this frame to the
+ // OverflowContainers list.
+ AutoFrameListPtr selfExcessOCFrames(PresContext(),
+ StealExcessOverflowContainers());
+ if (selfExcessOCFrames) {
+ nsFrameList toMove;
+ auto child = selfExcessOCFrames->FirstChild();
+ while (child) {
+ auto next = child->GetNextSibling();
+ MOZ_ASSERT(child->GetPrevInFlow(),
+ "ExcessOverflowContainers frames must be continuations");
+ if (child->GetPrevInFlow()->GetParent() != this) {
+ selfExcessOCFrames->RemoveFrame(child);
+ toMove.AppendFrame(nullptr, child);
+ }
+ child = next;
+ }
+
+ // If there's any remaining excess overflow containers, put them back.
+ if (selfExcessOCFrames->NotEmpty()) {
+ SetExcessOverflowContainers(std::move(*selfExcessOCFrames));
+ }
+
+ if (toMove.NotEmpty()) {
+ if (overflowContainers) {
+ aMergeFunc(*overflowContainers, toMove, this);
+ } else {
+ overflowContainers = SetOverflowContainers(std::move(toMove));
+ }
+ }
+ }
+
+ return overflowContainers;
+}
+
+nsIFrame* nsContainerFrame::GetNextInFlowChild(
+ ContinuationTraversingState& aState, bool* aIsInOverflow) {
+ nsContainerFrame*& nextInFlow = aState.mNextInFlow;
+ while (nextInFlow) {
+ // See if there is any frame in the container
+ nsIFrame* frame = nextInFlow->mFrames.FirstChild();
+ if (frame) {
+ if (aIsInOverflow) {
+ *aIsInOverflow = false;
+ }
+ return frame;
+ }
+ // No frames in the principal list, try its overflow list
+ nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames();
+ if (overflowFrames) {
+ if (aIsInOverflow) {
+ *aIsInOverflow = true;
+ }
+ return overflowFrames->FirstChild();
+ }
+ nextInFlow = static_cast<nsContainerFrame*>(nextInFlow->GetNextInFlow());
+ }
+ return nullptr;
+}
+
+nsIFrame* nsContainerFrame::PullNextInFlowChild(
+ ContinuationTraversingState& aState) {
+ bool isInOverflow;
+ nsIFrame* frame = GetNextInFlowChild(aState, &isInOverflow);
+ if (frame) {
+ nsContainerFrame* nextInFlow = aState.mNextInFlow;
+ if (isInOverflow) {
+ nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames();
+ overflowFrames->RemoveFirstChild();
+ if (overflowFrames->IsEmpty()) {
+ nextInFlow->DestroyOverflowList();
+ }
+ } else {
+ nextInFlow->mFrames.RemoveFirstChild();
+ }
+
+ // Move the frame to the principal frame list of this container
+ mFrames.AppendFrame(this, frame);
+ // AppendFrame has reparented the frame, we need
+ // to reparent the frame view then.
+ nsContainerFrame::ReparentFrameView(frame, nextInFlow, this);
+ }
+ return frame;
+}
+
+/* static */
+void nsContainerFrame::ReparentFloatsForInlineChild(nsIFrame* aOurLineContainer,
+ nsIFrame* aFrame,
+ bool aReparentSiblings) {
+ // XXXbz this would be better if it took a nsFrameList or a frame
+ // list slice....
+ NS_ASSERTION(aOurLineContainer->GetNextContinuation() ||
+ aOurLineContainer->GetPrevContinuation(),
+ "Don't call this when we have no continuation, it's a waste");
+ if (!aFrame) {
+ NS_ASSERTION(aReparentSiblings, "Why did we get called?");
+ return;
+ }
+
+ nsBlockFrame* frameBlock = nsLayoutUtils::GetFloatContainingBlock(aFrame);
+ if (!frameBlock || frameBlock == aOurLineContainer) {
+ return;
+ }
+
+ nsBlockFrame* ourBlock = do_QueryFrame(aOurLineContainer);
+ NS_ASSERTION(ourBlock, "Not a block, but broke vertically?");
+
+ while (true) {
+ ourBlock->ReparentFloats(aFrame, frameBlock, false);
+
+ if (!aReparentSiblings) return;
+ nsIFrame* next = aFrame->GetNextSibling();
+ if (!next) return;
+ if (next->GetParent() == aFrame->GetParent()) {
+ aFrame = next;
+ continue;
+ }
+ // This is paranoid and will hardly ever get hit ... but we can't actually
+ // trust that the frames in the sibling chain all have the same parent,
+ // because lazy reparenting may be going on. If we find a different
+ // parent we need to redo our analysis.
+ ReparentFloatsForInlineChild(aOurLineContainer, next, aReparentSiblings);
+ return;
+ }
+}
+
+bool nsContainerFrame::ResolvedOrientationIsVertical() {
+ StyleOrient orient = StyleDisplay()->mOrient;
+ switch (orient) {
+ case StyleOrient::Horizontal:
+ return false;
+ case StyleOrient::Vertical:
+ return true;
+ case StyleOrient::Inline:
+ return GetWritingMode().IsVertical();
+ case StyleOrient::Block:
+ return !GetWritingMode().IsVertical();
+ }
+ MOZ_ASSERT_UNREACHABLE("unexpected -moz-orient value");
+ return false;
+}
+
+LogicalSize nsContainerFrame::ComputeSizeWithIntrinsicDimensions(
+ gfxContext* aRenderingContext, WritingMode aWM,
+ const IntrinsicSize& aIntrinsicSize, const AspectRatio& aAspectRatio,
+ const LogicalSize& aCBSize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ const nsStylePosition* stylePos = StylePosition();
+ const auto& styleISize = aSizeOverrides.mStyleISize
+ ? *aSizeOverrides.mStyleISize
+ : stylePos->ISize(aWM);
+ const auto& styleBSize = aSizeOverrides.mStyleBSize
+ ? *aSizeOverrides.mStyleBSize
+ : stylePos->BSize(aWM);
+ const auto& aspectRatio =
+ aSizeOverrides.mAspectRatio ? *aSizeOverrides.mAspectRatio : aAspectRatio;
+
+ auto* parentFrame = GetParent();
+ const bool isGridItem = IsGridItem();
+ const bool isFlexItem =
+ IsFlexItem() && !parentFrame->HasAnyStateBits(
+ NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
+ // This variable only gets meaningfully set if isFlexItem is true. It
+ // indicates which axis (in this frame's own WM) corresponds to its
+ // flex container's main axis.
+ LogicalAxis flexMainAxis = eLogicalAxisBlock;
+ if (isFlexItem && nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)) {
+ flexMainAxis = eLogicalAxisInline;
+ }
+
+ // Handle intrinsic sizes and their interaction with
+ // {min-,max-,}{width,height} according to the rules in
+ // https://www.w3.org/TR/CSS22/visudet.html#min-max-widths and
+ // https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
+
+ // Note: throughout the following section of the function, I avoid
+ // a * (b / c) because of its reduced accuracy relative to a * b / c
+ // or (a * b) / c (which are equivalent).
+
+ const bool isAutoOrMaxContentISize =
+ styleISize.IsAuto() || styleISize.IsMaxContent();
+ const bool isAutoBSize =
+ nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM));
+
+ const auto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border
+ ? aBorderPadding
+ : LogicalSize(aWM);
+ const nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) +
+ aBorderPadding.ISize(aWM) -
+ boxSizingAdjust.ISize(aWM);
+
+ nscoord iSize, minISize, maxISize, bSize, minBSize, maxBSize;
+ enum class Stretch {
+ // stretch to fill the CB (preserving intrinsic ratio) in the relevant axis
+ StretchPreservingRatio,
+ // stretch to fill the CB in the relevant axis
+ Stretch,
+ // no stretching in the relevant axis
+ NoStretch,
+ };
+ // just to avoid having to type these out everywhere:
+ const auto eStretchPreservingRatio = Stretch::StretchPreservingRatio;
+ const auto eStretch = Stretch::Stretch;
+ const auto eNoStretch = Stretch::NoStretch;
+
+ Stretch stretchI = eNoStretch; // stretch behavior in the inline axis
+ Stretch stretchB = eNoStretch; // stretch behavior in the block axis
+
+ const bool isOrthogonal = aWM.IsOrthogonalTo(parentFrame->GetWritingMode());
+ const bool isVertical = aWM.IsVertical();
+ const LogicalSize fallbackIntrinsicSize(aWM, kFallbackIntrinsicSize);
+ const auto& isizeCoord =
+ isVertical ? aIntrinsicSize.height : aIntrinsicSize.width;
+ const bool hasIntrinsicISize = isizeCoord.isSome();
+ nscoord intrinsicISize = std::max(0, isizeCoord.valueOr(0));
+
+ const auto& bsizeCoord =
+ isVertical ? aIntrinsicSize.width : aIntrinsicSize.height;
+ const bool hasIntrinsicBSize = bsizeCoord.isSome();
+ nscoord intrinsicBSize = std::max(0, bsizeCoord.valueOr(0));
+
+ if (!isAutoOrMaxContentISize) {
+ iSize = ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust,
+ boxSizingToMarginEdgeISize, styleISize,
+ aSizeOverrides, aFlags)
+ .mISize;
+ } else if (MOZ_UNLIKELY(isGridItem) &&
+ !parentFrame->IsMasonry(isOrthogonal ? eLogicalAxisBlock
+ : eLogicalAxisInline)) {
+ MOZ_ASSERT(!IsTrueOverflowContainer());
+ // 'auto' inline-size for grid-level box - apply 'stretch' as needed:
+ auto cbSize = aCBSize.ISize(aWM);
+ if (cbSize != NS_UNCONSTRAINEDSIZE) {
+ if (!StyleMargin()->HasInlineAxisAuto(aWM)) {
+ auto inlineAxisAlignment =
+ isOrthogonal ? stylePos->UsedAlignSelf(GetParent()->Style())._0
+ : stylePos->UsedJustifySelf(GetParent()->Style())._0;
+ if (inlineAxisAlignment == StyleAlignFlags::STRETCH) {
+ stretchI = eStretch;
+ }
+ }
+ if (stretchI != eNoStretch ||
+ aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
+ iSize = std::max(nscoord(0), cbSize - aBorderPadding.ISize(aWM) -
+ aMargin.ISize(aWM));
+ }
+ } else {
+ // Reset this flag to avoid applying the clamping below.
+ aFlags -= ComputeSizeFlag::IClampMarginBoxMinSize;
+ }
+ }
+
+ const auto& maxISizeCoord = stylePos->MaxISize(aWM);
+
+ if (!maxISizeCoord.IsNone() &&
+ !(isFlexItem && flexMainAxis == eLogicalAxisInline)) {
+ maxISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
+ boxSizingAdjust, boxSizingToMarginEdgeISize,
+ maxISizeCoord, aSizeOverrides, aFlags)
+ .mISize;
+ } else {
+ maxISize = nscoord_MAX;
+ }
+
+ // NOTE: Flex items ignore their min & max sizing properties in their
+ // flex container's main-axis. (Those properties get applied later in
+ // the flexbox algorithm.)
+
+ const auto& minISizeCoord = stylePos->MinISize(aWM);
+
+ if (!minISizeCoord.IsAuto() &&
+ !(isFlexItem && flexMainAxis == eLogicalAxisInline)) {
+ minISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
+ boxSizingAdjust, boxSizingToMarginEdgeISize,
+ minISizeCoord, aSizeOverrides, aFlags)
+ .mISize;
+ } else {
+ // Treat "min-width: auto" as 0.
+ // NOTE: Technically, "auto" is supposed to behave like "min-content" on
+ // flex items. However, we don't need to worry about that here, because
+ // flex items' min-sizes are intentionally ignored until the flex
+ // container explicitly considers them during space distribution.
+ minISize = 0;
+ }
+
+ if (!isAutoBSize) {
+ bSize = nsLayoutUtils::ComputeBSizeValue(aCBSize.BSize(aWM),
+ boxSizingAdjust.BSize(aWM),
+ styleBSize.AsLengthPercentage());
+ } else if (MOZ_UNLIKELY(isGridItem) &&
+ !parentFrame->IsMasonry(isOrthogonal ? eLogicalAxisInline
+ : eLogicalAxisBlock)) {
+ MOZ_ASSERT(!IsTrueOverflowContainer());
+ // 'auto' block-size for grid-level box - apply 'stretch' as needed:
+ auto cbSize = aCBSize.BSize(aWM);
+ if (cbSize != NS_UNCONSTRAINEDSIZE) {
+ if (!StyleMargin()->HasBlockAxisAuto(aWM)) {
+ auto blockAxisAlignment =
+ !isOrthogonal ? stylePos->UsedAlignSelf(GetParent()->Style())._0
+ : stylePos->UsedJustifySelf(GetParent()->Style())._0;
+ if (blockAxisAlignment == StyleAlignFlags::STRETCH) {
+ stretchB = eStretch;
+ }
+ }
+ if (stretchB != eNoStretch ||
+ aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) {
+ bSize = std::max(nscoord(0), cbSize - aBorderPadding.BSize(aWM) -
+ aMargin.BSize(aWM));
+ }
+ } else {
+ // Reset this flag to avoid applying the clamping below.
+ aFlags -= ComputeSizeFlag::BClampMarginBoxMinSize;
+ }
+ }
+
+ const auto& maxBSizeCoord = stylePos->MaxBSize(aWM);
+
+ if (!nsLayoutUtils::IsAutoBSize(maxBSizeCoord, aCBSize.BSize(aWM)) &&
+ !(isFlexItem && flexMainAxis == eLogicalAxisBlock)) {
+ maxBSize = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ maxBSizeCoord.AsLengthPercentage());
+ } else {
+ maxBSize = nscoord_MAX;
+ }
+
+ const auto& minBSizeCoord = stylePos->MinBSize(aWM);
+
+ if (!nsLayoutUtils::IsAutoBSize(minBSizeCoord, aCBSize.BSize(aWM)) &&
+ !(isFlexItem && flexMainAxis == eLogicalAxisBlock)) {
+ minBSize = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ minBSizeCoord.AsLengthPercentage());
+ } else {
+ minBSize = 0;
+ }
+
+ NS_ASSERTION(aCBSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE,
+ "Our containing block must not have unconstrained inline-size!");
+
+ // Now calculate the used values for iSize and bSize:
+ if (isAutoOrMaxContentISize) {
+ if (isAutoBSize) {
+ // 'auto' iSize, 'auto' bSize
+
+ // Get tentative values - CSS 2.1 sections 10.3.2 and 10.6.2:
+
+ nscoord tentISize, tentBSize;
+
+ if (hasIntrinsicISize) {
+ tentISize = intrinsicISize;
+ } else if (hasIntrinsicBSize && aspectRatio) {
+ tentISize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, intrinsicBSize,
+ boxSizingAdjust);
+ } else if (aspectRatio) {
+ tentISize =
+ aCBSize.ISize(aWM) - boxSizingToMarginEdgeISize; // XXX scrollbar?
+ if (tentISize < 0) {
+ tentISize = 0;
+ }
+ } else {
+ tentISize = fallbackIntrinsicSize.ISize(aWM);
+ }
+
+ // If we need to clamp the inline size to fit the CB, we use the 'stretch'
+ // or 'normal' codepath. We use the ratio-preserving 'normal' codepath
+ // unless we have 'stretch' in the other axis.
+ if (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize) &&
+ stretchI != eStretch && tentISize > iSize) {
+ stretchI = (stretchB == eStretch ? eStretch : eStretchPreservingRatio);
+ }
+
+ if (hasIntrinsicBSize) {
+ tentBSize = intrinsicBSize;
+ } else if (aspectRatio) {
+ tentBSize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisBlock, aWM, tentISize, boxSizingAdjust);
+ } else {
+ tentBSize = fallbackIntrinsicSize.BSize(aWM);
+ }
+
+ // (ditto the comment about clamping the inline size above)
+ if (aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize) &&
+ stretchB != eStretch && tentBSize > bSize) {
+ stretchB = (stretchI == eStretch ? eStretch : eStretchPreservingRatio);
+ }
+
+ if (stretchI == eStretch) {
+ tentISize = iSize; // * / 'stretch'
+ if (stretchB == eStretch) {
+ tentBSize = bSize; // 'stretch' / 'stretch'
+ } else if (stretchB == eStretchPreservingRatio && aspectRatio) {
+ // 'normal' / 'stretch'
+ tentBSize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisBlock, aWM, iSize, boxSizingAdjust);
+ }
+ } else if (stretchB == eStretch) {
+ tentBSize = bSize; // 'stretch' / * (except 'stretch')
+ if (stretchI == eStretchPreservingRatio && aspectRatio) {
+ // 'stretch' / 'normal'
+ tentISize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
+ }
+ } else if (stretchI == eStretchPreservingRatio && aspectRatio) {
+ tentISize = iSize; // * (except 'stretch') / 'normal'
+ tentBSize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisBlock, aWM, iSize, boxSizingAdjust);
+ if (stretchB == eStretchPreservingRatio && tentBSize > bSize) {
+ // Stretch within the CB size with preserved intrinsic ratio.
+ tentBSize = bSize; // 'normal' / 'normal'
+ tentISize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
+ }
+ } else if (stretchB == eStretchPreservingRatio && aspectRatio) {
+ tentBSize = bSize; // 'normal' / * (except 'normal' and 'stretch')
+ tentISize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
+ }
+
+ // ComputeAutoSizeWithIntrinsicDimensions preserves the ratio when
+ // applying the min/max-size. We don't want that when we have 'stretch'
+ // in either axis because tentISize/tentBSize is likely not according to
+ // ratio now.
+ if (aspectRatio && stretchI != eStretch && stretchB != eStretch) {
+ nsSize autoSize = nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(
+ minISize, minBSize, maxISize, maxBSize, tentISize, tentBSize);
+ // The nsSize that ComputeAutoSizeWithIntrinsicDimensions returns will
+ // actually contain logical values if the parameters passed to it were
+ // logical coordinates, so we do NOT perform a physical-to-logical
+ // conversion here, but just assign the fields directly to our result.
+ iSize = autoSize.width;
+ bSize = autoSize.height;
+ } else {
+ // Not honoring an intrinsic ratio: clamp the dimensions independently.
+ iSize = NS_CSS_MINMAX(tentISize, minISize, maxISize);
+ bSize = NS_CSS_MINMAX(tentBSize, minBSize, maxBSize);
+ }
+ } else {
+ // 'auto' iSize, non-'auto' bSize
+ bSize = NS_CSS_MINMAX(bSize, minBSize, maxBSize);
+ if (stretchI != eStretch) {
+ if (aspectRatio) {
+ iSize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
+ } else if (hasIntrinsicISize) {
+ if (!(aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize) &&
+ intrinsicISize > iSize)) {
+ iSize = intrinsicISize;
+ } // else - leave iSize as is to fill the CB
+ } else {
+ iSize = fallbackIntrinsicSize.ISize(aWM);
+ }
+ } // else - leave iSize as is to fill the CB
+ iSize = NS_CSS_MINMAX(iSize, minISize, maxISize);
+ }
+ } else {
+ if (isAutoBSize) {
+ // non-'auto' iSize, 'auto' bSize
+ iSize = NS_CSS_MINMAX(iSize, minISize, maxISize);
+ if (stretchB != eStretch) {
+ if (aspectRatio) {
+ bSize = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisBlock, aWM, iSize, boxSizingAdjust);
+ } else if (hasIntrinsicBSize) {
+ if (!(aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize) &&
+ intrinsicBSize > bSize)) {
+ bSize = intrinsicBSize;
+ } // else - leave bSize as is to fill the CB
+ } else {
+ bSize = fallbackIntrinsicSize.BSize(aWM);
+ }
+ } // else - leave bSize as is to fill the CB
+ bSize = NS_CSS_MINMAX(bSize, minBSize, maxBSize);
+
+ } else {
+ // non-'auto' iSize, non-'auto' bSize
+ iSize = NS_CSS_MINMAX(iSize, minISize, maxISize);
+ bSize = NS_CSS_MINMAX(bSize, minBSize, maxBSize);
+ }
+ }
+
+ return LogicalSize(aWM, iSize, bSize);
+}
+
+nsRect nsContainerFrame::ComputeSimpleTightBounds(
+ DrawTarget* aDrawTarget) const {
+ if (StyleOutline()->ShouldPaintOutline() || StyleBorder()->HasBorder() ||
+ !StyleBackground()->IsTransparent(this) ||
+ StyleDisplay()->HasAppearance()) {
+ // Not necessarily tight, due to clipping, negative
+ // outline-offset, and lots of other issues, but that's OK
+ return InkOverflowRect();
+ }
+
+ nsRect r(0, 0, 0, 0);
+ for (const auto& childLists : ChildLists()) {
+ for (nsIFrame* child : childLists.mList) {
+ r.UnionRect(
+ r, child->ComputeTightBounds(aDrawTarget) + child->GetPosition());
+ }
+ }
+ return r;
+}
+
+void nsContainerFrame::PushDirtyBitToAbsoluteFrames() {
+ if (!HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ return; // No dirty bit to push.
+ }
+ if (!HasAbsolutelyPositionedChildren()) {
+ return; // No absolute children to push to.
+ }
+ GetAbsoluteContainingBlock()->MarkAllFramesDirty();
+}
+
+// Define the MAX_FRAME_DEPTH to be the ContentSink's MAX_REFLOW_DEPTH plus
+// 4 for the frames above the document's frames:
+// the Viewport, GFXScroll, ScrollPort, and Canvas
+#define MAX_FRAME_DEPTH (MAX_REFLOW_DEPTH + 4)
+
+bool nsContainerFrame::IsFrameTreeTooDeep(const ReflowInput& aReflowInput,
+ ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus) {
+ if (aReflowInput.mReflowDepth > MAX_FRAME_DEPTH) {
+ NS_WARNING("frame tree too deep; setting zero size and returning");
+ AddStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE);
+ ClearOverflowRects();
+ aMetrics.ClearSize();
+ aMetrics.SetBlockStartAscent(0);
+ aMetrics.mCarriedOutBEndMargin.Zero();
+ aMetrics.mOverflowAreas.Clear();
+
+ aStatus.Reset();
+ if (GetNextInFlow()) {
+ // Reflow depth might vary between reflows, so we might have
+ // successfully reflowed and split this frame before. If so, we
+ // shouldn't delete its continuations.
+ aStatus.SetIncomplete();
+ }
+
+ return true;
+ }
+ RemoveStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE);
+ return false;
+}
+
+bool nsContainerFrame::ShouldAvoidBreakInside(
+ const ReflowInput& aReflowInput) const {
+ MOZ_ASSERT(this == aReflowInput.mFrame,
+ "Caller should pass a ReflowInput for this frame!");
+
+ const auto* disp = StyleDisplay();
+ const bool mayAvoidBreak = [&] {
+ switch (disp->mBreakInside) {
+ case StyleBreakWithin::Auto:
+ return false;
+ case StyleBreakWithin::Avoid:
+ return true;
+ case StyleBreakWithin::AvoidPage:
+ return aReflowInput.mBreakType == ReflowInput::BreakType::Page;
+ case StyleBreakWithin::AvoidColumn:
+ return aReflowInput.mBreakType == ReflowInput::BreakType::Column;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown break-inside value");
+ return false;
+ }();
+
+ if (!mayAvoidBreak) {
+ return false;
+ }
+ if (aReflowInput.mFlags.mIsTopOfPage) {
+ return false;
+ }
+ if (IsAbsolutelyPositioned(disp)) {
+ return false;
+ }
+ if (GetPrevInFlow()) {
+ return false;
+ }
+ return true;
+}
+
+void nsContainerFrame::ConsiderChildOverflow(OverflowAreas& aOverflowAreas,
+ nsIFrame* aChildFrame) {
+ if (StyleDisplay()->IsContainLayout() && SupportsContainLayoutAndPaint()) {
+ // If we have layout containment and are not a non-atomic, inline-level
+ // principal box, we should only consider our child's ink overflow,
+ // leaving the scrollable regions of the parent unaffected.
+ // Note: scrollable overflow is a subset of ink overflow,
+ // so this has the same affect as unioning the child's ink and
+ // scrollable overflow with the parent's ink overflow.
+ const OverflowAreas childOverflows(aChildFrame->InkOverflowRect(),
+ nsRect());
+ aOverflowAreas.UnionWith(childOverflows + aChildFrame->GetPosition());
+ } else {
+ aOverflowAreas.UnionWith(
+ aChildFrame->GetActualAndNormalOverflowAreasRelativeToParent());
+ }
+}
+
+StyleAlignFlags nsContainerFrame::CSSAlignmentForAbsPosChild(
+ const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
+ MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(),
+ "This method should only be called for abspos children");
+ NS_ERROR(
+ "Child classes that use css box alignment for abspos children "
+ "should provide their own implementation of this method!");
+
+ // In the unexpected/unlikely event that this implementation gets invoked,
+ // just use "start" alignment.
+ return StyleAlignFlags::START;
+}
+
+nsOverflowContinuationTracker::nsOverflowContinuationTracker(
+ nsContainerFrame* aFrame, bool aWalkOOFFrames,
+ bool aSkipOverflowContainerChildren)
+ : mOverflowContList(nullptr),
+ mPrevOverflowCont(nullptr),
+ mSentry(nullptr),
+ mParent(aFrame),
+ mSkipOverflowContainerChildren(aSkipOverflowContainerChildren),
+ mWalkOOFFrames(aWalkOOFFrames) {
+ MOZ_ASSERT(aFrame, "null frame pointer");
+ SetupOverflowContList();
+}
+
+void nsOverflowContinuationTracker::SetupOverflowContList() {
+ MOZ_ASSERT(mParent, "null frame pointer");
+ MOZ_ASSERT(!mOverflowContList, "already have list");
+ nsContainerFrame* nif =
+ static_cast<nsContainerFrame*>(mParent->GetNextInFlow());
+ if (nif) {
+ mOverflowContList = nif->GetOverflowContainers();
+ if (mOverflowContList) {
+ mParent = nif;
+ SetUpListWalker();
+ }
+ }
+ if (!mOverflowContList) {
+ mOverflowContList = mParent->GetExcessOverflowContainers();
+ if (mOverflowContList) {
+ SetUpListWalker();
+ }
+ }
+}
+
+/**
+ * Helper function to walk past overflow continuations whose prev-in-flow
+ * isn't a normal child and to set mSentry and mPrevOverflowCont correctly.
+ */
+void nsOverflowContinuationTracker::SetUpListWalker() {
+ NS_ASSERTION(!mSentry && !mPrevOverflowCont,
+ "forgot to reset mSentry or mPrevOverflowCont");
+ if (mOverflowContList) {
+ nsIFrame* cur = mOverflowContList->FirstChild();
+ if (mSkipOverflowContainerChildren) {
+ while (cur && cur->GetPrevInFlow()->HasAnyStateBits(
+ NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ mPrevOverflowCont = cur;
+ cur = cur->GetNextSibling();
+ }
+ while (cur &&
+ (cur->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) != mWalkOOFFrames)) {
+ mPrevOverflowCont = cur;
+ cur = cur->GetNextSibling();
+ }
+ }
+ if (cur) {
+ mSentry = cur->GetPrevInFlow();
+ }
+ }
+}
+
+/**
+ * Helper function to step forward through the overflow continuations list.
+ * Sets mSentry and mPrevOverflowCont, skipping over OOF or non-OOF frames
+ * as appropriate. May only be called when we have already set up an
+ * mOverflowContList; mOverflowContList cannot be null.
+ */
+void nsOverflowContinuationTracker::StepForward() {
+ MOZ_ASSERT(mOverflowContList, "null list");
+
+ // Step forward
+ if (mPrevOverflowCont) {
+ mPrevOverflowCont = mPrevOverflowCont->GetNextSibling();
+ } else {
+ mPrevOverflowCont = mOverflowContList->FirstChild();
+ }
+
+ // Skip over oof or non-oof frames as appropriate
+ if (mSkipOverflowContainerChildren) {
+ nsIFrame* cur = mPrevOverflowCont->GetNextSibling();
+ while (cur &&
+ (cur->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) != mWalkOOFFrames)) {
+ mPrevOverflowCont = cur;
+ cur = cur->GetNextSibling();
+ }
+ }
+
+ // Set up the sentry
+ mSentry = (mPrevOverflowCont->GetNextSibling())
+ ? mPrevOverflowCont->GetNextSibling()->GetPrevInFlow()
+ : nullptr;
+}
+
+nsresult nsOverflowContinuationTracker::Insert(nsIFrame* aOverflowCont,
+ nsReflowStatus& aReflowStatus) {
+ MOZ_ASSERT(aOverflowCont, "null frame pointer");
+ MOZ_ASSERT(!mSkipOverflowContainerChildren ||
+ mWalkOOFFrames ==
+ aOverflowCont->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "shouldn't insert frame that doesn't match walker type");
+ MOZ_ASSERT(aOverflowCont->GetPrevInFlow(),
+ "overflow containers must have a prev-in-flow");
+
+ nsresult rv = NS_OK;
+ bool reparented = false;
+ nsPresContext* presContext = aOverflowCont->PresContext();
+ bool addToList = !mSentry || aOverflowCont != mSentry->GetNextInFlow();
+
+ // If we have a list and aOverflowCont is already in it then don't try to
+ // add it again.
+ if (addToList && aOverflowCont->GetParent() == mParent &&
+ aOverflowCont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
+ mOverflowContList && mOverflowContList->ContainsFrame(aOverflowCont)) {
+ addToList = false;
+ mPrevOverflowCont = aOverflowCont->GetPrevSibling();
+ }
+
+ if (addToList) {
+ if (aOverflowCont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ // aOverflowCont is in some other overflow container list,
+ // steal it first
+ NS_ASSERTION(!(mOverflowContList &&
+ mOverflowContList->ContainsFrame(aOverflowCont)),
+ "overflow containers out of order");
+ aOverflowCont->GetParent()->StealFrame(aOverflowCont);
+ } else {
+ aOverflowCont->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ }
+ if (!mOverflowContList) {
+ // Note: We don't use SetExcessOverflowContainers() since it requires
+ // setting a non-empty list. It's OK to manually set an empty list to
+ // ExcessOverflowContainersProperty() because we are going to insert
+ // aOverflowCont to mOverflowContList below, which guarantees an nonempty
+ // list in ExcessOverflowContainersProperty().
+ mOverflowContList = new (presContext->PresShell()) nsFrameList();
+ mParent->SetProperty(nsContainerFrame::ExcessOverflowContainersProperty(),
+ mOverflowContList);
+ SetUpListWalker();
+ }
+ if (aOverflowCont->GetParent() != mParent) {
+ nsContainerFrame::ReparentFrameView(aOverflowCont,
+ aOverflowCont->GetParent(), mParent);
+ reparented = true;
+ }
+
+ // If aOverflowCont has a prev/next-in-flow that might be in
+ // mOverflowContList we need to find it and insert after/before it to
+ // maintain the order amongst next-in-flows in this list.
+ nsIFrame* pif = aOverflowCont->GetPrevInFlow();
+ nsIFrame* nif = aOverflowCont->GetNextInFlow();
+ if ((pif && pif->GetParent() == mParent && pif != mPrevOverflowCont) ||
+ (nif && nif->GetParent() == mParent && mPrevOverflowCont)) {
+ for (nsIFrame* f : *mOverflowContList) {
+ if (f == pif) {
+ mPrevOverflowCont = pif;
+ break;
+ }
+ if (f == nif) {
+ mPrevOverflowCont = f->GetPrevSibling();
+ break;
+ }
+ }
+ }
+
+ mOverflowContList->InsertFrame(mParent, mPrevOverflowCont, aOverflowCont);
+ aReflowStatus.SetNextInFlowNeedsReflow();
+ }
+
+ // If we need to reflow it, mark it dirty
+ if (aReflowStatus.NextInFlowNeedsReflow()) {
+ aOverflowCont->MarkSubtreeDirty();
+ }
+
+ // It's in our list, just step forward
+ StepForward();
+ NS_ASSERTION(mPrevOverflowCont == aOverflowCont ||
+ (mSkipOverflowContainerChildren &&
+ mPrevOverflowCont->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) !=
+ aOverflowCont->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)),
+ "OverflowContTracker in unexpected state");
+
+ if (addToList) {
+ // Convert all non-overflow-container next-in-flows of aOverflowCont
+ // into overflow containers and move them to our overflow
+ // tracker. This preserves the invariant that the next-in-flows
+ // of an overflow container are also overflow containers.
+ nsIFrame* f = aOverflowCont->GetNextInFlow();
+ if (f && (!f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) ||
+ (!reparented && f->GetParent() == mParent) ||
+ (reparented && f->GetParent() != mParent))) {
+ if (!f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ f->GetParent()->StealFrame(f);
+ }
+ Insert(f, aReflowStatus);
+ }
+ }
+ return rv;
+}
+
+void nsOverflowContinuationTracker::BeginFinish(nsIFrame* aChild) {
+ MOZ_ASSERT(aChild, "null ptr");
+ MOZ_ASSERT(aChild->GetNextInFlow(),
+ "supposed to call Finish *before* deleting next-in-flow!");
+
+ for (nsIFrame* f = aChild; f; f = f->GetNextInFlow()) {
+ // We'll update these in EndFinish after the next-in-flows are gone.
+ if (f == mPrevOverflowCont) {
+ mSentry = nullptr;
+ mPrevOverflowCont = nullptr;
+ break;
+ }
+ if (f == mSentry) {
+ mSentry = nullptr;
+ break;
+ }
+ }
+}
+
+void nsOverflowContinuationTracker::EndFinish(nsIFrame* aChild) {
+ if (!mOverflowContList) {
+ return;
+ }
+ // Forget mOverflowContList if it was deleted.
+ nsFrameList* eoc = mParent->GetExcessOverflowContainers();
+ if (eoc != mOverflowContList) {
+ nsFrameList* oc = mParent->GetOverflowContainers();
+ if (oc != mOverflowContList) {
+ // mOverflowContList was deleted
+ mPrevOverflowCont = nullptr;
+ mSentry = nullptr;
+ mParent = aChild->GetParent();
+ mOverflowContList = nullptr;
+ SetupOverflowContList();
+ return;
+ }
+ }
+ // The list survived, update mSentry if needed.
+ if (!mSentry) {
+ if (!mPrevOverflowCont) {
+ SetUpListWalker();
+ } else {
+ mozilla::AutoRestore<nsIFrame*> saved(mPrevOverflowCont);
+ // step backward to make StepForward() use our current mPrevOverflowCont
+ mPrevOverflowCont = mPrevOverflowCont->GetPrevSibling();
+ StepForward();
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Debugging
+
+#ifdef DEBUG
+void nsContainerFrame::SanityCheckChildListsBeforeReflow() const {
+ MOZ_ASSERT(IsFlexOrGridContainer(),
+ "Only Flex / Grid containers can call this!");
+
+ const auto didPushItemsBit = IsFlexContainerFrame()
+ ? NS_STATE_FLEX_DID_PUSH_ITEMS
+ : NS_STATE_GRID_DID_PUSH_ITEMS;
+ ChildListIDs absLists = {FrameChildListID::Absolute, FrameChildListID::Fixed,
+ FrameChildListID::OverflowContainers,
+ FrameChildListID::ExcessOverflowContainers};
+ ChildListIDs itemLists = {FrameChildListID::Principal,
+ FrameChildListID::Overflow};
+ for (const nsIFrame* f = this; f; f = f->GetNextInFlow()) {
+ MOZ_ASSERT(!f->HasAnyStateBits(didPushItemsBit),
+ "At start of reflow, we should've pulled items back from all "
+ "NIFs and cleared the state bit stored in didPushItemsBit in "
+ "the process.");
+ for (const auto& [list, listID] : f->ChildLists()) {
+ if (!itemLists.contains(listID)) {
+ MOZ_ASSERT(
+ absLists.contains(listID) || listID == FrameChildListID::Backdrop,
+ "unexpected non-empty child list");
+ continue;
+ }
+ for (const auto* child : list) {
+ MOZ_ASSERT(f == this || child->GetPrevInFlow(),
+ "all pushed items must be pulled up before reflow");
+ }
+ }
+ }
+ // If we have a prev-in-flow, each of its children's next-in-flow
+ // should be one of our children or be null.
+ const auto* pif = static_cast<nsContainerFrame*>(GetPrevInFlow());
+ if (pif) {
+ const nsFrameList* oc = GetOverflowContainers();
+ const nsFrameList* eoc = GetExcessOverflowContainers();
+ const nsFrameList* pifEOC = pif->GetExcessOverflowContainers();
+ for (const nsIFrame* child : pif->PrincipalChildList()) {
+ const nsIFrame* childNIF = child->GetNextInFlow();
+ MOZ_ASSERT(!childNIF || mFrames.ContainsFrame(childNIF) ||
+ (pifEOC && pifEOC->ContainsFrame(childNIF)) ||
+ (oc && oc->ContainsFrame(childNIF)) ||
+ (eoc && eoc->ContainsFrame(childNIF)));
+ }
+ }
+}
+
+void nsContainerFrame::SetDidPushItemsBitIfNeeded(ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ MOZ_ASSERT(IsFlexOrGridContainer(),
+ "Only Flex / Grid containers can call this!");
+
+ // Note that FrameChildListID::Principal doesn't mean aOldFrame must be on
+ // that list. It can also be on FrameChildListID::Overflow, in which case it
+ // might be a pushed item, and if it's the only pushed item our DID_PUSH_ITEMS
+ // bit will lie.
+ if (aListID == FrameChildListID::Principal && !aOldFrame->GetPrevInFlow()) {
+ // Since the bit may lie, set the mDidPushItemsBitMayLie value to true for
+ // ourself and for all our prev-in-flows.
+ nsContainerFrame* frameThatMayLie = this;
+ do {
+ frameThatMayLie->mDidPushItemsBitMayLie = true;
+ frameThatMayLie =
+ static_cast<nsContainerFrame*>(frameThatMayLie->GetPrevInFlow());
+ } while (frameThatMayLie);
+ }
+}
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+void nsContainerFrame::List(FILE* out, const char* aPrefix,
+ ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+ ExtraContainerFrameInfo(str);
+
+ // Output the frame name and various fields.
+ fprintf_stderr(out, "%s <\n", str.get());
+
+ const nsCString pfx = nsCString(aPrefix) + " "_ns;
+
+ // Output principal child list separately since we want to omit its
+ // name and address.
+ for (nsIFrame* kid : PrincipalChildList()) {
+ kid->List(out, pfx.get(), aFlags);
+ }
+
+ // Output rest of the child lists.
+ const ChildListIDs skippedListIDs = {FrameChildListID::Principal};
+ ListChildLists(out, pfx.get(), aFlags, skippedListIDs);
+
+ fprintf_stderr(out, "%s>\n", aPrefix);
+}
+
+void nsContainerFrame::ListWithMatchedRules(FILE* out,
+ const char* aPrefix) const {
+ fprintf_stderr(out, "%s%s\n", aPrefix, ListTag().get());
+
+ nsCString rulePrefix;
+ rulePrefix += aPrefix;
+ rulePrefix += " ";
+ ListMatchedRules(out, rulePrefix.get());
+
+ nsCString childPrefix;
+ childPrefix += aPrefix;
+ childPrefix += " ";
+
+ for (const auto& childList : ChildLists()) {
+ for (const nsIFrame* kid : childList.mList) {
+ kid->ListWithMatchedRules(out, childPrefix.get());
+ }
+ }
+}
+
+void nsContainerFrame::ListChildLists(FILE* aOut, const char* aPrefix,
+ ListFlags aFlags,
+ ChildListIDs aSkippedListIDs) const {
+ const nsCString nestedPfx = nsCString(aPrefix) + " "_ns;
+
+ for (const auto& [list, listID] : ChildLists()) {
+ if (aSkippedListIDs.contains(listID)) {
+ continue;
+ }
+
+ // Use nsPrintfCString so that %p don't output prefix "0x". This is
+ // consistent with nsIFrame::ListTag().
+ const nsPrintfCString str("%s%s@%p <\n", aPrefix, ChildListName(listID),
+ &GetChildList(listID));
+ fprintf_stderr(aOut, "%s", str.get());
+
+ for (nsIFrame* kid : list) {
+ // Verify the child frame's parent frame pointer is correct.
+ NS_ASSERTION(kid->GetParent() == this, "Bad parent frame pointer!");
+ kid->List(aOut, nestedPfx.get(), aFlags);
+ }
+ fprintf_stderr(aOut, "%s>\n", aPrefix);
+ }
+}
+
+void nsContainerFrame::ExtraContainerFrameInfo(nsACString& aTo) const {
+ (void)aTo;
+}
+
+#endif
diff --git a/layout/generic/nsContainerFrame.h b/layout/generic/nsContainerFrame.h
new file mode 100644
index 0000000000..09f6cf76f5
--- /dev/null
+++ b/layout/generic/nsContainerFrame.h
@@ -0,0 +1,1201 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class #1 for rendering objects that have child lists */
+
+#ifndef nsContainerFrame_h___
+#define nsContainerFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "LayoutConstants.h"
+#include "nsISelectionDisplay.h"
+#include "nsSplittableFrame.h"
+#include "nsFrameList.h"
+#include "nsLineBox.h"
+#include "nsTHashSet.h"
+
+class nsOverflowContinuationTracker;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+// Some macros for container classes to do sanity checking on
+// width/height/x/y values computed during reflow.
+// NOTE: AppUnitsPerCSSPixel value hardwired here to remove the
+// dependency on nsDeviceContext.h. It doesn't matter if it's a
+// little off.
+#ifdef DEBUG
+// 10 million pixels, converted to app units. Note that this a bit larger
+// than 1/4 of nscoord_MAX. So, if any content gets to be this large, we're
+// definitely in danger of grazing up against nscoord_MAX; hence, it's ABSURD.
+# define ABSURD_COORD (10000000 * 60)
+# define ABSURD_SIZE(_x) (((_x) < -ABSURD_COORD) || ((_x) > ABSURD_COORD))
+#endif
+
+/**
+ * Implementation of a container frame.
+ */
+class nsContainerFrame : public nsSplittableFrame {
+ public:
+ NS_DECL_ABSTRACT_FRAME(nsContainerFrame)
+ NS_DECL_QUERYFRAME_TARGET(nsContainerFrame)
+ NS_DECL_QUERYFRAME
+
+ // nsIFrame overrides
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ nsContainerFrame* GetContentInsertionFrame() override { return this; }
+
+ const nsFrameList& GetChildList(ChildListID aList) const override;
+ void GetChildLists(nsTArray<ChildList>* aLists) const override;
+ void Destroy(DestroyContext&) override;
+
+ void ChildIsDirty(nsIFrame* aChild) override;
+
+ FrameSearchResult PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) override;
+ FrameSearchResult PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset,
+ PeekOffsetCharacterOptions aOptions =
+ PeekOffsetCharacterOptions()) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ void List(FILE* out = stderr, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const override;
+ void ListWithMatchedRules(FILE* out = stderr,
+ const char* aPrefix = "") const override;
+ void ListChildLists(FILE* aOut, const char* aPrefix, ListFlags aFlags,
+ ChildListIDs aSkippedListIDs) const;
+ virtual void ExtraContainerFrameInfo(nsACString& aTo) const;
+#endif
+
+ // nsContainerFrame methods
+
+ /**
+ * Called to set the initial list of frames. This happens after the frame
+ * has been initialized.
+ *
+ * This is only called once for a given child list, and won't be called
+ * at all for child lists with no initial list of frames.
+ *
+ * @param aListID the child list identifier.
+ * @param aChildList list of child frames. Each of the frames has its
+ * NS_FRAME_IS_DIRTY bit set. Must not be empty.
+ * This method cannot handle the child list returned by
+ * GetAbsoluteListID().
+ * @see #Init()
+ */
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList);
+
+ /**
+ * This method is responsible for appending frames to the frame
+ * list. The implementation should append the frames to the specified
+ * child list and then generate a reflow command.
+ *
+ * @param aListID the child list identifier.
+ * @param aFrameList list of child frames to append. Each of the frames has
+ * its NS_FRAME_IS_DIRTY bit set. Must not be empty.
+ */
+ virtual void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList);
+
+ /**
+ * This method is responsible for inserting frames into the frame
+ * list. The implementation should insert the new frames into the specified
+ * child list and then generate a reflow command.
+ *
+ * @param aListID the child list identifier.
+ * @param aPrevFrame the frame to insert frames <b>after</b>
+ * @param aPrevFrameLine (optional) if present (i.e., not null), the line
+ * box that aPrevFrame is part of.
+ * @param aFrameList list of child frames to insert <b>after</b> aPrevFrame.
+ * Each of the frames has its NS_FRAME_IS_DIRTY bit set
+ */
+ virtual void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList);
+
+ /**
+ * This method is responsible for removing a frame in the frame
+ * list. The implementation should do something with the removed frame
+ * and then generate a reflow command. The implementation is responsible
+ * for destroying the frame (the caller mustn't destroy it).
+ */
+ virtual void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*);
+
+ /**
+ * Helper method to create next-in-flows if necessary. If aFrame
+ * already has a next-in-flow then this method does
+ * nothing. Otherwise, a new continuation frame is created and
+ * linked into the flow. In addition, the new frame is inserted
+ * into the principal child list after aFrame.
+ * @note calling this method on a block frame is illegal. Use
+ * nsBlockFrame::CreateContinuationFor() instead.
+ * @return the next-in-flow <b>if and only if</b> one is created. If
+ * a next-in-flow already exists, nullptr will be returned.
+ */
+ nsIFrame* CreateNextInFlow(nsIFrame* aFrame);
+
+ /**
+ * Delete aNextInFlow and its next-in-flows.
+ * @param aDeletingEmptyFrames if set, then the reflow for aNextInFlow's
+ * content was complete before aNextInFlow, so aNextInFlow and its
+ * next-in-flows no longer map any real content.
+ */
+ virtual void DeleteNextInFlowChild(DestroyContext&, nsIFrame* aNextInFlow,
+ bool aDeletingEmptyFrames);
+
+ // Positions the frame's view based on the frame's origin
+ static void PositionFrameView(nsIFrame* aKidFrame);
+
+ static void ReparentFrameView(nsIFrame* aChildFrame,
+ nsIFrame* aOldParentFrame,
+ nsIFrame* aNewParentFrame);
+
+ static void ReparentFrameViewList(const nsFrameList& aChildFrameList,
+ nsIFrame* aOldParentFrame,
+ nsIFrame* aNewParentFrame);
+
+ /**
+ * Reparent aFrame from aOldParent to aNewParent.
+ */
+ static void ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent,
+ nsContainerFrame* aNewParent);
+
+ /**
+ * Reparent all the frames in aFrameList from aOldParent to aNewParent.
+ *
+ * Note: Reparenting a large frame list can be have huge performance impact.
+ * For example, instead of using this method, nsInlineFrame uses a "lazy
+ * reparenting" technique that it reparents a child frame just before
+ * reflowing the child. (See InlineReflowInput::mSetParentPointer.)
+ */
+ static void ReparentFrames(nsFrameList& aFrameList,
+ nsContainerFrame* aOldParent,
+ nsContainerFrame* aNewParent);
+
+ // Set the view's size and position after its frame has been reflowed.
+ static void SyncFrameViewAfterReflow(
+ nsPresContext* aPresContext, nsIFrame* aFrame, nsView* aView,
+ const nsRect& aInkOverflowArea,
+ ReflowChildFlags aFlags = ReflowChildFlags::Default);
+
+ /**
+ * Converts the minimum and maximum sizes given in inner window app units to
+ * outer window device pixel sizes and assigns these constraints to the
+ * widget.
+ *
+ * @param aPresContext pres context
+ * @param aWidget widget for this frame
+ * @param minimum size of the window in app units
+ * @param maxmimum size of the window in app units
+ */
+ static void SetSizeConstraints(nsPresContext* aPresContext,
+ nsIWidget* aWidget, const nsSize& aMinSize,
+ const nsSize& aMaxSize);
+
+ /**
+ * Helper for calculating intrinsic inline size for inline containers.
+ *
+ * @param aData the intrinsic inline size data, either an InlineMinISizeData
+ * or an InlinePrefISizeData
+ * @param aHandleChildren a callback function invoked for each in-flow
+ * continuation, with the continuation frame and the intrinsic inline size
+ * data passed into it.
+ */
+ template <typename ISizeData, typename F>
+ void DoInlineIntrinsicISize(ISizeData* aData, F& aHandleChildren);
+
+ void DoInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData);
+ void DoInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData);
+
+ /**
+ * This is the CSS block concept of computing 'auto' widths, which most
+ * classes derived from nsContainerFrame want.
+ */
+ virtual mozilla::LogicalSize ComputeAutoSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+
+ /**
+ * Positions aKidFrame and its view (if requested), and then calls Reflow().
+ * If the reflow status after reflowing the child is FullyComplete then any
+ * next-in-flows are deleted using DeleteNextInFlowChild().
+ *
+ * @param aReflowInput the reflow input for aKidFrame.
+ * @param aWM aPos's writing-mode (any writing mode will do).
+ * @param aPos Position of the aKidFrame to be moved, in terms of aWM.
+ * @param aContainerSize Size of the border-box of the containing frame.
+ *
+ * Note: If ReflowChildFlags::NoMoveFrame is requested, both aPos and
+ * aContainerSize are ignored.
+ */
+ void ReflowChild(nsIFrame* aKidFrame, nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
+ const mozilla::WritingMode& aWM,
+ const mozilla::LogicalPoint& aPos,
+ const nsSize& aContainerSize, ReflowChildFlags aFlags,
+ nsReflowStatus& aStatus,
+ nsOverflowContinuationTracker* aTracker = nullptr);
+
+ /**
+ * The second half of frame reflow. Does the following:
+ * - sets the frame's bounds
+ * - sizes and positions (if requested) the frame's view. If the frame's final
+ * position differs from the current position and the frame itself does not
+ * have a view, then any child frames with views are positioned so they stay
+ * in sync
+ * - sets the view's visibility, opacity, content transparency, and clip
+ * - invoked the DidReflow() function
+ *
+ * @param aReflowInput the reflow input for aKidFrame.
+ * @param aWM aPos's writing-mode (any writing mode will do).
+ * @param aPos Position of the aKidFrame to be moved, in terms of aWM.
+ * @param aContainerSize Size of the border-box of the containing frame.
+ *
+ * Note: If ReflowChildFlags::NoMoveFrame is requested, both aPos and
+ * aContainerSize are ignored unless
+ * ReflowChildFlags::ApplyRelativePositioning is requested.
+ */
+ static void FinishReflowChild(
+ nsIFrame* aKidFrame, nsPresContext* aPresContext,
+ const ReflowOutput& aDesiredSize, const ReflowInput* aReflowInput,
+ const mozilla::WritingMode& aWM, const mozilla::LogicalPoint& aPos,
+ const nsSize& aContainerSize, ReflowChildFlags aFlags);
+
+ // XXX temporary: hold on to a copy of the old physical versions of
+ // ReflowChild and FinishReflowChild so that we can convert callers
+ // incrementally.
+ void ReflowChild(nsIFrame* aKidFrame, nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
+ nscoord aX, nscoord aY, ReflowChildFlags aFlags,
+ nsReflowStatus& aStatus,
+ nsOverflowContinuationTracker* aTracker = nullptr);
+
+ static void FinishReflowChild(nsIFrame* aKidFrame,
+ nsPresContext* aPresContext,
+ const ReflowOutput& aDesiredSize,
+ const ReflowInput* aReflowInput, nscoord aX,
+ nscoord aY, ReflowChildFlags aFlags);
+
+ static void PositionChildViews(nsIFrame* aFrame);
+
+ // ==========================================================================
+ /* Overflow containers are continuation frames that hold overflow. They
+ * are created when the frame runs out of computed block-size, but still has
+ * too much content to fit in the AvailableBSize. The parent creates a
+ * continuation as usual, but marks it as NS_FRAME_IS_OVERFLOW_CONTAINER
+ * and adds it to its next-in-flow's overflow container list, either by
+ * adding it directly or by putting it in its own excess overflow containers
+ * list (to be drained by the next-in-flow when it calls
+ * ReflowOverflowContainerChildren). The parent continues reflow as if
+ * the frame was complete once it ran out of computed block-size, but returns
+ * a reflow status with either IsIncomplete() or IsOverflowIncomplete() equal
+ * to true to request a next-in-flow. The parent's next-in-flow is then
+ * responsible for calling ReflowOverflowContainerChildren to (drain and)
+ * reflow these overflow continuations. Overflow containers do not affect
+ * other frames' size or position during reflow (but do affect their
+ * parent's overflow area).
+ *
+ * Overflow container continuations are different from normal continuations
+ * in that
+ * - more than one child of the frame can have its next-in-flow broken
+ * off and pushed into the frame's next-in-flow
+ * - new continuations may need to be spliced into the middle of the list
+ * or deleted continuations slipped out
+ * e.g. A, B, C are all fixed-size containers on one page, all have
+ * overflow beyond AvailableBSize, and content is dynamically added
+ * and removed from B
+ * As a result, it is not possible to simply prepend the new continuations
+ * to the old list as with the OverflowProperty mechanism. To avoid
+ * complicated list splicing, the code assumes only one overflow containers
+ * list exists for a given frame: either its own OverflowContainersProperty
+ * or its prev-in-flow's ExcessOverflowContainersProperty, not both.
+ *
+ * The nsOverflowContinuationTracker helper class should be used for tracking
+ * overflow containers and adding them to the appropriate list.
+ * See nsBlockFrame::Reflow for a sample implementation.
+ *
+ * For more information, see https://wiki.mozilla.org/Gecko:Continuation_Model
+ *
+ * Note that Flex/GridContainerFrame doesn't use nsOverflowContinuationTracker
+ * so the above doesn't apply. Flex/Grid containers may have items that
+ * aren't in document order between fragments, due to the 'order' property,
+ * but they do maintain the invariant that children in the same nsFrameList
+ * are in document order. This means that when pushing/pulling items or
+ * merging lists, the result needs to be sorted to restore the order.
+ * However, given that lists are individually sorted, it's a simple merge
+ * operation of the two lists to make the result sorted.
+ * DrainExcessOverflowContainersList takes a merging function to perform that
+ * operation. (By "document order" here we mean normal frame tree order,
+ * which is approximately flattened DOM tree order.)
+ */
+
+ friend class nsOverflowContinuationTracker;
+
+ typedef void (*ChildFrameMerger)(nsFrameList& aDest, nsFrameList& aSrc,
+ nsContainerFrame* aParent);
+ static inline void DefaultChildFrameMerge(nsFrameList& aDest,
+ nsFrameList& aSrc,
+ nsContainerFrame* aParent) {
+ aDest.AppendFrames(nullptr, std::move(aSrc));
+ }
+
+ /**
+ * Reflow overflow container children. They are invisible to normal reflow
+ * (i.e. don't affect sizing or placement of other children) and inherit
+ * width and horizontal position from their prev-in-flow.
+ *
+ * This method
+ * 1. Pulls excess overflow containers from the prev-in-flow and adds
+ * them to our overflow container list
+ * 2. Reflows all our overflow container kids
+ * 3. Expands aOverflowRect as necessary to accomodate these children.
+ * 4. Sets aStatus's mOverflowIncomplete flag (along with
+ * mNextInFlowNeedsReflow as necessary) if any overflow children
+ * are incomplete and
+ * 5. Prepends a list of their continuations to our excess overflow
+ * container list, to be drained into our next-in-flow when it is
+ * reflowed.
+ *
+ * The caller is responsible for tracking any new overflow container
+ * continuations it makes, removing them from its child list, and
+ * making sure they are stored properly in the overflow container lists.
+ * The nsOverflowContinuationTracker helper class should be used for this.
+ *
+ * @param aFlags is passed through to ReflowChild
+ * @param aMergeFunc is passed to DrainExcessOverflowContainersList
+ * @param aContainerSize is used only for converting logical coordinate to
+ * physical coordinate. If a tentative container size is used, caller
+ * may need to adjust the position of our overflow container children
+ * once the real size is known if our writing mode is vertical-rl.
+ */
+ void ReflowOverflowContainerChildren(
+ nsPresContext* aPresContext, const ReflowInput& aReflowInput,
+ mozilla::OverflowAreas& aOverflowRects, ReflowChildFlags aFlags,
+ nsReflowStatus& aStatus,
+ ChildFrameMerger aMergeFunc = DefaultChildFrameMerge,
+ Maybe<nsSize> aContainerSize = Nothing());
+
+ /**
+ * Move any frames on our overflow list to the end of our principal list.
+ * @return true if there were any overflow frames
+ */
+ virtual bool DrainSelfOverflowList() override;
+
+ /**
+ * Move all frames on our prev-in-flow's and our own ExcessOverflowContainers
+ * lists to our OverflowContainers list. If there are frames on multiple
+ * lists they are merged using aMergeFunc.
+ * @return a pointer to our OverflowContainers list, if any
+ */
+ nsFrameList* DrainExcessOverflowContainersList(
+ ChildFrameMerger aMergeFunc = DefaultChildFrameMerge);
+
+ /**
+ * Removes aChild without destroying it and without requesting reflow.
+ * Continuations are not affected. Checks the principal and overflow lists,
+ * and also the [excess] overflow containers lists if the frame bit
+ * NS_FRAME_IS_OVERFLOW_CONTAINER is set. It does not check any other lists.
+ * aChild must be in one of the above mentioned lists, or an assertion is
+ * triggered.
+ *
+ * Note: This method can destroy either overflow list or [excess] overflow
+ * containers list if aChild is the only child in the list. Any pointer to the
+ * list obtained prior to calling this method shouldn't be used.
+ */
+ virtual void StealFrame(nsIFrame* aChild);
+
+ /**
+ * Removes the next-siblings of aChild without destroying them and without
+ * requesting reflow. Checks the principal and overflow lists (not
+ * overflow containers / excess overflow containers). Does not check any
+ * other auxiliary lists.
+ * @param aChild a child frame or nullptr
+ * @return If aChild is non-null, the next-siblings of aChild, if any.
+ * If aChild is null, all child frames on the principal list, if any.
+ */
+ nsFrameList StealFramesAfter(nsIFrame* aChild);
+
+ /**
+ * Add overflow containers to the display list
+ */
+ void DisplayOverflowContainers(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists);
+
+ /**
+ * Builds display lists for the children. The background
+ * of each child is placed in the Content() list (suitable for inline
+ * children and other elements that behave like inlines,
+ * but not for in-flow block children of blocks). DOES NOT
+ * paint the background/borders/outline of this frame. This should
+ * probably be avoided and eventually removed. It's currently here
+ * to emulate what nsContainerFrame::Paint did.
+ */
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ static void PlaceFrameView(nsIFrame* aFrame) {
+ if (aFrame->HasView())
+ nsContainerFrame::PositionFrameView(aFrame);
+ else
+ nsContainerFrame::PositionChildViews(aFrame);
+ }
+
+ /**
+ * Returns a CSS Box Alignment constant which the caller can use to align
+ * the absolutely-positioned child (whose ReflowInput is aChildRI) within
+ * a CSS Box Alignment area associated with this container.
+ *
+ * The lower 8 bits of the returned value are guaranteed to form a valid
+ * argument for CSSAlignUtils::AlignJustifySelf(). (The upper 8 bits may
+ * encode an <overflow-position>.)
+ *
+ * NOTE: This default nsContainerFrame implementation is a stub, and isn't
+ * meant to be called. Subclasses must provide their own implementations, if
+ * they use CSS Box Alignment to determine the static position of their
+ * absolutely-positioned children. (Though: if subclasses share enough code,
+ * maybe this nsContainerFrame impl should include some shared code.)
+ *
+ * @param aChildRI A ReflowInput for the positioned child frame that's being
+ * aligned.
+ * @param aLogicalAxis The axis (of this container frame) in which the caller
+ * would like to align the child frame.
+ */
+ virtual mozilla::StyleAlignFlags CSSAlignmentForAbsPosChild(
+ const ReflowInput& aChildRI, mozilla::LogicalAxis aLogicalAxis) const;
+
+#define NS_DECLARE_FRAME_PROPERTY_FRAMELIST(prop) \
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(prop, nsFrameList)
+
+ using FrameListPropertyDescriptor =
+ mozilla::FrameProperties::Descriptor<nsFrameList>;
+
+ NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowProperty)
+ NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowContainersProperty)
+ NS_DECLARE_FRAME_PROPERTY_FRAMELIST(ExcessOverflowContainersProperty)
+ NS_DECLARE_FRAME_PROPERTY_FRAMELIST(BackdropProperty)
+
+ // Only really used on nsBlockFrame instances, but the caller thinks it could
+ // have arbitrary nsContainerFrames.
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(FirstLetterProperty, nsIFrame)
+
+ void SetHasFirstLetterChild() { mHasFirstLetterChild = true; }
+
+ void ClearHasFirstLetterChild() { mHasFirstLetterChild = false; }
+
+#ifdef DEBUG
+ // Use this to suppress the ABSURD_SIZE assertions.
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(DebugReflowingWithInfiniteISize, bool)
+ bool IsAbsurdSizeAssertSuppressed() const {
+ return GetProperty(DebugReflowingWithInfiniteISize());
+ }
+#endif
+
+ // Incorporate the child overflow areas into aOverflowAreas.
+ // If the child does not have a overflow, use the child area.
+ void ConsiderChildOverflow(mozilla::OverflowAreas& aOverflowAreas,
+ nsIFrame* aChildFrame);
+
+ protected:
+ nsContainerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : nsSplittableFrame(aStyle, aPresContext, aID) {}
+
+ ~nsContainerFrame();
+
+ /**
+ * Helper for DestroyFrom. DestroyAbsoluteFrames is called before
+ * destroying frames on lists that can contain placeholders.
+ * Derived classes must do that too, if they destroy such frame lists.
+ * See nsBlockFrame::DestroyFrom for an example.
+ */
+ void DestroyAbsoluteFrames(DestroyContext&);
+
+ /**
+ * Helper for StealFrame. Returns true if aChild was removed from its list.
+ */
+ bool MaybeStealOverflowContainerFrame(nsIFrame* aChild);
+
+ /**
+ * Builds a display list for non-block children that behave like
+ * inlines. This puts the background of each child into the
+ * Content() list (suitable for inline children but not for
+ * in-flow block children of blocks).
+ * @param aForcePseudoStack forces each child into a pseudo-stacking-context
+ * so its background and all other display items (except for positioned
+ * display items) go into the Content() list.
+ */
+ void BuildDisplayListForNonBlockChildren(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists,
+ DisplayChildFlags aFlags = {});
+
+ /**
+ * A version of BuildDisplayList that use DisplayChildFlag::Inline.
+ * Intended as a convenience for derived classes.
+ */
+ void BuildDisplayListForInline(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+ BuildDisplayListForNonBlockChildren(aBuilder, aLists,
+ DisplayChildFlag::Inline);
+ }
+
+ // ==========================================================================
+ /* Overflow Frames are frames that did not fit and must be pulled by
+ * our next-in-flow during its reflow. (The same concept for overflow
+ * containers is called "excess frames". We should probably make the
+ * names match.)
+ */
+
+ /**
+ * Get the frames on the overflow list, overflow containers list, or excess
+ * overflow containers list. Can return null if there are no frames in the
+ * list.
+ *
+ * The caller does NOT take ownership of the list; it's still owned by this
+ * frame. A non-null return value indicates that the list is non-empty.
+ */
+ [[nodiscard]] nsFrameList* GetOverflowFrames() const {
+ nsFrameList* list = GetProperty(OverflowProperty());
+ NS_ASSERTION(!list || !list->IsEmpty(), "Unexpected empty overflow list");
+ return list;
+ }
+ [[nodiscard]] nsFrameList* GetOverflowContainers() const {
+ nsFrameList* list = GetProperty(OverflowContainersProperty());
+ NS_ASSERTION(!list || !list->IsEmpty(),
+ "Unexpected empty overflow containers list");
+ return list;
+ }
+ [[nodiscard]] nsFrameList* GetExcessOverflowContainers() const {
+ nsFrameList* list = GetProperty(ExcessOverflowContainersProperty());
+ NS_ASSERTION(!list || !list->IsEmpty(),
+ "Unexpected empty overflow containers list");
+ return list;
+ }
+
+ /**
+ * Same as the Get methods above, but also remove and the property from this
+ * frame.
+ *
+ * The caller is responsible for deleting nsFrameList and either passing
+ * ownership of the frames to someone else or destroying the frames. A
+ * non-null return value indicates that the list is non-empty. The recommended
+ * way to use this function it to assign its return value into an
+ * AutoFrameListPtr.
+ */
+ [[nodiscard]] nsFrameList* StealOverflowFrames() {
+ nsFrameList* list = TakeProperty(OverflowProperty());
+ NS_ASSERTION(!list || !list->IsEmpty(), "Unexpected empty overflow list");
+ return list;
+ }
+ [[nodiscard]] nsFrameList* StealOverflowContainers() {
+ nsFrameList* list = TakeProperty(OverflowContainersProperty());
+ NS_ASSERTION(!list || !list->IsEmpty(), "Unexpected empty overflow list");
+ return list;
+ }
+ [[nodiscard]] nsFrameList* StealExcessOverflowContainers() {
+ nsFrameList* list = TakeProperty(ExcessOverflowContainersProperty());
+ NS_ASSERTION(!list || !list->IsEmpty(), "Unexpected empty overflow list");
+ return list;
+ }
+
+ /**
+ * Set the overflow list, overflow containers list, or excess overflow
+ * containers list. The argument must be a *non-empty* list.
+ *
+ * After this operation, the argument becomes an empty list.
+ *
+ * @return the frame list associated with the property.
+ */
+ nsFrameList* SetOverflowFrames(nsFrameList&& aOverflowFrames) {
+ MOZ_ASSERT(aOverflowFrames.NotEmpty(), "Shouldn't be called");
+ auto* list = new (PresShell()) nsFrameList(std::move(aOverflowFrames));
+ SetProperty(OverflowProperty(), list);
+ return list;
+ }
+ nsFrameList* SetOverflowContainers(nsFrameList&& aOverflowContainers) {
+ MOZ_ASSERT(aOverflowContainers.NotEmpty(), "Shouldn't set an empty list!");
+ MOZ_ASSERT(!GetProperty(OverflowContainersProperty()),
+ "Shouldn't override existing list!");
+ MOZ_ASSERT(CanContainOverflowContainers(),
+ "This type of frame can't have overflow containers!");
+ auto* list = new (PresShell()) nsFrameList(std::move(aOverflowContainers));
+ SetProperty(OverflowContainersProperty(), list);
+ return list;
+ }
+ nsFrameList* SetExcessOverflowContainers(
+ nsFrameList&& aExcessOverflowContainers) {
+ MOZ_ASSERT(aExcessOverflowContainers.NotEmpty(),
+ "Shouldn't set an empty list!");
+ MOZ_ASSERT(!GetProperty(ExcessOverflowContainersProperty()),
+ "Shouldn't override existing list!");
+ MOZ_ASSERT(CanContainOverflowContainers(),
+ "This type of frame can't have overflow containers!");
+ auto* list =
+ new (PresShell()) nsFrameList(std::move(aExcessOverflowContainers));
+ SetProperty(ExcessOverflowContainersProperty(), list);
+ return list;
+ }
+
+ /**
+ * Destroy the overflow list, overflow containers list, or excess overflow
+ * containers list.
+ *
+ * The list to be destroyed must be empty. That is, the caller is responsible
+ * for either passing ownership of the frames to someone else or destroying
+ * the frames before calling these methods.
+ */
+ void DestroyOverflowList() {
+ nsFrameList* list = TakeProperty(OverflowProperty());
+ MOZ_ASSERT(list && list->IsEmpty());
+ list->Delete(PresShell());
+ }
+ void DestroyOverflowContainers() {
+ nsFrameList* list = TakeProperty(OverflowContainersProperty());
+ MOZ_ASSERT(list && list->IsEmpty());
+ list->Delete(PresShell());
+ }
+ void DestroyExcessOverflowContainers() {
+ nsFrameList* list = TakeProperty(ExcessOverflowContainersProperty());
+ MOZ_ASSERT(list && list->IsEmpty());
+ list->Delete(PresShell());
+ }
+
+ /**
+ * Moves any frames on both the prev-in-flow's overflow list and the
+ * receiver's overflow to the receiver's child list.
+ *
+ * Resets the overlist pointers to nullptr, and updates the receiver's child
+ * count and content mapping.
+ *
+ * @return true if any frames were moved and false otherwise
+ */
+ bool MoveOverflowToChildList();
+
+ /**
+ * Merge a sorted frame list into our overflow list. aList becomes empty after
+ * this call.
+ */
+ void MergeSortedOverflow(nsFrameList& aList);
+
+ /**
+ * Merge a sorted frame list into our excess overflow containers list. aList
+ * becomes empty after this call.
+ */
+ void MergeSortedExcessOverflowContainers(nsFrameList& aList);
+
+ /**
+ * Moves all frames from aSrc into aDest such that the resulting aDest
+ * is still sorted in document content order and continuation order. aSrc
+ * becomes empty after this call.
+ *
+ * Precondition: both |aSrc| and |aDest| must be sorted to begin with.
+ * @param aCommonAncestor a hint for nsLayoutUtils::CompareTreePosition
+ */
+ static void MergeSortedFrameLists(nsFrameList& aDest, nsFrameList& aSrc,
+ nsIContent* aCommonAncestor);
+
+ /**
+ * This is intended to be used as a ChildFrameMerger argument for
+ * ReflowOverflowContainerChildren() and DrainExcessOverflowContainersList().
+ */
+ static inline void MergeSortedFrameListsFor(nsFrameList& aDest,
+ nsFrameList& aSrc,
+ nsContainerFrame* aParent) {
+ MergeSortedFrameLists(aDest, aSrc, aParent->GetContent());
+ }
+
+ /**
+ * Basically same as MoveOverflowToChildList, except that this is for
+ * handling inline children where children of prev-in-flow can be
+ * pushed to overflow list even if a next-in-flow exists.
+ *
+ * @param aLineContainer the line container of the current frame.
+ *
+ * @return true if any frames were moved and false otherwise
+ */
+ bool MoveInlineOverflowToChildList(nsIFrame* aLineContainer);
+
+ /**
+ * Push aFromChild and its next siblings to the overflow list.
+ *
+ * @param aFromChild the first child frame to push. It is disconnected
+ * from aPrevSibling
+ * @param aPrevSibling aFrameChild's previous sibling. Must not be null.
+ * It's an error to push a parent's first child frame.
+ */
+ void PushChildrenToOverflow(nsIFrame* aFromChild, nsIFrame* aPrevSibling);
+
+ /**
+ * Iterate our children in our principal child list in the normal document
+ * order, and append them (or their next-in-flows) to either our overflow list
+ * or excess overflow container list according to their presence in
+ * aPushedItems, aIncompleteItems, or aOverflowIncompleteItems.
+ *
+ * Note: This method is only intended for Grid / Flex containers.
+ * aPushedItems, aIncompleteItems, and aOverflowIncompleteItems are expected
+ * to contain only Grid / Flex items. That is, they should contain only
+ * in-flow children.
+ *
+ * @return true if any items are moved; false otherwise.
+ */
+ using FrameHashtable = nsTHashSet<nsIFrame*>;
+ bool PushIncompleteChildren(const FrameHashtable& aPushedItems,
+ const FrameHashtable& aIncompleteItems,
+ const FrameHashtable& aOverflowIncompleteItems);
+
+ /**
+ * Prepare our child lists so that they are ready to reflow by the following
+ * operations:
+ *
+ * - Merge overflow list from our prev-in-flow into our principal child list.
+ * - Merge our own overflow list into our principal child list,
+ * - Push any child's next-in-flows in our principal child list to our
+ * overflow list.
+ * - Pull up any first-in-flow child we might have pushed from our
+ * next-in-flows.
+ */
+ void NormalizeChildLists();
+
+ /**
+ * Helper to implement AppendFrames / InsertFrames for flex / grid
+ * containers.
+ */
+ void NoteNewChildren(ChildListID aListID, const nsFrameList& aFrameList);
+
+ /**
+ * Helper to implement DrainSelfOverflowList() for flex / grid containers.
+ */
+ bool DrainAndMergeSelfOverflowList();
+
+ /**
+ * Helper to find the first non-anonymous-box frame in the subtree rooted at
+ * aFrame.
+ */
+ static nsIFrame* GetFirstNonAnonBoxInSubtree(nsIFrame* aFrame);
+
+ /**
+ * Reparent floats whose placeholders are inline descendants of aFrame from
+ * whatever block they're currently parented by to aOurBlock.
+ * @param aReparentSiblings if this is true, we follow aFrame's
+ * GetNextSibling chain reparenting them all
+ */
+ static void ReparentFloatsForInlineChild(nsIFrame* aOurBlock,
+ nsIFrame* aFrame,
+ bool aReparentSiblings);
+
+ /**
+ * Try to remove aChildToRemove from the frame list stored in aProp.
+ * If aChildToRemove was removed from the aProp list and that list became
+ * empty, then aProp is removed from this frame and deleted.
+ * @note if aChildToRemove isn't on the aProp frame list, it might still be
+ * removed from whatever list it happens to be on, so use this method
+ * carefully. This method is primarily meant for removing frames from the
+ * [Excess]OverflowContainers lists.
+ * @return true if aChildToRemove was removed from some list
+ */
+ bool TryRemoveFrame(FrameListPropertyDescriptor aProp,
+ nsIFrame* aChildToRemove);
+
+ // ==========================================================================
+ /*
+ * Convenience methods for traversing continuations
+ */
+
+ struct ContinuationTraversingState {
+ nsContainerFrame* mNextInFlow;
+ explicit ContinuationTraversingState(nsContainerFrame* aFrame)
+ : mNextInFlow(static_cast<nsContainerFrame*>(aFrame->GetNextInFlow())) {
+ }
+ };
+
+ /**
+ * Find the first frame that is a child of this frame's next-in-flows,
+ * considering both their principal child lists and overflow lists.
+ */
+ nsIFrame* GetNextInFlowChild(ContinuationTraversingState& aState,
+ bool* aIsInOverflow = nullptr);
+
+ /**
+ * Remove the result of GetNextInFlowChild from its current parent and
+ * append it to this frame's principal child list.
+ */
+ nsIFrame* PullNextInFlowChild(ContinuationTraversingState& aState);
+
+ /**
+ * Safely destroy the frames on the nsFrameList stored on aProp for this
+ * frame then remove the property and delete the frame list.
+ * Nothing happens if the property doesn't exist.
+ */
+ void SafelyDestroyFrameListProp(DestroyContext&,
+ mozilla::PresShell* aPresShell,
+ FrameListPropertyDescriptor aProp);
+
+ // ==========================================================================
+
+ // Helper used by Progress and Meter frames. Returns true if the bar should
+ // be rendered vertically, based on writing-mode and -moz-orient properties.
+ bool ResolvedOrientationIsVertical();
+
+ /**
+ * Calculate the used values for 'width' and 'height' for a replaced element.
+ * http://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ *
+ * @param aAspectRatio the aspect ratio calculated by GetAspectRatio().
+ */
+ mozilla::LogicalSize ComputeSizeWithIntrinsicDimensions(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::IntrinsicSize& aIntrinsicSize,
+ const mozilla::AspectRatio& aAspectRatio,
+ const mozilla::LogicalSize& aCBSize, const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags);
+
+ // Compute tight bounds assuming this frame honours its border, background
+ // and outline, its children's tight bounds, and nothing else.
+ nsRect ComputeSimpleTightBounds(mozilla::gfx::DrawTarget* aDrawTarget) const;
+
+ /*
+ * If this frame is dirty, marks all absolutely-positioned children of this
+ * frame dirty. If this frame isn't dirty, or if there are no
+ * absolutely-positioned children, does nothing.
+ *
+ * It's necessary to use PushDirtyBitToAbsoluteFrames() when you plan to
+ * reflow this frame's absolutely-positioned children after the dirty bit on
+ * this frame has already been cleared, which prevents ReflowInput from
+ * propagating the dirty bit normally. This situation generally only arises
+ * when a multipass layout algorithm is used.
+ */
+ void PushDirtyBitToAbsoluteFrames();
+
+ // Helper function that tests if the frame tree is too deep; if it is
+ // it marks the frame as "unflowable", zeroes out the metrics, sets
+ // the reflow status, and returns true. Otherwise, the frame is
+ // unmarked "unflowable" and the metrics and reflow status are not
+ // touched and false is returned.
+ bool IsFrameTreeTooDeep(const ReflowInput& aReflowInput,
+ ReflowOutput& aMetrics, nsReflowStatus& aStatus);
+
+ /**
+ * @return true if we should avoid a page/column break in this frame.
+ */
+ bool ShouldAvoidBreakInside(const ReflowInput& aReflowInput) const;
+
+ /**
+ * To be called by |BuildDisplayLists| of this class or derived classes to add
+ * a translucent overlay if this frame's content is selected.
+ * @param aContentType an nsISelectionDisplay DISPLAY_ constant identifying
+ * which kind of content this is for
+ */
+ void DisplaySelectionOverlay(
+ nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
+ uint16_t aContentType = nsISelectionDisplay::DISPLAY_FRAMES);
+
+ // ==========================================================================
+
+#ifdef DEBUG
+ // A helper for flex / grid container to sanity check child lists before
+ // reflow. Intended to be called after calling NormalizeChildLists().
+ void SanityCheckChildListsBeforeReflow() const;
+
+ // A helper to set mDidPushItemsBitMayLie if needed. Intended to be called
+ // only in flex / grid container's RemoveFrame.
+ void SetDidPushItemsBitIfNeeded(ChildListID aListID, nsIFrame* aOldFrame);
+
+ // A flag for flex / grid containers. If true, NS_STATE_GRID_DID_PUSH_ITEMS or
+ // NS_STATE_FLEX_DID_PUSH_ITEMS may be set even though all pushed frames may
+ // have been removed. This is used to suppress an assertion in case
+ // RemoveFrame removed all associated child frames.
+ bool mDidPushItemsBitMayLie{false};
+#endif
+
+ nsFrameList mFrames;
+};
+
+// ==========================================================================
+/* The out-of-flow-related code below is for a hacky way of splitting
+ * absolutely-positioned frames. Basically what we do is split the frame
+ * in nsAbsoluteContainingBlock and pretend the continuation is an overflow
+ * container. This isn't an ideal solution, but it lets us print the content
+ * at least. See bug 154892.
+ */
+
+/**
+ * Helper class for tracking overflow container continuations during reflow.
+ *
+ * A frame is related to two sets of overflow containers: those that /are/
+ * its own children, and those that are /continuations/ of its children.
+ * This tracker walks through those continuations (the frame's NIF's children)
+ * and their prev-in-flows (a subset of the frame's normal and overflow
+ * container children) in parallel. It allows the reflower to synchronously
+ * walk its overflow continuations while it loops through and reflows its
+ * children. This makes it possible to insert new continuations at the correct
+ * place in the overflow containers list.
+ *
+ * The reflower is expected to loop through its children in the same order it
+ * looped through them the last time (if there was a last time).
+ * For each child, the reflower should either
+ * - call Skip for the child if was not reflowed in this pass
+ * - call Insert for the overflow continuation if the child was reflowed
+ * but has incomplete overflow
+ * - call Finished for the child if it was reflowed in this pass but
+ * is either complete or has a normal next-in-flow. This call can
+ * be skipped if the child did not previously have an overflow
+ * continuation.
+ */
+class nsOverflowContinuationTracker {
+ public:
+ /**
+ * Initializes an nsOverflowContinuationTracker to help track overflow
+ * continuations of aFrame's children. Typically invoked on 'this'.
+ *
+ * aWalkOOFFrames determines whether the walker skips out-of-flow frames
+ * or skips non-out-of-flow frames.
+ *
+ * Don't set aSkipOverflowContainerChildren to false unless you plan
+ * to walk your own overflow container children. (Usually they are handled
+ * by calling ReflowOverflowContainerChildren.) aWalkOOFFrames is ignored
+ * if aSkipOverflowContainerChildren is false.
+ */
+ nsOverflowContinuationTracker(nsContainerFrame* aFrame, bool aWalkOOFFrames,
+ bool aSkipOverflowContainerChildren = true);
+ /**
+ * This function adds an overflow continuation to our running list and
+ * sets its NS_FRAME_IS_OVERFLOW_CONTAINER flag.
+ *
+ * aReflowStatus should preferably be specific to the recently-reflowed
+ * child and not influenced by any of its siblings' statuses. This
+ * function sets the NS_FRAME_IS_DIRTY bit on aOverflowCont if it needs
+ * to be reflowed. (Its need for reflow depends on changes to its
+ * prev-in-flow, not to its parent--for whom it is invisible, reflow-wise.)
+ *
+ * The caller MUST disconnect the frame from its parent's child list
+ * if it was not previously an NS_FRAME_IS_OVERFLOW_CONTAINER (because
+ * StealFrame is much more inefficient than disconnecting in place
+ * during Reflow, which the caller is able to do but we are not).
+ *
+ * The caller MUST NOT disconnect the frame from its parent's
+ * child list if it is already an NS_FRAME_IS_OVERFLOW_CONTAINER.
+ * (In this case we will disconnect and reconnect it ourselves.)
+ */
+ nsresult Insert(nsIFrame* aOverflowCont, nsReflowStatus& aReflowStatus);
+ /**
+ * Begin/EndFinish() must be called for each child that is reflowed
+ * but no longer has an overflow continuation. (It may be called for
+ * other children, but in that case has no effect.) It increments our
+ * walker and makes sure we drop any dangling pointers to its
+ * next-in-flow. This function MUST be called before stealing or
+ * deleting aChild's next-in-flow.
+ * The AutoFinish helper object does that for you. Use it like so:
+ * if (kidNextInFlow) {
+ * nsOverflowContinuationTracker::AutoFinish fini(tracker, kid);
+ * ... DeleteNextInFlowChild/StealFrame(kidNextInFlow) here ...
+ * }
+ */
+ class MOZ_RAII AutoFinish {
+ public:
+ AutoFinish(nsOverflowContinuationTracker* aTracker, nsIFrame* aChild)
+ : mTracker(aTracker), mChild(aChild) {
+ if (mTracker) mTracker->BeginFinish(mChild);
+ }
+ ~AutoFinish() {
+ if (mTracker) mTracker->EndFinish(mChild);
+ }
+
+ private:
+ nsOverflowContinuationTracker* mTracker;
+ nsIFrame* mChild;
+ };
+
+ /**
+ * This function should be called for each child that isn't reflowed.
+ * It increments our walker and sets the mOverflowIncomplete
+ * reflow flag if it encounters an overflow continuation so that our
+ * next-in-flow doesn't get prematurely deleted. It MUST be called on
+ * each unreflowed child that has an overflow container continuation;
+ * it MAY be called on other children, but it isn't necessary (doesn't
+ * do anything).
+ */
+ void Skip(nsIFrame* aChild, nsReflowStatus& aReflowStatus) {
+ MOZ_ASSERT(aChild, "null ptr");
+ if (aChild == mSentry) {
+ StepForward();
+ if (aReflowStatus.IsComplete()) {
+ aReflowStatus.SetOverflowIncomplete();
+ }
+ }
+ }
+
+ private:
+ /**
+ * @see class AutoFinish
+ */
+ void BeginFinish(nsIFrame* aChild);
+ void EndFinish(nsIFrame* aChild);
+
+ void SetupOverflowContList();
+ void SetUpListWalker();
+ void StepForward();
+
+ /* We hold a pointer to either the next-in-flow's overflow containers list
+ or, if that doesn't exist, our frame's excess overflow containers list.
+ We need to make sure that we drop that pointer if the list becomes
+ empty and is deleted elsewhere. */
+ nsFrameList* mOverflowContList;
+ /* We hold a pointer to the most recently-reflowed child that has an
+ overflow container next-in-flow. We do this because it's a known
+ good point; this pointer won't be deleted on us. We can use it to
+ recover our place in the list. */
+ nsIFrame* mPrevOverflowCont;
+ /* This is a pointer to the next overflow container's prev-in-flow, which
+ is (or should be) a child of our frame. When we hit this, we will need
+ to increment this walker to the next overflow container. */
+ nsIFrame* mSentry;
+ /* Parent of all frames in mOverflowContList. If our mOverflowContList
+ is an excessOverflowContainersProperty, or null, then this is our frame
+ (the frame that was passed in to our constructor). Otherwise this is
+ that frame's next-in-flow, and our mOverflowContList is mParent's
+ overflowContainersProperty */
+ nsContainerFrame* mParent;
+ /* Tells SetUpListWalker whether or not to walk us past any continuations
+ of overflow containers. aWalkOOFFrames is ignored when this is false. */
+ bool mSkipOverflowContainerChildren;
+ /* Tells us whether to pay attention to OOF frames or non-OOF frames */
+ bool mWalkOOFFrames;
+};
+
+// Start Display Reflow Debugging
+#ifdef DEBUG
+
+struct DR_cookie {
+ DR_cookie(nsPresContext* aPresContext, nsIFrame* aFrame,
+ const mozilla::ReflowInput& aReflowInput,
+ mozilla::ReflowOutput& aMetrics, nsReflowStatus& aStatus);
+ ~DR_cookie();
+ void Change() const;
+
+ nsPresContext* mPresContext;
+ nsIFrame* mFrame;
+ const mozilla::ReflowInput& mReflowInput;
+ mozilla::ReflowOutput& mMetrics;
+ nsReflowStatus& mStatus;
+ void* mValue;
+};
+
+struct DR_layout_cookie {
+ explicit DR_layout_cookie(nsIFrame* aFrame);
+ ~DR_layout_cookie();
+
+ nsIFrame* mFrame;
+ void* mValue;
+};
+
+struct DR_intrinsic_inline_size_cookie {
+ DR_intrinsic_inline_size_cookie(nsIFrame* aFrame, const char* aType,
+ nscoord& aResult);
+ ~DR_intrinsic_inline_size_cookie();
+
+ nsIFrame* mFrame;
+ const char* mType;
+ nscoord& mResult;
+ void* mValue;
+};
+
+struct DR_intrinsic_size_cookie {
+ DR_intrinsic_size_cookie(nsIFrame* aFrame, const char* aType,
+ nsSize& aResult);
+ ~DR_intrinsic_size_cookie();
+
+ nsIFrame* mFrame;
+ const char* mType;
+ nsSize& mResult;
+ void* mValue;
+};
+
+struct DR_init_constraints_cookie {
+ DR_init_constraints_cookie(
+ nsIFrame* aFrame, mozilla::ReflowInput* aState, nscoord aCBWidth,
+ nscoord aCBHeight, const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
+ const mozilla::Maybe<mozilla::LogicalMargin> aPadding);
+ ~DR_init_constraints_cookie();
+
+ nsIFrame* mFrame;
+ mozilla::ReflowInput* mState;
+ void* mValue;
+};
+
+struct DR_init_offsets_cookie {
+ DR_init_offsets_cookie(nsIFrame* aFrame,
+ mozilla::SizeComputationInput* aState,
+ nscoord aPercentBasis,
+ mozilla::WritingMode aCBWritingMode,
+ const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
+ const mozilla::Maybe<mozilla::LogicalMargin> aPadding);
+ ~DR_init_offsets_cookie();
+
+ nsIFrame* mFrame;
+ mozilla::SizeComputationInput* mState;
+ void* mValue;
+};
+
+# define DISPLAY_REFLOW(dr_pres_context, dr_frame, dr_rf_state, \
+ dr_rf_metrics, dr_rf_status) \
+ DR_cookie dr_cookie(dr_pres_context, dr_frame, dr_rf_state, dr_rf_metrics, \
+ dr_rf_status);
+# define DISPLAY_REFLOW_CHANGE() dr_cookie.Change();
+# define DISPLAY_LAYOUT(dr_frame) DR_layout_cookie dr_cookie(dr_frame);
+# define DISPLAY_MIN_INLINE_SIZE(dr_frame, dr_result) \
+ DR_intrinsic_inline_size_cookie dr_cookie(dr_frame, "Min", dr_result)
+# define DISPLAY_PREF_INLINE_SIZE(dr_frame, dr_result) \
+ DR_intrinsic_inline_size_cookie dr_cookie(dr_frame, "Pref", dr_result)
+# define DISPLAY_PREF_SIZE(dr_frame, dr_result) \
+ DR_intrinsic_size_cookie dr_cookie(dr_frame, "Pref", dr_result)
+# define DISPLAY_MIN_SIZE(dr_frame, dr_result) \
+ DR_intrinsic_size_cookie dr_cookie(dr_frame, "Min", dr_result)
+# define DISPLAY_MAX_SIZE(dr_frame, dr_result) \
+ DR_intrinsic_size_cookie dr_cookie(dr_frame, "Max", dr_result)
+# define DISPLAY_INIT_CONSTRAINTS(dr_frame, dr_state, dr_cbw, dr_cbh, dr_bdr, \
+ dr_pad) \
+ DR_init_constraints_cookie dr_cookie(dr_frame, dr_state, dr_cbw, dr_cbh, \
+ dr_bdr, dr_pad)
+# define DISPLAY_INIT_OFFSETS(dr_frame, dr_state, dr_pb, dr_cbwm, dr_bdr, \
+ dr_pad) \
+ DR_init_offsets_cookie dr_cookie(dr_frame, dr_state, dr_pb, dr_cbwm, \
+ dr_bdr, dr_pad)
+
+#else
+
+# define DISPLAY_REFLOW(dr_pres_context, dr_frame, dr_rf_state, \
+ dr_rf_metrics, dr_rf_status)
+# define DISPLAY_REFLOW_CHANGE()
+# define DISPLAY_LAYOUT(dr_frame) PR_BEGIN_MACRO PR_END_MACRO
+# define DISPLAY_MIN_INLINE_SIZE(dr_frame, dr_result) \
+ PR_BEGIN_MACRO PR_END_MACRO
+# define DISPLAY_PREF_INLINE_SIZE(dr_frame, dr_result) \
+ PR_BEGIN_MACRO PR_END_MACRO
+# define DISPLAY_PREF_SIZE(dr_frame, dr_result) PR_BEGIN_MACRO PR_END_MACRO
+# define DISPLAY_MIN_SIZE(dr_frame, dr_result) PR_BEGIN_MACRO PR_END_MACRO
+# define DISPLAY_MAX_SIZE(dr_frame, dr_result) PR_BEGIN_MACRO PR_END_MACRO
+# define DISPLAY_INIT_CONSTRAINTS(dr_frame, dr_state, dr_cbw, dr_cbh, dr_bdr, \
+ dr_pad) \
+ PR_BEGIN_MACRO PR_END_MACRO
+# define DISPLAY_INIT_OFFSETS(dr_frame, dr_state, dr_pb, dr_cbwm, dr_bdr, \
+ dr_pad) \
+ PR_BEGIN_MACRO PR_END_MACRO
+
+#endif
+// End Display Reflow Debugging
+
+#endif /* nsContainerFrame_h___ */
diff --git a/layout/generic/nsContainerFrameInlines.h b/layout/generic/nsContainerFrameInlines.h
new file mode 100644
index 0000000000..f6c85d791e
--- /dev/null
+++ b/layout/generic/nsContainerFrameInlines.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContainerFrameInlines_h___
+#define nsContainerFrameInlines_h___
+
+#include "nsContainerFrame.h"
+
+template <typename ISizeData, typename F>
+void nsContainerFrame::DoInlineIntrinsicISize(ISizeData* aData,
+ F& aHandleChildren) {
+ using namespace mozilla;
+
+ auto GetMargin = [](const LengthPercentageOrAuto& aCoord) -> nscoord {
+ return aCoord.IsAuto() ? 0 : aCoord.AsLengthPercentage().Resolve(0);
+ };
+
+ if (GetPrevInFlow()) return; // Already added.
+
+ WritingMode wm = GetWritingMode();
+ Side startSide = wm.PhysicalSideForInlineAxis(eLogicalEdgeStart);
+ Side endSide = wm.PhysicalSideForInlineAxis(eLogicalEdgeEnd);
+
+ const nsStylePadding* stylePadding = StylePadding();
+ const nsStyleBorder* styleBorder = StyleBorder();
+ const nsStyleMargin* styleMargin = StyleMargin();
+
+ // This goes at the beginning no matter how things are broken and how
+ // messy the bidi situations are, since per CSS2.1 section 8.6
+ // (implemented in bug 328168), the startSide border is always on the
+ // first line.
+ // This frame is a first-in-flow, but it might have a previous bidi
+ // continuation, in which case that continuation should handle the startSide
+ // border.
+ // For box-decoration-break:clone we setup clonePBM = startPBM + endPBM and
+ // add that to each line. For box-decoration-break:slice clonePBM is zero.
+ nscoord clonePBM = 0; // PBM = PaddingBorderMargin
+ const bool sliceBreak =
+ styleBorder->mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
+ if (!GetPrevContinuation() || MOZ_UNLIKELY(!sliceBreak)) {
+ nscoord startPBM =
+ // clamp negative calc() to 0
+ std::max(stylePadding->mPadding.Get(startSide).Resolve(0), 0) +
+ styleBorder->GetComputedBorderWidth(startSide) +
+ GetMargin(styleMargin->mMargin.Get(startSide));
+ if (MOZ_LIKELY(sliceBreak)) {
+ aData->mCurrentLine += startPBM;
+ } else {
+ clonePBM = startPBM;
+ }
+ }
+
+ nscoord endPBM =
+ // clamp negative calc() to 0
+ std::max(stylePadding->mPadding.Get(endSide).Resolve(0), 0) +
+ styleBorder->GetComputedBorderWidth(endSide) +
+ GetMargin(styleMargin->mMargin.Get(endSide));
+ if (MOZ_UNLIKELY(!sliceBreak)) {
+ clonePBM += endPBM;
+ aData->mCurrentLine += clonePBM;
+ }
+
+ const nsLineList_iterator* savedLine = aData->mLine;
+ nsIFrame* const savedLineContainer = aData->LineContainer();
+
+ nsContainerFrame* lastInFlow;
+ for (nsContainerFrame* nif = this; nif;
+ nif = static_cast<nsContainerFrame*>(nif->GetNextInFlow())) {
+ if (aData->mCurrentLine == 0) {
+ aData->mCurrentLine = clonePBM;
+ }
+ aHandleChildren(nif, aData);
+
+ // After we advance to our next-in-flow, the stored line and line container
+ // may no longer be correct. Just forget them.
+ aData->mLine = nullptr;
+ aData->SetLineContainer(nullptr);
+
+ lastInFlow = nif;
+ }
+
+ aData->mLine = savedLine;
+ aData->SetLineContainer(savedLineContainer);
+
+ // This goes at the end no matter how things are broken and how
+ // messy the bidi situations are, since per CSS2.1 section 8.6
+ // (implemented in bug 328168), the endSide border is always on the
+ // last line.
+ // We reached the last-in-flow, but it might have a next bidi
+ // continuation, in which case that continuation should handle
+ // the endSide border.
+ if (MOZ_LIKELY(!lastInFlow->GetNextContinuation() && sliceBreak)) {
+ aData->mCurrentLine += endPBM;
+ }
+}
+
+#endif // nsContainerFrameInlines_h___
diff --git a/layout/generic/nsDirection.h b/layout/generic/nsDirection.h
new file mode 100644
index 0000000000..7bad26a599
--- /dev/null
+++ b/layout/generic/nsDirection.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDirection_h___
+#define nsDirection_h___
+
+#include <ostream>
+
+// This file makes the nsDirection enum present both in nsIFrame.h and
+// Selection.h.
+
+enum nsDirection { eDirNext = 0, eDirPrevious = 1 };
+
+std::ostream& operator<<(std::ostream& aStream, const nsDirection& aDirection);
+
+#endif
diff --git a/layout/generic/nsFirstLetterFrame.cpp b/layout/generic/nsFirstLetterFrame.cpp
new file mode 100644
index 0000000000..fe294652c7
--- /dev/null
+++ b/layout/generic/nsFirstLetterFrame.cpp
@@ -0,0 +1,459 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS :first-letter pseudo-element */
+
+#include "nsFirstLetterFrame.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsIContent.h"
+#include "nsLayoutUtils.h"
+#include "nsLineLayout.h"
+#include "nsGkAtoms.h"
+#include "nsFrameManager.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTextFrame.h"
+#include "nsCSSFrameConstructor.h"
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+nsFirstLetterFrame* NS_NewFirstLetterFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsFirstLetterFrame(aStyle, aPresShell->GetPresContext());
+}
+
+nsFirstLetterFrame* NS_NewFloatingFirstLetterFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsFloatingFirstLetterFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
+
+NS_QUERYFRAME_HEAD(nsFirstLetterFrame)
+ NS_QUERYFRAME_ENTRY(nsFirstLetterFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsFloatingFirstLetterFrame)
+NS_QUERYFRAME_HEAD(nsFloatingFirstLetterFrame)
+ NS_QUERYFRAME_ENTRY(nsFloatingFirstLetterFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsFirstLetterFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsFirstLetterFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Letter"_ns, aResult);
+}
+#endif
+
+void nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ BuildDisplayListForInline(aBuilder, aLists);
+}
+
+void nsFirstLetterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ RefPtr<ComputedStyle> newSC;
+ if (aPrevInFlow) {
+ // Get proper ComputedStyle for ourselves. We're creating the frame
+ // that represents everything *except* the first letter, so just create
+ // a ComputedStyle that inherits from our style parent, with no extra rules.
+ nsIFrame* styleParent =
+ CorrectStyleParentFrame(aParent, PseudoStyleType::firstLetter);
+ ComputedStyle* parentComputedStyle = styleParent->Style();
+ newSC = PresContext()->StyleSet()->ResolveStyleForFirstLetterContinuation(
+ parentComputedStyle);
+ SetComputedStyleWithoutNotification(newSC);
+ }
+
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+void nsFirstLetterFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal,
+ "Principal child list is the only "
+ "list that nsFirstLetterFrame should set via this function");
+ for (nsIFrame* f : aChildList) {
+ MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
+ MOZ_ASSERT(f->IsTextFrame(),
+ "We should not have kids that are containers!");
+ nsLayoutUtils::MarkDescendantsDirty(f); // Drops cached textruns
+ }
+
+ mFrames = std::move(aChildList);
+}
+
+nsresult nsFirstLetterFrame::GetChildFrameContainingOffset(
+ int32_t inContentOffset, bool inHint, int32_t* outFrameContentOffset,
+ nsIFrame** outChildFrame) {
+ nsIFrame* kid = mFrames.FirstChild();
+ if (kid) {
+ return kid->GetChildFrameContainingOffset(
+ inContentOffset, inHint, outFrameContentOffset, outChildFrame);
+ }
+ return nsIFrame::GetChildFrameContainingOffset(
+ inContentOffset, inHint, outFrameContentOffset, outChildFrame);
+}
+
+// Needed for non-floating first-letter frames and for the continuations
+// following the first-letter that we also use nsFirstLetterFrame for.
+/* virtual */
+void nsFirstLetterFrame::AddInlineMinISize(
+ gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
+ DoInlineMinISize(aRenderingContext, aData);
+}
+
+// Needed for non-floating first-letter frames and for the continuations
+// following the first-letter that we also use nsFirstLetterFrame for.
+/* virtual */
+void nsFirstLetterFrame::AddInlinePrefISize(
+ gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
+ DoInlinePrefISize(aRenderingContext, aData);
+}
+
+// Needed for floating first-letter frames.
+/* virtual */
+nscoord nsFirstLetterFrame::GetMinISize(gfxContext* aRenderingContext) {
+ return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
+}
+
+// Needed for floating first-letter frames.
+/* virtual */
+nscoord nsFirstLetterFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult nsFirstLetterFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ if (GetPrevInFlow()) {
+ // We're wrapping the text *after* the first letter, so behave like an
+ // inline frame.
+ return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
+ AspectRatioUsage::None};
+ }
+ return nsContainerFrame::ComputeSize(aRenderingContext, aWM, aCBSize,
+ aAvailableISize, aMargin, aBorderPadding,
+ aSizeOverrides, aFlags);
+}
+
+bool nsFirstLetterFrame::UseTightBounds() const {
+ int v = StaticPrefs::layout_css_floating_first_letter_tight_glyph_bounds();
+
+ // Check for the simple cases:
+ // pref value > 0: use legacy gecko behavior
+ // pref value = 0: use webkit/blink-like behavior
+ if (v > 0) {
+ return true;
+ }
+ if (v == 0) {
+ return false;
+ }
+
+ // Pref value < 0: use heuristics to determine whether the page is assuming
+ // webkit/blink-style behavior:
+ // If line-height is less than font-size, or there is a negative block-start
+ // or -end margin, use webkit/blink behavior.
+ if (nsTextFrame* textFrame = do_QueryFrame(mFrames.FirstChild())) {
+ RefPtr<nsFontMetrics> fm = textFrame->InflatedFontMetrics();
+ if (textFrame->ComputeLineHeight() < fm->EmHeight()) {
+ return false;
+ }
+ }
+
+ const auto wm = GetWritingMode();
+ const auto& margin = StyleMargin()->mMargin;
+ const auto& bStart = margin.GetBStart(wm);
+ // Currently, we only check for margins with negative *length* values;
+ // negative percentages seem unlikely to be used/useful in this context.
+ if (bStart.ConvertsToLength() && bStart.ToLength() < 0) {
+ return false;
+ }
+ const auto& bEnd = margin.GetBEnd(wm);
+ if (bEnd.ConvertsToLength() && bEnd.ToLength() < 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsFirstLetterFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aReflowStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aReflowStatus);
+ MOZ_ASSERT(aReflowStatus.IsEmpty(),
+ "Caller should pass a fresh reflow status!");
+
+ // Grab overflow list
+ DrainOverflowFrames(aPresContext);
+
+ nsIFrame* kid = mFrames.FirstChild();
+
+ // Setup reflow input for our child
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize availSize = aReflowInput.AvailableSize();
+ const auto bp = aReflowInput.ComputedLogicalBorderPadding(wm);
+ NS_ASSERTION(availSize.ISize(wm) != NS_UNCONSTRAINEDSIZE,
+ "should no longer use unconstrained inline size");
+ availSize.ISize(wm) -= bp.IStartEnd(wm);
+ if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
+ availSize.BSize(wm) -= bp.BStartEnd(wm);
+ }
+
+ WritingMode lineWM = aMetrics.GetWritingMode();
+ ReflowOutput kidMetrics(lineWM);
+
+ // Reflow the child
+ if (!aReflowInput.mLineLayout) {
+ // When there is no lineLayout provided, we provide our own. The
+ // only time that the first-letter-frame is not reflowing in a
+ // line context is when its floating.
+ WritingMode kidWritingMode = WritingModeForLine(wm, kid);
+ LogicalSize kidAvailSize = availSize.ConvertTo(kidWritingMode, wm);
+ ReflowInput rs(aPresContext, aReflowInput, kid, kidAvailSize);
+ nsLineLayout ll(aPresContext, nullptr, aReflowInput, nullptr, nullptr);
+
+ ll.BeginLineReflow(
+ bp.IStart(wm), bp.BStart(wm), availSize.ISize(wm), NS_UNCONSTRAINEDSIZE,
+ false, true, kidWritingMode,
+ nsSize(aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+ rs.mLineLayout = &ll;
+ ll.SetInFirstLetter(true);
+ ll.SetFirstLetterStyleOK(true);
+
+ kid->Reflow(aPresContext, kidMetrics, rs, aReflowStatus);
+
+ ll.EndLineReflow();
+ ll.SetInFirstLetter(false);
+
+ // In the floating first-letter case, we need to set this ourselves;
+ // nsLineLayout::BeginSpan will set it in the other case
+ mBaseline = kidMetrics.BlockStartAscent();
+
+ // Place and size the child and update the output metrics
+ LogicalSize convertedSize = kidMetrics.Size(wm);
+
+ const bool tightBounds = UseTightBounds();
+ const nscoord shift =
+ tightBounds ? 0
+ // Shift by half of the difference between the line-height
+ // we're going to use and current height of the kid frame.
+ : (rs.GetLineHeight() - convertedSize.BSize(wm)) / 2;
+
+ kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm) + shift,
+ convertedSize.ISize(wm), convertedSize.BSize(wm)));
+ kid->FinishAndStoreOverflow(&kidMetrics, rs.mStyleDisplay);
+ kid->DidReflow(aPresContext, nullptr);
+
+ if (!tightBounds) {
+ // Adjust size to account for line-height.
+ convertedSize.BSize(wm) = rs.GetLineHeight();
+ }
+
+ convertedSize.ISize(wm) += bp.IStartEnd(wm);
+ convertedSize.BSize(wm) += bp.BStartEnd(wm);
+ aMetrics.SetSize(wm, convertedSize);
+ aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + bp.BStart(wm));
+
+ // Ensure that the overflow rect contains the child textframe's
+ // overflow rect.
+ // Note that if this is floating, the overline/underline drawable
+ // area is in the overflow rect of the child textframe.
+ aMetrics.UnionOverflowAreasWithDesiredBounds();
+ ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
+
+ FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
+ } else {
+ // Pretend we are a span and reflow the child frame
+ nsLineLayout* ll = aReflowInput.mLineLayout;
+ bool pushedFrame;
+
+ ll->SetInFirstLetter(Style()->GetPseudoType() ==
+ PseudoStyleType::firstLetter);
+ ll->BeginSpan(this, &aReflowInput, bp.IStart(wm), availSize.ISize(wm),
+ &mBaseline);
+ ll->ReflowFrame(kid, aReflowStatus, &kidMetrics, pushedFrame);
+ NS_ASSERTION(lineWM.IsVertical() == wm.IsVertical(),
+ "we're assuming we can mix sizes between lineWM and wm "
+ "since we shouldn't have orthogonal writing modes within "
+ "a line.");
+ aMetrics.ISize(lineWM) = ll->EndSpan(this) + bp.IStartEnd(wm);
+ ll->SetInFirstLetter(false);
+
+ if (mComputedStyle->StyleTextReset()->mInitialLetterSize != 0.0f) {
+ aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() +
+ bp.BStart(wm));
+ aMetrics.BSize(lineWM) = kidMetrics.BSize(lineWM) + bp.BStartEnd(wm);
+ } else {
+ nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, bp, lineWM, wm);
+ }
+ }
+
+ if (!aReflowStatus.IsInlineBreakBefore()) {
+ // Create a continuation or remove existing continuations based on
+ // the reflow completion status.
+ if (aReflowStatus.IsComplete()) {
+ if (aReflowInput.mLineLayout) {
+ aReflowInput.mLineLayout->SetFirstLetterStyleOK(false);
+ }
+ if (nsIFrame* kidNextInFlow = kid->GetNextInFlow()) {
+ DestroyContext context(PresShell());
+ // Remove all of the childs next-in-flows
+ kidNextInFlow->GetParent()->DeleteNextInFlowChild(context,
+ kidNextInFlow, true);
+ }
+ } else {
+ // Create a continuation for the child frame if it doesn't already
+ // have one.
+ if (!IsFloating()) {
+ CreateNextInFlow(kid);
+ // And then push it to our overflow list
+ nsFrameList overflow = mFrames.TakeFramesAfter(kid);
+ if (overflow.NotEmpty()) {
+ SetOverflowFrames(std::move(overflow));
+ }
+ } else if (!kid->GetNextInFlow()) {
+ // For floating first letter frames (if a continuation wasn't already
+ // created for us) we need to put the continuation with the rest of the
+ // text that the first letter frame was made out of.
+ nsIFrame* continuation;
+ CreateContinuationForFloatingParent(kid, &continuation, true);
+ }
+ }
+ }
+}
+
+/* virtual */
+bool nsFirstLetterFrame::CanContinueTextRun() const {
+ // We can continue a text run through a first-letter frame.
+ return true;
+}
+
+void nsFirstLetterFrame::CreateContinuationForFloatingParent(
+ nsIFrame* aChild, nsIFrame** aContinuation, bool aIsFluid) {
+ NS_ASSERTION(IsFloating(),
+ "can only call this on floating first letter frames");
+ MOZ_ASSERT(aContinuation, "bad args");
+
+ *aContinuation = nullptr;
+
+ mozilla::PresShell* presShell = PresShell();
+ nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrame();
+ nsContainerFrame* parent = placeholderFrame->GetParent();
+
+ nsIFrame* continuation = presShell->FrameConstructor()->CreateContinuingFrame(
+ aChild, parent, aIsFluid);
+
+ // The continuation will have gotten the first letter style from its
+ // prev continuation, so we need to repair the ComputedStyle so it
+ // doesn't have the first letter styling.
+ //
+ // Note that getting parent frame's ComputedStyle is different from getting
+ // this frame's ComputedStyle's parent in the presence of ::first-line,
+ // which we do want the continuation to inherit from.
+ ComputedStyle* parentSC = parent->Style();
+ if (parentSC) {
+ RefPtr<ComputedStyle> newSC;
+ newSC =
+ presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC);
+ continuation->SetComputedStyle(newSC);
+ nsLayoutUtils::MarkDescendantsDirty(continuation);
+ }
+
+ // XXX Bidi may not be involved but we have to use the list name
+ // FrameChildListID::NoReflowPrincipal because this is just like creating a
+ // continuation except we have to insert it in a different place and we don't
+ // want a reflow command to try to be issued.
+ parent->InsertFrames(FrameChildListID::NoReflowPrincipal, placeholderFrame,
+ nullptr, nsFrameList(continuation, continuation));
+
+ *aContinuation = continuation;
+}
+
+void nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) {
+ // Check for an overflow list with our prev-in-flow
+ nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow();
+ if (prevInFlow) {
+ AutoFrameListPtr overflowFrames(aPresContext,
+ prevInFlow->StealOverflowFrames());
+ if (overflowFrames) {
+ NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list");
+
+ // When pushing and pulling frames we need to check for whether any
+ // views need to be reparented.
+ nsContainerFrame::ReparentFrameViewList(*overflowFrames, prevInFlow,
+ this);
+ mFrames.InsertFrames(this, nullptr, std::move(*overflowFrames));
+ }
+ }
+
+ // It's also possible that we have an overflow list for ourselves
+ AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames());
+ if (overflowFrames) {
+ NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
+ mFrames.AppendFrames(nullptr, std::move(*overflowFrames));
+ }
+
+ // Now repair our first frames ComputedStyle (since we only reflow
+ // one frame there is no point in doing any other ones until they
+ // are reflowed)
+ nsIFrame* kid = mFrames.FirstChild();
+ if (kid) {
+ nsIContent* kidContent = kid->GetContent();
+ if (kidContent) {
+ NS_ASSERTION(kidContent->IsText(), "should contain only text nodes");
+ ComputedStyle* parentSC;
+ if (prevInFlow) {
+ // This is for the rest of the content not in the first-letter.
+ nsIFrame* styleParent =
+ CorrectStyleParentFrame(GetParent(), PseudoStyleType::firstLetter);
+ parentSC = styleParent->Style();
+ } else {
+ // And this for the first-letter style.
+ parentSC = mComputedStyle;
+ }
+ RefPtr<ComputedStyle> sc =
+ aPresContext->StyleSet()->ResolveStyleForText(kidContent, parentSC);
+ kid->SetComputedStyle(sc);
+ nsLayoutUtils::MarkDescendantsDirty(kid);
+ }
+ }
+}
+
+Maybe<nscoord> nsFirstLetterFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const {
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return Nothing{};
+ }
+ return Some(mBaseline);
+}
+
+LogicalSides nsFirstLetterFrame::GetLogicalSkipSides() const {
+ if (GetPrevContinuation()) {
+ // We shouldn't get calls to GetSkipSides for later continuations since
+ // they have separate ComputedStyles with initial values for all the
+ // properties that could trigger a call to GetSkipSides. Then again,
+ // it's not really an error to call GetSkipSides on any frame, so
+ // that's why we handle it properly.
+ return LogicalSides(mWritingMode, eLogicalSideBitsAll);
+ }
+ return LogicalSides(mWritingMode); // first continuation displays all sides
+}
diff --git a/layout/generic/nsFirstLetterFrame.h b/layout/generic/nsFirstLetterFrame.h
new file mode 100644
index 0000000000..1fed26a7ec
--- /dev/null
+++ b/layout/generic/nsFirstLetterFrame.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFirstLetterFrame_h__
+#define nsFirstLetterFrame_h__
+
+/* rendering object for CSS :first-letter pseudo-element */
+
+#include "nsContainerFrame.h"
+
+class nsFirstLetterFrame : public nsContainerFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
+
+ nsFirstLetterFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aClassID)
+ : nsContainerFrame(aStyle, aPresContext, aClassID) {}
+
+ nsFirstLetterFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {}
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) final;
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) final;
+ void SetInitialChildList(ChildListID aListID, nsFrameList&& aChildList) final;
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const final;
+#endif
+
+ bool IsFloating() const { return HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); }
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) final;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) final;
+ void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) final;
+ void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) final;
+
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) final;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final;
+
+ bool CanContinueTextRun() const final;
+ Maybe<nscoord> GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const final;
+ LogicalSides GetLogicalSkipSides() const final;
+
+ // final of nsFrame method
+ nsresult GetChildFrameContainingOffset(int32_t inContentOffset, bool inHint,
+ int32_t* outFrameContentOffset,
+ nsIFrame** outChildFrame) final;
+
+ nscoord GetFirstLetterBaseline() const { return mBaseline; }
+
+ // For floating first letter frames, create a continuation for aChild and
+ // place it in the correct place. aContinuation is an outparam for the
+ // continuation that is created. aIsFluid determines if the continuation is
+ // fluid or not.
+ void CreateContinuationForFloatingParent(nsIFrame* aChild,
+ nsIFrame** aContinuation,
+ bool aIsFluid);
+
+ // Whether to use tight glyph bounds for a floating first-letter frame,
+ // or "loose" bounds based on font metrics rather than individual glyphs.
+ bool UseTightBounds() const;
+
+ protected:
+ nscoord mBaseline;
+
+ void DrainOverflowFrames(nsPresContext* aPresContext);
+};
+
+class nsFloatingFirstLetterFrame : public nsFirstLetterFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsFloatingFirstLetterFrame)
+
+ nsFloatingFirstLetterFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsFirstLetterFrame(aStyle, aPresContext, kClassID) {}
+};
+
+#endif /* nsFirstLetterFrame_h__ */
diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp
new file mode 100644
index 0000000000..e5a719fee2
--- /dev/null
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -0,0 +1,6464 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: flex" */
+
+#include "nsFlexContainerFrame.h"
+
+#include <algorithm>
+
+#include "gfxContext.h"
+#include "mozilla/Baseline.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/CSSOrderAwareFrameIterator.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/WritingModes.h"
+#include "nsBlockFrame.h"
+#include "nsContentUtils.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsDebug.h"
+#include "nsDisplayList.h"
+#include "nsFieldSetFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+// Convenience typedefs for helper classes that we forward-declare in .h file
+// (so that nsFlexContainerFrame methods can use them as parameters):
+using FlexItem = nsFlexContainerFrame::FlexItem;
+using FlexLine = nsFlexContainerFrame::FlexLine;
+using FlexboxAxisTracker = nsFlexContainerFrame::FlexboxAxisTracker;
+using StrutInfo = nsFlexContainerFrame::StrutInfo;
+using CachedBAxisMeasurement = nsFlexContainerFrame::CachedBAxisMeasurement;
+using CachedFlexItemData = nsFlexContainerFrame::CachedFlexItemData;
+
+static mozilla::LazyLogModule gFlexContainerLog("FlexContainer");
+#define FLEX_LOG(...) \
+ MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (__VA_ARGS__));
+#define FLEX_LOGV(...) \
+ MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (__VA_ARGS__));
+
+// Returns true if aFlexContainer is a frame for some element that has
+// display:-webkit-{inline-}box (or -moz-{inline-}box). aFlexContainer is
+// expected to be an instance of nsFlexContainerFrame (enforced with an assert);
+// otherwise, this function's state-bit-check here is bogus.
+static bool IsLegacyBox(const nsIFrame* aFlexContainer) {
+ MOZ_ASSERT(aFlexContainer->IsFlexContainerFrame(),
+ "only flex containers may be passed to this function");
+ return aFlexContainer->HasAnyStateBits(
+ NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
+}
+
+// Returns the OrderState enum we should pass to CSSOrderAwareFrameIterator
+// (depending on whether aFlexContainer has
+// NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER state bit).
+static CSSOrderAwareFrameIterator::OrderState OrderStateForIter(
+ const nsFlexContainerFrame* aFlexContainer) {
+ return aFlexContainer->HasAnyStateBits(
+ NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
+ ? CSSOrderAwareFrameIterator::OrderState::Ordered
+ : CSSOrderAwareFrameIterator::OrderState::Unordered;
+}
+
+// Returns the OrderingProperty enum that we should pass to
+// CSSOrderAwareFrameIterator (depending on whether it's a legacy box).
+static CSSOrderAwareFrameIterator::OrderingProperty OrderingPropertyForIter(
+ const nsFlexContainerFrame* aFlexContainer) {
+ return IsLegacyBox(aFlexContainer)
+ ? CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup
+ : CSSOrderAwareFrameIterator::OrderingProperty::Order;
+}
+
+// Returns the "align-items" value that's equivalent to the legacy "box-align"
+// value in the given style struct.
+static StyleAlignFlags ConvertLegacyStyleToAlignItems(
+ const nsStyleXUL* aStyleXUL) {
+ // -[moz|webkit]-box-align corresponds to modern "align-items"
+ switch (aStyleXUL->mBoxAlign) {
+ case StyleBoxAlign::Stretch:
+ return StyleAlignFlags::STRETCH;
+ case StyleBoxAlign::Start:
+ return StyleAlignFlags::FLEX_START;
+ case StyleBoxAlign::Center:
+ return StyleAlignFlags::CENTER;
+ case StyleBoxAlign::Baseline:
+ return StyleAlignFlags::BASELINE;
+ case StyleBoxAlign::End:
+ return StyleAlignFlags::FLEX_END;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxAlign enum value");
+ // Fall back to default value of "align-items" property:
+ return StyleAlignFlags::STRETCH;
+}
+
+// Returns the "justify-content" value that's equivalent to the legacy
+// "box-pack" value in the given style struct.
+static StyleContentDistribution ConvertLegacyStyleToJustifyContent(
+ const nsStyleXUL* aStyleXUL) {
+ // -[moz|webkit]-box-pack corresponds to modern "justify-content"
+ switch (aStyleXUL->mBoxPack) {
+ case StyleBoxPack::Start:
+ return {StyleAlignFlags::FLEX_START};
+ case StyleBoxPack::Center:
+ return {StyleAlignFlags::CENTER};
+ case StyleBoxPack::End:
+ return {StyleAlignFlags::FLEX_END};
+ case StyleBoxPack::Justify:
+ return {StyleAlignFlags::SPACE_BETWEEN};
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxPack enum value");
+ // Fall back to default value of "justify-content" property:
+ return {StyleAlignFlags::FLEX_START};
+}
+
+// Check if the size is auto or it is a keyword in the block axis.
+// |aIsInline| should represent whether aSize is in the inline axis, from the
+// perspective of the writing mode of the flex item that the size comes from.
+//
+// max-content and min-content should behave as property's initial value.
+// Bug 567039: We treat -moz-fit-content and -moz-available as property's
+// initial value for now.
+static inline bool IsAutoOrEnumOnBSize(const StyleSize& aSize, bool aIsInline) {
+ return aSize.IsAuto() || (!aIsInline && !aSize.IsLengthPercentage());
+}
+
+// Encapsulates our flex container's main & cross axes. This class is backed by
+// a FlexboxAxisInfo helper member variable, and it adds some convenience APIs
+// on top of what that struct offers.
+class MOZ_STACK_CLASS nsFlexContainerFrame::FlexboxAxisTracker {
+ public:
+ explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer);
+
+ // Accessors:
+ LogicalAxis MainAxis() const {
+ return IsRowOriented() ? eLogicalAxisInline : eLogicalAxisBlock;
+ }
+ LogicalAxis CrossAxis() const {
+ return IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
+ }
+
+ LogicalSide MainAxisStartSide() const;
+ LogicalSide MainAxisEndSide() const {
+ return GetOppositeSide(MainAxisStartSide());
+ }
+
+ LogicalSide CrossAxisStartSide() const;
+ LogicalSide CrossAxisEndSide() const {
+ return GetOppositeSide(CrossAxisStartSide());
+ }
+
+ mozilla::Side MainAxisPhysicalStartSide() const {
+ return mWM.PhysicalSide(MainAxisStartSide());
+ }
+ mozilla::Side MainAxisPhysicalEndSide() const {
+ return mWM.PhysicalSide(MainAxisEndSide());
+ }
+
+ mozilla::Side CrossAxisPhysicalStartSide() const {
+ return mWM.PhysicalSide(CrossAxisStartSide());
+ }
+ mozilla::Side CrossAxisPhysicalEndSide() const {
+ return mWM.PhysicalSide(CrossAxisEndSide());
+ }
+
+ // Returns the flex container's writing mode.
+ WritingMode GetWritingMode() const { return mWM; }
+
+ // Returns true if our main axis is in the reverse direction of our
+ // writing mode's corresponding axis. (From 'flex-direction: *-reverse')
+ bool IsMainAxisReversed() const { return mAxisInfo.mIsMainAxisReversed; }
+ // Returns true if our cross axis is in the reverse direction of our
+ // writing mode's corresponding axis. (From 'flex-wrap: *-reverse')
+ bool IsCrossAxisReversed() const { return mAxisInfo.mIsCrossAxisReversed; }
+
+ bool IsRowOriented() const { return mAxisInfo.mIsRowOriented; }
+ bool IsColumnOriented() const { return !IsRowOriented(); }
+
+ // aSize is expected to match the flex container's WritingMode.
+ nscoord MainComponent(const LogicalSize& aSize) const {
+ return IsRowOriented() ? aSize.ISize(mWM) : aSize.BSize(mWM);
+ }
+ int32_t MainComponent(const LayoutDeviceIntSize& aIntSize) const {
+ return IsMainAxisHorizontal() ? aIntSize.width : aIntSize.height;
+ }
+
+ // aSize is expected to match the flex container's WritingMode.
+ nscoord CrossComponent(const LogicalSize& aSize) const {
+ return IsRowOriented() ? aSize.BSize(mWM) : aSize.ISize(mWM);
+ }
+ int32_t CrossComponent(const LayoutDeviceIntSize& aIntSize) const {
+ return IsMainAxisHorizontal() ? aIntSize.height : aIntSize.width;
+ }
+
+ // NOTE: aMargin is expected to use the flex container's WritingMode.
+ nscoord MarginSizeInMainAxis(const LogicalMargin& aMargin) const {
+ // If we're row-oriented, our main axis is the inline axis.
+ return IsRowOriented() ? aMargin.IStartEnd(mWM) : aMargin.BStartEnd(mWM);
+ }
+ nscoord MarginSizeInCrossAxis(const LogicalMargin& aMargin) const {
+ // If we're row-oriented, our cross axis is the block axis.
+ return IsRowOriented() ? aMargin.BStartEnd(mWM) : aMargin.IStartEnd(mWM);
+ }
+
+ /**
+ * Converts a "flex-relative" point (a main-axis & cross-axis coordinate)
+ * into a LogicalPoint, using the flex container's writing mode.
+ *
+ * @arg aMainCoord The main-axis coordinate -- i.e an offset from the
+ * main-start edge of the flex container's content box.
+ * @arg aCrossCoord The cross-axis coordinate -- i.e an offset from the
+ * cross-start edge of the flex container's content box.
+ * @arg aContainerMainSize The main size of flex container's content box.
+ * @arg aContainerCrossSize The cross size of flex container's content box.
+ * @return A LogicalPoint, with the flex container's writing mode, that
+ * represents the same position. The logical coordinates are
+ * relative to the flex container's content box.
+ */
+ LogicalPoint LogicalPointFromFlexRelativePoint(
+ nscoord aMainCoord, nscoord aCrossCoord, nscoord aContainerMainSize,
+ nscoord aContainerCrossSize) const {
+ nscoord logicalCoordInMainAxis =
+ IsMainAxisReversed() ? aContainerMainSize - aMainCoord : aMainCoord;
+ nscoord logicalCoordInCrossAxis =
+ IsCrossAxisReversed() ? aContainerCrossSize - aCrossCoord : aCrossCoord;
+
+ return IsRowOriented() ? LogicalPoint(mWM, logicalCoordInMainAxis,
+ logicalCoordInCrossAxis)
+ : LogicalPoint(mWM, logicalCoordInCrossAxis,
+ logicalCoordInMainAxis);
+ }
+
+ /**
+ * Converts a "flex-relative" size (a main-axis & cross-axis size)
+ * into a LogicalSize, using the flex container's writing mode.
+ *
+ * @arg aMainSize The main-axis size.
+ * @arg aCrossSize The cross-axis size.
+ * @return A LogicalSize, with the flex container's writing mode, that
+ * represents the same size.
+ */
+ LogicalSize LogicalSizeFromFlexRelativeSizes(nscoord aMainSize,
+ nscoord aCrossSize) const {
+ return IsRowOriented() ? LogicalSize(mWM, aMainSize, aCrossSize)
+ : LogicalSize(mWM, aCrossSize, aMainSize);
+ }
+
+ /**
+ * Converts a "flex-relative" ascent (the distance from the flex container's
+ * content-box cross-start edge to its baseline) into a logical ascent (the
+ * distance from the flex container's content-box block-start edge to its
+ * baseline).
+ */
+ nscoord LogicalAscentFromFlexRelativeAscent(
+ nscoord aFlexRelativeAscent, nscoord aContentBoxCrossSize) const {
+ return (IsCrossAxisReversed() ? aContentBoxCrossSize - aFlexRelativeAscent
+ : aFlexRelativeAscent);
+ }
+
+ bool IsMainAxisHorizontal() const {
+ // If we're row-oriented, and our writing mode is NOT vertical,
+ // or we're column-oriented and our writing mode IS vertical,
+ // then our main axis is horizontal. This handles all cases:
+ return IsRowOriented() != mWM.IsVertical();
+ }
+
+ // Returns true if this flex item's inline axis in aItemWM is parallel (or
+ // antiparallel) to the container's main axis. Returns false, otherwise.
+ //
+ // Note: this is a helper used before constructing FlexItem. Inside of flex
+ // reflow code, FlexItem::IsInlineAxisMainAxis() is equivalent & more optimal.
+ bool IsInlineAxisMainAxis(WritingMode aItemWM) const {
+ return IsRowOriented() != GetWritingMode().IsOrthogonalTo(aItemWM);
+ }
+
+ // Maps justify-*: 'left' or 'right' to 'start' or 'end'.
+ StyleAlignFlags ResolveJustifyLeftRight(const StyleAlignFlags& aFlags) const {
+ MOZ_ASSERT(
+ aFlags == StyleAlignFlags::LEFT || aFlags == StyleAlignFlags::RIGHT,
+ "This helper accepts only 'LEFT' or 'RIGHT' flags!");
+
+ const auto wm = GetWritingMode();
+ const bool isJustifyLeft = aFlags == StyleAlignFlags::LEFT;
+ if (IsColumnOriented()) {
+ if (!wm.IsVertical()) {
+ // Container's alignment axis (main axis) is *not* parallel to the
+ // line-left <-> line-right axis or the physical left <-> physical right
+ // axis, so we map both 'left' and 'right' to 'start'.
+ return StyleAlignFlags::START;
+ }
+
+ MOZ_ASSERT(wm.PhysicalAxis(MainAxis()) == eAxisHorizontal,
+ "Vertical column-oriented flex container's main axis should "
+ "be parallel to physical left <-> right axis!");
+ // Map 'left' or 'right' to 'start' or 'end', depending on its block flow
+ // direction.
+ return isJustifyLeft == wm.IsVerticalLR() ? StyleAlignFlags::START
+ : StyleAlignFlags::END;
+ }
+
+ MOZ_ASSERT(MainAxis() == eLogicalAxisInline,
+ "Row-oriented flex container's main axis should be parallel to "
+ "line-left <-> line-right axis!");
+
+ // If we get here, we're operating on the flex container's inline axis,
+ // so we map 'left' to whichever of 'start' or 'end' corresponds to the
+ // *line-relative* left side; and similar for 'right'.
+ return isJustifyLeft == wm.IsBidiLTR() ? StyleAlignFlags::START
+ : StyleAlignFlags::END;
+ }
+
+ // Delete copy-constructor & reassignment operator, to prevent accidental
+ // (unnecessary) copying.
+ FlexboxAxisTracker(const FlexboxAxisTracker&) = delete;
+ FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete;
+
+ private:
+ const WritingMode mWM; // The flex container's writing mode.
+ const FlexboxAxisInfo mAxisInfo;
+};
+
+/**
+ * Represents a flex item.
+ * Includes the various pieces of input that the Flexbox Layout Algorithm uses
+ * to resolve a flexible width.
+ */
+class nsFlexContainerFrame::FlexItem final {
+ public:
+ // Normal constructor:
+ FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
+ float aFlexShrink, nscoord aFlexBaseSize, nscoord aMainMinSize,
+ nscoord aMainMaxSize, nscoord aTentativeCrossSize,
+ nscoord aCrossMinSize, nscoord aCrossMaxSize,
+ const FlexboxAxisTracker& aAxisTracker);
+
+ // Simplified constructor, to be used only for generating "struts":
+ // (NOTE: This "strut" constructor uses the *container's* writing mode, which
+ // we'll use on this FlexItem instead of the child frame's real writing mode.
+ // This is fine - it doesn't matter what writing mode we use for a
+ // strut, since it won't render any content and we already know its size.)
+ FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, WritingMode aContainerWM,
+ const FlexboxAxisTracker& aAxisTracker);
+
+ // Clone existing FlexItem for its underlying frame's continuation.
+ // @param aContinuation a continuation in our next-in-flow chain.
+ FlexItem CloneFor(nsIFrame* const aContinuation) const {
+ MOZ_ASSERT(Frame() == aContinuation->FirstInFlow(),
+ "aContinuation should be in aItem's continuation chain!");
+ FlexItem item(*this);
+ item.mFrame = aContinuation;
+ item.mHadMeasuringReflow = false;
+ return item;
+ }
+
+ // Accessors
+ nsIFrame* Frame() const { return mFrame; }
+ nscoord FlexBaseSize() const { return mFlexBaseSize; }
+
+ nscoord MainMinSize() const {
+ MOZ_ASSERT(!mNeedsMinSizeAutoResolution,
+ "Someone's using an unresolved 'auto' main min-size");
+ return mMainMinSize;
+ }
+ nscoord MainMaxSize() const { return mMainMaxSize; }
+
+ // Note: These return the main-axis position and size of our *content box*.
+ nscoord MainSize() const { return mMainSize; }
+ nscoord MainPosition() const { return mMainPosn; }
+
+ nscoord CrossMinSize() const { return mCrossMinSize; }
+ nscoord CrossMaxSize() const { return mCrossMaxSize; }
+
+ // Note: These return the cross-axis position and size of our *content box*.
+ nscoord CrossSize() const { return mCrossSize; }
+ nscoord CrossPosition() const { return mCrossPosn; }
+
+ // Lazy getter for mAscent or mAscentForLast.
+ nscoord ResolvedAscent(bool aUseFirstBaseline) const {
+ // XXX We should be using the *container's* writing-mode (mCBWM) here,
+ // instead of the item's (mWM). This is essentially bug 1155322.
+ nscoord& ascent = aUseFirstBaseline ? mAscent : mAscentForLast;
+ if (ascent != ReflowOutput::ASK_FOR_BASELINE) {
+ return ascent;
+ }
+
+ // Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate:
+ bool found = aUseFirstBaseline
+ ? nsLayoutUtils::GetFirstLineBaseline(mWM, mFrame, &ascent)
+ : nsLayoutUtils::GetLastLineBaseline(mWM, mFrame, &ascent);
+ if (found) {
+ return ascent;
+ }
+
+ // If the nsLayoutUtils getter fails, then ask the frame directly:
+ auto baselineGroup = aUseFirstBaseline ? BaselineSharingGroup::First
+ : BaselineSharingGroup::Last;
+ if (auto baseline = mFrame->GetNaturalBaselineBOffset(
+ mWM, baselineGroup, BaselineExportContext::Other)) {
+ // Offset for last baseline from `GetNaturalBaselineBOffset` originates
+ // from the frame's block end, so convert it back.
+ ascent = baselineGroup == BaselineSharingGroup::First
+ ? *baseline
+ : mFrame->BSize(mWM) - *baseline;
+ return ascent;
+ }
+
+ // We couldn't determine a baseline, so we synthesize one from border box:
+ ascent = Baseline::SynthesizeBOffsetFromBorderBox(
+ mFrame, mWM, BaselineSharingGroup::First);
+ return ascent;
+ }
+
+ // Convenience methods to compute the main & cross size of our *margin-box*.
+ nscoord OuterMainSize() const {
+ return mMainSize + MarginBorderPaddingSizeInMainAxis();
+ }
+
+ nscoord OuterCrossSize() const {
+ return mCrossSize + MarginBorderPaddingSizeInCrossAxis();
+ }
+
+ // Convenience method to return the content-box block-size.
+ nscoord BSize() const {
+ return IsBlockAxisMainAxis() ? MainSize() : CrossSize();
+ }
+
+ // Convenience method to return the measured content-box block-size computed
+ // in nsFlexContainerFrame::MeasureBSizeForFlexItem().
+ Maybe<nscoord> MeasuredBSize() const;
+
+ // Convenience methods to synthesize a style main size or a style cross size
+ // with box-size considered, to provide the size overrides when constructing
+ // ReflowInput for flex items.
+ StyleSize StyleMainSize() const {
+ nscoord mainSize = MainSize();
+ if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
+ mainSize += BorderPaddingSizeInMainAxis();
+ }
+ return StyleSize::LengthPercentage(
+ LengthPercentage::FromAppUnits(mainSize));
+ }
+
+ StyleSize StyleCrossSize() const {
+ nscoord crossSize = CrossSize();
+ if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
+ crossSize += BorderPaddingSizeInCrossAxis();
+ }
+ return StyleSize::LengthPercentage(
+ LengthPercentage::FromAppUnits(crossSize));
+ }
+
+ // Returns the distance between this FlexItem's baseline and the cross-start
+ // edge of its margin-box. Used in baseline alignment.
+ //
+ // (This function needs to be told which physical start side we're measuring
+ // the baseline from, so that it can look up the appropriate components from
+ // margin.)
+ nscoord BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide,
+ bool aUseFirstLineBaseline) const;
+
+ double ShareOfWeightSoFar() const { return mShareOfWeightSoFar; }
+
+ bool IsFrozen() const { return mIsFrozen; }
+
+ bool HadMinViolation() const {
+ MOZ_ASSERT(!mIsFrozen, "min violation has no meaning for frozen items.");
+ return mHadMinViolation;
+ }
+
+ bool HadMaxViolation() const {
+ MOZ_ASSERT(!mIsFrozen, "max violation has no meaning for frozen items.");
+ return mHadMaxViolation;
+ }
+
+ bool WasMinClamped() const {
+ MOZ_ASSERT(mIsFrozen, "min clamping has no meaning for unfrozen items.");
+ return mHadMinViolation;
+ }
+
+ bool WasMaxClamped() const {
+ MOZ_ASSERT(mIsFrozen, "max clamping has no meaning for unfrozen items.");
+ return mHadMaxViolation;
+ }
+
+ // Indicates whether this item received a preliminary "measuring" reflow
+ // before its actual reflow.
+ bool HadMeasuringReflow() const { return mHadMeasuringReflow; }
+
+ // Indicates whether this item's computed cross-size property is 'auto'.
+ bool IsCrossSizeAuto() const;
+
+ // Indicates whether the cross-size property is set to something definite,
+ // for the purpose of preferred aspect ratio calculations.
+ bool IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const;
+
+ // Indicates whether this item's cross-size has been stretched (from having
+ // "align-self: stretch" with an auto cross-size and no auto margins in the
+ // cross axis).
+ bool IsStretched() const { return mIsStretched; }
+
+ bool IsFlexBaseSizeContentBSize() const {
+ return mIsFlexBaseSizeContentBSize;
+ }
+
+ bool IsMainMinSizeContentBSize() const { return mIsMainMinSizeContentBSize; }
+
+ // Indicates whether we need to resolve an 'auto' value for the main-axis
+ // min-[width|height] property.
+ bool NeedsMinSizeAutoResolution() const {
+ return mNeedsMinSizeAutoResolution;
+ }
+
+ bool HasAnyAutoMargin() const { return mHasAnyAutoMargin; }
+
+ BaselineSharingGroup ItemBaselineSharingGroup() const {
+ MOZ_ASSERT(mAlignSelf._0 == StyleAlignFlags::BASELINE ||
+ mAlignSelf._0 == StyleAlignFlags::LAST_BASELINE,
+ "mBaselineSharingGroup only gets a meaningful value "
+ "for baseline-aligned items");
+ return mBaselineSharingGroup;
+ }
+
+ // Indicates whether this item is a "strut" left behind by an element with
+ // visibility:collapse.
+ bool IsStrut() const { return mIsStrut; }
+
+ // The main axis and cross axis are relative to mCBWM.
+ LogicalAxis MainAxis() const { return mMainAxis; }
+ LogicalAxis CrossAxis() const { return GetOrthogonalAxis(mMainAxis); }
+
+ // IsInlineAxisMainAxis() returns true if this item's inline axis is parallel
+ // (or antiparallel) to the container's main axis. Otherwise (i.e. if this
+ // item's inline axis is orthogonal to the container's main axis), this
+ // function returns false. The next 3 methods are all other ways of asking
+ // the same question, and only exist for readability at callsites (depending
+ // on which axes those callsites are reasoning about).
+ bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; }
+ bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis; }
+ bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis; }
+ bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis; }
+
+ WritingMode GetWritingMode() const { return mWM; }
+ WritingMode ContainingBlockWM() const { return mCBWM; }
+ StyleAlignSelf AlignSelf() const { return mAlignSelf; }
+ StyleAlignFlags AlignSelfFlags() const { return mAlignSelfFlags; }
+
+ // Returns the flex factor (flex-grow or flex-shrink), depending on
+ // 'aIsUsingFlexGrow'.
+ //
+ // Asserts fatally if called on a frozen item (since frozen items are not
+ // flexible).
+ float GetFlexFactor(bool aIsUsingFlexGrow) {
+ MOZ_ASSERT(!IsFrozen(), "shouldn't need flex factor after item is frozen");
+
+ return aIsUsingFlexGrow ? mFlexGrow : mFlexShrink;
+ }
+
+ // Returns the weight that we should use in the "resolving flexible lengths"
+ // algorithm. If we're using the flex grow factor, we just return that;
+ // otherwise, we return the "scaled flex shrink factor" (scaled by our flex
+ // base size, so that when both large and small items are shrinking, the large
+ // items shrink more).
+ //
+ // I'm calling this a "weight" instead of a "[scaled] flex-[grow|shrink]
+ // factor", to more clearly distinguish it from the actual flex-grow &
+ // flex-shrink factors.
+ //
+ // Asserts fatally if called on a frozen item (since frozen items are not
+ // flexible).
+ float GetWeight(bool aIsUsingFlexGrow) {
+ MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen");
+
+ if (aIsUsingFlexGrow) {
+ return mFlexGrow;
+ }
+
+ // We're using flex-shrink --> return mFlexShrink * mFlexBaseSize
+ if (mFlexBaseSize == 0) {
+ // Special-case for mFlexBaseSize == 0 -- we have no room to shrink, so
+ // regardless of mFlexShrink, we should just return 0.
+ // (This is really a special-case for when mFlexShrink is infinity, to
+ // avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.)
+ return 0.0f;
+ }
+ return mFlexShrink * mFlexBaseSize;
+ }
+
+ bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
+
+ const AspectRatio& GetAspectRatio() const { return mAspectRatio; }
+ bool HasAspectRatio() const { return !!mAspectRatio; }
+
+ // Getters for margin:
+ // ===================
+ LogicalMargin Margin() const { return mMargin; }
+ nsMargin PhysicalMargin() const { return mMargin.GetPhysicalMargin(mCBWM); }
+
+ // Returns the margin component for a given LogicalSide in flex container's
+ // writing-mode.
+ nscoord GetMarginComponentForSide(LogicalSide aSide) const {
+ return mMargin.Side(aSide, mCBWM);
+ }
+
+ // Returns the total space occupied by this item's margins in the given axis
+ nscoord MarginSizeInMainAxis() const {
+ return mMargin.StartEnd(MainAxis(), mCBWM);
+ }
+ nscoord MarginSizeInCrossAxis() const {
+ return mMargin.StartEnd(CrossAxis(), mCBWM);
+ }
+
+ // Getters for border/padding
+ // ==========================
+ // Returns the total space occupied by this item's borders and padding in
+ // the given axis
+ LogicalMargin BorderPadding() const { return mBorderPadding; }
+ nscoord BorderPaddingSizeInMainAxis() const {
+ return mBorderPadding.StartEnd(MainAxis(), mCBWM);
+ }
+ nscoord BorderPaddingSizeInCrossAxis() const {
+ return mBorderPadding.StartEnd(CrossAxis(), mCBWM);
+ }
+
+ // Getter for combined margin/border/padding
+ // =========================================
+ // Returns the total space occupied by this item's margins, borders and
+ // padding in the given axis
+ nscoord MarginBorderPaddingSizeInMainAxis() const {
+ return MarginSizeInMainAxis() + BorderPaddingSizeInMainAxis();
+ }
+ nscoord MarginBorderPaddingSizeInCrossAxis() const {
+ return MarginSizeInCrossAxis() + BorderPaddingSizeInCrossAxis();
+ }
+
+ // Setters
+ // =======
+ // Helper to set the resolved value of min-[width|height]:auto for the main
+ // axis. (Should only be used if NeedsMinSizeAutoResolution() returns true.)
+ void UpdateMainMinSize(nscoord aNewMinSize) {
+ NS_ASSERTION(aNewMinSize >= 0,
+ "How did we end up with a negative min-size?");
+ MOZ_ASSERT(
+ mMainMaxSize == NS_UNCONSTRAINEDSIZE || mMainMaxSize >= aNewMinSize,
+ "Should only use this function for resolving min-size:auto, "
+ "and main max-size should be an upper-bound for resolved val");
+ MOZ_ASSERT(
+ mNeedsMinSizeAutoResolution &&
+ (mMainMinSize == 0 || mFrame->IsThemed(mFrame->StyleDisplay())),
+ "Should only use this function for resolving min-size:auto, "
+ "so we shouldn't already have a nonzero min-size established "
+ "(unless it's a themed-widget-imposed minimum size)");
+
+ if (aNewMinSize > mMainMinSize) {
+ mMainMinSize = aNewMinSize;
+ // Also clamp main-size to be >= new min-size:
+ mMainSize = std::max(mMainSize, aNewMinSize);
+ }
+ mNeedsMinSizeAutoResolution = false;
+ }
+
+ // This sets our flex base size, and then sets our main size to the
+ // resulting "hypothetical main size" (the base size clamped to our
+ // main-axis [min,max] sizing constraints).
+ void SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize) {
+ MOZ_ASSERT(!mIsFrozen || mFlexBaseSize == NS_UNCONSTRAINEDSIZE,
+ "flex base size shouldn't change after we're frozen "
+ "(unless we're just resolving an intrinsic size)");
+ mFlexBaseSize = aNewFlexBaseSize;
+
+ // Before we've resolved flexible lengths, we keep mMainSize set to
+ // the 'hypothetical main size', which is the flex base size, clamped
+ // to the [min,max] range:
+ mMainSize = NS_CSS_MINMAX(mFlexBaseSize, mMainMinSize, mMainMaxSize);
+
+ FLEX_LOGV(
+ "Set flex base size: %d, hypothetical main size: %d for flex item %p",
+ mFlexBaseSize, mMainSize, mFrame);
+ }
+
+ // Setters used while we're resolving flexible lengths
+ // ---------------------------------------------------
+
+ // Sets the main-size of our flex item's content-box.
+ void SetMainSize(nscoord aNewMainSize) {
+ MOZ_ASSERT(!mIsFrozen, "main size shouldn't change after we're frozen");
+ mMainSize = aNewMainSize;
+ }
+
+ void SetShareOfWeightSoFar(double aNewShare) {
+ MOZ_ASSERT(!mIsFrozen || aNewShare == 0.0,
+ "shouldn't be giving this item any share of the weight "
+ "after it's frozen");
+ mShareOfWeightSoFar = aNewShare;
+ }
+
+ void Freeze() {
+ mIsFrozen = true;
+ // Now that we are frozen, the meaning of mHadMinViolation and
+ // mHadMaxViolation changes to indicate min and max clamping. Clear
+ // both of the member variables so that they are ready to be set
+ // as clamping state later, if necessary.
+ mHadMinViolation = false;
+ mHadMaxViolation = false;
+ }
+
+ void SetHadMinViolation() {
+ MOZ_ASSERT(!mIsFrozen,
+ "shouldn't be changing main size & having violations "
+ "after we're frozen");
+ mHadMinViolation = true;
+ }
+ void SetHadMaxViolation() {
+ MOZ_ASSERT(!mIsFrozen,
+ "shouldn't be changing main size & having violations "
+ "after we're frozen");
+ mHadMaxViolation = true;
+ }
+ void ClearViolationFlags() {
+ MOZ_ASSERT(!mIsFrozen,
+ "shouldn't be altering violation flags after we're "
+ "frozen");
+ mHadMinViolation = mHadMaxViolation = false;
+ }
+
+ void SetWasMinClamped() {
+ MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
+ // This reuses the mHadMinViolation member variable to track clamping
+ // events. This is allowable because mHadMinViolation only reflects
+ // a violation up until the item is frozen.
+ MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
+ mHadMinViolation = true;
+ }
+ void SetWasMaxClamped() {
+ MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
+ // This reuses the mHadMaxViolation member variable to track clamping
+ // events. This is allowable because mHadMaxViolation only reflects
+ // a violation up until the item is frozen.
+ MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
+ mHadMaxViolation = true;
+ }
+
+ // Setters for values that are determined after we've resolved our main size
+ // -------------------------------------------------------------------------
+
+ // Sets the main-axis position of our flex item's content-box.
+ // (This is the distance between the main-start edge of the flex container
+ // and the main-start edge of the flex item's content-box.)
+ void SetMainPosition(nscoord aPosn) {
+ MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
+ mMainPosn = aPosn;
+ }
+
+ // Sets the cross-size of our flex item's content-box.
+ void SetCrossSize(nscoord aCrossSize) {
+ MOZ_ASSERT(!mIsStretched,
+ "Cross size shouldn't be modified after it's been stretched");
+ mCrossSize = aCrossSize;
+ }
+
+ // Sets the cross-axis position of our flex item's content-box.
+ // (This is the distance between the cross-start edge of the flex container
+ // and the cross-start edge of the flex item.)
+ void SetCrossPosition(nscoord aPosn) {
+ MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
+ mCrossPosn = aPosn;
+ }
+
+ // After a FlexItem has had a reflow, this method can be used to cache its
+ // (possibly-unresolved) ascent, in case it's needed later for
+ // baseline-alignment or to establish the container's baseline.
+ // (NOTE: This can be marked 'const' even though it's modifying mAscent,
+ // because mAscent is mutable. It's nice for this to be 'const', because it
+ // means our final reflow can iterate over const FlexItem pointers, and we
+ // can be sure it's not modifying those FlexItems, except via this method.)
+ void SetAscent(nscoord aAscent) const {
+ mAscent = aAscent; // NOTE: this may be ASK_FOR_BASELINE
+ }
+
+ void SetHadMeasuringReflow() { mHadMeasuringReflow = true; }
+
+ void SetIsStretched() {
+ MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
+ mIsStretched = true;
+ }
+
+ void SetIsFlexBaseSizeContentBSize() { mIsFlexBaseSizeContentBSize = true; }
+
+ void SetIsMainMinSizeContentBSize() { mIsMainMinSizeContentBSize = true; }
+
+ // Setter for margin components (for resolving "auto" margins)
+ void SetMarginComponentForSide(LogicalSide aSide, nscoord aLength) {
+ MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
+ mMargin.Side(aSide, mCBWM) = aLength;
+ }
+
+ void ResolveStretchedCrossSize(nscoord aLineCrossSize);
+
+ // Resolves flex base size if flex-basis' used value is 'content', using this
+ // item's preferred aspect ratio and cross size.
+ void ResolveFlexBaseSizeFromAspectRatio(const ReflowInput& aItemReflowInput);
+
+ uint32_t NumAutoMarginsInMainAxis() const {
+ return NumAutoMarginsInAxis(MainAxis());
+ };
+
+ uint32_t NumAutoMarginsInCrossAxis() const {
+ return NumAutoMarginsInAxis(CrossAxis());
+ };
+
+ // Once the main size has been resolved, should we bother doing layout to
+ // establish the cross size?
+ bool CanMainSizeInfluenceCrossSize() const;
+
+ // Returns a main size, clamped by any definite min and max cross size
+ // converted through the preferred aspect ratio. The caller is responsible for
+ // ensuring that the flex item's preferred aspect ratio is not zero.
+ nscoord ClampMainSizeViaCrossAxisConstraints(
+ nscoord aMainSize, const ReflowInput& aItemReflowInput) const;
+
+ // Indicates whether we think this flex item needs a "final" reflow
+ // (after its final flexed size & final position have been determined).
+ //
+ // @param aParentReflowInput the flex container's reflow input.
+ // @return true if such a reflow is needed, or false if we believe it can
+ // simply be moved to its final position and skip the reflow.
+ bool NeedsFinalReflow(const ReflowInput& aParentReflowInput) const;
+
+ // Gets the block frame that contains the flex item's content. This is
+ // Frame() itself or one of its descendants.
+ nsBlockFrame* BlockFrame() const;
+
+ protected:
+ bool IsMinSizeAutoResolutionNeeded() const;
+
+ uint32_t NumAutoMarginsInAxis(LogicalAxis aAxis) const;
+
+ // Values that we already know in constructor, and remain unchanged:
+ // The flex item's frame.
+ nsIFrame* mFrame = nullptr;
+ float mFlexGrow = 0.0f;
+ float mFlexShrink = 0.0f;
+ AspectRatio mAspectRatio;
+
+ // The flex item's writing mode.
+ WritingMode mWM;
+
+ // The flex container's writing mode.
+ WritingMode mCBWM;
+
+ // The flex container's main axis in flex container's writing mode.
+ LogicalAxis mMainAxis;
+
+ // Stored in flex container's writing mode.
+ LogicalMargin mBorderPadding;
+
+ // Stored in flex container's writing mode. Its value can change when we
+ // resolve "auto" marigns.
+ LogicalMargin mMargin;
+
+ // These are non-const so that we can lazily update them with the item's
+ // intrinsic size (obtained via a "measuring" reflow), when necessary.
+ // (e.g. for "flex-basis:auto;height:auto" & "min-height:auto")
+ nscoord mFlexBaseSize = 0;
+ nscoord mMainMinSize = 0;
+ nscoord mMainMaxSize = 0;
+
+ // mCrossMinSize and mCrossMaxSize are not changed after constructor.
+ nscoord mCrossMinSize = 0;
+ nscoord mCrossMaxSize = 0;
+
+ // Values that we compute after constructor:
+ nscoord mMainSize = 0;
+ nscoord mMainPosn = 0;
+ nscoord mCrossSize = 0;
+ nscoord mCrossPosn = 0;
+
+ // Mutable b/c it's set & resolved lazily, sometimes via const pointer. See
+ // comment above SetAscent().
+ // We initialize this to ASK_FOR_BASELINE, and opportunistically fill it in
+ // with a real value if we end up reflowing this flex item. (But if we don't
+ // reflow this flex item, then this sentinel tells us that we don't know it
+ // yet & anyone who cares will need to explicitly request it.)
+ //
+ // Both mAscent and mAscentForLast are distance from the frame's border-box
+ // block-start edge.
+ mutable nscoord mAscent = ReflowOutput::ASK_FOR_BASELINE;
+ mutable nscoord mAscentForLast = ReflowOutput::ASK_FOR_BASELINE;
+
+ // Temporary state, while we're resolving flexible widths (for our main size)
+ // XXXdholbert To save space, we could use a union to make these variables
+ // overlay the same memory as some other member vars that aren't touched
+ // until after main-size has been resolved. In particular, these could share
+ // memory with mMainPosn through mAscent, and mIsStretched.
+ double mShareOfWeightSoFar = 0.0;
+
+ bool mIsFrozen = false;
+ bool mHadMinViolation = false;
+ bool mHadMaxViolation = false;
+
+ // Did this item get a preliminary reflow, to measure its desired height?
+ bool mHadMeasuringReflow = false;
+
+ // See IsStretched() documentation.
+ bool mIsStretched = false;
+
+ // Is this item a "strut" left behind by an element with visibility:collapse?
+ bool mIsStrut = false;
+
+ // See IsInlineAxisMainAxis() documentation. This is not changed after
+ // constructor.
+ bool mIsInlineAxisMainAxis = true;
+
+ // Does this item need to resolve a min-[width|height]:auto (in main-axis)?
+ //
+ // Note: mNeedsMinSizeAutoResolution needs to be declared towards the end of
+ // the member variables since it's initialized in a method that depends on
+ // other members declared above such as mCBWM, mMainAxis, and
+ // mIsInlineAxisMainAxis.
+ bool mNeedsMinSizeAutoResolution = false;
+
+ // Should we take care to treat this item's resolved BSize as indefinite?
+ bool mTreatBSizeAsIndefinite = false;
+
+ // Does this item have an auto margin in either main or cross axis?
+ bool mHasAnyAutoMargin = false;
+
+ // Does this item have a content-based flex base size (and is that a size in
+ // its block-axis)?
+ bool mIsFlexBaseSizeContentBSize = false;
+
+ // Does this item have a content-based resolved auto min size (and is that a
+ // size in its block-axis)?
+ bool mIsMainMinSizeContentBSize = false;
+
+ // If this item is {first,last}-baseline-aligned using 'align-self', which of
+ // its FlexLine's baseline sharing groups does it participate in?
+ BaselineSharingGroup mBaselineSharingGroup = BaselineSharingGroup::First;
+
+ // My "align-self" computed value (with "auto" swapped out for parent"s
+ // "align-items" value, in our constructor).
+ StyleAlignSelf mAlignSelf{StyleAlignFlags::AUTO};
+
+ // Flags for 'align-self' (safe/unsafe/legacy).
+ StyleAlignFlags mAlignSelfFlags{0};
+};
+
+/**
+ * Represents a single flex line in a flex container.
+ * Manages an array of the FlexItems that are in the line.
+ */
+class nsFlexContainerFrame::FlexLine final {
+ public:
+ explicit FlexLine(nscoord aMainGapSize) : mMainGapSize(aMainGapSize) {}
+
+ nscoord SumOfGaps() const {
+ return NumItems() > 0 ? (NumItems() - 1) * mMainGapSize : 0;
+ }
+
+ // Returns the sum of our FlexItems' outer hypothetical main sizes plus the
+ // sum of main axis {row,column}-gaps between items.
+ // ("outer" = margin-box, and "hypothetical" = before flexing)
+ AuCoord64 TotalOuterHypotheticalMainSize() const {
+ return mTotalOuterHypotheticalMainSize;
+ }
+
+ // Accessors for our FlexItems & information about them:
+ //
+ // Note: Callers must use IsEmpty() to ensure that the FlexLine is non-empty
+ // before calling accessors that return FlexItem.
+ FlexItem& FirstItem() { return mItems[0]; }
+ const FlexItem& FirstItem() const { return mItems[0]; }
+
+ FlexItem& LastItem() { return mItems.LastElement(); }
+ const FlexItem& LastItem() const { return mItems.LastElement(); }
+
+ // The "startmost"/"endmost" is from the perspective of the flex container's
+ // writing-mode, not from the perspective of the flex-relative main axis.
+ const FlexItem& StartmostItem(const FlexboxAxisTracker& aAxisTracker) const {
+ return aAxisTracker.IsMainAxisReversed() ? LastItem() : FirstItem();
+ }
+ const FlexItem& EndmostItem(const FlexboxAxisTracker& aAxisTracker) const {
+ return aAxisTracker.IsMainAxisReversed() ? FirstItem() : LastItem();
+ }
+
+ bool IsEmpty() const { return mItems.IsEmpty(); }
+
+ uint32_t NumItems() const { return mItems.Length(); }
+
+ nsTArray<FlexItem>& Items() { return mItems; }
+ const nsTArray<FlexItem>& Items() const { return mItems; }
+
+ // Adds the last flex item's hypothetical outer main-size and
+ // margin/border/padding to our totals. This should be called exactly once for
+ // each flex item, after we've determined that this line is the correct home
+ // for that item.
+ void AddLastItemToMainSizeTotals() {
+ const FlexItem& lastItem = Items().LastElement();
+
+ // Update our various bookkeeping member-vars:
+ if (lastItem.IsFrozen()) {
+ mNumFrozenItems++;
+ }
+
+ mTotalItemMBP += lastItem.MarginBorderPaddingSizeInMainAxis();
+ mTotalOuterHypotheticalMainSize += lastItem.OuterMainSize();
+
+ // If the item added was not the first item in the line, we add in any gap
+ // space as needed.
+ if (NumItems() >= 2) {
+ mTotalOuterHypotheticalMainSize += mMainGapSize;
+ }
+ }
+
+ // Computes the cross-size and baseline position of this FlexLine, based on
+ // its FlexItems.
+ void ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker);
+
+ // Returns the cross-size of this line.
+ nscoord LineCrossSize() const { return mLineCrossSize; }
+
+ // Setter for line cross-size -- needed for cases where the flex container
+ // imposes a cross-size on the line. (e.g. for single-line flexbox, or for
+ // multi-line flexbox with 'align-content: stretch')
+ void SetLineCrossSize(nscoord aLineCrossSize) {
+ mLineCrossSize = aLineCrossSize;
+ }
+
+ /**
+ * Returns the offset within this line where any baseline-aligned FlexItems
+ * should place their baseline. The return value represents a distance from
+ * the line's cross-start edge.
+ *
+ * If there are no baseline-aligned FlexItems, returns nscoord_MIN.
+ */
+ nscoord FirstBaselineOffset() const { return mFirstBaselineOffset; }
+
+ /**
+ * Returns the offset within this line where any last baseline-aligned
+ * FlexItems should place their baseline. Opposite the case of the first
+ * baseline offset, this represents a distance from the line's cross-end
+ * edge (since last baseline-aligned items are flush to the cross-end edge).
+ *
+ * If there are no last baseline-aligned FlexItems, returns nscoord_MIN.
+ */
+ nscoord LastBaselineOffset() const { return mLastBaselineOffset; }
+
+ // Extract a baseline from this line, which would be suitable for use as the
+ // flex container's 'aBaselineGroup' (i.e. first/last) baseline.
+ // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
+ //
+ // The return value always represents a distance from the line's cross-start
+ // edge, even if we are querying last baseline. If this line has no flex items
+ // in its aBaselineGroup group, this method falls back to trying the opposite
+ // group. If this line has no baseline-aligned items at all, this returns
+ // nscoord_MIN.
+ nscoord ExtractBaselineOffset(BaselineSharingGroup aBaselineGroup) const;
+
+ /**
+ * Returns the gap size in the main axis for this line. Used for gap
+ * calculations.
+ */
+ nscoord MainGapSize() const { return mMainGapSize; }
+
+ // Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the
+ // CSS flexbox spec to distribute aFlexContainerMainSize among our flex items.
+ // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
+ void ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
+ ComputedFlexLineInfo* aLineInfo);
+
+ void PositionItemsInMainAxis(const StyleContentDistribution& aJustifyContent,
+ nscoord aContentBoxMainSize,
+ const FlexboxAxisTracker& aAxisTracker);
+
+ void PositionItemsInCrossAxis(nscoord aLineStartPosition,
+ const FlexboxAxisTracker& aAxisTracker);
+
+ private:
+ // Helpers for ResolveFlexibleLengths():
+ void FreezeItemsEarly(bool aIsUsingFlexGrow, ComputedFlexLineInfo* aLineInfo);
+
+ void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
+ bool aIsFinalIteration);
+
+ // Stores this line's flex items.
+ nsTArray<FlexItem> mItems;
+
+ // Number of *frozen* FlexItems in this line, based on FlexItem::IsFrozen().
+ // Mostly used for optimization purposes, e.g. to bail out early from loops
+ // when we can tell they have nothing left to do.
+ uint32_t mNumFrozenItems = 0;
+
+ // Sum of margin/border/padding for the FlexItems in this FlexLine.
+ nscoord mTotalItemMBP = 0;
+
+ // Sum of FlexItems' outer hypothetical main sizes and all main-axis
+ // {row,columnm}-gaps between items.
+ // (i.e. their flex base sizes, clamped via their min/max-size properties,
+ // plus their main-axis margin/border/padding, plus the sum of the gaps.)
+ //
+ // This variable uses a 64-bit coord type to avoid integer overflow in case
+ // several of the individual items have huge hypothetical main sizes, which
+ // can happen with percent-width table-layout:fixed descendants. We have to
+ // avoid integer overflow in order to shrink items properly in that scenario.
+ AuCoord64 mTotalOuterHypotheticalMainSize = 0;
+
+ nscoord mLineCrossSize = 0;
+ nscoord mFirstBaselineOffset = nscoord_MIN;
+ nscoord mLastBaselineOffset = nscoord_MIN;
+
+ // Maintain size of each {row,column}-gap in the main axis
+ const nscoord mMainGapSize;
+};
+
+// The "startmost"/"endmost" is from the perspective of the flex container's
+// writing-mode, not from the perspective of the flex-relative cross axis.
+const FlexLine& StartmostLine(const nsTArray<FlexLine>& aLines,
+ const FlexboxAxisTracker& aAxisTracker) {
+ return aAxisTracker.IsCrossAxisReversed() ? aLines.LastElement() : aLines[0];
+}
+const FlexLine& EndmostLine(const nsTArray<FlexLine>& aLines,
+ const FlexboxAxisTracker& aAxisTracker) {
+ return aAxisTracker.IsCrossAxisReversed() ? aLines[0] : aLines.LastElement();
+}
+
+// Information about a strut left behind by a FlexItem that's been collapsed
+// using "visibility:collapse".
+struct nsFlexContainerFrame::StrutInfo {
+ StrutInfo(uint32_t aItemIdx, nscoord aStrutCrossSize)
+ : mItemIdx(aItemIdx), mStrutCrossSize(aStrutCrossSize) {}
+
+ uint32_t mItemIdx; // Index in the child list.
+ nscoord mStrutCrossSize; // The cross-size of this strut.
+};
+
+// Flex data shared by the flex container frames in a continuation chain, owned
+// by the first-in-flow. The data is initialized at the end of the
+// first-in-flow's Reflow().
+struct nsFlexContainerFrame::SharedFlexData final {
+ // The flex lines generated in DoFlexLayout() by our first-in-flow.
+ nsTArray<FlexLine> mLines;
+
+ // The final content main/cross size computed by DoFlexLayout.
+ nscoord mContentBoxMainSize = NS_UNCONSTRAINEDSIZE;
+ nscoord mContentBoxCrossSize = NS_UNCONSTRAINEDSIZE;
+
+ // Update this struct. Called by the first-in-flow.
+ void Update(FlexLayoutResult&& aFlr) {
+ mLines = std::move(aFlr.mLines);
+ mContentBoxMainSize = aFlr.mContentBoxMainSize;
+ mContentBoxCrossSize = aFlr.mContentBoxCrossSize;
+ }
+
+ // The frame property under which this struct is stored. Set only on the
+ // first-in-flow.
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedFlexData)
+};
+
+// Flex data stored in every flex container's in-flow fragment (continuation).
+//
+// It's intended to prevent quadratic operations resulting from each fragment
+// having to walk its full prev-in-flow chain, and also serves as an argument to
+// the flex container next-in-flow's ReflowChildren(), to compute the position
+// offset for each flex item.
+struct nsFlexContainerFrame::PerFragmentFlexData final {
+ // Suppose D is the distance from a flex container fragment's content-box
+ // block-start edge to whichever is larger of either (a) the block-end edge of
+ // its children, or (b) the available space's block-end edge. (Note: in case
+ // (b), D is conceptually the sum of the block-size of the children, the
+ // packing space before & in between them, and part of the packing space after
+ // them.)
+ //
+ // This variable stores the sum of the D values for the current flex container
+ // fragments and for all its previous fragments
+ nscoord mCumulativeContentBoxBSize = 0;
+
+ // This variable accumulates FirstLineOrFirstItemBAxisMetrics::mBEndEdgeShift,
+ // for the current flex container fragment and for all its previous fragments.
+ // See the comment of mBEndEdgeShift for its computation details. In short,
+ // this value is the net block-end edge shift, accumulated for the children in
+ // all the previous fragments. This number is non-negative.
+ //
+ // This value is also used to grow a flex container's block-size if the
+ // container's computed block-size is unconstrained. For example: a tall item
+ // may be pushed to the next page/column, which leaves some wasted area at the
+ // bottom of the current flex container fragment, and causes the flex
+ // container fragments to be (collectively) larger than the hypothetical
+ // unfragmented size. Another example: a tall flex item may be broken into
+ // multiple fragments, and those fragments may have a larger collective
+ // block-size as compared to the item's original unfragmented size; the
+ // container would need to increase its block-size to account for this.
+ nscoord mCumulativeBEndEdgeShift = 0;
+
+ // The frame property under which this struct is stored. Cached on every
+ // in-flow fragment (continuation) at the end of the flex container's
+ // Reflow().
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, PerFragmentFlexData)
+};
+
+static void BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine>& aLines,
+ nsTArray<StrutInfo>& aStruts) {
+ MOZ_ASSERT(aStruts.IsEmpty(),
+ "We should only build up StrutInfo once per reflow, so "
+ "aStruts should be empty when this is called");
+
+ uint32_t itemIdxInContainer = 0;
+ for (const FlexLine& line : aLines) {
+ for (const FlexItem& item : line.Items()) {
+ if (item.Frame()->StyleVisibility()->IsCollapse()) {
+ // Note the cross size of the line as the item's strut size.
+ aStruts.AppendElement(
+ StrutInfo(itemIdxInContainer, line.LineCrossSize()));
+ }
+ itemIdxInContainer++;
+ }
+ }
+}
+
+static mozilla::StyleAlignFlags SimplifyAlignOrJustifyContentForOneItem(
+ const StyleContentDistribution& aAlignmentVal, bool aIsAlign) {
+ // Mask away any explicit fallback, to get the main (non-fallback) part of
+ // the specified value:
+ StyleAlignFlags specified = aAlignmentVal.primary;
+
+ // XXX strip off <overflow-position> bits until we implement it (bug 1311892)
+ specified &= ~StyleAlignFlags::FLAG_BITS;
+
+ // FIRST: handle a special-case for "justify-content:stretch" (or equivalent),
+ // which requires that we ignore any author-provided explicit fallback value.
+ if (specified == StyleAlignFlags::NORMAL) {
+ // In a flex container, *-content: "'normal' behaves as 'stretch'".
+ // Do that conversion early, so it benefits from our 'stretch' special-case.
+ // https://drafts.csswg.org/css-align-3/#distribution-flex
+ specified = StyleAlignFlags::STRETCH;
+ }
+ if (!aIsAlign && specified == StyleAlignFlags::STRETCH) {
+ // In a flex container, in "justify-content Axis: [...] 'stretch' behaves
+ // as 'flex-start' (ignoring the specified fallback alignment, if any)."
+ // https://drafts.csswg.org/css-align-3/#distribution-flex
+ // So, we just directly return 'flex-start', & ignore explicit fallback..
+ return StyleAlignFlags::FLEX_START;
+ }
+
+ // TODO: Check for an explicit fallback value (and if it's present, use it)
+ // here once we parse it, see https://github.com/w3c/csswg-drafts/issues/1002.
+
+ // If there's no explicit fallback, use the implied fallback values for
+ // space-{between,around,evenly} (since those values only make sense with
+ // multiple alignment subjects), and otherwise just use the specified value:
+ if (specified == StyleAlignFlags::SPACE_BETWEEN) {
+ return StyleAlignFlags::FLEX_START;
+ }
+ if (specified == StyleAlignFlags::SPACE_AROUND ||
+ specified == StyleAlignFlags::SPACE_EVENLY) {
+ return StyleAlignFlags::CENTER;
+ }
+ return specified;
+}
+
+bool nsFlexContainerFrame::DrainSelfOverflowList() {
+ return DrainAndMergeSelfOverflowList();
+}
+
+void nsFlexContainerFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ NoteNewChildren(aListID, aFrameList);
+ nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
+}
+
+void nsFlexContainerFrame::InsertFrames(
+ ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
+ NoteNewChildren(aListID, aFrameList);
+ nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+}
+
+void nsFlexContainerFrame::RemoveFrame(DestroyContext& aContext,
+ ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
+
+#ifdef DEBUG
+ SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
+#endif
+
+ nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
+}
+
+StyleAlignFlags nsFlexContainerFrame::CSSAlignmentForAbsPosChild(
+ const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
+ const FlexboxAxisTracker axisTracker(this);
+
+ // If we're row-oriented and the caller is asking about our inline axis (or
+ // alternately, if we're column-oriented and the caller is asking about our
+ // block axis), then the caller is really asking about our *main* axis.
+ // Otherwise, the caller is asking about our cross axis.
+ const bool isMainAxis =
+ (axisTracker.IsRowOriented() == (aLogicalAxis == eLogicalAxisInline));
+ const nsStylePosition* containerStylePos = StylePosition();
+ const bool isAxisReversed = isMainAxis ? axisTracker.IsMainAxisReversed()
+ : axisTracker.IsCrossAxisReversed();
+
+ StyleAlignFlags alignment{0};
+ StyleAlignFlags alignmentFlags{0};
+ if (isMainAxis) {
+ // We're aligning in the main axis: align according to 'justify-content'.
+ // (We don't care about justify-self; it has no effect on children of flex
+ // containers, unless https://github.com/w3c/csswg-drafts/issues/7644
+ // changes that.)
+ alignment = SimplifyAlignOrJustifyContentForOneItem(
+ containerStylePos->mJustifyContent,
+ /*aIsAlign = */ false);
+ } else {
+ // We're aligning in the cross axis: align according to 'align-self'.
+ // (We don't care about align-content; it has no effect on abspos flex
+ // children, per https://github.com/w3c/csswg-drafts/issues/7596 )
+ alignment = aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
+ // Extract and strip align flag bits
+ alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS;
+ alignment &= ~StyleAlignFlags::FLAG_BITS;
+
+ if (alignment == StyleAlignFlags::NORMAL) {
+ // "the 'normal' keyword behaves as 'start' on replaced
+ // absolutely-positioned boxes, and behaves as 'stretch' on all other
+ // absolutely-positioned boxes."
+ // https://drafts.csswg.org/css-align/#align-abspos
+ alignment = aChildRI.mFrame->IsReplaced() ? StyleAlignFlags::START
+ : StyleAlignFlags::STRETCH;
+ }
+ }
+
+ if (alignment == StyleAlignFlags::STRETCH) {
+ // The default fallback alignment for 'stretch' is 'flex-start'.
+ alignment = StyleAlignFlags::FLEX_START;
+ }
+
+ // Resolve flex-start, flex-end, auto, left, right, baseline, last baseline;
+ if (alignment == StyleAlignFlags::FLEX_START) {
+ alignment = isAxisReversed ? StyleAlignFlags::END : StyleAlignFlags::START;
+ } else if (alignment == StyleAlignFlags::FLEX_END) {
+ alignment = isAxisReversed ? StyleAlignFlags::START : StyleAlignFlags::END;
+ } else if (alignment == StyleAlignFlags::LEFT ||
+ alignment == StyleAlignFlags::RIGHT) {
+ MOZ_ASSERT(isMainAxis, "Only justify-* can have 'left' and 'right'!");
+ alignment = axisTracker.ResolveJustifyLeftRight(alignment);
+ } else if (alignment == StyleAlignFlags::BASELINE) {
+ alignment = StyleAlignFlags::START;
+ } else if (alignment == StyleAlignFlags::LAST_BASELINE) {
+ alignment = StyleAlignFlags::END;
+ }
+
+ MOZ_ASSERT(alignment != StyleAlignFlags::STRETCH,
+ "We should've converted 'stretch' to the fallback alignment!");
+ MOZ_ASSERT(alignment != StyleAlignFlags::FLEX_START &&
+ alignment != StyleAlignFlags::FLEX_END,
+ "nsAbsoluteContainingBlock doesn't know how to handle "
+ "flex-relative axis for flex containers!");
+
+ return (alignment | alignmentFlags);
+}
+
+void nsFlexContainerFrame::GenerateFlexItemForChild(
+ FlexLine& aLine, nsIFrame* aChildFrame,
+ const ReflowInput& aParentReflowInput,
+ const FlexboxAxisTracker& aAxisTracker,
+ const nscoord aTentativeContentBoxCrossSize) {
+ const auto flexWM = aAxisTracker.GetWritingMode();
+ const auto childWM = aChildFrame->GetWritingMode();
+
+ // Note: we use GetStyleFrame() to access the sizing & flex properties here.
+ // This lets us correctly handle table wrapper frames as flex items since
+ // their inline-size and block-size properties are always 'auto'. In order for
+ // 'flex-basis:auto' to actually resolve to the author's specified inline-size
+ // or block-size, we need to dig through to the inner table.
+ const auto* stylePos =
+ nsLayoutUtils::GetStyleFrame(aChildFrame)->StylePosition();
+
+ // Construct a StyleSizeOverrides for this flex item so that its ReflowInput
+ // below will use and resolve its flex base size rather than its corresponding
+ // preferred main size property (only for modern CSS flexbox).
+ StyleSizeOverrides sizeOverrides;
+ if (!IsLegacyBox(this)) {
+ Maybe<StyleSize> styleFlexBaseSize;
+
+ // When resolving flex base size, flex items use their 'flex-basis' property
+ // in place of their preferred main size (e.g. 'width') for sizing purposes,
+ // *unless* they have 'flex-basis:auto' in which case they use their
+ // preferred main size after all.
+ const auto& flexBasis = stylePos->mFlexBasis;
+ const auto& styleMainSize = stylePos->Size(aAxisTracker.MainAxis(), flexWM);
+ if (IsUsedFlexBasisContent(flexBasis, styleMainSize)) {
+ // If we get here, we're resolving the flex base size for a flex item, and
+ // we fall into the flexbox spec section 9.2 step 3, substep C (if we have
+ // a definite cross size) or E (if not).
+ styleFlexBaseSize.emplace(StyleSize::MaxContent());
+ } else if (flexBasis.IsSize() && !flexBasis.IsAuto()) {
+ // For all other non-'auto' flex-basis values, we just swap in the
+ // flex-basis itself for the preferred main-size property.
+ styleFlexBaseSize.emplace(flexBasis.AsSize());
+ } else {
+ // else: flex-basis is 'auto', which is deferring to some explicit value
+ // in the preferred main size.
+ MOZ_ASSERT(flexBasis.IsAuto());
+ styleFlexBaseSize.emplace(styleMainSize);
+ }
+
+ MOZ_ASSERT(styleFlexBaseSize, "We should've emplace styleFlexBaseSize!");
+
+ // Provide the size override for the preferred main size property.
+ if (aAxisTracker.IsInlineAxisMainAxis(childWM)) {
+ sizeOverrides.mStyleISize = std::move(styleFlexBaseSize);
+ } else {
+ sizeOverrides.mStyleBSize = std::move(styleFlexBaseSize);
+ }
+
+ // 'flex-basis' should works on the inner table frame for a table flex item,
+ // just like how 'height' works on a table element.
+ sizeOverrides.mApplyOverridesVerbatim = true;
+ }
+
+ // Create temporary reflow input just for sizing -- to get hypothetical
+ // main-size and the computed values of min / max main-size property.
+ // (This reflow input will _not_ be used for reflow.)
+ ReflowInput childRI(PresContext(), aParentReflowInput, aChildFrame,
+ aParentReflowInput.ComputedSize(childWM), Nothing(), {},
+ sizeOverrides, {ComputeSizeFlag::ShrinkWrap});
+
+ // FLEX GROW & SHRINK WEIGHTS
+ // --------------------------
+ float flexGrow, flexShrink;
+ if (IsLegacyBox(this)) {
+ flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex;
+ } else {
+ flexGrow = stylePos->mFlexGrow;
+ flexShrink = stylePos->mFlexShrink;
+ }
+
+ // MAIN SIZES (flex base size, min/max size)
+ // -----------------------------------------
+ const LogicalSize computedSizeInFlexWM = childRI.ComputedSize(flexWM);
+ const LogicalSize computedMinSizeInFlexWM = childRI.ComputedMinSize(flexWM);
+ const LogicalSize computedMaxSizeInFlexWM = childRI.ComputedMaxSize(flexWM);
+
+ const nscoord flexBaseSize = aAxisTracker.MainComponent(computedSizeInFlexWM);
+ const nscoord mainMinSize =
+ aAxisTracker.MainComponent(computedMinSizeInFlexWM);
+ const nscoord mainMaxSize =
+ aAxisTracker.MainComponent(computedMaxSizeInFlexWM);
+
+ // This is enforced by the ReflowInput where these values come from:
+ MOZ_ASSERT(mainMinSize <= mainMaxSize, "min size is larger than max size");
+
+ // CROSS SIZES (tentative cross size, min/max cross size)
+ // ------------------------------------------------------
+ // Grab the cross size from the reflow input. This might be the right value,
+ // or we might resolve it to something else in SizeItemInCrossAxis(); hence,
+ // it's tentative. See comment under "Cross Size Determination" for more.
+ const nscoord tentativeCrossSize =
+ aAxisTracker.CrossComponent(computedSizeInFlexWM);
+ const nscoord crossMinSize =
+ aAxisTracker.CrossComponent(computedMinSizeInFlexWM);
+ const nscoord crossMaxSize =
+ aAxisTracker.CrossComponent(computedMaxSizeInFlexWM);
+
+ // Construct the flex item!
+ FlexItem& item = *aLine.Items().EmplaceBack(
+ childRI, flexGrow, flexShrink, flexBaseSize, mainMinSize, mainMaxSize,
+ tentativeCrossSize, crossMinSize, crossMaxSize, aAxisTracker);
+
+ // We may be about to do computations based on our item's cross-size
+ // (e.g. using it as a constraint when measuring our content in the
+ // main axis, or using it with the preferred aspect ratio to obtain a main
+ // size). BEFORE WE DO THAT, we need let the item "pre-stretch" its cross size
+ // (if it's got 'align-self:stretch'), for a certain case where the spec says
+ // the stretched cross size is considered "definite". That case is if we
+ // have a single-line (nowrap) flex container which itself has a definite
+ // cross-size. Otherwise, we'll wait to do stretching, since (in other
+ // cases) we don't know how much the item should stretch yet.
+ const bool isSingleLine =
+ StyleFlexWrap::Nowrap == aParentReflowInput.mStylePosition->mFlexWrap;
+ if (isSingleLine) {
+ // Is container's cross size "definite"?
+ // - If it's column-oriented, then "yes", because its cross size is its
+ // inline-size which is always definite from its descendants' perspective.
+ // - Otherwise (if it's row-oriented), then we check the actual size
+ // and call it definite if it's not NS_UNCONSTRAINEDSIZE.
+ if (aAxisTracker.IsColumnOriented() ||
+ aTentativeContentBoxCrossSize != NS_UNCONSTRAINEDSIZE) {
+ // Container's cross size is "definite", so we can resolve the item's
+ // stretched cross size using that.
+ item.ResolveStretchedCrossSize(aTentativeContentBoxCrossSize);
+ }
+ }
+
+ // Before thinking about freezing the item at its base size, we need to give
+ // it a chance to recalculate the base size from its cross size and aspect
+ // ratio (since its cross size might've *just* now become definite due to
+ // 'stretch' above)
+ item.ResolveFlexBaseSizeFromAspectRatio(childRI);
+
+ // If we're inflexible, we can just freeze to our hypothetical main-size
+ // up-front.
+ if (flexGrow == 0.0f && flexShrink == 0.0f) {
+ item.Freeze();
+ if (flexBaseSize < mainMinSize) {
+ item.SetWasMinClamped();
+ } else if (flexBaseSize > mainMaxSize) {
+ item.SetWasMaxClamped();
+ }
+ }
+
+ // Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might
+ // require us to reflow the item to measure content height)
+ ResolveAutoFlexBasisAndMinSize(item, childRI, aAxisTracker);
+}
+
+// Static helper-functions for ResolveAutoFlexBasisAndMinSize():
+// -------------------------------------------------------------
+// Partially resolves "min-[width|height]:auto" and returns the resulting value.
+// By "partially", I mean we don't consider the min-content size (but we do
+// consider the main-size and main max-size properties, and the preferred aspect
+// ratio). The caller is responsible for computing & considering the min-content
+// size in combination with the partially-resolved value that this function
+// returns.
+//
+// Basically, this function gets the specified size suggestion; if not, the
+// transferred size suggestion; if both sizes do not exist, return nscoord_MAX.
+//
+// Spec reference: https://drafts.csswg.org/css-flexbox-1/#min-size-auto
+static nscoord PartiallyResolveAutoMinSize(
+ const FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
+ const FlexboxAxisTracker& aAxisTracker) {
+ MOZ_ASSERT(aFlexItem.NeedsMinSizeAutoResolution(),
+ "only call for FlexItems that need min-size auto resolution");
+
+ const auto itemWM = aFlexItem.GetWritingMode();
+ const auto cbWM = aAxisTracker.GetWritingMode();
+ const auto& mainStyleSize =
+ aItemReflowInput.mStylePosition->Size(aAxisTracker.MainAxis(), cbWM);
+ const auto& maxMainStyleSize =
+ aItemReflowInput.mStylePosition->MaxSize(aAxisTracker.MainAxis(), cbWM);
+ const auto boxSizingAdjust =
+ aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
+ ? aFlexItem.BorderPadding().Size(cbWM)
+ : LogicalSize(cbWM);
+
+ // If this flex item is a compressible replaced element list in CSS Sizing 3
+ // §5.2.2, CSS Sizing 3 §5.2.1c requires us to resolve the percentage part of
+ // the preferred main size property against zero, yielding a definite
+ // specified size suggestion. Here we can use a zero percentage basis to
+ // fulfill this requirement.
+ const auto percentBasis =
+ aFlexItem.Frame()->IsPercentageResolvedAgainstZero(mainStyleSize,
+ maxMainStyleSize)
+ ? LogicalSize(cbWM, 0, 0)
+ : aItemReflowInput.mContainingBlockSize.ConvertTo(cbWM, itemWM);
+
+ // Compute the specified size suggestion, which is the main-size property if
+ // it's definite.
+ nscoord specifiedSizeSuggestion = nscoord_MAX;
+
+ if (aAxisTracker.IsRowOriented()) {
+ if (mainStyleSize.IsLengthPercentage()) {
+ // NOTE: We ignore extremum inline-size. This is OK because the caller is
+ // responsible for computing the min-content inline-size and min()'ing it
+ // with the value we return.
+ specifiedSizeSuggestion = aFlexItem.Frame()->ComputeISizeValue(
+ cbWM, percentBasis, boxSizingAdjust,
+ mainStyleSize.AsLengthPercentage());
+ }
+ } else {
+ if (!nsLayoutUtils::IsAutoBSize(mainStyleSize, percentBasis.BSize(cbWM))) {
+ // NOTE: We ignore auto and extremum block-size. This is OK because the
+ // caller is responsible for computing the min-content block-size and
+ // min()'ing it with the value we return.
+ specifiedSizeSuggestion = nsLayoutUtils::ComputeBSizeValue(
+ percentBasis.BSize(cbWM), boxSizingAdjust.BSize(cbWM),
+ mainStyleSize.AsLengthPercentage());
+ }
+ }
+
+ if (specifiedSizeSuggestion != nscoord_MAX) {
+ // We have the specified size suggestion. Return it now since we don't need
+ // to consider transferred size suggestion.
+ FLEX_LOGV(" Specified size suggestion: %d", specifiedSizeSuggestion);
+ return specifiedSizeSuggestion;
+ }
+
+ // Compute the transferred size suggestion, which is the cross size converted
+ // through the aspect ratio (if the item is replaced, and it has an aspect
+ // ratio and a definite cross size).
+ if (const auto& aspectRatio = aFlexItem.GetAspectRatio();
+ aFlexItem.Frame()->IsReplaced() && aspectRatio &&
+ aFlexItem.IsCrossSizeDefinite(aItemReflowInput)) {
+ // We have a usable aspect ratio. (not going to divide by 0)
+ nscoord transferredSizeSuggestion = aspectRatio.ComputeRatioDependentSize(
+ aFlexItem.MainAxis(), cbWM, aFlexItem.CrossSize(), boxSizingAdjust);
+
+ // Clamp the transferred size suggestion by any definite min and max
+ // cross size converted through the aspect ratio.
+ transferredSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
+ transferredSizeSuggestion, aItemReflowInput);
+
+ FLEX_LOGV(" Transferred size suggestion: %d", transferredSizeSuggestion);
+ return transferredSizeSuggestion;
+ }
+
+ return nscoord_MAX;
+}
+
+// Note: If & when we handle "min-height: min-content" for flex items,
+// we may want to resolve that in this function, too.
+void nsFlexContainerFrame::ResolveAutoFlexBasisAndMinSize(
+ FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
+ const FlexboxAxisTracker& aAxisTracker) {
+ // (Note: We can guarantee that the flex-basis will have already been
+ // resolved if the main axis is the same as the item's inline
+ // axis. Inline-axis values should always be resolvable without reflow.)
+ const bool isMainSizeAuto =
+ (!aFlexItem.IsInlineAxisMainAxis() &&
+ NS_UNCONSTRAINEDSIZE == aFlexItem.FlexBaseSize());
+
+ const bool isMainMinSizeAuto = aFlexItem.NeedsMinSizeAutoResolution();
+
+ if (!isMainSizeAuto && !isMainMinSizeAuto) {
+ // Nothing to do; this function is only needed for flex items
+ // with a used flex-basis of "auto" or a min-main-size of "auto".
+ return;
+ }
+
+ FLEX_LOGV("Resolving auto main size or auto min main size for flex item %p",
+ aFlexItem.Frame());
+
+ nscoord resolvedMinSize; // (only set/used if isMainMinSizeAuto==true)
+ bool minSizeNeedsToMeasureContent = false; // assume the best
+ if (isMainMinSizeAuto) {
+ // Resolve the min-size, except for considering the min-content size.
+ // (We'll consider that later, if we need to.)
+ resolvedMinSize =
+ PartiallyResolveAutoMinSize(aFlexItem, aItemReflowInput, aAxisTracker);
+ if (resolvedMinSize > 0) {
+ // If resolvedMinSize were already at 0, we could skip calculating content
+ // size suggestion because it can't go any lower.
+ minSizeNeedsToMeasureContent = true;
+ }
+ }
+
+ const bool flexBasisNeedsToMeasureContent = isMainSizeAuto;
+
+ // Measure content, if needed (w/ intrinsic-width method or a reflow)
+ if (minSizeNeedsToMeasureContent || flexBasisNeedsToMeasureContent) {
+ // Compute the content size suggestion, which is the min-content size in the
+ // main axis.
+ nscoord contentSizeSuggestion = nscoord_MAX;
+
+ if (aFlexItem.IsInlineAxisMainAxis()) {
+ if (minSizeNeedsToMeasureContent) {
+ // Compute the flex item's content size suggestion, which is the
+ // 'min-content' size on the main axis.
+ // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion
+ const auto cbWM = aAxisTracker.GetWritingMode();
+ const auto itemWM = aFlexItem.GetWritingMode();
+ const nscoord availISize = 0; // for min-content size
+ StyleSizeOverrides sizeOverrides;
+ sizeOverrides.mStyleISize.emplace(StyleSize::Auto());
+ const auto sizeInItemWM = aFlexItem.Frame()->ComputeSize(
+ aItemReflowInput.mRenderingContext, itemWM,
+ aItemReflowInput.mContainingBlockSize, availISize,
+ aItemReflowInput.ComputedLogicalMargin(itemWM).Size(itemWM),
+ aItemReflowInput.ComputedLogicalBorderPadding(itemWM).Size(itemWM),
+ sizeOverrides, {ComputeSizeFlag::ShrinkWrap});
+
+ contentSizeSuggestion = aAxisTracker.MainComponent(
+ sizeInItemWM.mLogicalSize.ConvertTo(cbWM, itemWM));
+ }
+ NS_ASSERTION(!flexBasisNeedsToMeasureContent,
+ "flex-basis:auto should have been resolved in the "
+ "reflow input, for horizontal flexbox. It shouldn't need "
+ "special handling here");
+ } else {
+ // If this item is flexible (in its block axis)...
+ // OR if we're measuring its 'auto' min-BSize, with its main-size (in its
+ // block axis) being something non-"auto"...
+ // THEN: we assume that the computed BSize that we're reflowing with now
+ // could be different from the one we'll use for this flex item's
+ // "actual" reflow later on. In that case, we need to be sure the flex
+ // item treats this as a block-axis resize (regardless of whether there
+ // are actually any ancestors being resized in that axis).
+ // (Note: We don't have to do this for the inline axis, because
+ // InitResizeFlags will always turn on mIsIResize on when it sees that
+ // the computed ISize is different from current ISize, and that's all we
+ // need.)
+ bool forceBResizeForMeasuringReflow =
+ !aFlexItem.IsFrozen() || // Is the item flexible?
+ !flexBasisNeedsToMeasureContent; // Are we *only* measuring it for
+ // 'min-block-size:auto'?
+
+ const ReflowInput& flexContainerRI = *aItemReflowInput.mParentReflowInput;
+ nscoord contentBSize = MeasureFlexItemContentBSize(
+ aFlexItem, forceBResizeForMeasuringReflow, flexContainerRI);
+ if (minSizeNeedsToMeasureContent) {
+ contentSizeSuggestion = contentBSize;
+ }
+ if (flexBasisNeedsToMeasureContent) {
+ aFlexItem.SetFlexBaseSizeAndMainSize(contentBSize);
+ aFlexItem.SetIsFlexBaseSizeContentBSize();
+ }
+ }
+
+ if (minSizeNeedsToMeasureContent) {
+ // Clamp the content size suggestion by any definite min and max cross
+ // size converted through the aspect ratio.
+ if (aFlexItem.HasAspectRatio()) {
+ contentSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
+ contentSizeSuggestion, aItemReflowInput);
+ }
+
+ FLEX_LOGV(" Content size suggestion: %d", contentSizeSuggestion);
+ resolvedMinSize = std::min(resolvedMinSize, contentSizeSuggestion);
+
+ // Clamp the resolved min main size by the max main size if it's definite.
+ if (aFlexItem.MainMaxSize() != NS_UNCONSTRAINEDSIZE) {
+ resolvedMinSize = std::min(resolvedMinSize, aFlexItem.MainMaxSize());
+ } else if (MOZ_UNLIKELY(resolvedMinSize > nscoord_MAX)) {
+ NS_WARNING("Bogus resolved auto min main size!");
+ // Our resolved min-size is bogus, probably due to some huge sizes in
+ // the content. Clamp it to the valid nscoord range, so that we can at
+ // least depend on it being <= the max-size (which is also the
+ // nscoord_MAX sentinel value if we reach this point).
+ resolvedMinSize = nscoord_MAX;
+ }
+ FLEX_LOGV(" Resolved auto min main size: %d", resolvedMinSize);
+
+ if (resolvedMinSize == contentSizeSuggestion) {
+ // When we are here, we've measured the item's content-based size, and
+ // we used it as the resolved auto min main size. Record the fact so
+ // that we can use it to determine whether we allow a flex item to grow
+ // its block-size in ReflowFlexItem().
+ aFlexItem.SetIsMainMinSizeContentBSize();
+ }
+ }
+ }
+
+ if (isMainMinSizeAuto) {
+ aFlexItem.UpdateMainMinSize(resolvedMinSize);
+ }
+}
+
+/**
+ * A cached result for a flex item's block-axis measuring reflow. This cache
+ * prevents us from doing exponential reflows in cases of deeply nested flex
+ * and scroll frames.
+ *
+ * We store the cached value in the flex item's frame property table, for
+ * simplicity.
+ *
+ * Right now, we cache the following as a "key", from the item's ReflowInput:
+ * - its ComputedSize
+ * - its min/max block size (in case its ComputedBSize is unconstrained)
+ * - its AvailableBSize
+ * ...and we cache the following as the "value", from the item's ReflowOutput:
+ * - its final content-box BSize
+ *
+ * The assumption here is that a given flex item measurement from our "value"
+ * won't change unless one of the pieces of the "key" change, or the flex
+ * item's intrinsic size is marked as dirty (due to a style or DOM change).
+ * (The latter will cause the cached value to be discarded, in
+ * nsIFrame::MarkIntrinsicISizesDirty.)
+ *
+ * Note that the components of "Key" (mComputed{MinB,MaxB,}Size and
+ * mAvailableBSize) are sufficient to catch any changes to the flex container's
+ * size that the item may care about for its measuring reflow. Specifically:
+ * - If the item cares about the container's size (e.g. if it has a percent
+ * height and the container's height changes, in a horizontal-WM container)
+ * then that'll be detectable via the item's ReflowInput's "ComputedSize()"
+ * differing from the value in our Key. And the same applies for the
+ * inline axis.
+ * - If the item is fragmentable (pending bug 939897) and its measured BSize
+ * depends on where it gets fragmented, then that sort of change can be
+ * detected due to the item's ReflowInput's "AvailableBSize()" differing
+ * from the value in our Key.
+ *
+ * One particular case to consider (& need to be sure not to break when
+ * changing this class): the flex item's computed BSize may change between
+ * measuring reflows due to how the mIsFlexContainerMeasuringBSize flag affects
+ * size computation (see bug 1336708). This is one reason we need to use the
+ * computed BSize as part of the key.
+ */
+class nsFlexContainerFrame::CachedBAxisMeasurement {
+ struct Key {
+ const LogicalSize mComputedSize;
+ const nscoord mComputedMinBSize;
+ const nscoord mComputedMaxBSize;
+ const nscoord mAvailableBSize;
+
+ explicit Key(const ReflowInput& aRI)
+ : mComputedSize(aRI.ComputedSize()),
+ mComputedMinBSize(aRI.ComputedMinBSize()),
+ mComputedMaxBSize(aRI.ComputedMaxBSize()),
+ mAvailableBSize(aRI.AvailableBSize()) {}
+
+ bool operator==(const Key& aOther) const {
+ return mComputedSize == aOther.mComputedSize &&
+ mComputedMinBSize == aOther.mComputedMinBSize &&
+ mComputedMaxBSize == aOther.mComputedMaxBSize &&
+ mAvailableBSize == aOther.mAvailableBSize;
+ }
+ };
+
+ const Key mKey;
+
+ // This could/should be const, but it's non-const for now just because it's
+ // assigned via a series of steps in the constructor body:
+ nscoord mBSize;
+
+ public:
+ CachedBAxisMeasurement(const ReflowInput& aReflowInput,
+ const ReflowOutput& aReflowOutput)
+ : mKey(aReflowInput) {
+ // To get content-box bsize, we have to subtract off border & padding
+ // (and floor at 0 in case the border/padding are too large):
+ WritingMode itemWM = aReflowInput.GetWritingMode();
+ nscoord borderBoxBSize = aReflowOutput.BSize(itemWM);
+ mBSize =
+ borderBoxBSize -
+ aReflowInput.ComputedLogicalBorderPadding(itemWM).BStartEnd(itemWM);
+ mBSize = std::max(0, mBSize);
+ }
+
+ /**
+ * Returns true if this cached flex item measurement is valid for (i.e. can
+ * be expected to match the output of) a measuring reflow whose input
+ * parameters are given via aReflowInput.
+ */
+ bool IsValidFor(const ReflowInput& aReflowInput) const {
+ return mKey == Key(aReflowInput);
+ }
+
+ nscoord BSize() const { return mBSize; }
+};
+
+/**
+ * A cached copy of various metrics from a flex item's most recent final reflow.
+ * It can be used to determine whether we can optimize away the flex item's
+ * final reflow, when we perform an incremental reflow of its flex container.
+ */
+class CachedFinalReflowMetrics final {
+ public:
+ CachedFinalReflowMetrics(const ReflowInput& aReflowInput,
+ const ReflowOutput& aReflowOutput)
+ : CachedFinalReflowMetrics(aReflowInput.GetWritingMode(), aReflowInput,
+ aReflowOutput) {}
+
+ CachedFinalReflowMetrics(const FlexItem& aItem, const LogicalSize& aSize)
+ : mBorderPadding(aItem.BorderPadding().ConvertTo(
+ aItem.GetWritingMode(), aItem.ContainingBlockWM())),
+ mSize(aSize),
+ mTreatBSizeAsIndefinite(aItem.TreatBSizeAsIndefinite()) {}
+
+ const LogicalSize& Size() const { return mSize; }
+ const LogicalMargin& BorderPadding() const { return mBorderPadding; }
+ bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
+
+ private:
+ // A convenience constructor with a WritingMode argument.
+ CachedFinalReflowMetrics(WritingMode aWM, const ReflowInput& aReflowInput,
+ const ReflowOutput& aReflowOutput)
+ : mBorderPadding(aReflowInput.ComputedLogicalBorderPadding(aWM)),
+ mSize(aReflowOutput.Size(aWM) - mBorderPadding.Size(aWM)),
+ mTreatBSizeAsIndefinite(aReflowInput.mFlags.mTreatBSizeAsIndefinite) {}
+
+ // The flex item's border and padding, in its own writing-mode, that it used
+ // used during its most recent "final reflow".
+ LogicalMargin mBorderPadding;
+
+ // The flex item's content-box size, in its own writing-mode, that it used
+ // during its most recent "final reflow".
+ LogicalSize mSize;
+
+ // True if the flex item's BSize was considered "indefinite" in its most
+ // recent "final reflow". (For a flex item "final reflow", this is fully
+ // determined by the mTreatBSizeAsIndefinite flag in ReflowInput. See the
+ // flag's documentation for more information.)
+ bool mTreatBSizeAsIndefinite;
+};
+
+/**
+ * When we instantiate/update a CachedFlexItemData, this enum must be used to
+ * indicate the sort of reflow whose results we're capturing. This impacts
+ * what we cache & how we use the cached information.
+ */
+enum class FlexItemReflowType {
+ // A reflow to measure the block-axis size of a flex item (as an input to the
+ // flex layout algorithm).
+ Measuring,
+
+ // A reflow with the flex item's "final" size at the end of the flex layout
+ // algorithm.
+ Final,
+};
+
+/**
+ * This class stores information about the conditions and results for the most
+ * recent ReflowChild call that we made on a given flex item. This information
+ * helps us reason about whether we can assume that a subsequent ReflowChild()
+ * invocation is unnecessary & skippable.
+ */
+class nsFlexContainerFrame::CachedFlexItemData {
+ public:
+ CachedFlexItemData(const ReflowInput& aReflowInput,
+ const ReflowOutput& aReflowOutput,
+ FlexItemReflowType aType) {
+ Update(aReflowInput, aReflowOutput, aType);
+ }
+
+ // This method is intended to be called after we perform either a "measuring
+ // reflow" or a "final reflow" for a given flex item.
+ void Update(const ReflowInput& aReflowInput,
+ const ReflowOutput& aReflowOutput, FlexItemReflowType aType) {
+ if (aType == FlexItemReflowType::Measuring) {
+ mBAxisMeasurement.reset();
+ mBAxisMeasurement.emplace(aReflowInput, aReflowOutput);
+ // Clear any cached "last final reflow metrics", too, because now the most
+ // recent reflow was *not* a "final reflow".
+ mFinalReflowMetrics.reset();
+ return;
+ }
+
+ MOZ_ASSERT(aType == FlexItemReflowType::Final);
+ mFinalReflowMetrics.reset();
+ mFinalReflowMetrics.emplace(aReflowInput, aReflowOutput);
+ }
+
+ // This method is intended to be called for situations where we decide to
+ // skip a final reflow because we've just done a measuring reflow which left
+ // us (and our descendants) with the correct sizes. In this scenario, we
+ // still want to cache the size as if we did a final reflow (because we've
+ // determined that the recent measuring reflow was sufficient). That way,
+ // our flex container can still skip a final reflow for this item in the
+ // future as long as conditions are right.
+ void Update(const FlexItem& aItem, const LogicalSize& aSize) {
+ MOZ_ASSERT(!mFinalReflowMetrics,
+ "This version of the method is only intended to be called when "
+ "the most recent reflow was a 'measuring reflow'; and that "
+ "should have cleared out mFinalReflowMetrics");
+
+ mFinalReflowMetrics.reset(); // Just in case this assert^ fails.
+ mFinalReflowMetrics.emplace(aItem, aSize);
+ }
+
+ // If the flex container needs a measuring reflow for the flex item, then the
+ // resulting block-axis measurements can be cached here. If no measurement
+ // has been needed so far, then this member will be Nothing().
+ Maybe<CachedBAxisMeasurement> mBAxisMeasurement;
+
+ // The metrics that the corresponding flex item used in its most recent
+ // "final reflow". (Note: the assumption here is that this reflow was this
+ // item's most recent reflow of any type. If the item ends up undergoing a
+ // subsequent measuring reflow, then this value needs to be cleared, because
+ // at that point it's no longer an accurate way of reasoning about the
+ // current state of the frame tree.)
+ Maybe<CachedFinalReflowMetrics> mFinalReflowMetrics;
+
+ // Instances of this class are stored under this frame property, on
+ // frames that are flex items:
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, CachedFlexItemData)
+};
+
+void nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(
+ nsIFrame* aItemFrame) {
+ MOZ_ASSERT(aItemFrame->IsFlexItem());
+ if (auto* cache = aItemFrame->GetProperty(CachedFlexItemData::Prop())) {
+ cache->mBAxisMeasurement.reset();
+ cache->mFinalReflowMetrics.reset();
+ }
+}
+
+const CachedBAxisMeasurement& nsFlexContainerFrame::MeasureBSizeForFlexItem(
+ FlexItem& aItem, ReflowInput& aChildReflowInput) {
+ auto* cachedData = aItem.Frame()->GetProperty(CachedFlexItemData::Prop());
+
+ if (cachedData && cachedData->mBAxisMeasurement) {
+ if (!aItem.Frame()->IsSubtreeDirty() &&
+ cachedData->mBAxisMeasurement->IsValidFor(aChildReflowInput)) {
+ FLEX_LOG("[perf] MeasureBSizeForFlexItem accepted cached value");
+ return *(cachedData->mBAxisMeasurement);
+ }
+ FLEX_LOG("[perf] MeasureBSizeForFlexItem rejected cached value");
+ } else {
+ FLEX_LOG("[perf] MeasureBSizeForFlexItem didn't have a cached value");
+ }
+
+ // CachedFlexItemData is stored in item's writing mode, so we pass
+ // aChildReflowInput into ReflowOutput's constructor.
+ ReflowOutput childReflowOutput(aChildReflowInput);
+ nsReflowStatus childReflowStatus;
+
+ const ReflowChildFlags flags = ReflowChildFlags::NoMoveFrame;
+ const WritingMode outerWM = GetWritingMode();
+ const LogicalPoint dummyPosition(outerWM);
+ const nsSize dummyContainerSize;
+
+ // We use NoMoveFrame, so the position and container size used here are
+ // unimportant.
+ ReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
+ aChildReflowInput, outerWM, dummyPosition, dummyContainerSize,
+ flags, childReflowStatus);
+ aItem.SetHadMeasuringReflow();
+
+ // We always use unconstrained available block-size to measure flex items,
+ // which means they should always complete.
+ MOZ_ASSERT(childReflowStatus.IsComplete(),
+ "We gave flex item unconstrained available block-size, so it "
+ "should be complete");
+
+ // Tell the child we're done with its initial reflow.
+ // (Necessary for e.g. GetBaseline() to work below w/out asserting)
+ FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
+ &aChildReflowInput, outerWM, dummyPosition,
+ dummyContainerSize, flags);
+
+ aItem.SetAscent(childReflowOutput.BlockStartAscent());
+
+ // Update (or add) our cached measurement, so that we can hopefully skip this
+ // measuring reflow the next time around:
+ if (cachedData) {
+ cachedData->Update(aChildReflowInput, childReflowOutput,
+ FlexItemReflowType::Measuring);
+ } else {
+ cachedData = new CachedFlexItemData(aChildReflowInput, childReflowOutput,
+ FlexItemReflowType::Measuring);
+ aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cachedData);
+ }
+ return *(cachedData->mBAxisMeasurement);
+}
+
+/* virtual */
+void nsFlexContainerFrame::MarkIntrinsicISizesDirty() {
+ mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+
+ nsContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+nscoord nsFlexContainerFrame::MeasureFlexItemContentBSize(
+ FlexItem& aFlexItem, bool aForceBResizeForMeasuringReflow,
+ const ReflowInput& aParentReflowInput) {
+ FLEX_LOG("Measuring flex item's content block-size");
+
+ // Set up a reflow input for measuring the flex item's content block-size:
+ WritingMode wm = aFlexItem.Frame()->GetWritingMode();
+ LogicalSize availSize = aParentReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+
+ StyleSizeOverrides sizeOverrides;
+ if (aFlexItem.IsStretched()) {
+ sizeOverrides.mStyleISize.emplace(aFlexItem.StyleCrossSize());
+ // Suppress any AspectRatio that we might have to prevent ComputeSize() from
+ // transferring our inline-size override through the aspect-ratio to set the
+ // block-size, because that would prevent us from measuring the content
+ // block-size.
+ sizeOverrides.mAspectRatio.emplace(AspectRatio());
+ FLEX_LOGV(" Cross size override: %d", aFlexItem.CrossSize());
+ }
+ sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
+
+ ReflowInput childRIForMeasuringBSize(
+ PresContext(), aParentReflowInput, aFlexItem.Frame(), availSize,
+ Nothing(), {}, sizeOverrides, {ComputeSizeFlag::ShrinkWrap});
+
+ // When measuring flex item's content block-size, disregard the item's
+ // min-block-size and max-block-size by resetting both to to their
+ // unconstraining (extreme) values. The flexbox layout algorithm does still
+ // explicitly clamp both sizes when resolving the target main size.
+ childRIForMeasuringBSize.SetComputedMinBSize(0);
+ childRIForMeasuringBSize.SetComputedMaxBSize(NS_UNCONSTRAINEDSIZE);
+
+ if (aForceBResizeForMeasuringReflow) {
+ childRIForMeasuringBSize.SetBResize(true);
+ // Not 100% sure this is needed, but be conservative for now:
+ childRIForMeasuringBSize.mFlags.mIsBResizeForPercentages = true;
+ }
+
+ const CachedBAxisMeasurement& measurement =
+ MeasureBSizeForFlexItem(aFlexItem, childRIForMeasuringBSize);
+
+ return measurement.BSize();
+}
+
+FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
+ float aFlexShrink, nscoord aFlexBaseSize,
+ nscoord aMainMinSize, nscoord aMainMaxSize,
+ nscoord aTentativeCrossSize, nscoord aCrossMinSize,
+ nscoord aCrossMaxSize,
+ const FlexboxAxisTracker& aAxisTracker)
+ : mFrame(aFlexItemReflowInput.mFrame),
+ mFlexGrow(aFlexGrow),
+ mFlexShrink(aFlexShrink),
+ mAspectRatio(mFrame->GetAspectRatio()),
+ mWM(aFlexItemReflowInput.GetWritingMode()),
+ mCBWM(aAxisTracker.GetWritingMode()),
+ mMainAxis(aAxisTracker.MainAxis()),
+ mBorderPadding(aFlexItemReflowInput.ComputedLogicalBorderPadding(mCBWM)),
+ mMargin(aFlexItemReflowInput.ComputedLogicalMargin(mCBWM)),
+ mMainMinSize(aMainMinSize),
+ mMainMaxSize(aMainMaxSize),
+ mCrossMinSize(aCrossMinSize),
+ mCrossMaxSize(aCrossMaxSize),
+ mCrossSize(aTentativeCrossSize),
+ mIsInlineAxisMainAxis(aAxisTracker.IsInlineAxisMainAxis(mWM)),
+ mNeedsMinSizeAutoResolution(IsMinSizeAutoResolutionNeeded())
+// mAlignSelf, mHasAnyAutoMargin see below
+{
+ MOZ_ASSERT(mFrame, "expecting a non-null child frame");
+ MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
+ "placeholder frames should not be treated as flex items");
+ MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "out-of-flow frames should not be treated as flex items");
+ MOZ_ASSERT(mIsInlineAxisMainAxis ==
+ nsFlexContainerFrame::IsItemInlineAxisMainAxis(mFrame),
+ "public API should be consistent with internal state (about "
+ "whether flex item's inline axis is flex container's main axis)");
+
+ const ReflowInput* containerRS = aFlexItemReflowInput.mParentReflowInput;
+ if (IsLegacyBox(containerRS->mFrame)) {
+ // For -webkit-{inline-}box and -moz-{inline-}box, we need to:
+ // (1) Use prefixed "box-align" instead of "align-items" to determine the
+ // container's cross-axis alignment behavior.
+ // (2) Suppress the ability for flex items to override that with their own
+ // cross-axis alignment. (The legacy box model doesn't support this.)
+ // So, each FlexItem simply copies the container's converted "align-items"
+ // value and disregards their own "align-self" property.
+ const nsStyleXUL* containerStyleXUL = containerRS->mFrame->StyleXUL();
+ mAlignSelf = {ConvertLegacyStyleToAlignItems(containerStyleXUL)};
+ mAlignSelfFlags = {0};
+ } else {
+ mAlignSelf = aFlexItemReflowInput.mStylePosition->UsedAlignSelf(
+ containerRS->mFrame->Style());
+ if (MOZ_LIKELY(mAlignSelf._0 == StyleAlignFlags::NORMAL)) {
+ mAlignSelf = {StyleAlignFlags::STRETCH};
+ }
+
+ // Store and strip off the <overflow-position> bits
+ mAlignSelfFlags = mAlignSelf._0 & StyleAlignFlags::FLAG_BITS;
+ mAlignSelf._0 &= ~StyleAlignFlags::FLAG_BITS;
+ }
+
+ // Our main-size is considered definite if any of these are true:
+ // (a) main axis is the item's inline axis.
+ // (b) flex container has definite main size.
+ // (c) flex item has a definite flex basis.
+ //
+ // Hence, we need to take care to treat the final main-size as *indefinite*
+ // if none of these conditions are satisfied.
+ if (mIsInlineAxisMainAxis) {
+ // The item's block-axis is the flex container's cross axis. We don't need
+ // any special handling to treat cross sizes as indefinite, because the
+ // cases where we stomp on the cross size with a definite value are all...
+ // - situations where the spec requires us to treat the cross size as
+ // definite; specifically, `align-self:stretch` whose cross size is
+ // definite.
+ // - situations where definiteness doesn't matter (e.g. for an element with
+ // an aspect ratio, which for now are all leaf nodes and hence
+ // can't have any percent-height descendants that would care about the
+ // definiteness of its size. (Once bug 1528375 is fixed, we might need to
+ // be more careful about definite vs. indefinite sizing on flex items with
+ // aspect ratios.)
+ mTreatBSizeAsIndefinite = false;
+ } else {
+ // The item's block-axis is the flex container's main axis. So, the flex
+ // item's main size is its BSize, and is considered definite under certain
+ // conditions laid out for definite flex-item main-sizes in the spec.
+ if (aAxisTracker.IsRowOriented() ||
+ (containerRS->ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
+ !containerRS->mFlags.mTreatBSizeAsIndefinite)) {
+ // The flex *container* has a definite main-size (either by being
+ // row-oriented [and using its own inline size which is by definition
+ // definite, or by being column-oriented and having a definite
+ // block-size). The spec says this means all of the flex items'
+ // post-flexing main sizes should *also* be treated as definite.
+ mTreatBSizeAsIndefinite = false;
+ } else if (aFlexBaseSize != NS_UNCONSTRAINEDSIZE) {
+ // The flex item has a definite flex basis, which we'll treat as making
+ // its main-size definite.
+ mTreatBSizeAsIndefinite = false;
+ } else {
+ // Otherwise, we have to treat the item's BSize as indefinite.
+ mTreatBSizeAsIndefinite = true;
+ }
+ }
+
+ SetFlexBaseSizeAndMainSize(aFlexBaseSize);
+
+ const nsStyleMargin* styleMargin = aFlexItemReflowInput.mStyleMargin;
+ mHasAnyAutoMargin = styleMargin->HasInlineAxisAuto(mCBWM) ||
+ styleMargin->HasBlockAxisAuto(mCBWM);
+
+ // Assert that any "auto" margin components are set to 0.
+ // (We'll resolve them later; until then, we want to treat them as 0-sized.)
+#ifdef DEBUG
+ {
+ for (const auto side : AllLogicalSides()) {
+ if (styleMargin->mMargin.Get(mCBWM, side).IsAuto()) {
+ MOZ_ASSERT(GetMarginComponentForSide(side) == 0,
+ "Someone else tried to resolve our auto margin");
+ }
+ }
+ }
+#endif // DEBUG
+
+ if (mAlignSelf._0 == StyleAlignFlags::BASELINE ||
+ mAlignSelf._0 == StyleAlignFlags::LAST_BASELINE) {
+ // Check which of the item's baselines we're meant to use (first vs. last)
+ const bool usingItemFirstBaseline =
+ (mAlignSelf._0 == StyleAlignFlags::BASELINE);
+ if (IsBlockAxisCrossAxis()) {
+ // The flex item wants to be aligned in the cross axis using one of its
+ // baselines; and the cross axis is the item's block axis, so
+ // baseline-alignment in that axis makes sense.
+
+ // To determine the item's baseline sharing group, we check whether the
+ // item's block axis has the same vs. opposite flow direction as the
+ // corresponding LogicalAxis on the flex container. We do this by
+ // getting the physical side that corresponds to these axes' "logical
+ // start" sides, and we compare those physical sides to find out if
+ // they're the same vs. opposite.
+ mozilla::Side itemBlockStartSide = mWM.PhysicalSide(eLogicalSideBStart);
+
+ // (Note: this is *not* the "flex-start" side; rather, it's the *logical*
+ // i.e. WM-relative block-start or inline-start side.)
+ mozilla::Side containerStartSideInCrossAxis = mCBWM.PhysicalSide(
+ MakeLogicalSide(aAxisTracker.CrossAxis(), eLogicalEdgeStart));
+
+ // We already know these two Sides (the item's block-start and the
+ // container's 'logical start' side for its cross axis) are in the same
+ // physical axis, since we're inside of a check for
+ // FlexItem::IsBlockAxisCrossAxis(). So these two Sides must be either
+ // the same physical side or opposite from each other. If the Sides are
+ // the same, then the flow direction is the same, which means the item's
+ // {first,last} baseline participates in the {first,last}
+ // baseline-sharing group in its FlexLine. Otherwise, the flow direction
+ // is opposite, and so the item's {first,last} baseline participates in
+ // the opposite i.e. {last,first} baseline-sharing group. This is
+ // roughly per css-align-3 section 9.2, specifically the definition of
+ // what makes baseline alignment preferences "compatible".
+ bool itemBlockAxisFlowDirMatchesContainer =
+ (itemBlockStartSide == containerStartSideInCrossAxis);
+ mBaselineSharingGroup =
+ (itemBlockAxisFlowDirMatchesContainer == usingItemFirstBaseline)
+ ? BaselineSharingGroup::First
+ : BaselineSharingGroup::Last;
+ } else {
+ // The flex item wants to be aligned in the cross axis using one of its
+ // baselines, but we cannot get its baseline because the FlexItem's block
+ // axis is *orthogonal* to the container's cross axis. To handle this, we
+ // are supposed to synthesize a baseline from the item's border box and
+ // using that for baseline alignment.
+ mBaselineSharingGroup = usingItemFirstBaseline
+ ? BaselineSharingGroup::First
+ : BaselineSharingGroup::Last;
+ }
+ }
+}
+
+// Simplified constructor for creating a special "strut" FlexItem, for a child
+// with visibility:collapse. The strut has 0 main-size, and it only exists to
+// impose a minimum cross size on whichever FlexLine it ends up in.
+FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize,
+ WritingMode aContainerWM,
+ const FlexboxAxisTracker& aAxisTracker)
+ : mFrame(aChildFrame),
+ mWM(aChildFrame->GetWritingMode()),
+ mCBWM(aContainerWM),
+ mMainAxis(aAxisTracker.MainAxis()),
+ mBorderPadding(mCBWM),
+ mMargin(mCBWM),
+ mCrossSize(aCrossSize),
+ // Struts don't do layout, so its WM doesn't matter at this point. So, we
+ // just share container's WM for simplicity:
+ mIsFrozen(true),
+ mIsStrut(true), // (this is the constructor for making struts, after all)
+ mAlignSelf({StyleAlignFlags::FLEX_START}) {
+ MOZ_ASSERT(mFrame, "expecting a non-null child frame");
+ MOZ_ASSERT(mFrame->StyleVisibility()->IsCollapse(),
+ "Should only make struts for children with 'visibility:collapse'");
+ MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
+ "placeholder frames should not be treated as flex items");
+ MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "out-of-flow frames should not be treated as flex items");
+}
+
+bool FlexItem::IsMinSizeAutoResolutionNeeded() const {
+ // We'll need special behavior for "min-[width|height]:auto" (whichever is in
+ // the flex container's main axis) iff:
+ // (a) its computed value is "auto", and
+ // (b) the item is *not* a scroll container. (A scroll container's automatic
+ // minimum size is zero.)
+ // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
+ //
+ // Note that the scroll container case is redefined to be looking at the
+ // computed value instead, see https://github.com/w3c/csswg-drafts/issues/7714
+ const auto& mainMinSize =
+ Frame()->StylePosition()->MinSize(MainAxis(), ContainingBlockWM());
+
+ return IsAutoOrEnumOnBSize(mainMinSize, IsInlineAxisMainAxis()) &&
+ !Frame()->StyleDisplay()->IsScrollableOverflow();
+}
+
+Maybe<nscoord> FlexItem::MeasuredBSize() const {
+ auto* cachedData =
+ Frame()->FirstInFlow()->GetProperty(CachedFlexItemData::Prop());
+ if (!cachedData || !cachedData->mBAxisMeasurement) {
+ return Nothing();
+ }
+ return Some(cachedData->mBAxisMeasurement->BSize());
+}
+
+nscoord FlexItem::BaselineOffsetFromOuterCrossEdge(
+ mozilla::Side aStartSide, bool aUseFirstLineBaseline) const {
+ // NOTE:
+ // * We only use baselines for aligning in the flex container's cross axis.
+ // * Baselines are a measurement in the item's block axis.
+ if (IsBlockAxisMainAxis()) {
+ // We get here if the item's block axis is *orthogonal* the container's
+ // cross axis. For example, a flex item with writing-mode:horizontal-tb in a
+ // column-oriented flex container. We need to synthesize the item's baseline
+ // from its border-box edge.
+ const bool isMainAxisHorizontal =
+ mCBWM.PhysicalAxis(MainAxis()) == mozilla::eAxisHorizontal;
+
+ // When the main axis is horizontal, the synthesized baseline is the bottom
+ // edge of the item's border-box. Otherwise, when the main axis is vertical,
+ // the left edge. This is for compatibility with Google Chrome.
+ nscoord marginTopOrLeftToBaseline =
+ isMainAxisHorizontal ? PhysicalMargin().top : PhysicalMargin().left;
+ if (mCBWM.IsAlphabeticalBaseline()) {
+ marginTopOrLeftToBaseline += (isMainAxisHorizontal ? CrossSize() : 0);
+ } else {
+ MOZ_ASSERT(mCBWM.IsCentralBaseline());
+ marginTopOrLeftToBaseline += CrossSize() / 2;
+ }
+
+ return aStartSide == mozilla::eSideTop || aStartSide == mozilla::eSideLeft
+ ? marginTopOrLeftToBaseline
+ : OuterCrossSize() - marginTopOrLeftToBaseline;
+ }
+
+ // We get here if the item's block axis is parallel (or antiparallel) to the
+ // container's cross axis. We call ResolvedAscent() to get the item's
+ // baseline. If the item has no baseline, the method will synthesize one from
+ // the border-box edge.
+ MOZ_ASSERT(IsBlockAxisCrossAxis(),
+ "Only expecting to be doing baseline computations when the "
+ "cross axis is the block axis");
+
+ mozilla::Side itemBlockStartSide = mWM.PhysicalSide(eLogicalSideBStart);
+
+ nscoord marginBStartToBaseline = ResolvedAscent(aUseFirstLineBaseline) +
+ PhysicalMargin().Side(itemBlockStartSide);
+
+ return (aStartSide == itemBlockStartSide)
+ ? marginBStartToBaseline
+ : OuterCrossSize() - marginBStartToBaseline;
+}
+
+bool FlexItem::IsCrossSizeAuto() const {
+ const nsStylePosition* stylePos =
+ nsLayoutUtils::GetStyleFrame(mFrame)->StylePosition();
+ // Check whichever component is in the flex container's cross axis.
+ // (IsInlineAxisCrossAxis() tells us whether that's our ISize or BSize, in
+ // terms of our own WritingMode, mWM.)
+ return IsInlineAxisCrossAxis() ? stylePos->ISize(mWM).IsAuto()
+ : stylePos->BSize(mWM).IsAuto();
+}
+
+bool FlexItem::IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const {
+ if (IsStretched()) {
+ // Definite cross-size, imposed via 'align-self:stretch' & flex container.
+ return true;
+ }
+
+ const nsStylePosition* pos = aItemReflowInput.mStylePosition;
+ const auto itemWM = GetWritingMode();
+
+ // The logic here should be similar to the logic for isAutoISize/isAutoBSize
+ // in nsContainerFrame::ComputeSizeWithIntrinsicDimensions().
+ if (IsInlineAxisCrossAxis()) {
+ return !pos->ISize(itemWM).IsAuto();
+ }
+
+ nscoord cbBSize = aItemReflowInput.mContainingBlockSize.BSize(itemWM);
+ return !nsLayoutUtils::IsAutoBSize(pos->BSize(itemWM), cbBSize);
+}
+
+void FlexItem::ResolveFlexBaseSizeFromAspectRatio(
+ const ReflowInput& aItemReflowInput) {
+ // This implements the Flex Layout Algorithm Step 3B:
+ // https://drafts.csswg.org/css-flexbox-1/#algo-main-item
+ // If the flex item has ...
+ // - an aspect ratio,
+ // - a [used] flex-basis of 'content', and
+ // - a definite cross size
+ // then the flex base size is calculated from its inner cross size and the
+ // flex item's preferred aspect ratio.
+ if (HasAspectRatio() &&
+ nsFlexContainerFrame::IsUsedFlexBasisContent(
+ aItemReflowInput.mStylePosition->mFlexBasis,
+ aItemReflowInput.mStylePosition->Size(MainAxis(), mCBWM)) &&
+ IsCrossSizeDefinite(aItemReflowInput)) {
+ const LogicalSize contentBoxSizeToBoxSizingAdjust =
+ aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
+ ? BorderPadding().Size(mCBWM)
+ : LogicalSize(mCBWM);
+ const nscoord mainSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
+ MainAxis(), mCBWM, CrossSize(), contentBoxSizeToBoxSizingAdjust);
+ SetFlexBaseSizeAndMainSize(mainSizeFromRatio);
+ }
+}
+
+uint32_t FlexItem::NumAutoMarginsInAxis(LogicalAxis aAxis) const {
+ uint32_t numAutoMargins = 0;
+ const auto& styleMargin = mFrame->StyleMargin()->mMargin;
+ for (const auto edge : {eLogicalEdgeStart, eLogicalEdgeEnd}) {
+ const auto side = MakeLogicalSide(aAxis, edge);
+ if (styleMargin.Get(mCBWM, side).IsAuto()) {
+ numAutoMargins++;
+ }
+ }
+
+ // Mostly for clarity:
+ MOZ_ASSERT(numAutoMargins <= 2,
+ "We're just looking at one item along one dimension, so we "
+ "should only have examined 2 margins");
+
+ return numAutoMargins;
+}
+
+bool FlexItem::CanMainSizeInfluenceCrossSize() const {
+ if (mIsStretched) {
+ // We've already had our cross-size stretched for "align-self:stretch").
+ // The container is imposing its cross size on us.
+ return false;
+ }
+
+ if (mIsStrut) {
+ // Struts (for visibility:collapse items) have a predetermined size;
+ // no need to measure anything.
+ return false;
+ }
+
+ if (HasAspectRatio()) {
+ // For flex items that have an aspect ratio (and maintain it, i.e. are
+ // not stretched, which we already checked above): changes to main-size
+ // *do* influence the cross size.
+ return true;
+ }
+
+ if (IsInlineAxisCrossAxis()) {
+ // If we get here, this function is really asking: "can changes to this
+ // item's block size have an influence on its inline size"? For blocks and
+ // tables, the answer is "no".
+ if (mFrame->IsBlockFrame() || mFrame->IsTableWrapperFrame()) {
+ // XXXdholbert (Maybe use an IsFrameOfType query or something more
+ // general to test this across all frame types? For now, I'm just
+ // optimizing for block and table, since those are common containers that
+ // can contain arbitrarily-large subtrees (and that reliably have ISize
+ // being unaffected by BSize, per CSS2). So optimizing away needless
+ // relayout is possible & especially valuable for these containers.)
+ return false;
+ }
+ // Other opt-outs can go here, as they're identified as being useful
+ // (particularly for containers where an extra reflow is expensive). But in
+ // general, we have to assume that a flexed BSize *could* influence the
+ // ISize. Some examples where this can definitely happen:
+ // * Intrinsically-sized multicol with fixed-ISize columns, which adds
+ // columns (i.e. grows in inline axis) depending on its block size.
+ // * Intrinsically-sized multi-line column-oriented flex container, which
+ // adds flex lines (i.e. grows in inline axis) depending on its block size.
+ }
+
+ // Default assumption, if we haven't proven otherwise: the resolved main size
+ // *can* change the cross size.
+ return true;
+}
+
+nscoord FlexItem::ClampMainSizeViaCrossAxisConstraints(
+ nscoord aMainSize, const ReflowInput& aItemReflowInput) const {
+ MOZ_ASSERT(HasAspectRatio(), "Caller should've checked the ratio is valid!");
+
+ const LogicalSize contentBoxSizeToBoxSizingAdjust =
+ aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
+ ? BorderPadding().Size(mCBWM)
+ : LogicalSize(mCBWM);
+
+ const nscoord mainMinSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
+ MainAxis(), mCBWM, CrossMinSize(), contentBoxSizeToBoxSizingAdjust);
+ nscoord clampedMainSize = std::max(aMainSize, mainMinSizeFromRatio);
+
+ if (CrossMaxSize() != NS_UNCONSTRAINEDSIZE) {
+ const nscoord mainMaxSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
+ MainAxis(), mCBWM, CrossMaxSize(), contentBoxSizeToBoxSizingAdjust);
+ clampedMainSize = std::min(clampedMainSize, mainMaxSizeFromRatio);
+ }
+
+ return clampedMainSize;
+}
+
+/**
+ * Returns true if aFrame or any of its children have the
+ * NS_FRAME_CONTAINS_RELATIVE_BSIZE flag set -- i.e. if any of these frames (or
+ * their descendants) might have a relative-BSize dependency on aFrame (or its
+ * ancestors).
+ */
+static bool FrameHasRelativeBSizeDependency(nsIFrame* aFrame) {
+ if (aFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ return true;
+ }
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* childFrame : childList.mList) {
+ if (childFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool FlexItem::NeedsFinalReflow(const ReflowInput& aParentReflowInput) const {
+ if (!StaticPrefs::layout_flexbox_item_final_reflow_optimization_enabled()) {
+ FLEX_LOG(
+ "[perf] Flex item %p needed a final reflow due to optimization being "
+ "disabled via the preference",
+ mFrame);
+ return true;
+ }
+
+ // NOTE: We can have continuations from an earlier constrained reflow.
+ if (mFrame->GetPrevInFlow() || mFrame->GetNextInFlow()) {
+ // This is an item has continuation(s). Reflow it.
+ FLEX_LOG("[frag] Flex item %p needed a final reflow due to continuation(s)",
+ mFrame);
+ return true;
+ }
+
+ // A flex item can grow its block-size in a fragmented context if there's any
+ // force break within it (bug 1663079), or if it has a repeated table header
+ // or footer (bug 1744363). We currently always reflow it.
+ //
+ // Bug 1815294: investigate if we can design a more specific condition to
+ // prevent triggering O(n^2) behavior when printing a deeply-nested flex
+ // container.
+ if (aParentReflowInput.IsInFragmentedContext()) {
+ FLEX_LOG(
+ "[frag] Flex item %p needed both a measuring reflow and a final "
+ "reflow due to being in a fragmented context.",
+ mFrame);
+ return true;
+ }
+
+ // Flex item's final content-box size (in terms of its own writing-mode):
+ const LogicalSize finalSize = mIsInlineAxisMainAxis
+ ? LogicalSize(mWM, mMainSize, mCrossSize)
+ : LogicalSize(mWM, mCrossSize, mMainSize);
+
+ if (HadMeasuringReflow()) {
+ // We've already reflowed this flex item once, to measure it. In that
+ // reflow, did its frame happen to end up with the correct final size
+ // that the flex container would like it to have?
+ if (finalSize != mFrame->ContentSize(mWM)) {
+ // The measuring reflow left the item with a different size than its
+ // final flexed size. So, we need to reflow to give it the correct size.
+ FLEX_LOG(
+ "[perf] Flex item %p needed both a measuring reflow and a final "
+ "reflow due to measured size disagreeing with final size",
+ mFrame);
+ return true;
+ }
+
+ if (FrameHasRelativeBSizeDependency(mFrame)) {
+ // This item has descendants with relative BSizes who may care that its
+ // size may now be considered "definite" in the final reflow (whereas it
+ // was indefinite during the measuring reflow).
+ FLEX_LOG(
+ "[perf] Flex item %p needed both a measuring reflow and a final "
+ "reflow due to BSize potentially becoming definite",
+ mFrame);
+ return true;
+ }
+
+ // If we get here, then this flex item had a measuring reflow, it left us
+ // with the correct size, none of its descendants care that its BSize may
+ // now be considered definite, and it can fit into the available block-size.
+ // So it doesn't need a final reflow.
+ //
+ // We now cache this size as if we had done a final reflow (because we've
+ // determined that the measuring reflow was effectively equivalent). This
+ // way, in our next time through flex layout, we may be able to skip both
+ // the measuring reflow *and* the final reflow (if conditions are the same
+ // as they are now).
+ if (auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop())) {
+ cache->Update(*this, finalSize);
+ }
+
+ return false;
+ }
+
+ // This item didn't receive a measuring reflow (at least, not during this
+ // reflow of our flex container). We may still be able to skip reflowing it
+ // (i.e. return false from this function), if its subtree is clean & its most
+ // recent "final reflow" had it at the correct content-box size &
+ // definiteness.
+ // Let's check for each condition that would still require us to reflow:
+ if (mFrame->IsSubtreeDirty()) {
+ FLEX_LOG(
+ "[perf] Flex item %p needed a final reflow due to its subtree "
+ "being dirty",
+ mFrame);
+ return true;
+ }
+
+ // Cool; this item & its subtree haven't experienced any style/content
+ // changes that would automatically require a reflow.
+
+ // Did we cache the metrics from its most recent "final reflow"?
+ auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop());
+ if (!cache || !cache->mFinalReflowMetrics) {
+ FLEX_LOG(
+ "[perf] Flex item %p needed a final reflow due to lacking a "
+ "cached mFinalReflowMetrics (maybe cache was cleared)",
+ mFrame);
+ return true;
+ }
+
+ // Does the cached size match our current size?
+ if (cache->mFinalReflowMetrics->Size() != finalSize) {
+ FLEX_LOG(
+ "[perf] Flex item %p needed a final reflow due to having a "
+ "different content box size vs. its most recent final reflow",
+ mFrame);
+ return true;
+ }
+
+ // Does the cached border and padding match our current ones?
+ //
+ // Note: this is just to detect cases where we have a percent padding whose
+ // basis has changed. Any other sort of change to BorderPadding() (e.g. a new
+ // specified value) should result in the frame being marked dirty via proper
+ // change hint (see nsStylePadding::CalcDifference()), which will force it to
+ // reflow.
+ if (cache->mFinalReflowMetrics->BorderPadding() !=
+ BorderPadding().ConvertTo(mWM, mCBWM)) {
+ FLEX_LOG(
+ "[perf] Flex item %p needed a final reflow due to having a "
+ "different border and padding vs. its most recent final reflow",
+ mFrame);
+ return true;
+ }
+
+ // The flex container is giving this flex item the same size that the item
+ // had on its most recent "final reflow". But if its definiteness changed and
+ // one of the descendants cares, then it would still need a reflow.
+ if (cache->mFinalReflowMetrics->TreatBSizeAsIndefinite() !=
+ mTreatBSizeAsIndefinite &&
+ FrameHasRelativeBSizeDependency(mFrame)) {
+ FLEX_LOG(
+ "[perf] Flex item %p needed a final reflow due to having "
+ "its BSize change definiteness & having a rel-BSize child",
+ mFrame);
+ return true;
+ }
+
+ // If we get here, we can skip the final reflow! (The item's subtree isn't
+ // dirty, and our current conditions are sufficiently similar to the most
+ // recent "final reflow" that it should have left our subtree in the correct
+ // state.)
+ FLEX_LOG("[perf] Flex item %p didn't need a final reflow", mFrame);
+ return false;
+}
+
+// Keeps track of our position along a particular axis (where a '0' position
+// corresponds to the 'start' edge of that axis).
+// This class shouldn't be instantiated directly -- rather, it should only be
+// instantiated via its subclasses defined below.
+class MOZ_STACK_CLASS PositionTracker {
+ public:
+ // Accessor for the current value of the position that we're tracking.
+ inline nscoord Position() const { return mPosition; }
+ inline LogicalAxis Axis() const { return mAxis; }
+
+ inline LogicalSide StartSide() {
+ return MakeLogicalSide(
+ mAxis, mIsAxisReversed ? eLogicalEdgeEnd : eLogicalEdgeStart);
+ }
+
+ inline LogicalSide EndSide() {
+ return MakeLogicalSide(
+ mAxis, mIsAxisReversed ? eLogicalEdgeStart : eLogicalEdgeEnd);
+ }
+
+ // Advances our position across the start edge of the given margin, in the
+ // axis we're tracking.
+ void EnterMargin(const LogicalMargin& aMargin) {
+ mPosition += aMargin.Side(StartSide(), mWM);
+ }
+
+ // Advances our position across the end edge of the given margin, in the axis
+ // we're tracking.
+ void ExitMargin(const LogicalMargin& aMargin) {
+ mPosition += aMargin.Side(EndSide(), mWM);
+ }
+
+ // Advances our current position from the start side of a child frame's
+ // border-box to the frame's upper or left edge (depending on our axis).
+ // (Note that this is a no-op if our axis grows in the same direction as
+ // the corresponding logical axis.)
+ void EnterChildFrame(nscoord aChildFrameSize) {
+ if (mIsAxisReversed) {
+ mPosition += aChildFrameSize;
+ }
+ }
+
+ // Advances our current position from a frame's upper or left border-box edge
+ // (whichever is in the axis we're tracking) to the 'end' side of the frame
+ // in the axis that we're tracking. (Note that this is a no-op if our axis
+ // is reversed with respect to the corresponding logical axis.)
+ void ExitChildFrame(nscoord aChildFrameSize) {
+ if (!mIsAxisReversed) {
+ mPosition += aChildFrameSize;
+ }
+ }
+
+ // Delete copy-constructor & reassignment operator, to prevent accidental
+ // (unnecessary) copying.
+ PositionTracker(const PositionTracker&) = delete;
+ PositionTracker& operator=(const PositionTracker&) = delete;
+
+ protected:
+ // Protected constructor, to be sure we're only instantiated via a subclass.
+ PositionTracker(WritingMode aWM, LogicalAxis aAxis, bool aIsAxisReversed)
+ : mWM(aWM), mAxis(aAxis), mIsAxisReversed(aIsAxisReversed) {}
+
+ // Member data:
+ // The position we're tracking.
+ nscoord mPosition = 0;
+
+ // The flex container's writing mode.
+ const WritingMode mWM;
+
+ // The axis along which we're moving.
+ const LogicalAxis mAxis = eLogicalAxisInline;
+
+ // Is the axis along which we're moving reversed (e.g. LTR vs RTL) with
+ // respect to the corresponding axis on the flex container's WM?
+ const bool mIsAxisReversed = false;
+};
+
+// Tracks our position in the main axis, when we're laying out flex items.
+// The "0" position represents the main-start edge of the flex container's
+// content-box.
+class MOZ_STACK_CLASS MainAxisPositionTracker : public PositionTracker {
+ public:
+ MainAxisPositionTracker(const FlexboxAxisTracker& aAxisTracker,
+ const FlexLine* aLine,
+ const StyleContentDistribution& aJustifyContent,
+ nscoord aContentBoxMainSize);
+
+ ~MainAxisPositionTracker() {
+ MOZ_ASSERT(mNumPackingSpacesRemaining == 0,
+ "miscounted the number of packing spaces");
+ MOZ_ASSERT(mNumAutoMarginsInMainAxis == 0,
+ "miscounted the number of auto margins");
+ }
+
+ // Advances past the gap space (if any) between two flex items
+ void TraverseGap(nscoord aGapSize) { mPosition += aGapSize; }
+
+ // Advances past the packing space (if any) between two flex items
+ void TraversePackingSpace();
+
+ // If aItem has any 'auto' margins in the main axis, this method updates the
+ // corresponding values in its margin.
+ void ResolveAutoMarginsInMainAxis(FlexItem& aItem);
+
+ private:
+ nscoord mPackingSpaceRemaining = 0;
+ uint32_t mNumAutoMarginsInMainAxis = 0;
+ uint32_t mNumPackingSpacesRemaining = 0;
+ StyleContentDistribution mJustifyContent = {StyleAlignFlags::AUTO};
+};
+
+// Utility class for managing our position along the cross axis along
+// the whole flex container (at a higher level than a single line).
+// The "0" position represents the cross-start edge of the flex container's
+// content-box.
+class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker {
+ public:
+ CrossAxisPositionTracker(nsTArray<FlexLine>& aLines,
+ const ReflowInput& aReflowInput,
+ nscoord aContentBoxCrossSize,
+ bool aIsCrossSizeDefinite,
+ const FlexboxAxisTracker& aAxisTracker,
+ const nscoord aCrossGapSize);
+
+ // Advances past the gap (if any) between two flex lines
+ void TraverseGap() { mPosition += mCrossGapSize; }
+
+ // Advances past the packing space (if any) between two flex lines
+ void TraversePackingSpace();
+
+ // Advances past the given FlexLine
+ void TraverseLine(FlexLine& aLine) { mPosition += aLine.LineCrossSize(); }
+
+ // Redeclare the frame-related methods from PositionTracker with
+ // = delete, to be sure (at compile time) that no client code can invoke
+ // them. (Unlike the other PositionTracker derived classes, this class here
+ // deals with FlexLines, not with individual FlexItems or frames.)
+ void EnterMargin(const LogicalMargin& aMargin) = delete;
+ void ExitMargin(const LogicalMargin& aMargin) = delete;
+ void EnterChildFrame(nscoord aChildFrameSize) = delete;
+ void ExitChildFrame(nscoord aChildFrameSize) = delete;
+
+ private:
+ nscoord mPackingSpaceRemaining = 0;
+ uint32_t mNumPackingSpacesRemaining = 0;
+ StyleContentDistribution mAlignContent = {StyleAlignFlags::AUTO};
+
+ const nscoord mCrossGapSize;
+};
+
+// Utility class for managing our position along the cross axis, *within* a
+// single flex line.
+class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker
+ : public PositionTracker {
+ public:
+ explicit SingleLineCrossAxisPositionTracker(
+ const FlexboxAxisTracker& aAxisTracker);
+
+ void ResolveAutoMarginsInCrossAxis(const FlexLine& aLine, FlexItem& aItem);
+
+ void EnterAlignPackingSpace(const FlexLine& aLine, const FlexItem& aItem,
+ const FlexboxAxisTracker& aAxisTracker);
+
+ // Resets our position to the cross-start edge of this line.
+ inline void ResetPosition() { mPosition = 0; }
+};
+
+//----------------------------------------------------------------------
+
+// Frame class boilerplate
+// =======================
+
+NS_QUERYFRAME_HEAD(nsFlexContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsFlexContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsFlexContainerFrame)
+
+nsContainerFrame* NS_NewFlexContainerFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsFlexContainerFrame(aStyle, aPresShell->GetPresContext());
+}
+
+//----------------------------------------------------------------------
+
+// nsFlexContainerFrame Method Implementations
+// ===========================================
+
+/* virtual */
+nsFlexContainerFrame::~nsFlexContainerFrame() = default;
+
+/* virtual */
+void nsFlexContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+
+ auto displayInside = StyleDisplay()->DisplayInside();
+ // If this frame is for a scrollable element, then it will actually have
+ // "display:block", and its *parent frame* will have the real
+ // flex-flavored display value. So in that case, check the parent frame to
+ // find out if we're legacy.
+ //
+ // TODO(emilio): Maybe ::-moz-scrolled-content and co should inherit `display`
+ // (or a blockified version thereof, to not hit bug 456484).
+ if (displayInside == StyleDisplayInside::Flow) {
+ MOZ_ASSERT(StyleDisplay()->mDisplay == StyleDisplay::Block);
+ MOZ_ASSERT(Style()->GetPseudoType() == PseudoStyleType::buttonContent ||
+ Style()->GetPseudoType() == PseudoStyleType::scrolledContent,
+ "The only way a nsFlexContainerFrame can have 'display:block' "
+ "should be if it's the inner part of a scrollable or button "
+ "element");
+ displayInside = GetParent()->StyleDisplay()->DisplayInside();
+ }
+
+ // Figure out if we should set a frame state bit to indicate that this frame
+ // represents a legacy -moz-{inline-}box or -webkit-{inline-}box container.
+ if (displayInside == StyleDisplayInside::WebkitBox) {
+ AddStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
+ }
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsFlexContainerFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"FlexContainer"_ns, aResult);
+}
+#endif
+
+void nsFlexContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ nsDisplayListCollection tempLists(aBuilder);
+
+ DisplayBorderBackgroundOutline(aBuilder, tempLists);
+ if (GetPrevInFlow()) {
+ DisplayOverflowContainers(aBuilder, tempLists);
+ }
+
+ // Our children are all block-level, so their borders/backgrounds all go on
+ // the BlockBorderBackgrounds list.
+ nsDisplayListSet childLists(tempLists, tempLists.BlockBorderBackgrounds());
+
+ CSSOrderAwareFrameIterator iter(
+ this, FrameChildListID::Principal,
+ CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
+ OrderStateForIter(this), OrderingPropertyForIter(this));
+
+ const auto flags = DisplayFlagsForFlexOrGridItem();
+ for (; !iter.AtEnd(); iter.Next()) {
+ nsIFrame* childFrame = *iter;
+ BuildDisplayListForChild(aBuilder, childFrame, childLists, flags);
+ }
+
+ tempLists.MoveTo(aLists);
+}
+
+void FlexLine::FreezeItemsEarly(bool aIsUsingFlexGrow,
+ ComputedFlexLineInfo* aLineInfo) {
+ // After we've established the type of flexing we're doing (growing vs.
+ // shrinking), and before we try to flex any items, we freeze items that
+ // obviously *can't* flex.
+ //
+ // Quoting the spec:
+ // # Freeze, setting its target main size to its hypothetical main size...
+ // # - any item that has a flex factor of zero
+ // # - if using the flex grow factor: any item that has a flex base size
+ // # greater than its hypothetical main size
+ // # - if using the flex shrink factor: any item that has a flex base size
+ // # smaller than its hypothetical main size
+ // https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths
+ //
+ // (NOTE: At this point, item->MainSize() *is* the item's hypothetical
+ // main size, since SetFlexBaseSizeAndMainSize() sets it up that way, and the
+ // item hasn't had a chance to flex away from that yet.)
+
+ // Since this loop only operates on unfrozen flex items, we can break as
+ // soon as we have seen all of them.
+ uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
+ for (FlexItem& item : Items()) {
+ if (numUnfrozenItemsToBeSeen == 0) {
+ break;
+ }
+
+ if (!item.IsFrozen()) {
+ numUnfrozenItemsToBeSeen--;
+ bool shouldFreeze = (0.0f == item.GetFlexFactor(aIsUsingFlexGrow));
+ if (!shouldFreeze) {
+ if (aIsUsingFlexGrow) {
+ if (item.FlexBaseSize() > item.MainSize()) {
+ shouldFreeze = true;
+ }
+ } else { // using flex-shrink
+ if (item.FlexBaseSize() < item.MainSize()) {
+ shouldFreeze = true;
+ }
+ }
+ }
+ if (shouldFreeze) {
+ // Freeze item! (at its hypothetical main size)
+ item.Freeze();
+ if (item.FlexBaseSize() < item.MainSize()) {
+ item.SetWasMinClamped();
+ } else if (item.FlexBaseSize() > item.MainSize()) {
+ item.SetWasMaxClamped();
+ }
+ mNumFrozenItems++;
+ }
+ }
+ }
+
+ MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
+}
+
+// Based on the sign of aTotalViolation, this function freezes a subset of our
+// flexible sizes, and restores the remaining ones to their initial pref sizes.
+void FlexLine::FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
+ bool aIsFinalIteration) {
+ enum FreezeType {
+ eFreezeEverything,
+ eFreezeMinViolations,
+ eFreezeMaxViolations
+ };
+
+ FreezeType freezeType;
+ if (aTotalViolation == 0) {
+ freezeType = eFreezeEverything;
+ } else if (aTotalViolation > 0) {
+ freezeType = eFreezeMinViolations;
+ } else { // aTotalViolation < 0
+ freezeType = eFreezeMaxViolations;
+ }
+
+ // Since this loop only operates on unfrozen flex items, we can break as
+ // soon as we have seen all of them.
+ uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
+ for (FlexItem& item : Items()) {
+ if (numUnfrozenItemsToBeSeen == 0) {
+ break;
+ }
+
+ if (!item.IsFrozen()) {
+ numUnfrozenItemsToBeSeen--;
+
+ MOZ_ASSERT(!item.HadMinViolation() || !item.HadMaxViolation(),
+ "Can have either min or max violation, but not both");
+
+ bool hadMinViolation = item.HadMinViolation();
+ bool hadMaxViolation = item.HadMaxViolation();
+ if (eFreezeEverything == freezeType ||
+ (eFreezeMinViolations == freezeType && hadMinViolation) ||
+ (eFreezeMaxViolations == freezeType && hadMaxViolation)) {
+ MOZ_ASSERT(item.MainSize() >= item.MainMinSize(),
+ "Freezing item at a size below its minimum");
+ MOZ_ASSERT(item.MainSize() <= item.MainMaxSize(),
+ "Freezing item at a size above its maximum");
+
+ item.Freeze();
+ if (hadMinViolation) {
+ item.SetWasMinClamped();
+ } else if (hadMaxViolation) {
+ item.SetWasMaxClamped();
+ }
+ mNumFrozenItems++;
+ } else if (MOZ_UNLIKELY(aIsFinalIteration)) {
+ // XXXdholbert If & when bug 765861 is fixed, we should upgrade this
+ // assertion to be fatal except in documents with enormous lengths.
+ NS_ERROR(
+ "Final iteration still has unfrozen items, this shouldn't"
+ " happen unless there was nscoord under/overflow.");
+ item.Freeze();
+ mNumFrozenItems++;
+ } // else, we'll reset this item's main size to its flex base size on the
+ // next iteration of this algorithm.
+
+ if (!item.IsFrozen()) {
+ // Clear this item's violation(s), now that we've dealt with them
+ item.ClearViolationFlags();
+ }
+ }
+ }
+
+ MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
+}
+
+void FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
+ ComputedFlexLineInfo* aLineInfo) {
+ // In this function, we use 64-bit coord type to avoid integer overflow in
+ // case several of the individual items have huge hypothetical main sizes,
+ // which can happen with percent-width table-layout:fixed descendants. Here we
+ // promote the container's main size to 64-bit to make the arithmetic
+ // convenient.
+ AuCoord64 flexContainerMainSize(aFlexContainerMainSize);
+
+ // Before we start resolving sizes: if we have an aLineInfo structure to fill
+ // out, we inform it of each item's base size, and we initialize the "delta"
+ // for each item to 0. (And if the flex algorithm wants to grow or shrink the
+ // item, we'll update this delta further down.)
+ if (aLineInfo) {
+ uint32_t itemIndex = 0;
+ for (FlexItem& item : Items()) {
+ aLineInfo->mItems[itemIndex].mMainBaseSize = item.FlexBaseSize();
+ aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
+ ++itemIndex;
+ }
+ }
+
+ // Determine whether we're going to be growing or shrinking items.
+ const bool isUsingFlexGrow =
+ (mTotalOuterHypotheticalMainSize < flexContainerMainSize);
+
+ if (aLineInfo) {
+ aLineInfo->mGrowthState =
+ isUsingFlexGrow ? mozilla::dom::FlexLineGrowthState::Growing
+ : mozilla::dom::FlexLineGrowthState::Shrinking;
+ }
+
+ // Do an "early freeze" for flex items that obviously can't flex in the
+ // direction we've chosen:
+ FreezeItemsEarly(isUsingFlexGrow, aLineInfo);
+
+ if ((mNumFrozenItems == NumItems()) && !aLineInfo) {
+ // All our items are frozen, so we have no flexible lengths to resolve,
+ // and we aren't being asked to generate computed line info.
+ FLEX_LOG("No flexible length to resolve");
+ return;
+ }
+ MOZ_ASSERT(!IsEmpty() || aLineInfo,
+ "empty lines should take the early-return above");
+
+ FLEX_LOG("Resolving flexible lengths for items");
+
+ // Subtract space occupied by our items' margins/borders/padding/gaps, so
+ // we can just be dealing with the space available for our flex items' content
+ // boxes.
+ const AuCoord64 totalItemMBPAndGaps = mTotalItemMBP + SumOfGaps();
+ const AuCoord64 spaceAvailableForFlexItemsContentBoxes =
+ flexContainerMainSize - totalItemMBPAndGaps;
+
+ Maybe<AuCoord64> origAvailableFreeSpace;
+
+ // NOTE: I claim that this chunk of the algorithm (the looping part) needs to
+ // run the loop at MOST NumItems() times. This claim should hold up
+ // because we'll freeze at least one item on each loop iteration, and once
+ // we've run out of items to freeze, there's nothing left to do. However,
+ // in most cases, we'll break out of this loop long before we hit that many
+ // iterations.
+ for (uint32_t iterationCounter = 0; iterationCounter < NumItems();
+ iterationCounter++) {
+ // Set every not-yet-frozen item's used main size to its
+ // flex base size, and subtract all the used main sizes from our
+ // total amount of space to determine the 'available free space'
+ // (positive or negative) to be distributed among our flexible items.
+ AuCoord64 availableFreeSpace = spaceAvailableForFlexItemsContentBoxes;
+ for (FlexItem& item : Items()) {
+ if (!item.IsFrozen()) {
+ item.SetMainSize(item.FlexBaseSize());
+ }
+ availableFreeSpace -= item.MainSize();
+ }
+
+ FLEX_LOG(" available free space: %" PRId64 "; flex items should \"%s\"",
+ availableFreeSpace.value, isUsingFlexGrow ? "grow" : "shrink");
+
+ // The sign of our free space should agree with the type of flexing
+ // (grow/shrink) that we're doing. Any disagreement should've made us use
+ // the other type of flexing, or should've been resolved in
+ // FreezeItemsEarly.
+ //
+ // Note: it's possible that an individual flex item has huge
+ // margin/border/padding that makes either its
+ // MarginBorderPaddingSizeInMainAxis() or OuterMainSize() negative due to
+ // integer overflow. If that happens, the accumulated
+ // mTotalOuterHypotheticalMainSize or mTotalItemMBP could be negative due to
+ // that one item's negative (overflowed) size. Likewise, a huge main gap
+ // size between flex items can also make our accumulated SumOfGaps()
+ // negative. In these case, we throw up our hands and don't require
+ // isUsingFlexGrow to agree with availableFreeSpace. Luckily, we won't get
+ // stuck in the algorithm below, and just distribute the wrong
+ // availableFreeSpace with the wrong grow/shrink factors.
+ MOZ_ASSERT(!(mTotalOuterHypotheticalMainSize >= 0 && mTotalItemMBP >= 0 &&
+ totalItemMBPAndGaps >= 0) ||
+ (isUsingFlexGrow && availableFreeSpace >= 0) ||
+ (!isUsingFlexGrow && availableFreeSpace <= 0),
+ "availableFreeSpace's sign should match isUsingFlexGrow");
+
+ // If we have any free space available, give each flexible item a portion
+ // of availableFreeSpace.
+ if (availableFreeSpace != AuCoord64(0)) {
+ // The first time we do this, we initialize origAvailableFreeSpace.
+ if (!origAvailableFreeSpace) {
+ origAvailableFreeSpace.emplace(availableFreeSpace);
+ }
+
+ // STRATEGY: On each item, we compute & store its "share" of the total
+ // weight that we've seen so far:
+ // curWeight / weightSum
+ //
+ // Then, when we go to actually distribute the space (in the next loop),
+ // we can simply walk backwards through the elements and give each item
+ // its "share" multiplied by the remaining available space.
+ //
+ // SPECIAL CASE: If the sum of the weights is larger than the
+ // maximum representable double (overflowing to infinity), then we can't
+ // sensibly divide out proportional shares anymore. In that case, we
+ // simply treat the flex item(s) with the largest weights as if
+ // their weights were infinite (dwarfing all the others), and we
+ // distribute all of the available space among them.
+ double weightSum = 0.0;
+ double flexFactorSum = 0.0;
+ double largestWeight = 0.0;
+ uint32_t numItemsWithLargestWeight = 0;
+
+ // Since this loop only operates on unfrozen flex items, we can break as
+ // soon as we have seen all of them.
+ uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
+ for (FlexItem& item : Items()) {
+ if (numUnfrozenItemsToBeSeen == 0) {
+ break;
+ }
+
+ if (!item.IsFrozen()) {
+ numUnfrozenItemsToBeSeen--;
+
+ const double curWeight = item.GetWeight(isUsingFlexGrow);
+ const double curFlexFactor = item.GetFlexFactor(isUsingFlexGrow);
+ MOZ_ASSERT(curWeight >= 0.0, "weights are non-negative");
+ MOZ_ASSERT(curFlexFactor >= 0.0, "flex factors are non-negative");
+
+ weightSum += curWeight;
+ flexFactorSum += curFlexFactor;
+
+ if (std::isfinite(weightSum)) {
+ if (curWeight == 0.0) {
+ item.SetShareOfWeightSoFar(0.0);
+ } else {
+ item.SetShareOfWeightSoFar(curWeight / weightSum);
+ }
+ } // else, the sum of weights overflows to infinity, in which
+ // case we don't bother with "SetShareOfWeightSoFar" since
+ // we know we won't use it. (instead, we'll just give every
+ // item with the largest weight an equal share of space.)
+
+ // Update our largest-weight tracking vars
+ if (curWeight > largestWeight) {
+ largestWeight = curWeight;
+ numItemsWithLargestWeight = 1;
+ } else if (curWeight == largestWeight) {
+ numItemsWithLargestWeight++;
+ }
+ }
+ }
+
+ MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
+
+ if (weightSum != 0.0) {
+ MOZ_ASSERT(flexFactorSum != 0.0,
+ "flex factor sum can't be 0, if a weighted sum "
+ "of its components (weightSum) is nonzero");
+ if (flexFactorSum < 1.0) {
+ // Our unfrozen flex items don't want all of the original free space!
+ // (Their flex factors add up to something less than 1.)
+ // Hence, make sure we don't distribute any more than the portion of
+ // our original free space that these items actually want.
+ auto totalDesiredPortionOfOrigFreeSpace =
+ AuCoord64::FromRound(*origAvailableFreeSpace * flexFactorSum);
+
+ // Clamp availableFreeSpace to be no larger than that ^^.
+ // (using min or max, depending on sign).
+ // This should not change the sign of availableFreeSpace (except
+ // possibly by setting it to 0), as enforced by this assertion:
+ NS_ASSERTION(totalDesiredPortionOfOrigFreeSpace == AuCoord64(0) ||
+ ((totalDesiredPortionOfOrigFreeSpace > 0) ==
+ (availableFreeSpace > 0)),
+ "When we reduce available free space for flex "
+ "factors < 1, we shouldn't change the sign of the "
+ "free space...");
+
+ if (availableFreeSpace > 0) {
+ availableFreeSpace = std::min(availableFreeSpace,
+ totalDesiredPortionOfOrigFreeSpace);
+ } else {
+ availableFreeSpace = std::max(availableFreeSpace,
+ totalDesiredPortionOfOrigFreeSpace);
+ }
+ }
+
+ FLEX_LOG(" Distributing available space:");
+ // Since this loop only operates on unfrozen flex items, we can break as
+ // soon as we have seen all of them.
+ numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
+
+ // NOTE: It's important that we traverse our items in *reverse* order
+ // here, for correct width distribution according to the items'
+ // "ShareOfWeightSoFar" progressively-calculated values.
+ for (FlexItem& item : Reversed(Items())) {
+ if (numUnfrozenItemsToBeSeen == 0) {
+ break;
+ }
+
+ if (!item.IsFrozen()) {
+ numUnfrozenItemsToBeSeen--;
+
+ // To avoid rounding issues, we compute the change in size for this
+ // item, and then subtract it from the remaining available space.
+ AuCoord64 sizeDelta = 0;
+ if (std::isfinite(weightSum)) {
+ double myShareOfRemainingSpace = item.ShareOfWeightSoFar();
+
+ MOZ_ASSERT(myShareOfRemainingSpace >= 0.0 &&
+ myShareOfRemainingSpace <= 1.0,
+ "my share should be nonnegative fractional amount");
+
+ if (myShareOfRemainingSpace == 1.0) {
+ // (We special-case 1.0 to avoid float error from converting
+ // availableFreeSpace from integer*1.0 --> double --> integer)
+ sizeDelta = availableFreeSpace;
+ } else if (myShareOfRemainingSpace > 0.0) {
+ sizeDelta = AuCoord64::FromRound(availableFreeSpace *
+ myShareOfRemainingSpace);
+ }
+ } else if (item.GetWeight(isUsingFlexGrow) == largestWeight) {
+ // Total flexibility is infinite, so we're just distributing
+ // the available space equally among the items that are tied for
+ // having the largest weight (and this is one of those items).
+ sizeDelta = AuCoord64::FromRound(
+ availableFreeSpace / double(numItemsWithLargestWeight));
+ numItemsWithLargestWeight--;
+ }
+
+ availableFreeSpace -= sizeDelta;
+
+ item.SetMainSize(item.MainSize() +
+ nscoord(sizeDelta.ToMinMaxClamped()));
+ FLEX_LOG(" flex item %p receives %" PRId64 ", for a total of %d",
+ item.Frame(), sizeDelta.value, item.MainSize());
+ }
+ }
+
+ MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
+
+ // If we have an aLineInfo structure to fill out, capture any
+ // size changes that may have occurred in the previous loop.
+ // We don't do this inside the previous loop, because we don't
+ // want to burden layout when aLineInfo is null.
+ if (aLineInfo) {
+ uint32_t itemIndex = 0;
+ for (FlexItem& item : Items()) {
+ if (!item.IsFrozen()) {
+ // Calculate a deltaSize that represents how much the flex sizing
+ // algorithm "wants" to stretch or shrink this item during this
+ // pass through the algorithm. Later passes through the algorithm
+ // may overwrite this, until this item is frozen. Note that this
+ // value may not reflect how much the size of the item is
+ // actually changed, since the size of the item will be clamped
+ // to min and max values later in this pass. That's intentional,
+ // since we want to report the value that the sizing algorithm
+ // tried to stretch or shrink the item.
+ nscoord deltaSize =
+ item.MainSize() - aLineInfo->mItems[itemIndex].mMainBaseSize;
+
+ aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
+ }
+ ++itemIndex;
+ }
+ }
+ }
+ }
+
+ // Fix min/max violations:
+ nscoord totalViolation = 0; // keeps track of adjustments for min/max
+ FLEX_LOG(" Checking for violations:");
+
+ // Since this loop only operates on unfrozen flex items, we can break as
+ // soon as we have seen all of them.
+ uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
+ for (FlexItem& item : Items()) {
+ if (numUnfrozenItemsToBeSeen == 0) {
+ break;
+ }
+
+ if (!item.IsFrozen()) {
+ numUnfrozenItemsToBeSeen--;
+
+ if (item.MainSize() < item.MainMinSize()) {
+ // min violation
+ totalViolation += item.MainMinSize() - item.MainSize();
+ item.SetMainSize(item.MainMinSize());
+ item.SetHadMinViolation();
+ } else if (item.MainSize() > item.MainMaxSize()) {
+ // max violation
+ totalViolation += item.MainMaxSize() - item.MainSize();
+ item.SetMainSize(item.MainMaxSize());
+ item.SetHadMaxViolation();
+ }
+ }
+ }
+
+ MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
+
+ FreezeOrRestoreEachFlexibleSize(totalViolation,
+ iterationCounter + 1 == NumItems());
+
+ FLEX_LOG(" Total violation: %d", totalViolation);
+
+ if (mNumFrozenItems == NumItems()) {
+ break;
+ }
+
+ MOZ_ASSERT(totalViolation != 0,
+ "Zero violation should've made us freeze all items & break");
+ }
+
+#ifdef DEBUG
+ // Post-condition: all items should've been frozen.
+ // Make sure the counts match:
+ MOZ_ASSERT(mNumFrozenItems == NumItems(), "All items should be frozen");
+
+ // For good measure, check each item directly, in case our counts are busted:
+ for (const FlexItem& item : Items()) {
+ MOZ_ASSERT(item.IsFrozen(), "All items should be frozen");
+ }
+#endif // DEBUG
+}
+
+MainAxisPositionTracker::MainAxisPositionTracker(
+ const FlexboxAxisTracker& aAxisTracker, const FlexLine* aLine,
+ const StyleContentDistribution& aJustifyContent,
+ nscoord aContentBoxMainSize)
+ : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.MainAxis(),
+ aAxisTracker.IsMainAxisReversed()),
+ // we chip away at this below
+ mPackingSpaceRemaining(aContentBoxMainSize),
+ mJustifyContent(aJustifyContent) {
+ // Extract the flag portion of mJustifyContent and strip off the flag bits
+ // NOTE: This must happen before any assignment to mJustifyContent to
+ // avoid overwriting the flag bits.
+ StyleAlignFlags justifyContentFlags =
+ mJustifyContent.primary & StyleAlignFlags::FLAG_BITS;
+ mJustifyContent.primary &= ~StyleAlignFlags::FLAG_BITS;
+
+ // 'normal' behaves as 'stretch', and 'stretch' behaves as 'flex-start',
+ // in the main axis
+ // https://drafts.csswg.org/css-align-3/#propdef-justify-content
+ if (mJustifyContent.primary == StyleAlignFlags::NORMAL ||
+ mJustifyContent.primary == StyleAlignFlags::STRETCH) {
+ mJustifyContent.primary = StyleAlignFlags::FLEX_START;
+ }
+
+ // mPackingSpaceRemaining is initialized to the container's main size. Now
+ // we'll subtract out the main sizes of our flex items, so that it ends up
+ // with the *actual* amount of packing space.
+ for (const FlexItem& item : aLine->Items()) {
+ mPackingSpaceRemaining -= item.OuterMainSize();
+ mNumAutoMarginsInMainAxis += item.NumAutoMarginsInMainAxis();
+ }
+
+ // Subtract space required for row/col gap from the remaining packing space
+ mPackingSpaceRemaining -= aLine->SumOfGaps();
+
+ if (mPackingSpaceRemaining <= 0) {
+ // No available packing space to use for resolving auto margins.
+ mNumAutoMarginsInMainAxis = 0;
+ // If packing space is negative and <overflow-position> is set to 'safe'
+ // all justify options fall back to 'start'
+ if (justifyContentFlags & StyleAlignFlags::SAFE) {
+ mJustifyContent.primary = StyleAlignFlags::START;
+ }
+ }
+
+ // If packing space is negative or we only have one item, 'space-between'
+ // falls back to 'flex-start', and 'space-around' & 'space-evenly' fall back
+ // to 'center'. In those cases, it's simplest to just pretend we have a
+ // different 'justify-content' value and share code.
+ if (mPackingSpaceRemaining < 0 || aLine->NumItems() == 1) {
+ if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN) {
+ mJustifyContent.primary = StyleAlignFlags::FLEX_START;
+ } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
+ mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) {
+ mJustifyContent.primary = StyleAlignFlags::CENTER;
+ }
+ }
+
+ // Map 'left'/'right' to 'start'/'end'
+ if (mJustifyContent.primary == StyleAlignFlags::LEFT ||
+ mJustifyContent.primary == StyleAlignFlags::RIGHT) {
+ mJustifyContent.primary =
+ aAxisTracker.ResolveJustifyLeftRight(mJustifyContent.primary);
+ }
+
+ // Map 'start'/'end' to 'flex-start'/'flex-end'.
+ if (mJustifyContent.primary == StyleAlignFlags::START) {
+ mJustifyContent.primary = aAxisTracker.IsMainAxisReversed()
+ ? StyleAlignFlags::FLEX_END
+ : StyleAlignFlags::FLEX_START;
+ } else if (mJustifyContent.primary == StyleAlignFlags::END) {
+ mJustifyContent.primary = aAxisTracker.IsMainAxisReversed()
+ ? StyleAlignFlags::FLEX_START
+ : StyleAlignFlags::FLEX_END;
+ }
+
+ // Figure out how much space we'll set aside for auto margins or
+ // packing spaces, and advance past any leading packing-space.
+ if (mNumAutoMarginsInMainAxis == 0 && mPackingSpaceRemaining != 0 &&
+ !aLine->IsEmpty()) {
+ if (mJustifyContent.primary == StyleAlignFlags::FLEX_START) {
+ // All packing space should go at the end --> nothing to do here.
+ } else if (mJustifyContent.primary == StyleAlignFlags::FLEX_END) {
+ // All packing space goes at the beginning
+ mPosition += mPackingSpaceRemaining;
+ } else if (mJustifyContent.primary == StyleAlignFlags::CENTER) {
+ // Half the packing space goes at the beginning
+ mPosition += mPackingSpaceRemaining / 2;
+ } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
+ mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
+ mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) {
+ nsFlexContainerFrame::CalculatePackingSpace(
+ aLine->NumItems(), mJustifyContent, &mPosition,
+ &mNumPackingSpacesRemaining, &mPackingSpaceRemaining);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected justify-content value");
+ }
+ }
+
+ MOZ_ASSERT(mNumPackingSpacesRemaining == 0 || mNumAutoMarginsInMainAxis == 0,
+ "extra space should either go to packing space or to "
+ "auto margins, but not to both");
+}
+
+void MainAxisPositionTracker::ResolveAutoMarginsInMainAxis(FlexItem& aItem) {
+ if (mNumAutoMarginsInMainAxis) {
+ const auto& styleMargin = aItem.Frame()->StyleMargin()->mMargin;
+ for (const auto side : {StartSide(), EndSide()}) {
+ if (styleMargin.Get(mWM, side).IsAuto()) {
+ // NOTE: This integer math will skew the distribution of remainder
+ // app-units towards the end, which is fine.
+ nscoord curAutoMarginSize =
+ mPackingSpaceRemaining / mNumAutoMarginsInMainAxis;
+
+ MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0,
+ "Expecting auto margins to have value '0' before we "
+ "resolve them");
+ aItem.SetMarginComponentForSide(side, curAutoMarginSize);
+
+ mNumAutoMarginsInMainAxis--;
+ mPackingSpaceRemaining -= curAutoMarginSize;
+ }
+ }
+ }
+}
+
+void MainAxisPositionTracker::TraversePackingSpace() {
+ if (mNumPackingSpacesRemaining) {
+ MOZ_ASSERT(mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
+ mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
+ mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY,
+ "mNumPackingSpacesRemaining only applies for "
+ "space-between/space-around/space-evenly");
+
+ MOZ_ASSERT(mPackingSpaceRemaining >= 0,
+ "ran out of packing space earlier than we expected");
+
+ // NOTE: This integer math will skew the distribution of remainder
+ // app-units towards the end, which is fine.
+ nscoord curPackingSpace =
+ mPackingSpaceRemaining / mNumPackingSpacesRemaining;
+
+ mPosition += curPackingSpace;
+ mNumPackingSpacesRemaining--;
+ mPackingSpaceRemaining -= curPackingSpace;
+ }
+}
+
+CrossAxisPositionTracker::CrossAxisPositionTracker(
+ nsTArray<FlexLine>& aLines, const ReflowInput& aReflowInput,
+ nscoord aContentBoxCrossSize, bool aIsCrossSizeDefinite,
+ const FlexboxAxisTracker& aAxisTracker, const nscoord aCrossGapSize)
+ : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(),
+ aAxisTracker.IsCrossAxisReversed()),
+ mAlignContent(aReflowInput.mStylePosition->mAlignContent),
+ mCrossGapSize(aCrossGapSize) {
+ // Extract and strip the flag bits from alignContent
+ StyleAlignFlags alignContentFlags =
+ mAlignContent.primary & StyleAlignFlags::FLAG_BITS;
+ mAlignContent.primary &= ~StyleAlignFlags::FLAG_BITS;
+
+ // 'normal' behaves as 'stretch'
+ if (mAlignContent.primary == StyleAlignFlags::NORMAL) {
+ mAlignContent.primary = StyleAlignFlags::STRETCH;
+ }
+
+ const bool isSingleLine =
+ StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
+ if (isSingleLine) {
+ MOZ_ASSERT(aLines.Length() == 1,
+ "If we're styled as single-line, we should only have 1 line");
+ // "If the flex container is single-line and has a definite cross size, the
+ // cross size of the flex line is the flex container's inner cross size."
+ //
+ // SOURCE: https://drafts.csswg.org/css-flexbox/#algo-cross-line
+ // NOTE: This means (by definition) that there's no packing space, which
+ // means we don't need to be concerned with "align-content" at all and we
+ // can return early. This is handy, because this is the usual case (for
+ // single-line flexbox).
+ if (aIsCrossSizeDefinite) {
+ aLines[0].SetLineCrossSize(aContentBoxCrossSize);
+ return;
+ }
+
+ // "If the flex container is single-line, then clamp the line's
+ // cross-size to be within the container's computed min and max cross-size
+ // properties."
+ aLines[0].SetLineCrossSize(
+ aReflowInput.ApplyMinMaxBSize(aLines[0].LineCrossSize()));
+ }
+
+ // NOTE: The rest of this function should essentially match
+ // MainAxisPositionTracker's constructor, though with FlexLines instead of
+ // FlexItems, and with the additional value "stretch" (and of course with
+ // cross sizes instead of main sizes.)
+
+ // Figure out how much packing space we have (container's cross size minus
+ // all the lines' cross sizes). Also, share this loop to count how many
+ // lines we have. (We need that count in some cases below.)
+ mPackingSpaceRemaining = aContentBoxCrossSize;
+ uint32_t numLines = 0;
+ for (FlexLine& line : aLines) {
+ mPackingSpaceRemaining -= line.LineCrossSize();
+ numLines++;
+ }
+
+ // Subtract space required for row/col gap from the remaining packing space
+ MOZ_ASSERT(numLines >= 1,
+ "GenerateFlexLines should've produced at least 1 line");
+ mPackingSpaceRemaining -= aCrossGapSize * (numLines - 1);
+
+ // If <overflow-position> is 'safe' and packing space is negative
+ // all align options fall back to 'start'
+ if ((alignContentFlags & StyleAlignFlags::SAFE) &&
+ mPackingSpaceRemaining < 0) {
+ mAlignContent.primary = StyleAlignFlags::START;
+ }
+
+ // If packing space is negative, 'space-between' and 'stretch' behave like
+ // 'flex-start', and 'space-around' and 'space-evenly' behave like 'center'.
+ // In those cases, it's simplest to just pretend we have a different
+ // 'align-content' value and share code. (If we only have one line, all of
+ // the 'space-*' keywords fall back as well, but 'stretch' doesn't because
+ // even a single line can still stretch.)
+ if (mPackingSpaceRemaining < 0 &&
+ mAlignContent.primary == StyleAlignFlags::STRETCH) {
+ mAlignContent.primary = StyleAlignFlags::FLEX_START;
+ } else if (mPackingSpaceRemaining < 0 || numLines == 1) {
+ if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN) {
+ mAlignContent.primary = StyleAlignFlags::FLEX_START;
+ } else if (mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
+ mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) {
+ mAlignContent.primary = StyleAlignFlags::CENTER;
+ }
+ }
+
+ // Map 'start'/'end' to 'flex-start'/'flex-end'.
+ if (mAlignContent.primary == StyleAlignFlags::START) {
+ mAlignContent.primary = aAxisTracker.IsCrossAxisReversed()
+ ? StyleAlignFlags::FLEX_END
+ : StyleAlignFlags::FLEX_START;
+ } else if (mAlignContent.primary == StyleAlignFlags::END) {
+ mAlignContent.primary = aAxisTracker.IsCrossAxisReversed()
+ ? StyleAlignFlags::FLEX_START
+ : StyleAlignFlags::FLEX_END;
+ }
+
+ // Figure out how much space we'll set aside for packing spaces, and advance
+ // past any leading packing-space.
+ if (mPackingSpaceRemaining != 0) {
+ if (mAlignContent.primary == StyleAlignFlags::BASELINE ||
+ mAlignContent.primary == StyleAlignFlags::LAST_BASELINE) {
+ // TODO: Bug 1480850 will implement 'align-content: [first/last] baseline'
+ // for flexbox. Until then, behaves as if align-content is 'flex-start' by
+ // doing nothing.
+ } else if (mAlignContent.primary == StyleAlignFlags::FLEX_START) {
+ // All packing space should go at the end --> nothing to do here.
+ } else if (mAlignContent.primary == StyleAlignFlags::FLEX_END) {
+ // All packing space goes at the beginning
+ mPosition += mPackingSpaceRemaining;
+ } else if (mAlignContent.primary == StyleAlignFlags::CENTER) {
+ // Half the packing space goes at the beginning
+ mPosition += mPackingSpaceRemaining / 2;
+ } else if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
+ mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
+ mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) {
+ nsFlexContainerFrame::CalculatePackingSpace(
+ numLines, mAlignContent, &mPosition, &mNumPackingSpacesRemaining,
+ &mPackingSpaceRemaining);
+ } else if (mAlignContent.primary == StyleAlignFlags::STRETCH) {
+ // Split space equally between the lines:
+ MOZ_ASSERT(mPackingSpaceRemaining > 0,
+ "negative packing space should make us use 'flex-start' "
+ "instead of 'stretch' (and we shouldn't bother with this "
+ "code if we have 0 packing space)");
+
+ uint32_t numLinesLeft = numLines;
+ for (FlexLine& line : aLines) {
+ // Our share is the amount of space remaining, divided by the number
+ // of lines remainig.
+ MOZ_ASSERT(numLinesLeft > 0, "miscalculated num lines");
+ nscoord shareOfExtraSpace = mPackingSpaceRemaining / numLinesLeft;
+ nscoord newSize = line.LineCrossSize() + shareOfExtraSpace;
+ line.SetLineCrossSize(newSize);
+
+ mPackingSpaceRemaining -= shareOfExtraSpace;
+ numLinesLeft--;
+ }
+ MOZ_ASSERT(numLinesLeft == 0, "miscalculated num lines");
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected align-content value");
+ }
+ }
+}
+
+void CrossAxisPositionTracker::TraversePackingSpace() {
+ if (mNumPackingSpacesRemaining) {
+ MOZ_ASSERT(mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
+ mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
+ mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY,
+ "mNumPackingSpacesRemaining only applies for "
+ "space-between/space-around/space-evenly");
+
+ MOZ_ASSERT(mPackingSpaceRemaining >= 0,
+ "ran out of packing space earlier than we expected");
+
+ // NOTE: This integer math will skew the distribution of remainder
+ // app-units towards the end, which is fine.
+ nscoord curPackingSpace =
+ mPackingSpaceRemaining / mNumPackingSpacesRemaining;
+
+ mPosition += curPackingSpace;
+ mNumPackingSpacesRemaining--;
+ mPackingSpaceRemaining -= curPackingSpace;
+ }
+}
+
+SingleLineCrossAxisPositionTracker::SingleLineCrossAxisPositionTracker(
+ const FlexboxAxisTracker& aAxisTracker)
+ : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(),
+ aAxisTracker.IsCrossAxisReversed()) {}
+
+void FlexLine::ComputeCrossSizeAndBaseline(
+ const FlexboxAxisTracker& aAxisTracker) {
+ // NOTE: in these "cross{Start,End}ToFurthest{First,Last}Baseline" variables,
+ // the "first/last" term is referring to the flex *line's* baseline-sharing
+ // groups, which may or may not match any flex *item's* exact align-self
+ // value. See the code that sets FlexItem::mBaselineSharingGroup for more
+ // details.
+ nscoord crossStartToFurthestFirstBaseline = nscoord_MIN;
+ nscoord crossEndToFurthestFirstBaseline = nscoord_MIN;
+ nscoord crossStartToFurthestLastBaseline = nscoord_MIN;
+ nscoord crossEndToFurthestLastBaseline = nscoord_MIN;
+
+ nscoord largestOuterCrossSize = 0;
+ for (const FlexItem& item : Items()) {
+ nscoord curOuterCrossSize = item.OuterCrossSize();
+
+ if ((item.AlignSelf()._0 == StyleAlignFlags::BASELINE ||
+ item.AlignSelf()._0 == StyleAlignFlags::LAST_BASELINE) &&
+ item.NumAutoMarginsInCrossAxis() == 0) {
+ const bool usingItemFirstBaseline =
+ (item.AlignSelf()._0 == StyleAlignFlags::BASELINE);
+
+ // Find distance from our item's cross-start and cross-end margin-box
+ // edges to its baseline.
+ //
+ // Here's a diagram of a flex-item that we might be doing this on.
+ // "mmm" is the margin-box, "bbb" is the border-box. The bottom of
+ // the text "BASE" is the baseline.
+ //
+ // ---(cross-start)---
+ // ___ ___ ___
+ // mmmmmmmmmmmm | |margin-start |
+ // m m | _|_ ___ |
+ // m bbbbbbbb m |curOuterCrossSize | |crossStartToBaseline
+ // m b b m | |ascent |
+ // m b BASE b m | _|_ _|_
+ // m b b m | |
+ // m bbbbbbbb m | |crossEndToBaseline
+ // m m | |
+ // mmmmmmmmmmmm _|_ _|_
+ //
+ // ---(cross-end)---
+ //
+ // We already have the curOuterCrossSize, margin-start, and the ascent.
+ // * We can get crossStartToBaseline by adding margin-start + ascent.
+ // * If we subtract that from the curOuterCrossSize, we get
+ // crossEndToBaseline.
+
+ nscoord crossStartToBaseline = item.BaselineOffsetFromOuterCrossEdge(
+ aAxisTracker.CrossAxisPhysicalStartSide(), usingItemFirstBaseline);
+ nscoord crossEndToBaseline = curOuterCrossSize - crossStartToBaseline;
+
+ // Now, update our "largest" values for these (across all the flex items
+ // in this flex line), so we can use them in computing the line's cross
+ // size below:
+ if (item.ItemBaselineSharingGroup() == BaselineSharingGroup::First) {
+ crossStartToFurthestFirstBaseline =
+ std::max(crossStartToFurthestFirstBaseline, crossStartToBaseline);
+ crossEndToFurthestFirstBaseline =
+ std::max(crossEndToFurthestFirstBaseline, crossEndToBaseline);
+ } else {
+ crossStartToFurthestLastBaseline =
+ std::max(crossStartToFurthestLastBaseline, crossStartToBaseline);
+ crossEndToFurthestLastBaseline =
+ std::max(crossEndToFurthestLastBaseline, crossEndToBaseline);
+ }
+ } else {
+ largestOuterCrossSize =
+ std::max(largestOuterCrossSize, curOuterCrossSize);
+ }
+ }
+
+ // The line's baseline offset is the distance from the line's edge to the
+ // furthest item-baseline. The item(s) with that baseline will be exactly
+ // aligned with the line's edge.
+ mFirstBaselineOffset = crossStartToFurthestFirstBaseline;
+ mLastBaselineOffset = crossEndToFurthestLastBaseline;
+
+ // The line's cross-size is the larger of:
+ // (a) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
+ // all baseline-aligned items with no cross-axis auto margins...
+ // and
+ // (b) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
+ // all last baseline-aligned items with no cross-axis auto margins...
+ // and
+ // (c) largest cross-size of all other children.
+ mLineCrossSize = std::max(
+ std::max(
+ crossStartToFurthestFirstBaseline + crossEndToFurthestFirstBaseline,
+ crossStartToFurthestLastBaseline + crossEndToFurthestLastBaseline),
+ largestOuterCrossSize);
+}
+
+nscoord FlexLine::ExtractBaselineOffset(
+ BaselineSharingGroup aBaselineGroup) const {
+ auto LastBaselineOffsetFromStartEdge = [this]() {
+ // Convert the distance to be relative from the line's cross-start edge.
+ const nscoord offset = LastBaselineOffset();
+ return offset != nscoord_MIN ? LineCrossSize() - offset : offset;
+ };
+
+ auto PrimaryBaseline = [=]() {
+ return aBaselineGroup == BaselineSharingGroup::First
+ ? FirstBaselineOffset()
+ : LastBaselineOffsetFromStartEdge();
+ };
+ auto SecondaryBaseline = [=]() {
+ return aBaselineGroup == BaselineSharingGroup::First
+ ? LastBaselineOffsetFromStartEdge()
+ : FirstBaselineOffset();
+ };
+
+ const nscoord primaryBaseline = PrimaryBaseline();
+ if (primaryBaseline != nscoord_MIN) {
+ return primaryBaseline;
+ }
+ return SecondaryBaseline();
+}
+
+void FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize) {
+ // We stretch IFF we are align-self:stretch, have no auto margins in
+ // cross axis, and have cross-axis size property == "auto". If any of those
+ // conditions don't hold up, we won't stretch.
+ if (mAlignSelf._0 != StyleAlignFlags::STRETCH ||
+ NumAutoMarginsInCrossAxis() != 0 || !IsCrossSizeAuto()) {
+ return;
+ }
+
+ // If we've already been stretched, we can bail out early, too.
+ // No need to redo the calculation.
+ if (mIsStretched) {
+ return;
+ }
+
+ // Reserve space for margins & border & padding, and then use whatever
+ // remains as our item's cross-size (clamped to its min/max range).
+ nscoord stretchedSize = aLineCrossSize - MarginBorderPaddingSizeInCrossAxis();
+
+ stretchedSize = NS_CSS_MINMAX(stretchedSize, mCrossMinSize, mCrossMaxSize);
+
+ // Update the cross-size & make a note that it's stretched, so we know to
+ // override the reflow input's computed cross-size in our final reflow.
+ SetCrossSize(stretchedSize);
+ mIsStretched = true;
+}
+
+static nsBlockFrame* FindFlexItemBlockFrame(nsIFrame* aFrame) {
+ if (nsBlockFrame* block = do_QueryFrame(aFrame)) {
+ return block;
+ }
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ if (nsBlockFrame* block = FindFlexItemBlockFrame(f)) {
+ return block;
+ }
+ }
+ return nullptr;
+}
+
+nsBlockFrame* FlexItem::BlockFrame() const {
+ return FindFlexItemBlockFrame(Frame());
+}
+
+void SingleLineCrossAxisPositionTracker::ResolveAutoMarginsInCrossAxis(
+ const FlexLine& aLine, FlexItem& aItem) {
+ // Subtract the space that our item is already occupying, to see how much
+ // space (if any) is available for its auto margins.
+ nscoord spaceForAutoMargins = aLine.LineCrossSize() - aItem.OuterCrossSize();
+
+ if (spaceForAutoMargins <= 0) {
+ return; // No available space --> nothing to do
+ }
+
+ uint32_t numAutoMargins = aItem.NumAutoMarginsInCrossAxis();
+ if (numAutoMargins == 0) {
+ return; // No auto margins --> nothing to do.
+ }
+
+ // OK, we have at least one auto margin and we have some available space.
+ // Give each auto margin a share of the space.
+ const auto& styleMargin = aItem.Frame()->StyleMargin()->mMargin;
+ for (const auto side : {StartSide(), EndSide()}) {
+ if (styleMargin.Get(mWM, side).IsAuto()) {
+ MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0,
+ "Expecting auto margins to have value '0' before we "
+ "update them");
+
+ // NOTE: integer divison is fine here; numAutoMargins is either 1 or 2.
+ // If it's 2 & spaceForAutoMargins is odd, 1st margin gets smaller half.
+ nscoord curAutoMarginSize = spaceForAutoMargins / numAutoMargins;
+ aItem.SetMarginComponentForSide(side, curAutoMarginSize);
+ numAutoMargins--;
+ spaceForAutoMargins -= curAutoMarginSize;
+ }
+ }
+}
+
+void SingleLineCrossAxisPositionTracker::EnterAlignPackingSpace(
+ const FlexLine& aLine, const FlexItem& aItem,
+ const FlexboxAxisTracker& aAxisTracker) {
+ // We don't do align-self alignment on items that have auto margins
+ // in the cross axis.
+ if (aItem.NumAutoMarginsInCrossAxis()) {
+ return;
+ }
+
+ StyleAlignFlags alignSelf = aItem.AlignSelf()._0;
+ // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any
+ // auto-sized items (which we've already done).
+ if (alignSelf == StyleAlignFlags::STRETCH) {
+ alignSelf = StyleAlignFlags::FLEX_START;
+ }
+
+ // Map 'self-start'/'self-end' to 'start'/'end'
+ if (alignSelf == StyleAlignFlags::SELF_START ||
+ alignSelf == StyleAlignFlags::SELF_END) {
+ const LogicalAxis logCrossAxis =
+ aAxisTracker.IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
+ const WritingMode cWM = aAxisTracker.GetWritingMode();
+ const bool sameStart =
+ cWM.ParallelAxisStartsOnSameSide(logCrossAxis, aItem.GetWritingMode());
+ alignSelf = sameStart == (alignSelf == StyleAlignFlags::SELF_START)
+ ? StyleAlignFlags::START
+ : StyleAlignFlags::END;
+ }
+
+ // Map 'start'/'end' to 'flex-start'/'flex-end'.
+ if (alignSelf == StyleAlignFlags::START) {
+ alignSelf = aAxisTracker.IsCrossAxisReversed()
+ ? StyleAlignFlags::FLEX_END
+ : StyleAlignFlags::FLEX_START;
+ } else if (alignSelf == StyleAlignFlags::END) {
+ alignSelf = aAxisTracker.IsCrossAxisReversed() ? StyleAlignFlags::FLEX_START
+ : StyleAlignFlags::FLEX_END;
+ }
+
+ // 'align-self' falls back to 'flex-start' if it is 'center'/'flex-end' and we
+ // have cross axis overflow
+ // XXX we should really be falling back to 'start' as of bug 1472843
+ if (aLine.LineCrossSize() < aItem.OuterCrossSize() &&
+ (aItem.AlignSelfFlags() & StyleAlignFlags::SAFE)) {
+ alignSelf = StyleAlignFlags::FLEX_START;
+ }
+
+ if (alignSelf == StyleAlignFlags::FLEX_START) {
+ // No space to skip over -- we're done.
+ } else if (alignSelf == StyleAlignFlags::FLEX_END) {
+ mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
+ } else if (alignSelf == StyleAlignFlags::CENTER) {
+ // Note: If cross-size is odd, the "after" space will get the extra unit.
+ mPosition += (aLine.LineCrossSize() - aItem.OuterCrossSize()) / 2;
+ } else if (alignSelf == StyleAlignFlags::BASELINE ||
+ alignSelf == StyleAlignFlags::LAST_BASELINE) {
+ const bool usingItemFirstBaseline =
+ (alignSelf == StyleAlignFlags::BASELINE);
+
+ // The first-baseline sharing group gets (collectively) aligned to the
+ // FlexLine's cross-start side, and similarly the last-baseline sharing
+ // group gets snapped to the cross-end side.
+ const bool isFirstBaselineSharingGroup =
+ aItem.ItemBaselineSharingGroup() == BaselineSharingGroup::First;
+ const mozilla::Side alignSide =
+ isFirstBaselineSharingGroup ? aAxisTracker.CrossAxisPhysicalStartSide()
+ : aAxisTracker.CrossAxisPhysicalEndSide();
+
+ // To compute the aligned position for our flex item, we determine:
+ // (1) The distance from the item's alignSide edge to the item's relevant
+ // baseline.
+ nscoord itemBaselineOffset = aItem.BaselineOffsetFromOuterCrossEdge(
+ alignSide, usingItemFirstBaseline);
+
+ // (2) The distance between the FlexLine's alignSide edge and the relevant
+ // baseline-sharing-group's baseline position.
+ nscoord lineBaselineOffset = isFirstBaselineSharingGroup
+ ? aLine.FirstBaselineOffset()
+ : aLine.LastBaselineOffset();
+
+ NS_ASSERTION(lineBaselineOffset >= itemBaselineOffset,
+ "failed at finding largest baseline offset");
+
+ // (3) The difference between the above offsets, which tells us how far we
+ // need to shift the item away from the FlexLine's alignSide edge so
+ // that its baseline is at the proper position for its group.
+ nscoord itemOffsetFromLineEdge = lineBaselineOffset - itemBaselineOffset;
+
+ if (isFirstBaselineSharingGroup) {
+ // alignSide is the line's cross-start edge. mPosition is already there.
+ // From there, we step *forward* by the baseline adjustment:
+ mPosition += itemOffsetFromLineEdge;
+ } else {
+ // alignSide is the line's cross-end edge. Advance mPosition to align
+ // item with that edge (as in FLEX_END case)...
+ mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
+ // ...and step *back* by the baseline adjustment:
+ mPosition -= itemOffsetFromLineEdge;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected align-self value");
+ }
+}
+
+FlexboxAxisInfo::FlexboxAxisInfo(const nsIFrame* aFlexContainer) {
+ MOZ_ASSERT(aFlexContainer && aFlexContainer->IsFlexContainerFrame(),
+ "Only flex containers may be passed to this constructor!");
+ if (IsLegacyBox(aFlexContainer)) {
+ InitAxesFromLegacyProps(aFlexContainer);
+ } else {
+ InitAxesFromModernProps(aFlexContainer);
+ }
+}
+
+void FlexboxAxisInfo::InitAxesFromLegacyProps(const nsIFrame* aFlexContainer) {
+ const nsStyleXUL* styleXUL = aFlexContainer->StyleXUL();
+
+ const bool boxOrientIsVertical =
+ styleXUL->mBoxOrient == StyleBoxOrient::Vertical;
+ const bool wmIsVertical = aFlexContainer->GetWritingMode().IsVertical();
+
+ // If box-orient agrees with our writing-mode, then we're "row-oriented"
+ // (i.e. the flexbox main axis is the same as our writing mode's inline
+ // direction). Otherwise, we're column-oriented (i.e. the flexbox's main
+ // axis is perpendicular to the writing-mode's inline direction).
+ mIsRowOriented = (boxOrientIsVertical == wmIsVertical);
+
+ // Legacy flexbox can use "-webkit-box-direction: reverse" to reverse the
+ // main axis (so it runs in the reverse direction of the inline axis):
+ mIsMainAxisReversed = styleXUL->mBoxDirection == StyleBoxDirection::Reverse;
+
+ // Legacy flexbox does not support reversing the cross axis -- it has no
+ // equivalent of modern flexbox's "flex-wrap: wrap-reverse".
+ mIsCrossAxisReversed = false;
+}
+
+void FlexboxAxisInfo::InitAxesFromModernProps(const nsIFrame* aFlexContainer) {
+ const nsStylePosition* stylePos = aFlexContainer->StylePosition();
+ StyleFlexDirection flexDirection = stylePos->mFlexDirection;
+
+ // Determine main axis:
+ switch (flexDirection) {
+ case StyleFlexDirection::Row:
+ mIsRowOriented = true;
+ mIsMainAxisReversed = false;
+ break;
+ case StyleFlexDirection::RowReverse:
+ mIsRowOriented = true;
+ mIsMainAxisReversed = true;
+ break;
+ case StyleFlexDirection::Column:
+ mIsRowOriented = false;
+ mIsMainAxisReversed = false;
+ break;
+ case StyleFlexDirection::ColumnReverse:
+ mIsRowOriented = false;
+ mIsMainAxisReversed = true;
+ break;
+ }
+
+ // "flex-wrap: wrap-reverse" reverses our cross axis.
+ mIsCrossAxisReversed = stylePos->mFlexWrap == StyleFlexWrap::WrapReverse;
+}
+
+FlexboxAxisTracker::FlexboxAxisTracker(
+ const nsFlexContainerFrame* aFlexContainer)
+ : mWM(aFlexContainer->GetWritingMode()), mAxisInfo(aFlexContainer) {}
+
+LogicalSide FlexboxAxisTracker::MainAxisStartSide() const {
+ return MakeLogicalSide(
+ MainAxis(), IsMainAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
+}
+
+LogicalSide FlexboxAxisTracker::CrossAxisStartSide() const {
+ return MakeLogicalSide(
+ CrossAxis(), IsCrossAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
+}
+
+void nsFlexContainerFrame::GenerateFlexLines(
+ const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
+ const nscoord aTentativeContentBoxCrossSize,
+ const nsTArray<StrutInfo>& aStruts, const FlexboxAxisTracker& aAxisTracker,
+ nscoord aMainGapSize, nsTArray<nsIFrame*>& aPlaceholders,
+ nsTArray<FlexLine>& aLines, bool& aHasCollapsedItems) {
+ MOZ_ASSERT(aLines.IsEmpty(), "Expecting outparam to start out empty");
+
+ auto ConstructNewFlexLine = [&aLines, aMainGapSize]() {
+ return aLines.EmplaceBack(aMainGapSize);
+ };
+
+ const bool isSingleLine =
+ StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
+
+ // We have at least one FlexLine. Even an empty flex container has a single
+ // (empty) flex line.
+ FlexLine* curLine = ConstructNewFlexLine();
+
+ nscoord wrapThreshold;
+ if (isSingleLine) {
+ // Not wrapping. Set threshold to sentinel value that tells us not to wrap.
+ wrapThreshold = NS_UNCONSTRAINEDSIZE;
+ } else {
+ // Wrapping! Set wrap threshold to flex container's content-box main-size.
+ wrapThreshold = aTentativeContentBoxMainSize;
+
+ // If the flex container doesn't have a definite content-box main-size
+ // (e.g. if main axis is vertical & 'height' is 'auto'), make sure we at
+ // least wrap when we hit its max main-size.
+ if (wrapThreshold == NS_UNCONSTRAINEDSIZE) {
+ const nscoord flexContainerMaxMainSize =
+ aAxisTracker.MainComponent(aReflowInput.ComputedMaxSize());
+ wrapThreshold = flexContainerMaxMainSize;
+ }
+ }
+
+ // Tracks the index of the next strut, in aStruts (and when this hits
+ // aStruts.Length(), that means there are no more struts):
+ uint32_t nextStrutIdx = 0;
+
+ // Overall index of the current flex item in the flex container. (This gets
+ // checked against entries in aStruts.)
+ uint32_t itemIdxInContainer = 0;
+
+ CSSOrderAwareFrameIterator iter(
+ this, FrameChildListID::Principal,
+ CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
+ CSSOrderAwareFrameIterator::OrderState::Unknown,
+ OrderingPropertyForIter(this));
+
+ AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
+ iter.ItemsAreAlreadyInOrder());
+
+ const bool useMozBoxCollapseBehavior =
+ StyleVisibility()->UseLegacyCollapseBehavior();
+
+ for (; !iter.AtEnd(); iter.Next()) {
+ nsIFrame* childFrame = *iter;
+ // Don't create flex items / lines for placeholder frames:
+ if (childFrame->IsPlaceholderFrame()) {
+ aPlaceholders.AppendElement(childFrame);
+ continue;
+ }
+
+ const bool collapsed = childFrame->StyleVisibility()->IsCollapse();
+ aHasCollapsedItems = aHasCollapsedItems || collapsed;
+
+ if (useMozBoxCollapseBehavior && collapsed) {
+ // Legacy visibility:collapse behavior: make a 0-sized strut. (No need to
+ // bother with aStruts and remembering cross size.)
+ curLine->Items().EmplaceBack(childFrame, 0, aReflowInput.GetWritingMode(),
+ aAxisTracker);
+ } else if (nextStrutIdx < aStruts.Length() &&
+ aStruts[nextStrutIdx].mItemIdx == itemIdxInContainer) {
+ // Use the simplified "strut" FlexItem constructor:
+ curLine->Items().EmplaceBack(childFrame,
+ aStruts[nextStrutIdx].mStrutCrossSize,
+ aReflowInput.GetWritingMode(), aAxisTracker);
+ nextStrutIdx++;
+ } else {
+ GenerateFlexItemForChild(*curLine, childFrame, aReflowInput, aAxisTracker,
+ aTentativeContentBoxCrossSize);
+ }
+
+ // Check if we need to wrap the newly appended item to a new line, i.e. if
+ // its outer hypothetical main size pushes our line over the threshold.
+ // But we don't wrap if the line-length is unconstrained, nor do we wrap if
+ // this was the first item on the line.
+ if (wrapThreshold != NS_UNCONSTRAINEDSIZE &&
+ curLine->Items().Length() > 1) {
+ // If the line will be longer than wrapThreshold or at least as long as
+ // nscoord_MAX because of the newly appended item, then wrap and move the
+ // item to a new line.
+ auto newOuterSize = curLine->TotalOuterHypotheticalMainSize();
+ newOuterSize += curLine->Items().LastElement().OuterMainSize();
+
+ // Account for gap between this line's previous item and this item.
+ newOuterSize += aMainGapSize;
+
+ if (newOuterSize >= nscoord_MAX || newOuterSize > wrapThreshold) {
+ curLine = ConstructNewFlexLine();
+
+ // Get the previous line after adding a new line because the address can
+ // change if nsTArray needs to reallocate a new space for the new line.
+ FlexLine& prevLine = aLines[aLines.Length() - 2];
+
+ // Move the item from the end of prevLine to the end of curLine.
+ curLine->Items().AppendElement(prevLine.Items().PopLastElement());
+ }
+ }
+
+ // Update the line's bookkeeping about how large its items collectively are.
+ curLine->AddLastItemToMainSizeTotals();
+ itemIdxInContainer++;
+ }
+}
+
+nsFlexContainerFrame::FlexLayoutResult
+nsFlexContainerFrame::GenerateFlexLayoutResult() {
+ MOZ_ASSERT(GetPrevInFlow(), "This should be called by non-first-in-flows!");
+
+ auto* data = FirstInFlow()->GetProperty(SharedFlexData::Prop());
+ MOZ_ASSERT(data, "SharedFlexData should be set by our first-in-flow!");
+
+ FlexLayoutResult flr;
+
+ // The order state of the children is consistent across entire continuation
+ // chain due to calling nsContainerFrame::NormalizeChildLists() at the
+ // beginning of Reflow(), so we can align our state bit with our
+ // prev-in-flow's state. Setup here before calling OrderStateForIter() below.
+ AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
+ GetPrevInFlow()->HasAnyStateBits(
+ NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER));
+
+ // Construct flex items for this flex container fragment from existing flex
+ // items in SharedFlexData.
+ CSSOrderAwareFrameIterator iter(
+ this, FrameChildListID::Principal,
+ CSSOrderAwareFrameIterator::ChildFilter::SkipPlaceholders,
+ OrderStateForIter(this), OrderingPropertyForIter(this));
+
+ auto ConstructNewFlexLine = [&flr]() {
+ // Use zero main gap size since it doesn't matter in flex container's
+ // next-in-flows. We've computed flex items' positions in first-in-flow.
+ return flr.mLines.EmplaceBack(0);
+ };
+
+ // We have at least one FlexLine. Even an empty flex container has a single
+ // (empty) flex line.
+ FlexLine* currentLine = ConstructNewFlexLine();
+
+ if (!iter.AtEnd()) {
+ nsIFrame* child = *iter;
+ nsIFrame* childFirstInFlow = child->FirstInFlow();
+
+ // We are iterating nested for-loops over the FlexLines and FlexItems
+ // generated by GenerateFlexLines() and cached in flex container's
+ // first-in-flow. For each flex item, check if its frame (must be a
+ // first-in-flow) is the first-in-flow of the first child frame in this flex
+ // container continuation. If so, clone the data from that FlexItem into a
+ // FlexLine. When we find a match for the item, we know that the next child
+ // frame might have its first-in-flow as the next item in the same original
+ // line. In this case, we'll put the cloned data in the same line here as
+ // well.
+ for (const FlexLine& line : data->mLines) {
+ // If currentLine is empty, either it is the first line, or all the items
+ // in the previous line have been placed in our prev-in-flows. No need to
+ // construct a new line.
+ if (!currentLine->IsEmpty()) {
+ currentLine = ConstructNewFlexLine();
+ }
+ for (const FlexItem& item : line.Items()) {
+ if (item.Frame() == childFirstInFlow) {
+ currentLine->Items().AppendElement(item.CloneFor(child));
+ iter.Next();
+ if (iter.AtEnd()) {
+ // We've constructed flex items for all children. No need to check
+ // rest of the items.
+ child = childFirstInFlow = nullptr;
+ break;
+ }
+ child = *iter;
+ childFirstInFlow = child->FirstInFlow();
+ }
+ }
+ if (iter.AtEnd()) {
+ // We've constructed flex items for all children. No need to check
+ // rest of the lines.
+ break;
+ }
+ }
+ }
+
+ flr.mContentBoxMainSize = data->mContentBoxMainSize;
+ flr.mContentBoxCrossSize = data->mContentBoxCrossSize;
+
+ return flr;
+}
+
+// Returns the largest outer hypothetical main-size of any line in |aLines|.
+// (i.e. the hypothetical main-size of the largest line)
+static AuCoord64 GetLargestLineMainSize(nsTArray<FlexLine>& aLines) {
+ AuCoord64 largestLineOuterSize = 0;
+ for (const FlexLine& line : aLines) {
+ largestLineOuterSize =
+ std::max(largestLineOuterSize, line.TotalOuterHypotheticalMainSize());
+ }
+ return largestLineOuterSize;
+}
+
+nscoord nsFlexContainerFrame::ComputeMainSize(
+ const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
+ const nscoord aTentativeContentBoxMainSize,
+ nsTArray<FlexLine>& aLines) const {
+ if (aAxisTracker.IsRowOriented()) {
+ // Row-oriented --> our main axis is the inline axis, so our main size
+ // is our inline size (which should already be resolved).
+ return aTentativeContentBoxMainSize;
+ }
+
+ const bool shouldApplyAutomaticMinimumOnBlockAxis =
+ aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis();
+ if (aTentativeContentBoxMainSize != NS_UNCONSTRAINEDSIZE &&
+ !shouldApplyAutomaticMinimumOnBlockAxis) {
+ // Column-oriented case, with fixed BSize:
+ // Just use our fixed block-size because we always assume the available
+ // block-size is unconstrained, and the reflow input has already done the
+ // appropriate min/max-BSize clamping.
+ return aTentativeContentBoxMainSize;
+ }
+
+ // Column-oriented case, with size-containment in block axis:
+ // Behave as if we had no content and just use our MinBSize.
+ if (Maybe<nscoord> containBSize =
+ aReflowInput.mFrame->ContainIntrinsicBSize()) {
+ return aReflowInput.ApplyMinMaxBSize(*containBSize);
+ }
+
+ const AuCoord64 largestLineMainSize = GetLargestLineMainSize(aLines);
+ const nscoord contentBSize = aReflowInput.ApplyMinMaxBSize(
+ nscoord(largestLineMainSize.ToMinMaxClamped()));
+
+ // If the clamped largest FlexLine length is larger than the tentative main
+ // size (which is resolved by aspect-ratio), we extend it to contain the
+ // entire FlexLine.
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ if (shouldApplyAutomaticMinimumOnBlockAxis) {
+ // Column-oriented case, with auto BSize which is resolved by
+ // aspect-ratio.
+ return std::max(contentBSize, aTentativeContentBoxMainSize);
+ }
+
+ // Column-oriented case, with auto BSize:
+ // Resolve auto BSize to the largest FlexLine length, clamped to our
+ // computed min/max main-size properties.
+ return contentBSize;
+}
+
+nscoord nsFlexContainerFrame::ComputeCrossSize(
+ const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
+ const nscoord aTentativeContentBoxCrossSize, nscoord aSumLineCrossSizes,
+ bool* aIsDefinite) const {
+ MOZ_ASSERT(aIsDefinite, "outparam pointer must be non-null");
+
+ if (aAxisTracker.IsColumnOriented()) {
+ // Column-oriented --> our cross axis is the inline axis, so our cross size
+ // is our inline size (which should already be resolved).
+ *aIsDefinite = true;
+ // FIXME: Bug 1661847 - there are cases where aTentativeContentBoxCrossSize
+ // (i.e. aReflowInput.ComputedISize()) might not be the right thing to
+ // return here. Specifically: if our cross size is an intrinsic size, and we
+ // have flex items that are flexible and have aspect ratios, then we may
+ // need to take their post-flexing main sizes into account (multiplied
+ // through their aspect ratios to get their cross sizes), in order to
+ // determine their flex line's size & the flex container's cross size (e.g.
+ // as `aSumLineCrossSizes`).
+ return aTentativeContentBoxCrossSize;
+ }
+
+ const bool shouldApplyAutomaticMinimumOnBlockAxis =
+ aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis();
+ const nscoord computedBSize = aReflowInput.ComputedBSize();
+ if (computedBSize != NS_UNCONSTRAINEDSIZE &&
+ !shouldApplyAutomaticMinimumOnBlockAxis) {
+ // Row-oriented case (cross axis is block-axis), with fixed BSize:
+ *aIsDefinite = true;
+
+ // Just use our fixed block-size because we always assume the available
+ // block-size is unconstrained, and the reflow input has already done the
+ // appropriate min/max-BSize clamping.
+ return computedBSize;
+ }
+
+ // Row-oriented case, with size-containment in block axis:
+ // Behave as if we had no content and just use our MinBSize.
+ if (Maybe<nscoord> containBSize =
+ aReflowInput.mFrame->ContainIntrinsicBSize()) {
+ *aIsDefinite = true;
+ return aReflowInput.ApplyMinMaxBSize(*containBSize);
+ }
+
+ // The cross size must not be definite in the following cases.
+ *aIsDefinite = false;
+
+ const nscoord contentBSize =
+ aReflowInput.ApplyMinMaxBSize(aSumLineCrossSizes);
+ // If the content block-size is larger than the effective computed
+ // block-size, we extend the block-size to contain all the content.
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ if (shouldApplyAutomaticMinimumOnBlockAxis) {
+ // Row-oriented case (cross axis is block-axis), with auto BSize which is
+ // resolved by aspect-ratio or content size.
+ return std::max(contentBSize, computedBSize);
+ }
+
+ // Row-oriented case (cross axis is block axis), with auto BSize:
+ // Shrink-wrap our line(s), subject to our min-size / max-size
+ // constraints in that (block) axis.
+ return contentBSize;
+}
+
+LogicalSize nsFlexContainerFrame::ComputeAvailableSizeForItems(
+ const ReflowInput& aReflowInput,
+ const mozilla::LogicalMargin& aBorderPadding) const {
+ const WritingMode wm = GetWritingMode();
+ nscoord availableBSize = aReflowInput.AvailableBSize();
+
+ if (availableBSize != NS_UNCONSTRAINEDSIZE) {
+ // Available block-size is constrained. Subtract block-start border and
+ // padding from it.
+ availableBSize -= aBorderPadding.BStart(wm);
+
+ if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone) {
+ // We have box-decoration-break:clone. Subtract block-end border and
+ // padding from the available block-size as well.
+ availableBSize -= aBorderPadding.BEnd(wm);
+ }
+
+ // Available block-size can became negative after subtracting block-axis
+ // border and padding. Per spec, to guarantee progress, fragmentainers are
+ // assumed to have a minimum block size of 1px regardless of their used
+ // size. https://drafts.csswg.org/css-break/#breaking-rules
+ availableBSize =
+ std::max(nsPresContext::CSSPixelsToAppUnits(1), availableBSize);
+ }
+
+ return LogicalSize(wm, aReflowInput.ComputedISize(), availableBSize);
+}
+
+void FlexLine::PositionItemsInMainAxis(
+ const StyleContentDistribution& aJustifyContent,
+ nscoord aContentBoxMainSize, const FlexboxAxisTracker& aAxisTracker) {
+ MainAxisPositionTracker mainAxisPosnTracker(
+ aAxisTracker, this, aJustifyContent, aContentBoxMainSize);
+ for (FlexItem& item : Items()) {
+ nscoord itemMainBorderBoxSize =
+ item.MainSize() + item.BorderPaddingSizeInMainAxis();
+
+ // Resolve any main-axis 'auto' margins on aChild to an actual value.
+ mainAxisPosnTracker.ResolveAutoMarginsInMainAxis(item);
+
+ // Advance our position tracker to child's upper-left content-box corner,
+ // and use that as its position in the main axis.
+ mainAxisPosnTracker.EnterMargin(item.Margin());
+ mainAxisPosnTracker.EnterChildFrame(itemMainBorderBoxSize);
+
+ item.SetMainPosition(mainAxisPosnTracker.Position());
+
+ mainAxisPosnTracker.ExitChildFrame(itemMainBorderBoxSize);
+ mainAxisPosnTracker.ExitMargin(item.Margin());
+ mainAxisPosnTracker.TraversePackingSpace();
+ if (&item != &Items().LastElement()) {
+ mainAxisPosnTracker.TraverseGap(mMainGapSize);
+ }
+ }
+}
+
+void nsFlexContainerFrame::SizeItemInCrossAxis(ReflowInput& aChildReflowInput,
+ FlexItem& aItem) {
+ // If cross axis is the item's inline axis, just use ISize from reflow input,
+ // and don't bother with a full reflow.
+ if (aItem.IsInlineAxisCrossAxis()) {
+ aItem.SetCrossSize(aChildReflowInput.ComputedISize());
+ return;
+ }
+
+ MOZ_ASSERT(!aItem.HadMeasuringReflow(),
+ "We shouldn't need more than one measuring reflow");
+
+ if (aItem.AlignSelf()._0 == StyleAlignFlags::STRETCH) {
+ // This item's got "align-self: stretch", so we probably imposed a
+ // stretched computed cross-size on it during its previous
+ // reflow. We're not imposing that BSize for *this* "measuring" reflow, so
+ // we need to tell it to treat this reflow as a resize in its block axis
+ // (regardless of whether any of its ancestors are actually being resized).
+ // (Note: we know that the cross axis is the item's *block* axis -- if it
+ // weren't, then we would've taken the early-return above.)
+ aChildReflowInput.SetBResize(true);
+ // Not 100% sure this is needed, but be conservative for now:
+ aChildReflowInput.mFlags.mIsBResizeForPercentages = true;
+ }
+
+ // Potentially reflow the item, and get the sizing info.
+ const CachedBAxisMeasurement& measurement =
+ MeasureBSizeForFlexItem(aItem, aChildReflowInput);
+
+ // Save the sizing info that we learned from this reflow
+ // -----------------------------------------------------
+
+ // Tentatively store the child's desired content-box cross-size.
+ aItem.SetCrossSize(measurement.BSize());
+}
+
+void FlexLine::PositionItemsInCrossAxis(
+ nscoord aLineStartPosition, const FlexboxAxisTracker& aAxisTracker) {
+ SingleLineCrossAxisPositionTracker lineCrossAxisPosnTracker(aAxisTracker);
+
+ for (FlexItem& item : Items()) {
+ // First, stretch the item's cross size (if appropriate), and resolve any
+ // auto margins in this axis.
+ item.ResolveStretchedCrossSize(mLineCrossSize);
+ lineCrossAxisPosnTracker.ResolveAutoMarginsInCrossAxis(*this, item);
+
+ // Compute the cross-axis position of this item
+ nscoord itemCrossBorderBoxSize =
+ item.CrossSize() + item.BorderPaddingSizeInCrossAxis();
+ lineCrossAxisPosnTracker.EnterAlignPackingSpace(*this, item, aAxisTracker);
+ lineCrossAxisPosnTracker.EnterMargin(item.Margin());
+ lineCrossAxisPosnTracker.EnterChildFrame(itemCrossBorderBoxSize);
+
+ item.SetCrossPosition(aLineStartPosition +
+ lineCrossAxisPosnTracker.Position());
+
+ // Back out to cross-axis edge of the line.
+ lineCrossAxisPosnTracker.ResetPosition();
+ }
+}
+
+void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ return;
+ }
+
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsFlexContainerFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ MOZ_ASSERT(aPresContext == PresContext());
+ NS_WARNING_ASSERTION(
+ aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
+ "Unconstrained inline size; this should only result from huge sizes "
+ "(not intrinsic sizing w/ orthogonal flows)");
+
+ FLEX_LOG("Reflow() for nsFlexContainerFrame %p", this);
+
+ if (IsFrameTreeTooDeep(aReflowInput, aReflowOutput, aStatus)) {
+ return;
+ }
+
+ NormalizeChildLists();
+
+#ifdef DEBUG
+ mDidPushItemsBitMayLie = false;
+ SanityCheckChildListsBeforeReflow();
+#endif // DEBUG
+
+ // We (and our children) can only depend on our ancestor's bsize if we have
+ // a percent-bsize, or if we're positioned and we have "block-start" and
+ // "block-end" set and have block-size:auto. (There are actually other cases,
+ // too -- e.g. if our parent is itself a block-dir flex container and we're
+ // flexible -- but we'll let our ancestors handle those sorts of cases.)
+ //
+ // TODO(emilio): the !bsize.IsLengthPercentage() preserves behavior, but it's
+ // too conservative. min/max-content don't really depend on the container.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ const nsStylePosition* stylePos = StylePosition();
+ const auto& bsize = stylePos->BSize(wm);
+ if (bsize.HasPercent() || (StyleDisplay()->IsAbsolutelyPositionedStyle() &&
+ (bsize.IsAuto() || !bsize.IsLengthPercentage()) &&
+ !stylePos->mOffset.GetBStart(wm).IsAuto() &&
+ !stylePos->mOffset.GetBEnd(wm).IsAuto())) {
+ AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+
+ const FlexboxAxisTracker axisTracker(this);
+
+ // Check to see if we need to create a computed info structure, to
+ // be filled out for use by devtools.
+ ComputedFlexContainerInfo* containerInfo = CreateOrClearFlexContainerInfo();
+
+ FlexLayoutResult flr;
+ PerFragmentFlexData fragmentData;
+ const nsIFrame* prevInFlow = GetPrevInFlow();
+ if (!prevInFlow) {
+ const LogicalSize tentativeContentBoxSize = aReflowInput.ComputedSize();
+ const nscoord tentativeContentBoxMainSize =
+ axisTracker.MainComponent(tentativeContentBoxSize);
+ const nscoord tentativeContentBoxCrossSize =
+ axisTracker.CrossComponent(tentativeContentBoxSize);
+
+ // Calculate gap sizes for main and cross axis. We only need them in
+ // DoFlexLayout in the first-in-flow, so no need to worry about consumed
+ // block-size.
+ const auto& mainGapStyle =
+ axisTracker.IsRowOriented() ? stylePos->mColumnGap : stylePos->mRowGap;
+ const auto& crossGapStyle =
+ axisTracker.IsRowOriented() ? stylePos->mRowGap : stylePos->mColumnGap;
+ const nscoord mainGapSize = nsLayoutUtils::ResolveGapToLength(
+ mainGapStyle, tentativeContentBoxMainSize);
+ const nscoord crossGapSize = nsLayoutUtils::ResolveGapToLength(
+ crossGapStyle, tentativeContentBoxCrossSize);
+
+ // When fragmenting a flex container, we run the flex algorithm without
+ // regards to pagination in order to compute the flex container's desired
+ // content-box size. https://drafts.csswg.org/css-flexbox-1/#pagination-algo
+ //
+ // Note: For a multi-line column-oriented flex container, the sample
+ // algorithm suggests we wrap the flex line at the block-end edge of a
+ // column/page, but we do not implement it intentionally. This brings the
+ // layout result closer to the one as if there's no fragmentation.
+ AutoTArray<StrutInfo, 1> struts;
+ flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
+ tentativeContentBoxCrossSize, axisTracker, mainGapSize,
+ crossGapSize, struts, containerInfo);
+
+ if (!struts.IsEmpty()) {
+ // We're restarting flex layout, with new knowledge of collapsed items.
+ flr.mLines.Clear();
+ flr.mPlaceholders.Clear();
+ flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
+ tentativeContentBoxCrossSize, axisTracker, mainGapSize,
+ crossGapSize, struts, containerInfo);
+ }
+ } else {
+ flr = GenerateFlexLayoutResult();
+ auto* fragmentDataProp =
+ prevInFlow->GetProperty(PerFragmentFlexData::Prop());
+ MOZ_ASSERT(fragmentDataProp,
+ "PerFragmentFlexData should be set in our prev-in-flow!");
+ fragmentData = *fragmentDataProp;
+ }
+
+ LogicalSize contentBoxSize = axisTracker.LogicalSizeFromFlexRelativeSizes(
+ flr.mContentBoxMainSize, flr.mContentBoxCrossSize);
+
+ const nscoord consumedBSize = CalcAndCacheConsumedBSize();
+ const nscoord effectiveContentBSize =
+ contentBoxSize.BSize(wm) - consumedBSize;
+ LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm);
+ if (MOZ_UNLIKELY(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
+ // We assume we are the last fragment by using
+ // PreReflowBlockLevelLogicalSkipSides(), and skip block-end border and
+ // padding if needed.
+ borderPadding.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides());
+ }
+
+ // Determine this frame's tentative border-box size. This is used for logical
+ // to physical coordinate conversion when positioning children.
+ //
+ // Note that vertical-rl writing-mode is the only case where the block flow
+ // direction progresses in a negative physical direction, and therefore block
+ // direction coordinate conversion depends on knowing the width of the
+ // coordinate space in order to translate between the logical and physical
+ // origins. As a result, if our final border-box block-size is different from
+ // this tentative one, and we are in vertical-rl writing mode, we need to
+ // adjust our children's position after reflowing them.
+ const LogicalSize tentativeBorderBoxSize(
+ wm, contentBoxSize.ISize(wm) + borderPadding.IStartEnd(wm),
+ std::min(effectiveContentBSize + borderPadding.BStartEnd(wm),
+ aReflowInput.AvailableBSize()));
+ const nsSize containerSize = tentativeBorderBoxSize.GetPhysicalSize(wm);
+
+ OverflowAreas ocBounds;
+ nsReflowStatus ocStatus;
+ if (prevInFlow) {
+ ReflowOverflowContainerChildren(
+ aPresContext, aReflowInput, ocBounds, ReflowChildFlags::Default,
+ ocStatus, MergeSortedFrameListsFor, Some(containerSize));
+ }
+
+ const LogicalSize availableSizeForItems =
+ ComputeAvailableSizeForItems(aReflowInput, borderPadding);
+ const auto [childrenBEndEdge, childrenStatus] =
+ ReflowChildren(aReflowInput, containerSize, availableSizeForItems,
+ borderPadding, axisTracker, flr, fragmentData);
+
+ bool mayNeedNextInFlow = false;
+ if (aReflowInput.IsInFragmentedContext()) {
+ // This fragment's contribution to the flex container's cumulative
+ // content-box block-size, if it turns out that this is the final vs.
+ // non-final fragment:
+ //
+ // * If it turns out we *are* the final fragment, then this fragment's
+ // content-box contribution is the distance from the start of our content
+ // box to the block-end edge of our children (note the borderPadding
+ // subtraction is just to get us to a content-box-relative offset here):
+ const nscoord bSizeContributionIfFinalFragment =
+ childrenBEndEdge - borderPadding.BStart(wm);
+
+ // * If it turns out we're *not* the final fragment, then this fragment's
+ // content-box extends to the edge of the availableSizeForItems (at least),
+ // regardless of whether we actually have items at that location:
+ const nscoord bSizeContributionIfNotFinalFragment = std::max(
+ bSizeContributionIfFinalFragment, availableSizeForItems.BSize(wm));
+
+ // mCumulativeBEndEdgeShift was updated in ReflowChildren(), and our
+ // children's block-size may grow in fragmented context. If our block-size
+ // and max-block-size are unconstrained, then we allow the flex container to
+ // grow to accommodate any children whose sizes grew as a result of
+ // fragmentation.
+ if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
+ contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(
+ contentBoxSize.BSize(wm) + fragmentData.mCumulativeBEndEdgeShift);
+
+ if (childrenStatus.IsComplete()) {
+ // All of the children fit! We know that we're using a content-based
+ // block-size, and we know our children's block-size may have grown due
+ // to fragmentation. So we allow ourselves to grow our block-size here
+ // to contain the block-end edge of our last child (subject to our
+ // min/max constraints).
+ contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(std::max(
+ contentBoxSize.BSize(wm), fragmentData.mCumulativeContentBoxBSize +
+ bSizeContributionIfFinalFragment));
+ } else {
+ // As in the if-branch above, we extend our block-size, but in this case
+ // we know that a child didn't fit and might overshot our available
+ // size, so we assume this fragment won't be the final fragment, and
+ // hence it should contribute bSizeContributionIfNotFinalFragment
+ // (subject to our min/max constraints).
+ contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(std::max(
+ contentBoxSize.BSize(wm), fragmentData.mCumulativeContentBoxBSize +
+ bSizeContributionIfNotFinalFragment));
+
+ if (aReflowInput.ComputedMaxBSize() == NS_UNCONSTRAINEDSIZE) {
+ mayNeedNextInFlow = true;
+ } else {
+ // The definite max-block-size can be the upper bound of our
+ // content-box block-size. We should check whether we need a
+ // next-in-flow.
+ mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize >
+ availableSizeForItems.BSize(wm);
+ }
+ }
+ } else {
+ mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize >
+ availableSizeForItems.BSize(wm);
+ }
+ fragmentData.mCumulativeContentBoxBSize +=
+ bSizeContributionIfNotFinalFragment;
+
+ // If we may need a next-in-flow, we'll need to skip block-end border and
+ // padding.
+ if (mayNeedNextInFlow && aReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ borderPadding.BEnd(wm) = 0;
+ }
+ }
+
+ PopulateReflowOutput(aReflowOutput, aReflowInput, aStatus, contentBoxSize,
+ borderPadding, consumedBSize, mayNeedNextInFlow,
+ childrenBEndEdge, childrenStatus, axisTracker, flr);
+
+ if (wm.IsVerticalRL()) {
+ // If the final border-box block-size is different from the tentative one,
+ // adjust our children's position.
+ const nscoord deltaBCoord =
+ tentativeBorderBoxSize.BSize(wm) - aReflowOutput.Size(wm).BSize(wm);
+ if (deltaBCoord != 0) {
+ const LogicalPoint delta(wm, 0, deltaBCoord);
+ for (const FlexLine& line : flr.mLines) {
+ for (const FlexItem& item : line.Items()) {
+ item.Frame()->MovePositionBy(wm, delta);
+ }
+ }
+ }
+ }
+
+ // Overflow area = union(my overflow area, children's overflow areas)
+ aReflowOutput.SetOverflowAreasToDesiredBounds();
+ UnionInFlowChildOverflow(aReflowOutput.mOverflowAreas);
+
+ // Merge overflow container bounds and status.
+ aReflowOutput.mOverflowAreas.UnionWith(ocBounds);
+ aStatus.MergeCompletionStatusFrom(ocStatus);
+
+ FinishReflowWithAbsoluteFrames(PresContext(), aReflowOutput, aReflowInput,
+ aStatus);
+
+ // Finally update our line and item measurements in our containerInfo.
+ if (MOZ_UNLIKELY(containerInfo)) {
+ UpdateFlexLineAndItemInfo(*containerInfo, flr.mLines);
+ }
+
+ // If we are the first-in-flow, we want to store data for our next-in-flows,
+ // or clear the existing data if it is not needed.
+ if (!prevInFlow) {
+ SharedFlexData* sharedData = GetProperty(SharedFlexData::Prop());
+ if (!aStatus.IsFullyComplete()) {
+ if (!sharedData) {
+ sharedData = new SharedFlexData;
+ SetProperty(SharedFlexData::Prop(), sharedData);
+ }
+ sharedData->Update(std::move(flr));
+ } else if (sharedData && !GetNextInFlow()) {
+ // We are fully-complete, so no next-in-flow is needed. However, if we
+ // report SetInlineLineBreakBeforeAndReset() in an incremental reflow, our
+ // next-in-flow might still exist. It can be reflowed again before us if
+ // it is an overflow container. Delete the existing data only if we don't
+ // have a next-in-flow.
+ RemoveProperty(SharedFlexData::Prop());
+ }
+ }
+
+ PerFragmentFlexData* fragmentDataProp =
+ GetProperty(PerFragmentFlexData::Prop());
+ if (!aStatus.IsFullyComplete()) {
+ if (!fragmentDataProp) {
+ fragmentDataProp = new PerFragmentFlexData;
+ SetProperty(PerFragmentFlexData::Prop(), fragmentDataProp);
+ }
+ *fragmentDataProp = fragmentData;
+ } else if (fragmentDataProp && !GetNextInFlow()) {
+ // Similar to the condition to remove SharedFlexData, delete the
+ // existing data only if we don't have a next-in-flow.
+ RemoveProperty(PerFragmentFlexData::Prop());
+ }
+}
+
+Maybe<nscoord> nsFlexContainerFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const {
+ if (StyleDisplay()->IsContainLayout() ||
+ HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
+ return Nothing{};
+ }
+ return Some(aBaselineGroup == BaselineSharingGroup::First ? mFirstBaseline
+ : mLastBaseline);
+}
+
+void nsFlexContainerFrame::UnionInFlowChildOverflow(
+ OverflowAreas& aOverflowAreas) {
+ // The CSS Overflow spec [1] requires that a scrollable container's
+ // scrollable overflow should include the following areas.
+ //
+ // a) "the box's own content and padding areas": we treat the *content* as
+ // the scrolled inner frame's theoretical content-box that's intrinsically
+ // sized to the union of all the flex items' margin boxes, _without_
+ // relative positioning applied. The *padding areas* is just inflation on
+ // top of the theoretical content-box by the flex container's padding.
+ //
+ // b) "the margin areas of grid item and flex item boxes for which the box
+ // establishes a containing block": a) already includes the flex items'
+ // normal-positioned margin boxes into the scrollable overflow, but their
+ // relative-positioned margin boxes should also be included because relpos
+ // children are still flex items.
+ //
+ // [1] https://drafts.csswg.org/css-overflow-3/#scrollable.
+ const bool isScrolledContent =
+ Style()->GetPseudoType() == PseudoStyleType::scrolledContent;
+ bool anyScrolledContentItem = false;
+ // Union of normal-positioned margin boxes for all the items.
+ nsRect itemMarginBoxes;
+ // Union of relative-positioned margin boxes for the relpos items only.
+ nsRect relPosItemMarginBoxes;
+ const bool useMozBoxCollapseBehavior =
+ StyleVisibility()->UseLegacyCollapseBehavior();
+ for (nsIFrame* f : mFrames) {
+ if (useMozBoxCollapseBehavior && f->StyleVisibility()->IsCollapse()) {
+ continue;
+ }
+ ConsiderChildOverflow(aOverflowAreas, f);
+ if (!isScrolledContent) {
+ continue;
+ }
+ if (f->IsPlaceholderFrame()) {
+ continue;
+ }
+ anyScrolledContentItem = true;
+ if (MOZ_UNLIKELY(f->IsRelativelyOrStickyPositioned())) {
+ const nsRect marginRect = f->GetMarginRectRelativeToSelf();
+ itemMarginBoxes =
+ itemMarginBoxes.Union(marginRect + f->GetNormalPosition());
+ relPosItemMarginBoxes =
+ relPosItemMarginBoxes.Union(marginRect + f->GetPosition());
+ } else {
+ itemMarginBoxes = itemMarginBoxes.Union(f->GetMarginRect());
+ }
+ }
+
+ if (anyScrolledContentItem) {
+ itemMarginBoxes.Inflate(GetUsedPadding());
+ aOverflowAreas.UnionAllWith(itemMarginBoxes);
+ aOverflowAreas.UnionAllWith(relPosItemMarginBoxes);
+ }
+}
+
+void nsFlexContainerFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
+ UnionInFlowChildOverflow(aOverflowAreas);
+ // Union with child frames, skipping the principal list since we already
+ // handled those above.
+ nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas,
+ {FrameChildListID::Principal});
+}
+
+void nsFlexContainerFrame::CalculatePackingSpace(
+ uint32_t aNumThingsToPack, const StyleContentDistribution& aAlignVal,
+ nscoord* aFirstSubjectOffset, uint32_t* aNumPackingSpacesRemaining,
+ nscoord* aPackingSpaceRemaining) {
+ StyleAlignFlags val = aAlignVal.primary;
+ MOZ_ASSERT(val == StyleAlignFlags::SPACE_BETWEEN ||
+ val == StyleAlignFlags::SPACE_AROUND ||
+ val == StyleAlignFlags::SPACE_EVENLY,
+ "Unexpected alignment value");
+
+ MOZ_ASSERT(*aPackingSpaceRemaining >= 0,
+ "Should not be called with negative packing space");
+
+ // Note: In the aNumThingsToPack==1 case, the fallback behavior for
+ // 'space-between' depends on precise information about the axes that we
+ // don't have here. So, for that case, we just depend on the caller to
+ // explicitly convert 'space-{between,around,evenly}' keywords to the
+ // appropriate fallback alignment and skip this function.
+ MOZ_ASSERT(aNumThingsToPack > 1,
+ "Should not be called unless there's more than 1 thing to pack");
+
+ // Packing spaces between items:
+ *aNumPackingSpacesRemaining = aNumThingsToPack - 1;
+
+ if (val == StyleAlignFlags::SPACE_BETWEEN) {
+ // No need to reserve space at beginning/end, so we're done.
+ return;
+ }
+
+ // We need to add 1 or 2 packing spaces, split between beginning/end, for
+ // space-around / space-evenly:
+ size_t numPackingSpacesForEdges =
+ val == StyleAlignFlags::SPACE_AROUND ? 1 : 2;
+
+ // How big will each "full" packing space be:
+ nscoord packingSpaceSize =
+ *aPackingSpaceRemaining /
+ (*aNumPackingSpacesRemaining + numPackingSpacesForEdges);
+ // How much packing-space are we allocating to the edges:
+ nscoord totalEdgePackingSpace = numPackingSpacesForEdges * packingSpaceSize;
+
+ // Use half of that edge packing space right now:
+ *aFirstSubjectOffset += totalEdgePackingSpace / 2;
+ // ...but we need to subtract all of it right away, so that we won't
+ // hand out any of it to intermediate packing spaces.
+ *aPackingSpaceRemaining -= totalEdgePackingSpace;
+}
+
+ComputedFlexContainerInfo*
+nsFlexContainerFrame::CreateOrClearFlexContainerInfo() {
+ if (!HasAnyStateBits(NS_STATE_FLEX_COMPUTED_INFO)) {
+ return nullptr;
+ }
+
+ // The flag that sets ShouldGenerateComputedInfo() will never be cleared.
+ // That's acceptable because it's only set in a Chrome API invoked by
+ // devtools, and won't impact normal browsing.
+
+ // Re-use the ComputedFlexContainerInfo, if it exists.
+ ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo());
+ if (info) {
+ // We can reuse, as long as we clear out old data.
+ info->mLines.Clear();
+ } else {
+ info = new ComputedFlexContainerInfo();
+ SetProperty(FlexContainerInfo(), info);
+ }
+
+ return info;
+}
+
+nscoord nsFlexContainerFrame::FlexItemConsumedBSize(const FlexItem& aItem) {
+ nsSplittableFrame* f = do_QueryFrame(aItem.Frame());
+ return f ? ConsumedBSize(f) : 0;
+}
+
+void nsFlexContainerFrame::CreateFlexLineAndFlexItemInfo(
+ ComputedFlexContainerInfo& aContainerInfo,
+ const nsTArray<FlexLine>& aLines) {
+ for (const FlexLine& line : aLines) {
+ ComputedFlexLineInfo* lineInfo = aContainerInfo.mLines.AppendElement();
+ // Most of the remaining lineInfo properties will be filled out in
+ // UpdateFlexLineAndItemInfo (some will be provided by other functions),
+ // when we have real values. But we still add all the items here, so
+ // we can capture computed data for each item as we proceed.
+ for (const FlexItem& item : line.Items()) {
+ nsIFrame* frame = item.Frame();
+
+ // The frame may be for an element, or it may be for an
+ // anonymous flex item, e.g. wrapping one or more text nodes.
+ // DevTools wants the content node for the actual child in
+ // the DOM tree, so we descend through anonymous boxes.
+ nsIFrame* targetFrame = GetFirstNonAnonBoxInSubtree(frame);
+ nsIContent* content = targetFrame->GetContent();
+
+ // Skip over content that is only whitespace, which might
+ // have been broken off from a text node which is our real
+ // target.
+ while (content && content->TextIsOnlyWhitespace()) {
+ // If content is only whitespace, try the frame sibling.
+ targetFrame = targetFrame->GetNextSibling();
+ if (targetFrame) {
+ content = targetFrame->GetContent();
+ } else {
+ content = nullptr;
+ }
+ }
+
+ ComputedFlexItemInfo* itemInfo = lineInfo->mItems.AppendElement();
+
+ itemInfo->mNode = content;
+
+ // itemInfo->mMainBaseSize and mMainDeltaSize will be filled out
+ // in ResolveFlexibleLengths(). Other measurements will be captured in
+ // UpdateFlexLineAndItemInfo.
+ }
+ }
+}
+
+void nsFlexContainerFrame::ComputeFlexDirections(
+ ComputedFlexContainerInfo& aContainerInfo,
+ const FlexboxAxisTracker& aAxisTracker) {
+ auto ConvertPhysicalStartSideToFlexPhysicalDirection =
+ [](mozilla::Side aStartSide) {
+ switch (aStartSide) {
+ case eSideLeft:
+ return dom::FlexPhysicalDirection::Horizontal_lr;
+ case eSideRight:
+ return dom::FlexPhysicalDirection::Horizontal_rl;
+ case eSideTop:
+ return dom::FlexPhysicalDirection::Vertical_tb;
+ case eSideBottom:
+ return dom::FlexPhysicalDirection::Vertical_bt;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
+ return dom::FlexPhysicalDirection::Horizontal_lr;
+ };
+
+ aContainerInfo.mMainAxisDirection =
+ ConvertPhysicalStartSideToFlexPhysicalDirection(
+ aAxisTracker.MainAxisPhysicalStartSide());
+ aContainerInfo.mCrossAxisDirection =
+ ConvertPhysicalStartSideToFlexPhysicalDirection(
+ aAxisTracker.CrossAxisPhysicalStartSide());
+}
+
+void nsFlexContainerFrame::UpdateFlexLineAndItemInfo(
+ ComputedFlexContainerInfo& aContainerInfo,
+ const nsTArray<FlexLine>& aLines) {
+ uint32_t lineIndex = 0;
+ for (const FlexLine& line : aLines) {
+ ComputedFlexLineInfo& lineInfo = aContainerInfo.mLines[lineIndex];
+
+ lineInfo.mCrossSize = line.LineCrossSize();
+ lineInfo.mFirstBaselineOffset = line.FirstBaselineOffset();
+ lineInfo.mLastBaselineOffset = line.LastBaselineOffset();
+
+ uint32_t itemIndex = 0;
+ for (const FlexItem& item : line.Items()) {
+ ComputedFlexItemInfo& itemInfo = lineInfo.mItems[itemIndex];
+ itemInfo.mFrameRect = item.Frame()->GetRect();
+ itemInfo.mMainMinSize = item.MainMinSize();
+ itemInfo.mMainMaxSize = item.MainMaxSize();
+ itemInfo.mCrossMinSize = item.CrossMinSize();
+ itemInfo.mCrossMaxSize = item.CrossMaxSize();
+ itemInfo.mClampState =
+ item.WasMinClamped()
+ ? mozilla::dom::FlexItemClampState::Clamped_to_min
+ : (item.WasMaxClamped()
+ ? mozilla::dom::FlexItemClampState::Clamped_to_max
+ : mozilla::dom::FlexItemClampState::Unclamped);
+ ++itemIndex;
+ }
+ ++lineIndex;
+ }
+}
+
+nsFlexContainerFrame* nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
+ nsIFrame* aFrame) {
+ // Prepare a lambda function that we may need to call multiple times.
+ auto GetFlexContainerFrame = [](nsIFrame* aFrame) {
+ // Return the aFrame's content insertion frame, iff it is
+ // a flex container frame.
+ nsFlexContainerFrame* flexFrame = nullptr;
+
+ if (aFrame) {
+ nsIFrame* inner = aFrame;
+ if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) {
+ inner = static_cast<nsFieldSetFrame*>(aFrame)->GetInner();
+ }
+ // Since "Get" methods like GetInner and GetContentInsertionFrame can
+ // return null, we check the return values before dereferencing. Our
+ // calling pattern makes this unlikely, but we're being careful.
+ nsIFrame* insertionFrame =
+ inner ? inner->GetContentInsertionFrame() : nullptr;
+ nsIFrame* possibleFlexFrame = insertionFrame ? insertionFrame : aFrame;
+ flexFrame = possibleFlexFrame->IsFlexContainerFrame()
+ ? static_cast<nsFlexContainerFrame*>(possibleFlexFrame)
+ : nullptr;
+ }
+ return flexFrame;
+ };
+
+ nsFlexContainerFrame* flexFrame = GetFlexContainerFrame(aFrame);
+ if (!flexFrame) {
+ return nullptr;
+ }
+ // Generate the FlexContainerInfo data, if it's not already there.
+ if (flexFrame->HasProperty(FlexContainerInfo())) {
+ return flexFrame;
+ }
+ // Trigger a reflow that generates additional flex property data.
+ // Hold onto aFrame while we do this, in case reflow destroys it.
+ AutoWeakFrame weakFrameRef(aFrame);
+
+ RefPtr<mozilla::PresShell> presShell = flexFrame->PresShell();
+ flexFrame->AddStateBits(NS_STATE_FLEX_COMPUTED_INFO);
+ presShell->FrameNeedsReflow(flexFrame, IntrinsicDirty::None,
+ NS_FRAME_IS_DIRTY);
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ // Since the reflow may have side effects, get the flex frame
+ // again. But if the weakFrameRef is no longer valid, then we
+ // must bail out.
+ if (!weakFrameRef.IsAlive()) {
+ return nullptr;
+ }
+
+ flexFrame = GetFlexContainerFrame(weakFrameRef.GetFrame());
+
+ NS_WARNING_ASSERTION(
+ !flexFrame || flexFrame->HasProperty(FlexContainerInfo()),
+ "The state bit should've made our forced-reflow "
+ "generate a FlexContainerInfo object");
+ return flexFrame;
+}
+
+/* static */
+bool nsFlexContainerFrame::IsItemInlineAxisMainAxis(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame && aFrame->IsFlexItem(), "expecting arg to be a flex item");
+ const WritingMode flexItemWM = aFrame->GetWritingMode();
+ const nsIFrame* flexContainer = aFrame->GetParent();
+
+ if (IsLegacyBox(flexContainer)) {
+ // For legacy boxes, the main axis is determined by "box-orient", and we can
+ // just directly check if that's vertical, and compare that to whether the
+ // item's WM is also vertical:
+ bool boxOrientIsVertical =
+ flexContainer->StyleXUL()->mBoxOrient == StyleBoxOrient::Vertical;
+ return flexItemWM.IsVertical() == boxOrientIsVertical;
+ }
+
+ // For modern CSS flexbox, we get our return value by asking two questions
+ // and comparing their answers.
+ // Question 1: does aFrame have the same inline axis as its flex container?
+ bool itemInlineAxisIsParallelToParent =
+ !flexItemWM.IsOrthogonalTo(flexContainer->GetWritingMode());
+
+ // Question 2: is aFrame's flex container row-oriented? (This tells us
+ // whether the flex container's main axis is its inline axis.)
+ auto flexDirection = flexContainer->StylePosition()->mFlexDirection;
+ bool flexContainerIsRowOriented =
+ flexDirection == StyleFlexDirection::Row ||
+ flexDirection == StyleFlexDirection::RowReverse;
+
+ // aFrame's inline axis is its flex container's main axis IFF the above
+ // questions have the same answer.
+ return flexContainerIsRowOriented == itemInlineAxisIsParallelToParent;
+}
+
+/* static */
+bool nsFlexContainerFrame::IsUsedFlexBasisContent(
+ const StyleFlexBasis& aFlexBasis, const StyleSize& aMainSize) {
+ // We have a used flex-basis of 'content' if flex-basis explicitly has that
+ // value, OR if flex-basis is 'auto' (deferring to the main-size property)
+ // and the main-size property is also 'auto'.
+ // See https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
+ if (aFlexBasis.IsContent()) {
+ return true;
+ }
+ return aFlexBasis.IsAuto() && aMainSize.IsAuto();
+}
+
+nsFlexContainerFrame::FlexLayoutResult nsFlexContainerFrame::DoFlexLayout(
+ const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
+ const nscoord aTentativeContentBoxCrossSize,
+ const FlexboxAxisTracker& aAxisTracker, nscoord aMainGapSize,
+ nscoord aCrossGapSize, nsTArray<StrutInfo>& aStruts,
+ ComputedFlexContainerInfo* const aContainerInfo) {
+ FlexLayoutResult flr;
+
+ GenerateFlexLines(aReflowInput, aTentativeContentBoxMainSize,
+ aTentativeContentBoxCrossSize, aStruts, aAxisTracker,
+ aMainGapSize, flr.mPlaceholders, flr.mLines,
+ flr.mHasCollapsedItems);
+
+ if ((flr.mLines.Length() == 1 && flr.mLines[0].IsEmpty()) ||
+ aReflowInput.mStyleDisplay->IsContainLayout()) {
+ // We have no flex items, or we're layout-contained. So, we have no
+ // baseline, and our parent should synthesize a baseline if needed.
+ AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
+ } else {
+ RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
+ }
+
+ // Construct our computed info if we've been asked to do so. This is
+ // necessary to do now so we can capture some computed values for
+ // FlexItems during layout that would not otherwise be saved (like
+ // size adjustments). We'll later fix up the line properties,
+ // because the correct values aren't available yet.
+ if (aContainerInfo) {
+ MOZ_ASSERT(HasAnyStateBits(NS_STATE_FLEX_COMPUTED_INFO),
+ "We should only have the info struct if we should generate it");
+
+ if (!aStruts.IsEmpty()) {
+ // We restarted DoFlexLayout, and may have stale mLines to clear:
+ aContainerInfo->mLines.Clear();
+ } else {
+ MOZ_ASSERT(aContainerInfo->mLines.IsEmpty(), "Shouldn't have lines yet.");
+ }
+
+ CreateFlexLineAndFlexItemInfo(*aContainerInfo, flr.mLines);
+ ComputeFlexDirections(*aContainerInfo, aAxisTracker);
+ }
+
+ flr.mContentBoxMainSize = ComputeMainSize(
+ aReflowInput, aAxisTracker, aTentativeContentBoxMainSize, flr.mLines);
+
+ uint32_t lineIndex = 0;
+ for (FlexLine& line : flr.mLines) {
+ ComputedFlexLineInfo* lineInfo =
+ aContainerInfo ? &aContainerInfo->mLines[lineIndex] : nullptr;
+ line.ResolveFlexibleLengths(flr.mContentBoxMainSize, lineInfo);
+ ++lineIndex;
+ }
+
+ // Cross Size Determination - Flexbox spec section 9.4
+ // https://drafts.csswg.org/css-flexbox-1/#cross-sizing
+ // ===================================================
+ // Calculate the hypothetical cross size of each item:
+
+ // 'sumLineCrossSizes' includes the size of all gaps between lines. We
+ // initialize it with the sum of all the gaps, and add each line's cross size
+ // at the end of the following for-loop.
+ nscoord sumLineCrossSizes = aCrossGapSize * (flr.mLines.Length() - 1);
+ for (FlexLine& line : flr.mLines) {
+ for (FlexItem& item : line.Items()) {
+ // The item may already have the correct cross-size; only recalculate
+ // if the item's main size resolution (flexing) could have influenced it:
+ if (item.CanMainSizeInfluenceCrossSize()) {
+ StyleSizeOverrides sizeOverrides;
+ if (item.IsInlineAxisMainAxis()) {
+ sizeOverrides.mStyleISize.emplace(item.StyleMainSize());
+ } else {
+ sizeOverrides.mStyleBSize.emplace(item.StyleMainSize());
+ }
+ FLEX_LOG("Sizing flex item %p in cross axis", item.Frame());
+ FLEX_LOGV(" Main size override: %d", item.MainSize());
+
+ const WritingMode wm = item.GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput childReflowInput(PresContext(), aReflowInput, item.Frame(),
+ availSize, Nothing(), {}, sizeOverrides,
+ {ComputeSizeFlag::ShrinkWrap});
+ if (item.IsBlockAxisMainAxis() && item.TreatBSizeAsIndefinite()) {
+ childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
+ }
+
+ SizeItemInCrossAxis(childReflowInput, item);
+ }
+ }
+ // Now that we've finished with this line's items, size the line itself:
+ line.ComputeCrossSizeAndBaseline(aAxisTracker);
+ sumLineCrossSizes += line.LineCrossSize();
+ }
+
+ bool isCrossSizeDefinite;
+ flr.mContentBoxCrossSize = ComputeCrossSize(
+ aReflowInput, aAxisTracker, aTentativeContentBoxCrossSize,
+ sumLineCrossSizes, &isCrossSizeDefinite);
+
+ // Set up state for cross-axis alignment, at a high level (outside the
+ // scope of a particular flex line)
+ CrossAxisPositionTracker crossAxisPosnTracker(
+ flr.mLines, aReflowInput, flr.mContentBoxCrossSize, isCrossSizeDefinite,
+ aAxisTracker, aCrossGapSize);
+
+ // Now that we know the cross size of each line (including
+ // "align-content:stretch" adjustments, from the CrossAxisPositionTracker
+ // constructor), we can create struts for any flex items with
+ // "visibility: collapse" (and restart flex layout).
+ // Make sure to only do this if we had no struts.
+ if (aStruts.IsEmpty() && flr.mHasCollapsedItems &&
+ !StyleVisibility()->UseLegacyCollapseBehavior()) {
+ BuildStrutInfoFromCollapsedItems(flr.mLines, aStruts);
+ if (!aStruts.IsEmpty()) {
+ // Restart flex layout, using our struts.
+ return flr;
+ }
+ }
+
+ // If the flex container is row-oriented, it should derive its first/last
+ // baseline from the WM-relative startmost/endmost FlexLine if any items in
+ // the line participate in baseline alignment.
+ // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
+ //
+ // Initialize the relevant variables here so that we can establish baselines
+ // while iterating FlexLine later (while crossAxisPosnTracker is conveniently
+ // pointing at the cross-start edge of that line, which the line's baseline
+ // offset is measured from).
+ const FlexLine* lineForFirstBaseline = nullptr;
+ const FlexLine* lineForLastBaseline = nullptr;
+ if (aAxisTracker.IsRowOriented()) {
+ lineForFirstBaseline = &StartmostLine(flr.mLines, aAxisTracker);
+ lineForLastBaseline = &EndmostLine(flr.mLines, aAxisTracker);
+ } else {
+ // For column-oriented flex container, use sentinel value to prompt us to
+ // get baselines from the startmost/endmost items.
+ flr.mAscent = nscoord_MIN;
+ flr.mAscentForLast = nscoord_MIN;
+ }
+
+ const auto justifyContent =
+ IsLegacyBox(aReflowInput.mFrame)
+ ? ConvertLegacyStyleToJustifyContent(StyleXUL())
+ : aReflowInput.mStylePosition->mJustifyContent;
+
+ lineIndex = 0;
+ for (FlexLine& line : flr.mLines) {
+ // Main-Axis Alignment - Flexbox spec section 9.5
+ // https://drafts.csswg.org/css-flexbox-1/#main-alignment
+ // ==============================================
+ line.PositionItemsInMainAxis(justifyContent, flr.mContentBoxMainSize,
+ aAxisTracker);
+
+ // See if we need to extract some computed info for this line.
+ if (MOZ_UNLIKELY(aContainerInfo)) {
+ ComputedFlexLineInfo& lineInfo = aContainerInfo->mLines[lineIndex];
+ lineInfo.mCrossStart = crossAxisPosnTracker.Position();
+ }
+
+ // Cross-Axis Alignment - Flexbox spec section 9.6
+ // https://drafts.csswg.org/css-flexbox-1/#cross-alignment
+ // ===============================================
+ line.PositionItemsInCrossAxis(crossAxisPosnTracker.Position(),
+ aAxisTracker);
+
+ // Flex Container Baselines - Flexbox spec section 8.5
+ // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
+ auto ComputeAscentFromLine = [&](const FlexLine& aLine,
+ BaselineSharingGroup aBaselineGroup) {
+ MOZ_ASSERT(aAxisTracker.IsRowOriented(),
+ "This makes sense only if we are row-oriented!");
+
+ // baselineOffsetInLine is a distance from the line's cross-start edge.
+ const nscoord baselineOffsetInLine =
+ aLine.ExtractBaselineOffset(aBaselineGroup);
+
+ if (baselineOffsetInLine == nscoord_MIN) {
+ // No "first baseline"-aligned or "last baseline"-aligned items in
+ // aLine. Return a sentinel value to prompt us to get baseline from the
+ // startmost or endmost FlexItem after we've reflowed it.
+ return nscoord_MIN;
+ }
+
+ // This "ascent" variable is a distance from the flex container's
+ // content-box block-start edge.
+ const nscoord ascent = aAxisTracker.LogicalAscentFromFlexRelativeAscent(
+ crossAxisPosnTracker.Position() + baselineOffsetInLine,
+ flr.mContentBoxCrossSize);
+
+ // Convert "ascent" variable to a distance from border-box start or end
+ // edge, per documentation for FlexLayoutResult ascent members.
+ const auto wm = aAxisTracker.GetWritingMode();
+ if (aBaselineGroup == BaselineSharingGroup::First) {
+ return ascent +
+ aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
+ }
+ return flr.mContentBoxCrossSize - ascent +
+ aReflowInput.ComputedLogicalBorderPadding(wm).BEnd(wm);
+ };
+
+ if (lineForFirstBaseline && lineForFirstBaseline == &line) {
+ flr.mAscent = ComputeAscentFromLine(line, BaselineSharingGroup::First);
+ }
+ if (lineForLastBaseline && lineForLastBaseline == &line) {
+ flr.mAscentForLast =
+ ComputeAscentFromLine(line, BaselineSharingGroup::Last);
+ }
+
+ crossAxisPosnTracker.TraverseLine(line);
+ crossAxisPosnTracker.TraversePackingSpace();
+
+ if (&line != &flr.mLines.LastElement()) {
+ crossAxisPosnTracker.TraverseGap();
+ }
+ ++lineIndex;
+ }
+
+ return flr;
+}
+
+// This data structure is used in fragmentation, storing the block coordinate
+// metrics when reflowing 1) the BStart-most line in each fragment of a
+// row-oriented flex container or, 2) the BStart-most item in each fragment of a
+// single-line column-oriented flex container.
+//
+// When we lay out a row-oriented flex container fragment, its first line might
+// contain one or more monolithic items that were pushed from the previous
+// fragment specifically to avoid having those monolithic items overlap the
+// page/column break. The situation is similar for single-row column-oriented
+// flex container fragments, but a bit simpler; only their first item might have
+// been pushed to avoid overlapping a page/column break.
+//
+// We'll have to place any such pushed items at the block-start edge of the
+// current fragment's content-box, which is as close as we can get them to their
+// theoretical/unfragmented position (without slicing them); but it does
+// represent a shift away from their theoretical/unfragmented position (which
+// was somewhere in the previous fragment).
+//
+// When that happens, we need to record the maximum such shift that we had to
+// perform so that we can apply the same block-endwards shift to "downstream"
+// items (items towards the block-end edge) that we could otherwise collide
+// with. We also potentially apply the same shift when computing the block-end
+// edge of this flex container fragment's content-box so that we don't
+// inadvertently shift the last item (or line-of-items) to overlap the flex
+// container's border, or content beyond the flex container.
+//
+// We use this structure to keep track of several metrics, in service of this
+// goal. This structure is also necessary to adjust PerFragmentFlexData at the
+// end of ReflowChildren().
+//
+// Note: "First" in the struct name means "BStart-most", not the order in the
+// flex line array or flex item array.
+struct FirstLineOrFirstItemBAxisMetrics final {
+ // This value stores the block-end edge shift for 1) the BStart-most line in
+ // the current fragment of a row-oriented flex container, or 2) the
+ // BStart-most item in the current fragment of a single-line column-oriented
+ // flex container. This number is non-negative.
+ //
+ // This value may become positive when any item is a first-in-flow and also
+ // satisfies either the above condition 1) or 2), since that's a hint that it
+ // could be monolithic or have a monolithic first descendant, and therefore an
+ // item that might incur a page/column-break-dodging position-shift that this
+ // variable needs to track.
+ //
+ // This value also stores the fragmentation-imposed growth in the block-size
+ // of a) the BStart-most line in the current fragment of a row-oriented flex
+ // container, or b) the BStart-most item in the current fragment of a
+ // single-line column-oriented flex container. This number is non-negative.
+ nscoord mBEndEdgeShift = 0;
+
+ // The first and second value in the pair store the max block-end edges for
+ // items before and after applying the per-item position-shift in the block
+ // axis. We only record the block-end edges for items with first-in-flow
+ // frames placed in the current flex container fragment. This is used only by
+ // row-oriented flex containers.
+ Maybe<std::pair<nscoord, nscoord>> mMaxBEndEdge;
+};
+
+std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
+ const ReflowInput& aReflowInput, const nsSize& aContainerSize,
+ const LogicalSize& aAvailableSizeForItems,
+ const LogicalMargin& aBorderPadding, const FlexboxAxisTracker& aAxisTracker,
+ FlexLayoutResult& aFlr, PerFragmentFlexData& aFragmentData) {
+ if (HidesContentForLayout()) {
+ return {0, nsReflowStatus()};
+ }
+
+ // Before giving each child a final reflow, calculate the origin of the
+ // flex container's content box (with respect to its border-box), so that
+ // we can compute our flex item's final positions.
+ WritingMode flexWM = aReflowInput.GetWritingMode();
+ const LogicalPoint containerContentBoxOrigin =
+ aBorderPadding.StartOffset(flexWM);
+
+ // The block-end of children is relative to the flex container's border-box.
+ nscoord maxBlockEndEdgeOfChildren = containerContentBoxOrigin.B(flexWM);
+
+ FirstLineOrFirstItemBAxisMetrics bAxisMetrics;
+ FrameHashtable pushedItems;
+ FrameHashtable incompleteItems;
+ FrameHashtable overflowIncompleteItems;
+
+ const bool isSingleLine =
+ StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
+
+ // FINAL REFLOW: Give each child frame another chance to reflow, now that
+ // we know its final size and position.
+ const FlexLine& startmostLine = StartmostLine(aFlr.mLines, aAxisTracker);
+ const FlexItem* startmostItem =
+ startmostLine.IsEmpty() ? nullptr
+ : &startmostLine.StartmostItem(aAxisTracker);
+
+ const size_t numLines = aFlr.mLines.Length();
+ for (size_t lineIdx = 0; lineIdx < numLines; ++lineIdx) {
+ // Iterate flex lines from the startmost to endmost (relative to flex
+ // container's writing-mode).
+ const auto& line =
+ aFlr.mLines[aAxisTracker.IsCrossAxisReversed() ? numLines - lineIdx - 1
+ : lineIdx];
+ MOZ_ASSERT(lineIdx != 0 || &line == &startmostLine,
+ "Logic for finding startmost line should be consistent!");
+
+ const size_t numItems = line.Items().Length();
+ for (size_t itemIdx = 0; itemIdx < numItems; ++itemIdx) {
+ // Iterate flex items from the startmost to endmost (relative to flex
+ // container's writing-mode).
+ const FlexItem& item = line.Items()[aAxisTracker.IsMainAxisReversed()
+ ? numItems - itemIdx - 1
+ : itemIdx];
+ MOZ_ASSERT(lineIdx != 0 || itemIdx != 0 || &item == startmostItem,
+ "Logic for finding startmost item should be consistent!");
+
+ LogicalPoint framePos = aAxisTracker.LogicalPointFromFlexRelativePoint(
+ item.MainPosition(), item.CrossPosition(), aFlr.mContentBoxMainSize,
+ aFlr.mContentBoxCrossSize);
+ // This variable records the item's block-end edge before we give it a
+ // per-item-position-shift, if the item is a first-in-flow in the
+ // startmost line of a row-oriented flex container fragment. It is used to
+ // determine the block-end edge shift for the startmost line at the end of
+ // the outer loop.
+ Maybe<nscoord> frameBPosBeforePerItemShift;
+
+ if (item.Frame()->GetPrevInFlow()) {
+ // The item is a continuation. Lay it out at the beginning of the
+ // available space.
+ framePos.B(flexWM) = 0;
+ } else if (GetPrevInFlow()) {
+ // The item we're placing is not a continuation; though we're placing it
+ // into a flex container fragment which *is* a continuation. To compute
+ // the item's correct position in this fragment, we adjust the item's
+ // theoretical/unfragmented block-direction position by subtracting the
+ // cumulative content-box block-size for all the previous fragments and
+ // adding the cumulative block-end edge shift.
+ //
+ // Note that the item's position in this fragment has not been finalized
+ // yet. At this point, we've adjusted the item's
+ // theoretical/unfragmented position to be relative to the block-end
+ // edge of the previous container fragment's content-box. Later, we'll
+ // compute per-item position-shift to finalize its position.
+ framePos.B(flexWM) -= aFragmentData.mCumulativeContentBoxBSize;
+ framePos.B(flexWM) += aFragmentData.mCumulativeBEndEdgeShift;
+
+ // This helper gets the per-item position-shift in the block-axis.
+ auto GetPerItemPositionShiftToBEnd = [&]() {
+ if (framePos.B(flexWM) >= 0) {
+ // The item final position might be in current flex container
+ // fragment or in any of the later fragments. No adjustment needed.
+ return 0;
+ }
+
+ // The item's block position is negative, but we want to place it at
+ // the content-box block-start edge of this container fragment. To
+ // achieve this, return a negated (positive) value to make the final
+ // block position zero.
+ //
+ // This scenario occurs when fragmenting a row-oriented flex container
+ // where this item is pushed to this container fragment.
+ return -framePos.B(flexWM);
+ };
+
+ if (aAxisTracker.IsRowOriented()) {
+ if (&line == &startmostLine) {
+ frameBPosBeforePerItemShift.emplace(framePos.B(flexWM));
+ framePos.B(flexWM) += GetPerItemPositionShiftToBEnd();
+ } else {
+ // We've computed two things for the startmost line during the outer
+ // loop's first iteration: 1) how far the block-end edge had to
+ // shift and 2) how large the block-size needed to grow. Here, we
+ // just shift all items in the rest of the lines the same amount.
+ framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift;
+ }
+ } else {
+ MOZ_ASSERT(aAxisTracker.IsColumnOriented());
+ if (isSingleLine) {
+ if (&item == startmostItem) {
+ bAxisMetrics.mBEndEdgeShift = GetPerItemPositionShiftToBEnd();
+ }
+ framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift;
+ } else {
+ // Bug 1806717: We need a more sophisticated solution for multi-line
+ // column-oriented flex container when each line has a different
+ // position-shift value. For now, we don't shift them.
+ }
+ }
+ }
+
+ // Adjust available block-size for the item. (We compute it here because
+ // framePos is still relative to the container's content-box.)
+ //
+ // Note: The available block-size can become negative if item's
+ // block-direction position is below available space's block-end.
+ const nscoord availableBSizeForItem =
+ aAvailableSizeForItems.BSize(flexWM) == NS_UNCONSTRAINEDSIZE
+ ? NS_UNCONSTRAINEDSIZE
+ : aAvailableSizeForItems.BSize(flexWM) - framePos.B(flexWM);
+
+ // Adjust framePos to be relative to the container's border-box
+ // (i.e. its frame rect), instead of the container's content-box:
+ framePos += containerContentBoxOrigin;
+
+ // Check if we actually need to reflow the item -- if the item's position
+ // is below the available space's block-end, push it to our next-in-flow;
+ // if it does need a reflow, and we already reflowed it with the right
+ // content-box size.
+ const bool childBPosExceedAvailableSpaceBEnd =
+ availableBSizeForItem != NS_UNCONSTRAINEDSIZE &&
+ availableBSizeForItem <= 0;
+ bool itemInPushedItems = false;
+ if (childBPosExceedAvailableSpaceBEnd) {
+ // Note: Even if all of our items are beyond the available space & get
+ // pushed here, we'll be guaranteed to place at least one of them (and
+ // make progress) in one of the flex container's *next* fragment. It's
+ // because ComputeAvailableSizeForItems() always reserves at least 1px
+ // available block-size for its children, and we consume all available
+ // block-size and add it to
+ // PerFragmentFlexData::mCumulativeContentBoxBSize even if we are not
+ // laying out any child.
+ FLEX_LOG(
+ "[frag] Flex item %p needed to be pushed to container's "
+ "next-in-flow due to position below available space's block-end",
+ item.Frame());
+ pushedItems.Insert(item.Frame());
+ itemInPushedItems = true;
+ } else if (item.NeedsFinalReflow(aReflowInput)) {
+ // The available size must be in item's writing-mode.
+ const WritingMode itemWM = item.GetWritingMode();
+ const auto availableSize =
+ LogicalSize(flexWM, aAvailableSizeForItems.ISize(flexWM),
+ availableBSizeForItem)
+ .ConvertTo(itemWM, flexWM);
+
+ const nsReflowStatus childReflowStatus =
+ ReflowFlexItem(aAxisTracker, aReflowInput, item, framePos,
+ availableSize, aContainerSize);
+
+ const bool shouldPushItem = [&]() {
+ if (availableBSizeForItem == NS_UNCONSTRAINEDSIZE) {
+ // If the available block-size is unconstrained, then we're not
+ // fragmenting and we don't want to push the item.
+ return false;
+ }
+ if (framePos.B(flexWM) == containerContentBoxOrigin.B(flexWM)) {
+ // The flex item is adjacent with block-start of the container's
+ // content-box. Don't push it, or we'll trap in an infinite loop.
+ return false;
+ }
+ if (item.Frame()->BSize() <= availableBSizeForItem) {
+ return false;
+ }
+ if (aAxisTracker.IsColumnOriented() &&
+ item.Frame()->StyleDisplay()->mBreakBefore ==
+ StyleBreakBetween::Avoid) {
+ return false;
+ }
+ return true;
+ }();
+ if (shouldPushItem) {
+ FLEX_LOG(
+ "[frag] Flex item %p needed to be pushed to container's "
+ "next-in-flow because its block-size is larger than the "
+ "available space",
+ item.Frame());
+ pushedItems.Insert(item.Frame());
+ itemInPushedItems = true;
+ } else if (childReflowStatus.IsIncomplete()) {
+ incompleteItems.Insert(item.Frame());
+ } else if (childReflowStatus.IsOverflowIncomplete()) {
+ overflowIncompleteItems.Insert(item.Frame());
+ }
+ } else {
+ MoveFlexItemToFinalPosition(item, framePos, aContainerSize);
+ }
+
+ if (!itemInPushedItems) {
+ const nscoord borderBoxBSize = item.Frame()->BSize(flexWM);
+ const nscoord bEndEdgeAfterPerItemShift =
+ framePos.B(flexWM) + borderBoxBSize;
+
+ // The item (or a fragment thereof) was placed in this flex container
+ // fragment. Update the max block-end edge with the item's block-end
+ // edge.
+ maxBlockEndEdgeOfChildren =
+ std::max(maxBlockEndEdgeOfChildren, bEndEdgeAfterPerItemShift);
+
+ if (frameBPosBeforePerItemShift) {
+ // Make the block-end edge relative to flex container's border-box
+ // because bEndEdgeAfterPerItemShift is relative to the border-box.
+ const nscoord bEndEdgeBeforePerItemShift =
+ containerContentBoxOrigin.B(flexWM) +
+ *frameBPosBeforePerItemShift + borderBoxBSize;
+
+ if (bAxisMetrics.mMaxBEndEdge) {
+ auto& [before, after] = *bAxisMetrics.mMaxBEndEdge;
+ before = std::max(before, bEndEdgeBeforePerItemShift);
+ after = std::max(after, bEndEdgeAfterPerItemShift);
+ } else {
+ bAxisMetrics.mMaxBEndEdge.emplace(bEndEdgeBeforePerItemShift,
+ bEndEdgeAfterPerItemShift);
+ }
+ }
+
+ if (item.Frame()->GetPrevInFlow()) {
+ // Items with a previous-continuation may experience some
+ // fragmentation-imposed growth in their block-size; we compute that
+ // here.
+ const nscoord bSizeOfThisFragment =
+ item.Frame()->ContentSize(flexWM).BSize(flexWM);
+ const nscoord consumedBSize = FlexItemConsumedBSize(item);
+ const nscoord unfragmentedBSize = item.BSize();
+ nscoord bSizeGrowthOfThisFragment = 0;
+
+ if (consumedBSize >= unfragmentedBSize) {
+ // The item's block-size has been grown to exceed the unfragmented
+ // block-size in the previous fragments.
+ bSizeGrowthOfThisFragment = bSizeOfThisFragment;
+ } else if (consumedBSize + bSizeOfThisFragment >= unfragmentedBSize) {
+ // The item's block-size just grows in the current fragment to
+ // exceed the unfragmented block-size.
+ bSizeGrowthOfThisFragment =
+ consumedBSize + bSizeOfThisFragment - unfragmentedBSize;
+ }
+
+ if (aAxisTracker.IsRowOriented()) {
+ if (&line == &startmostLine) {
+ bAxisMetrics.mBEndEdgeShift = std::max(
+ bAxisMetrics.mBEndEdgeShift, bSizeGrowthOfThisFragment);
+ }
+ } else {
+ MOZ_ASSERT(aAxisTracker.IsColumnOriented());
+ if (isSingleLine) {
+ if (&item == startmostItem) {
+ MOZ_ASSERT(bAxisMetrics.mBEndEdgeShift == 0,
+ "The item's frame is a continuation, so it "
+ "shouldn't shift!");
+ bAxisMetrics.mBEndEdgeShift = bSizeGrowthOfThisFragment;
+ }
+ } else {
+ // Bug 1806717: We need a more sophisticated solution for
+ // multi-line column-oriented flex container when each line has a
+ // different block-size growth value. For now, we don't deal with
+ // them.
+ }
+ }
+ }
+ }
+
+ // If the item has auto margins, and we were tracking the UsedMargin
+ // property, set the property to the computed margin values.
+ if (item.HasAnyAutoMargin()) {
+ nsMargin* propValue =
+ item.Frame()->GetProperty(nsIFrame::UsedMarginProperty());
+ if (propValue) {
+ *propValue = item.PhysicalMargin();
+ }
+ }
+ }
+
+ // Now we've finished processing all the items in the startmost line.
+ // Determine the amount by which the startmost line's block-end edge has
+ // shifted, so we can apply the same shift for the remaining lines.
+ if (GetPrevInFlow() && aAxisTracker.IsRowOriented() &&
+ &line == &startmostLine && bAxisMetrics.mMaxBEndEdge) {
+ auto& [before, after] = *bAxisMetrics.mMaxBEndEdge;
+ bAxisMetrics.mBEndEdgeShift =
+ std::max(bAxisMetrics.mBEndEdgeShift, after - before);
+ }
+ }
+
+ if (!aFlr.mPlaceholders.IsEmpty()) {
+ ReflowPlaceholders(aReflowInput, aFlr.mPlaceholders,
+ containerContentBoxOrigin, aContainerSize);
+ }
+
+ nsReflowStatus childrenStatus;
+ if (!pushedItems.IsEmpty() || !incompleteItems.IsEmpty()) {
+ childrenStatus.SetIncomplete();
+ } else if (!overflowIncompleteItems.IsEmpty()) {
+ childrenStatus.SetOverflowIncomplete();
+ }
+ PushIncompleteChildren(pushedItems, incompleteItems, overflowIncompleteItems);
+
+ // TODO: Try making this a fatal assertion after we fix bug 1751260.
+ NS_ASSERTION(childrenStatus.IsFullyComplete() ||
+ aAvailableSizeForItems.BSize(flexWM) != NS_UNCONSTRAINEDSIZE,
+ "We shouldn't have any incomplete children if the available "
+ "block-size is unconstrained!");
+
+ if (!pushedItems.IsEmpty()) {
+ AddStateBits(NS_STATE_FLEX_DID_PUSH_ITEMS);
+ }
+
+ if (GetPrevInFlow()) {
+ aFragmentData.mCumulativeBEndEdgeShift += bAxisMetrics.mBEndEdgeShift;
+ }
+
+ return {maxBlockEndEdgeOfChildren, childrenStatus};
+}
+
+void nsFlexContainerFrame::PopulateReflowOutput(
+ ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus, const LogicalSize& aContentBoxSize,
+ const LogicalMargin& aBorderPadding, const nscoord aConsumedBSize,
+ const bool aMayNeedNextInFlow, const nscoord aMaxBlockEndEdgeOfChildren,
+ const nsReflowStatus& aChildrenStatus,
+ const FlexboxAxisTracker& aAxisTracker, FlexLayoutResult& aFlr) {
+ const WritingMode flexWM = aReflowInput.GetWritingMode();
+
+ // Compute flex container's desired size (in its own writing-mode).
+ LogicalSize desiredSizeInFlexWM(flexWM);
+ desiredSizeInFlexWM.ISize(flexWM) =
+ aContentBoxSize.ISize(flexWM) + aBorderPadding.IStartEnd(flexWM);
+
+ // Unconditionally skip adding block-end border and padding for now. We add it
+ // lower down, after we've established baseline and decided whether bottom
+ // border-padding fits (if we're fragmented).
+ const nscoord effectiveContentBSizeWithBStartBP =
+ aContentBoxSize.BSize(flexWM) - aConsumedBSize +
+ aBorderPadding.BStart(flexWM);
+ nscoord blockEndContainerBP = aBorderPadding.BEnd(flexWM);
+
+ if (aMayNeedNextInFlow) {
+ // We assume our status should be reported as incomplete because we may need
+ // a next-in-flow.
+ bool isStatusIncomplete = true;
+
+ const nscoord availableBSizeMinusBEndBP =
+ aReflowInput.AvailableBSize() - aBorderPadding.BEnd(flexWM);
+
+ if (aMaxBlockEndEdgeOfChildren <= availableBSizeMinusBEndBP) {
+ // Consume all the available block-size.
+ desiredSizeInFlexWM.BSize(flexWM) = availableBSizeMinusBEndBP;
+ } else {
+ // This case happens if we have some tall unbreakable children exceeding
+ // the available block-size.
+ desiredSizeInFlexWM.BSize(flexWM) = std::min(
+ effectiveContentBSizeWithBStartBP, aMaxBlockEndEdgeOfChildren);
+
+ if ((aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
+ aChildrenStatus.IsFullyComplete()) &&
+ aMaxBlockEndEdgeOfChildren >= effectiveContentBSizeWithBStartBP) {
+ // We have some tall unbreakable child that's sticking off the end of
+ // our fragment, *and* forcing us to consume all of our remaining
+ // content block-size and call ourselves complete.
+ //
+ // - If we have a definite block-size: we get here if the tall child
+ // makes us reach that block-size.
+ // - If we have a content-based block-size: we get here if the tall
+ // child makes us reach the content-based block-size from a
+ // theoretical unfragmented layout, *and* all our children are
+ // complete. (Note that if we have some incomplete child, then we
+ // instead prefer to return an incomplete status, so we can get a
+ // next-in-flow to include that child's requested next-in-flow, in the
+ // spirit of having a block-size that fits the content.)
+ //
+ // TODO: the auto-height case might need more subtlety; see bug 1828977.
+ isStatusIncomplete = false;
+
+ // We also potentially need to get the unskipped block-end border and
+ // padding (if we assumed it'd be skipped as part of our tentative
+ // assumption that we'd be incomplete).
+ if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ blockEndContainerBP =
+ aReflowInput.ComputedLogicalBorderPadding(flexWM).BEnd(flexWM);
+ }
+ }
+ }
+
+ if (isStatusIncomplete) {
+ aStatus.SetIncomplete();
+ }
+ } else {
+ // Our own effective content-box block-size can fit within the available
+ // block-size.
+ desiredSizeInFlexWM.BSize(flexWM) = effectiveContentBSizeWithBStartBP;
+ }
+
+ // Now, we account for how the block-end border and padding (if any) impacts
+ // our desired size. If adding it pushes us over the available block-size,
+ // then we become incomplete (unless we already weren't asking for any
+ // block-size, in which case we stay complete to avoid looping forever).
+ //
+ // NOTE: If we have auto block-size, we allow our block-end border and padding
+ // to push us over the available block-size without requesting a continuation,
+ // for consistency with the behavior of "display:block" elements.
+ const nscoord effectiveContentBSizeWithBStartEndBP =
+ desiredSizeInFlexWM.BSize(flexWM) + blockEndContainerBP;
+
+ if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
+ effectiveContentBSizeWithBStartEndBP > aReflowInput.AvailableBSize() &&
+ desiredSizeInFlexWM.BSize(flexWM) != 0 &&
+ aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
+ // We couldn't fit with the block-end border and padding included, so we'll
+ // need a continuation.
+ aStatus.SetIncomplete();
+
+ if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ blockEndContainerBP = 0;
+ }
+ }
+
+ // The variable "blockEndContainerBP" now accurately reflects how much (if
+ // any) block-end border and padding we want for this frame, so we can proceed
+ // to add it in.
+ desiredSizeInFlexWM.BSize(flexWM) += blockEndContainerBP;
+
+ if (aStatus.IsComplete() && !aChildrenStatus.IsFullyComplete()) {
+ aStatus.SetOverflowIncomplete();
+ aStatus.SetNextInFlowNeedsReflow();
+ }
+
+ // If we are the first-in-flow and not fully complete (either our block-size
+ // or any of our flex items cannot fit in the available block-size), and the
+ // style requires us to avoid breaking inside, set the status to prompt our
+ // parent to push us to the next page/column.
+ if (!GetPrevInFlow() && !aStatus.IsFullyComplete() &&
+ ShouldAvoidBreakInside(aReflowInput)) {
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return;
+ }
+
+ // If we haven't established a baseline for the container yet, i.e. if we
+ // don't have any flex item in the startmost flex line that participates in
+ // baseline alignment, then use the startmost flex item to derive the
+ // container's baseline.
+ if (const FlexLine& line = StartmostLine(aFlr.mLines, aAxisTracker);
+ aFlr.mAscent == nscoord_MIN && !line.IsEmpty()) {
+ const FlexItem& item = line.StartmostItem(aAxisTracker);
+ aFlr.mAscent = item.Frame()
+ ->GetLogicalPosition(
+ flexWM, desiredSizeInFlexWM.GetPhysicalSize(flexWM))
+ .B(flexWM) +
+ item.ResolvedAscent(true);
+ }
+
+ // Likewise, if we don't have any flex item in the endmost flex line that
+ // participates in last baseline alignment, then use the endmost flex item to
+ // derived the container's last baseline.
+ if (const FlexLine& line = EndmostLine(aFlr.mLines, aAxisTracker);
+ aFlr.mAscentForLast == nscoord_MIN && !line.IsEmpty()) {
+ const FlexItem& item = line.EndmostItem(aAxisTracker);
+ const nscoord lastAscent =
+ item.Frame()
+ ->GetLogicalPosition(flexWM,
+ desiredSizeInFlexWM.GetPhysicalSize(flexWM))
+ .B(flexWM) +
+ item.ResolvedAscent(false);
+
+ aFlr.mAscentForLast = desiredSizeInFlexWM.BSize(flexWM) - lastAscent;
+ }
+
+ if (aFlr.mAscent == nscoord_MIN) {
+ // Still don't have our baseline set -- this happens if we have no
+ // children, if our children are huge enough that they have nscoord_MIN
+ // as their baseline, or our content is hidden in which case, we'll use the
+ // wrong baseline (but no big deal).
+ NS_WARNING_ASSERTION(
+ HidesContentForLayout() || aFlr.mLines[0].IsEmpty(),
+ "Have flex items but didn't get an ascent - that's odd (or there are "
+ "just gigantic sizes involved)");
+ // Per spec, synthesize baseline from the flex container's content box
+ // (i.e. use block-end side of content-box)
+ // XXXdholbert This only makes sense if parent's writing mode is
+ // horizontal (& even then, really we should be using the BSize in terms
+ // of the parent's writing mode, not ours). Clean up in bug 1155322.
+ aFlr.mAscent = effectiveContentBSizeWithBStartBP;
+ }
+
+ if (aFlr.mAscentForLast == nscoord_MIN) {
+ // Still don't have our last baseline set -- this happens if we have no
+ // children, if our children are huge enough that they have nscoord_MIN
+ // as their baseline, or our content is hidden in which case, we'll use the
+ // wrong baseline (but no big deal).
+ NS_WARNING_ASSERTION(
+ HidesContentForLayout() || aFlr.mLines[0].IsEmpty(),
+ "Have flex items but didn't get an ascent - that's odd (or there are "
+ "just gigantic sizes involved)");
+ // Per spec, synthesize baseline from the flex container's content box
+ // (i.e. use block-end side of content-box)
+ // XXXdholbert This only makes sense if parent's writing mode is
+ // horizontal (& even then, really we should be using the BSize in terms
+ // of the parent's writing mode, not ours). Clean up in bug 1155322.
+ aFlr.mAscentForLast = blockEndContainerBP;
+ }
+
+ if (HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
+ // This will force our parent to call GetLogicalBaseline, which will
+ // synthesize a margin-box baseline.
+ aReflowOutput.SetBlockStartAscent(ReflowOutput::ASK_FOR_BASELINE);
+ } else {
+ // XXXdholbert aFlr.mAscent needs to be in terms of our parent's
+ // writing-mode here. See bug 1155322.
+ aReflowOutput.SetBlockStartAscent(aFlr.mAscent);
+ }
+
+ // Cache the container baselines so that our parent can baseline-align us.
+ mFirstBaseline = aFlr.mAscent;
+ mLastBaseline = aFlr.mAscentForLast;
+
+ // Convert flex container's final desired size to parent's WM, for outparam.
+ aReflowOutput.SetSize(flexWM, desiredSizeInFlexWM);
+}
+
+void nsFlexContainerFrame::MoveFlexItemToFinalPosition(
+ const FlexItem& aItem, const LogicalPoint& aFramePos,
+ const nsSize& aContainerSize) {
+ const WritingMode outerWM = aItem.ContainingBlockWM();
+ const nsStyleDisplay* display = aItem.Frame()->StyleDisplay();
+ LogicalPoint pos(aFramePos);
+ if (display->IsRelativelyOrStickyPositionedStyle()) {
+ // If the item is relatively positioned, look up its offsets (cached from
+ // previous reflow). A sticky positioned item can pass a dummy
+ // logicalOffsets into ApplyRelativePositioning().
+ LogicalMargin logicalOffsets(outerWM);
+ if (display->IsRelativelyPositionedStyle()) {
+ nsMargin* cachedOffsets =
+ aItem.Frame()->GetProperty(nsIFrame::ComputedOffsetProperty());
+ MOZ_ASSERT(
+ cachedOffsets,
+ "relpos previously-reflowed frame should've cached its offsets");
+ logicalOffsets = LogicalMargin(outerWM, *cachedOffsets);
+ }
+ ReflowInput::ApplyRelativePositioning(aItem.Frame(), outerWM,
+ logicalOffsets, &pos, aContainerSize);
+ }
+
+ FLEX_LOG("Moving flex item %p to its desired position %s", aItem.Frame(),
+ ToString(pos).c_str());
+ aItem.Frame()->SetPosition(outerWM, pos, aContainerSize);
+ PositionFrameView(aItem.Frame());
+ PositionChildViews(aItem.Frame());
+}
+
+nsReflowStatus nsFlexContainerFrame::ReflowFlexItem(
+ const FlexboxAxisTracker& aAxisTracker, const ReflowInput& aReflowInput,
+ const FlexItem& aItem, const LogicalPoint& aFramePos,
+ const LogicalSize& aAvailableSize, const nsSize& aContainerSize) {
+ FLEX_LOG("Doing final reflow for flex item %p", aItem.Frame());
+
+ // Returns true if we should use 'auto' in block axis's StyleSizeOverrides to
+ // allow fragmentation-imposed block-size growth.
+ auto ComputeBSizeOverrideWithAuto = [&]() {
+ if (!aReflowInput.IsInFragmentedContext()) {
+ return false;
+ }
+ if (aItem.Frame()->IsReplaced()) {
+ // Disallow fragmentation-imposed block-size growth for replaced elements
+ // since they are monolithic, and cannot be fragmented.
+ return false;
+ }
+ if (aItem.HasAspectRatio()) {
+ // Aspect-ratio's automatic content-based minimum size doesn't work
+ // properly in a fragmented context (Bug 1868284) when we use 'auto'
+ // block-size to apply the fragmentation-imposed block-size growth.
+ // Disable it for now so that items with aspect-ratios can still use their
+ // known block-sizes (from flex layout algorithm) in final reflow.
+ return false;
+ }
+ if (aItem.IsBlockAxisMainAxis()) {
+ if (aItem.IsFlexBaseSizeContentBSize()) {
+ // The flex item resolved its indefinite flex-basis to the content
+ // block-size.
+ if (aItem.IsMainMinSizeContentBSize()) {
+ // The item's flex base size and main min-size are both content
+ // block-size. We interpret this content-based block-size as
+ // permission to apply fragmentation-imposed block-size growth.
+ return true;
+ }
+ if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
+ // The flex container has an indefinite block-size. We allow the
+ // item's to apply fragmentation-imposed block-size growth.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ MOZ_ASSERT(aItem.IsBlockAxisCrossAxis());
+ MOZ_ASSERT(aItem.IsStretched(),
+ "No need to override block-size with 'auto' if the item is not "
+ "stretched in the cross axis!");
+
+ Maybe<nscoord> measuredBSize = aItem.MeasuredBSize();
+ if (measuredBSize && aItem.CrossSize() == *measuredBSize) {
+ // The item has a measured content-based block-size due to having an
+ // indefinite cross-size. If its cross-size is equal to the content-based
+ // block-size, then it is the tallest item that established the cross-size
+ // of the flex line. We allow it apply fragmentation-imposed block-size
+ // growth.
+ //
+ // Note: We only allow the tallest item to grow because it is likely to
+ // have the most impact on the overall flex container block-size growth.
+ // This is not a perfect solution since other shorter items in the same
+ // line might also have fragmentation-imposed block-size growth, but
+ // currently there is no reliable way to detect whether they will outgrow
+ // the tallest item.
+ return true;
+ }
+ return false;
+ };
+
+ StyleSizeOverrides sizeOverrides;
+ bool overrideBSizeWithAuto = false;
+
+ // Override flex item's main size.
+ if (aItem.IsInlineAxisMainAxis()) {
+ sizeOverrides.mStyleISize.emplace(aItem.StyleMainSize());
+ FLEX_LOGV(" Main size (inline-size) override: %d", aItem.MainSize());
+ } else {
+ overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto();
+ if (overrideBSizeWithAuto) {
+ sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
+ FLEX_LOGV(" Main size (block-size) override: Auto");
+ } else {
+ sizeOverrides.mStyleBSize.emplace(aItem.StyleMainSize());
+ FLEX_LOGV(" Main size (block-size) override: %d", aItem.MainSize());
+ }
+ }
+
+ // Override flex item's cross size if it was stretched in the cross axis (in
+ // which case we're imposing a cross size).
+ if (aItem.IsStretched()) {
+ if (aItem.IsInlineAxisCrossAxis()) {
+ sizeOverrides.mStyleISize.emplace(aItem.StyleCrossSize());
+ FLEX_LOGV(" Cross size (inline-size) override: %d", aItem.CrossSize());
+ } else {
+ overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto();
+ if (overrideBSizeWithAuto) {
+ sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
+ FLEX_LOGV(" Cross size (block-size) override: Auto");
+ } else {
+ sizeOverrides.mStyleBSize.emplace(aItem.StyleCrossSize());
+ FLEX_LOGV(" Cross size (block-size) override: %d", aItem.CrossSize());
+ }
+ }
+ }
+ if (sizeOverrides.mStyleBSize) {
+ // We are overriding the block-size. For robustness, we always assume that
+ // this represents a block-axis resize for the frame. This may be
+ // conservative, but we do capture all the conditions in the block-axis
+ // (checked in NeedsFinalReflow()) that make this item require a final
+ // reflow. This sets relevant flags in ReflowInput::InitResizeFlags().
+ aItem.Frame()->SetHasBSizeChange(true);
+ }
+
+ ReflowInput childReflowInput(PresContext(), aReflowInput, aItem.Frame(),
+ aAvailableSize, Nothing(), {}, sizeOverrides,
+ {ComputeSizeFlag::ShrinkWrap});
+ if (overrideBSizeWithAuto) {
+ // If we use 'auto' to override the item's block-size, set the item's
+ // original block-size to min-size as a lower bound.
+ childReflowInput.SetComputedMinBSize(aItem.BSize());
+
+ // Set the item's block-size as the percentage basis so that its children
+ // can resolve percentage sizes correctly.
+ childReflowInput.SetPercentageBasisInBlockAxis(aItem.BSize());
+ }
+
+ if (aItem.TreatBSizeAsIndefinite() && aItem.IsBlockAxisMainAxis()) {
+ childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
+ }
+
+ if (aItem.IsStretched() && aItem.IsBlockAxisCrossAxis()) {
+ // This item is stretched (in the cross axis), and that axis is its block
+ // axis. That stretching effectively gives it a relative BSize.
+ // XXXdholbert This flag only makes a difference if we use the flex items'
+ // frame-state when deciding whether to reflow them -- and we don't, as of
+ // the changes in bug 851607. So this has no effect right now, but it might
+ // make a difference if we optimize to use dirty bits in the
+ // future. (Reftests flexbox-resizeviewport-1.xhtml and -2.xhtml are
+ // intended to catch any regressions here, if we end up relying on this bit
+ // & neglecting to set it.)
+ aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+
+ // NOTE: Be very careful about doing anything else with childReflowInput
+ // after this point, because some of its methods (e.g. SetComputedWidth)
+ // internally call InitResizeFlags and stomp on mVResize & mHResize.
+
+ FLEX_LOG("Reflowing flex item %p at its desired position %s", aItem.Frame(),
+ ToString(aFramePos).c_str());
+
+ // CachedFlexItemData is stored in item's writing mode, so we pass
+ // aChildReflowInput into ReflowOutput's constructor.
+ ReflowOutput childReflowOutput(childReflowInput);
+ nsReflowStatus childReflowStatus;
+ WritingMode outerWM = aReflowInput.GetWritingMode();
+ ReflowChild(aItem.Frame(), PresContext(), childReflowOutput, childReflowInput,
+ outerWM, aFramePos, aContainerSize, ReflowChildFlags::Default,
+ childReflowStatus);
+
+ // XXXdholbert Perhaps we should call CheckForInterrupt here; see bug 1495532.
+
+ FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
+ &childReflowInput, outerWM, aFramePos, aContainerSize,
+ ReflowChildFlags::ApplyRelativePositioning);
+
+ aItem.SetAscent(childReflowOutput.BlockStartAscent());
+
+ // Update our cached flex item info:
+ if (auto* cached = aItem.Frame()->GetProperty(CachedFlexItemData::Prop())) {
+ cached->Update(childReflowInput, childReflowOutput,
+ FlexItemReflowType::Final);
+ } else {
+ cached = new CachedFlexItemData(childReflowInput, childReflowOutput,
+ FlexItemReflowType::Final);
+ aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cached);
+ }
+
+ return childReflowStatus;
+}
+
+void nsFlexContainerFrame::ReflowPlaceholders(
+ const ReflowInput& aReflowInput, nsTArray<nsIFrame*>& aPlaceholders,
+ const LogicalPoint& aContentBoxOrigin, const nsSize& aContainerSize) {
+ WritingMode outerWM = aReflowInput.GetWritingMode();
+
+ // As noted in this method's documentation, we'll reflow every entry in
+ // |aPlaceholders| at the container's content-box origin.
+ for (nsIFrame* placeholder : aPlaceholders) {
+ MOZ_ASSERT(placeholder->IsPlaceholderFrame(),
+ "placeholders array should only contain placeholder frames");
+ WritingMode wm = placeholder->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ ReflowInput childReflowInput(PresContext(), aReflowInput, placeholder,
+ availSize);
+ // No need to set the -webkit-line-clamp related flags when reflowing
+ // a placeholder.
+ ReflowOutput childReflowOutput(outerWM);
+ nsReflowStatus childReflowStatus;
+ ReflowChild(placeholder, PresContext(), childReflowOutput, childReflowInput,
+ outerWM, aContentBoxOrigin, aContainerSize,
+ ReflowChildFlags::Default, childReflowStatus);
+
+ FinishReflowChild(placeholder, PresContext(), childReflowOutput,
+ &childReflowInput, outerWM, aContentBoxOrigin,
+ aContainerSize, ReflowChildFlags::Default);
+
+ // Mark the placeholder frame to indicate that it's not actually at the
+ // element's static position, because we need to apply CSS Alignment after
+ // we determine the OOF's size:
+ placeholder->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
+ }
+}
+
+nscoord nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
+ IntrinsicISizeType aType) {
+ nscoord containerISize = 0;
+ const nsStylePosition* stylePos = StylePosition();
+ const FlexboxAxisTracker axisTracker(this);
+
+ nscoord mainGapSize;
+ if (axisTracker.IsRowOriented()) {
+ mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap,
+ NS_UNCONSTRAINEDSIZE);
+ } else {
+ mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap,
+ NS_UNCONSTRAINEDSIZE);
+ }
+
+ const bool useMozBoxCollapseBehavior =
+ StyleVisibility()->UseLegacyCollapseBehavior();
+
+ // The loop below sets aside space for a gap before each item besides the
+ // first. This bool helps us handle that special-case.
+ bool onFirstChild = true;
+
+ for (nsIFrame* childFrame : mFrames) {
+ // Skip out-of-flow children because they don't participate in flex layout.
+ if (childFrame->IsPlaceholderFrame()) {
+ continue;
+ }
+
+ if (useMozBoxCollapseBehavior &&
+ childFrame->StyleVisibility()->IsCollapse()) {
+ // If we're using legacy "visibility:collapse" behavior, then we don't
+ // care about the sizes of any collapsed children.
+ continue;
+ }
+
+ nscoord childISize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, childFrame, aType);
+
+ // * For a row-oriented single-line flex container, the intrinsic
+ // {min/pref}-isize is the sum of its items' {min/pref}-isizes and
+ // (n-1) column gaps.
+ // * For a column-oriented flex container, the intrinsic min isize
+ // is the max of its items' min isizes.
+ // * For a row-oriented multi-line flex container, the intrinsic
+ // pref isize is former (sum), and its min isize is the latter (max).
+ bool isSingleLine = (StyleFlexWrap::Nowrap == stylePos->mFlexWrap);
+ if (axisTracker.IsRowOriented() &&
+ (isSingleLine || aType == IntrinsicISizeType::PrefISize)) {
+ containerISize += childISize;
+ if (!onFirstChild) {
+ containerISize += mainGapSize;
+ }
+ onFirstChild = false;
+ } else { // (col-oriented, or MinISize for multi-line row flex container)
+ containerISize = std::max(containerISize, childISize);
+ }
+ }
+
+ return containerISize;
+}
+
+/* virtual */
+nscoord nsFlexContainerFrame::GetMinISize(gfxContext* aRenderingContext) {
+ DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
+ if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ mCachedMinISize = *containISize;
+ } else {
+ mCachedMinISize =
+ IntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
+ }
+ }
+
+ return mCachedMinISize;
+}
+
+/* virtual */
+nscoord nsFlexContainerFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
+ if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ mCachedPrefISize = *containISize;
+ } else {
+ mCachedPrefISize =
+ IntrinsicISize(aRenderingContext, IntrinsicISizeType::PrefISize);
+ }
+ }
+
+ return mCachedPrefISize;
+}
+
+int32_t nsFlexContainerFrame::GetNumLines() const {
+ // TODO(emilio, bug 1793251): Treating all row oriented frames as single-lines
+ // might not be great for flex-wrap'd containers, consider trying to do
+ // better? We probably would need to persist more stuff than we do after
+ // layout.
+ return FlexboxAxisInfo(this).mIsRowOriented ? 1 : mFrames.GetLength();
+}
+
+bool nsFlexContainerFrame::IsLineIteratorFlowRTL() {
+ FlexboxAxisInfo info(this);
+ if (info.mIsRowOriented) {
+ const bool isRtl = StyleVisibility()->mDirection == StyleDirection::Rtl;
+ return info.mIsMainAxisReversed != isRtl;
+ }
+ return false;
+}
+
+Result<nsILineIterator::LineInfo, nsresult> nsFlexContainerFrame::GetLine(
+ int32_t aLineNumber) {
+ if (aLineNumber < 0 || aLineNumber >= GetNumLines()) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ FlexboxAxisInfo info(this);
+ LineInfo lineInfo;
+ if (info.mIsRowOriented) {
+ lineInfo.mLineBounds = GetRect();
+ lineInfo.mFirstFrameOnLine = mFrames.FirstChild();
+ // This isn't quite ideal for multi-line row flexbox, see bug 1793251.
+ lineInfo.mNumFramesOnLine = mFrames.GetLength();
+ } else {
+ // TODO(emilio, bug 1793322): Deal with column-reverse (mIsMainAxisReversed)
+ nsIFrame* f = mFrames.FrameAt(aLineNumber);
+ lineInfo.mLineBounds = f->GetRect();
+ lineInfo.mFirstFrameOnLine = f;
+ lineInfo.mNumFramesOnLine = 1;
+ }
+ return lineInfo;
+}
+
+int32_t nsFlexContainerFrame::FindLineContaining(nsIFrame* aFrame,
+ int32_t aStartLine) {
+ const int32_t index = mFrames.IndexOf(aFrame);
+ if (index < 0) {
+ return -1;
+ }
+ const FlexboxAxisInfo info(this);
+ if (info.mIsRowOriented) {
+ return 0;
+ }
+ if (index < aStartLine) {
+ return -1;
+ }
+ return index;
+}
+
+NS_IMETHODIMP
+nsFlexContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) {
+ *aIsReordered = false;
+ *aFirstVisual = nullptr;
+ *aLastVisual = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFlexContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
+ nsIFrame** aFrameFound,
+ bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) {
+ const auto wm = GetWritingMode();
+ const LogicalPoint pos(wm, aPos, GetSize());
+ const FlexboxAxisInfo info(this);
+
+ *aFrameFound = nullptr;
+ *aPosIsBeforeFirstFrame = true;
+ *aPosIsAfterLastFrame = false;
+
+ if (!info.mIsRowOriented) {
+ nsIFrame* f = mFrames.FrameAt(aLineNumber);
+ if (!f) {
+ return NS_OK;
+ }
+
+ auto rect = f->GetLogicalRect(wm, GetSize());
+ *aFrameFound = f;
+ *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm);
+ *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm);
+ return NS_OK;
+ }
+
+ LineFrameFinder finder(aPos, GetSize(), GetWritingMode(),
+ IsLineIteratorFlowRTL());
+ for (nsIFrame* f : mFrames) {
+ finder.Scan(f);
+ if (finder.IsDone()) {
+ break;
+ }
+ }
+ finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
+ return NS_OK;
+}
diff --git a/layout/generic/nsFlexContainerFrame.h b/layout/generic/nsFlexContainerFrame.h
new file mode 100644
index 0000000000..30e440b401
--- /dev/null
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -0,0 +1,675 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: flex" and "display: -webkit-box" */
+
+#ifndef nsFlexContainerFrame_h___
+#define nsFlexContainerFrame_h___
+
+#include <tuple>
+
+#include "mozilla/dom/FlexBinding.h"
+#include "mozilla/UniquePtr.h"
+#include "nsContainerFrame.h"
+#include "nsILineIterator.h"
+
+namespace mozilla {
+class LogicalPoint;
+class PresShell;
+} // namespace mozilla
+
+nsContainerFrame* NS_NewFlexContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+/**
+ * These structures are used to capture data during reflow to be
+ * extracted by devtools via Chrome APIs. The structures are only
+ * created when requested in GetFlexFrameWithComputedInfo(), and
+ * the structures are attached to the nsFlexContainerFrame via the
+ * FlexContainerInfo property.
+ */
+struct ComputedFlexItemInfo {
+ nsCOMPtr<nsINode> mNode;
+ nsRect mFrameRect;
+ /**
+ * mMainBaseSize is a measure of the size of the item in the main
+ * axis before the flex sizing algorithm is applied. In the spec,
+ * this is called "flex base size", but we use this name to connect
+ * the value to the other main axis sizes.
+ */
+ nscoord mMainBaseSize;
+ /**
+ * mMainDeltaSize is the amount that the flex sizing algorithm
+ * adds to the mMainBaseSize, before clamping to mMainMinSize and
+ * mMainMaxSize. This can be thought of as the amount by which the
+ * flex layout algorithm "wants" to shrink or grow the item, and
+ * would do, if it was unconstrained. Since the flex sizing
+ * algorithm proceeds linearly, the mMainDeltaSize for an item only
+ * respects the resolved size of items already frozen.
+ */
+ nscoord mMainDeltaSize;
+ nscoord mMainMinSize;
+ nscoord mMainMaxSize;
+ nscoord mCrossMinSize;
+ nscoord mCrossMaxSize;
+ mozilla::dom::FlexItemClampState mClampState;
+};
+
+struct ComputedFlexLineInfo {
+ nsTArray<ComputedFlexItemInfo> mItems;
+ nscoord mCrossStart;
+ nscoord mCrossSize;
+ nscoord mFirstBaselineOffset;
+ nscoord mLastBaselineOffset;
+ mozilla::dom::FlexLineGrowthState mGrowthState;
+};
+
+struct ComputedFlexContainerInfo {
+ nsTArray<ComputedFlexLineInfo> mLines;
+ mozilla::dom::FlexPhysicalDirection mMainAxisDirection;
+ mozilla::dom::FlexPhysicalDirection mCrossAxisDirection;
+};
+
+/**
+ * Helper class to get the orientation of a flex container's axes.
+ */
+class MOZ_STACK_CLASS FlexboxAxisInfo final {
+ public:
+ explicit FlexboxAxisInfo(const nsIFrame* aFlexContainer);
+
+ // Is our main axis the inline axis? (Are we 'flex-direction:row[-reverse]'?)
+ bool mIsRowOriented = true;
+
+ // Is our main axis in the opposite direction as mWM's corresponding axis?
+ // (e.g. RTL vs LTR)
+ bool mIsMainAxisReversed = false;
+
+ // Is our cross axis in the opposite direction as mWM's corresponding axis?
+ // (e.g. BTT vs TTB)
+ bool mIsCrossAxisReversed = false;
+
+ private:
+ // Helpers for constructor which determine the orientation of our axes, based
+ // on legacy box properties (-webkit-box-orient, -webkit-box-direction) or
+ // modern flexbox properties (flex-direction, flex-wrap) depending on whether
+ // the flex container is a "legacy box" (as determined by IsLegacyBox).
+ void InitAxesFromLegacyProps(const nsIFrame* aFlexContainer);
+ void InitAxesFromModernProps(const nsIFrame* aFlexContainer);
+};
+
+/**
+ * This is the rendering object used for laying out elements with
+ * "display: flex" or "display: inline-flex".
+ *
+ * We also use this class for elements with "display: -webkit-box" or
+ * "display: -webkit-inline-box" (but not "-moz-box" / "-moz-inline-box" --
+ * those are rendered with old-school XUL frame classes).
+ *
+ * Note: we represent the -webkit-box family of properties (-webkit-box-orient,
+ * -webkit-box-flex, etc.) as aliases for their -moz equivalents. And for
+ * -webkit-{inline-}box containers, nsFlexContainerFrame will honor those
+ * "legacy" properties for alignment/flexibility/etc. *instead of* honoring the
+ * modern flexbox & alignment properties. For brevity, many comments in
+ * nsFlexContainerFrame.cpp simply refer to these properties using their
+ * "-webkit" versions, since we're mostly expecting to encounter them in that
+ * form. (Technically, the "-moz" versions of these properties *can* influence
+ * layout here as well (since that's what the -webkit versions are aliased to)
+ * -- but only inside of a "display:-webkit-{inline-}box" container.)
+ */
+class nsFlexContainerFrame final : public nsContainerFrame,
+ public nsILineIterator {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsFlexContainerFrame)
+ NS_DECL_QUERYFRAME
+
+ // Factory method:
+ friend nsContainerFrame* NS_NewFlexContainerFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ // Forward-decls of helper classes
+ class FlexItem;
+ class FlexLine;
+ class FlexboxAxisTracker;
+ struct StrutInfo;
+ class CachedBAxisMeasurement;
+ class CachedFlexItemData;
+ struct SharedFlexData;
+ struct PerFragmentFlexData;
+ class FlexItemIterator;
+
+ // nsIFrame overrides
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ void MarkIntrinsicISizesDirty() override;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const override;
+
+ // Unions the child overflow from our in-flow children.
+ void UnionInFlowChildOverflow(mozilla::OverflowAreas&);
+
+ // Unions the child overflow from all our children, including out of flows.
+ void UnionChildOverflow(mozilla::OverflowAreas&) final;
+
+ // nsContainerFrame overrides
+ bool DrainSelfOverflowList() override;
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override;
+ mozilla::StyleAlignFlags CSSAlignmentForAbsPosChild(
+ const ReflowInput& aChildRI,
+ mozilla::LogicalAxis aLogicalAxis) const override;
+
+ /**
+ * Helper function to calculate packing space and initial offset of alignment
+ * subjects in MainAxisPositionTracker() and CrossAxisPositionTracker() for
+ * space-between, space-around, and space-evenly.
+ * * @param aNumThingsToPack Number of alignment subjects.
+ * @param aAlignVal Value for align-content or
+ * justify-content.
+ * @param aFirstSubjectOffset Outparam for first subject offset.
+ * @param aNumPackingSpacesRemaining Outparam for number of equal-sized
+ * packing spaces to apply between each
+ * alignment subject.
+ * @param aPackingSpaceRemaining Outparam for total amount of packing
+ * space to be divided up.
+ */
+ static void CalculatePackingSpace(
+ uint32_t aNumThingsToPack,
+ const mozilla::StyleContentDistribution& aAlignVal,
+ nscoord* aFirstSubjectOffset, uint32_t* aNumPackingSpacesRemaining,
+ nscoord* aPackingSpaceRemaining);
+
+ /**
+ * This property is created by a call to
+ * nsFlexContainerFrame::GetFlexFrameWithComputedInfo.
+ */
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(FlexContainerInfo,
+ ComputedFlexContainerInfo)
+ /**
+ * This function should only be called on a nsFlexContainerFrame
+ * that has just been returned by a call to
+ * GetFlexFrameWithComputedInfo.
+ */
+ const ComputedFlexContainerInfo* GetFlexContainerInfo() {
+ const ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo());
+ NS_WARNING_ASSERTION(info,
+ "Property generation wasn't requested. "
+ "This is a known issue in Print Preview. "
+ "See Bug 1157012.");
+ return info;
+ }
+
+ /**
+ * Return aFrame as a flex frame after ensuring it has computed flex info.
+ * @return nullptr if aFrame is null or doesn't have a flex frame
+ * as its content insertion frame.
+ * @note this might destroy layout/style data since it may flush layout.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static nsFlexContainerFrame* GetFlexFrameWithComputedInfo(nsIFrame* aFrame);
+
+ /**
+ * Given a frame for a flex item, this method returns true IFF that flex
+ * item's inline axis is the same as (i.e. not orthogonal to) its flex
+ * container's main axis.
+ *
+ * (This method is only intended to be used from external
+ * callers. Inside of flex reflow code, FlexItem::IsInlineAxisMainAxis() is
+ * equivalent & more optimal.)
+ *
+ * @param aFrame a flex item (must return true from IsFlexItem)
+ * @return true iff aFrame's inline axis is the same as (i.e. not orthogonal
+ * to) its flex container's main axis. Otherwise, false.
+ */
+ static bool IsItemInlineAxisMainAxis(nsIFrame* aFrame);
+
+ /**
+ * Returns true iff the given computed 'flex-basis' & main-size property
+ * values collectively represent a used flex-basis of 'content'.
+ * See https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
+ *
+ * @param aFlexBasis the computed 'flex-basis' for a flex item.
+ * @param aMainSize the computed main-size property for a flex item.
+ */
+ static bool IsUsedFlexBasisContent(const mozilla::StyleFlexBasis& aFlexBasis,
+ const mozilla::StyleSize& aMainSize);
+
+ /**
+ * Callback for nsIFrame::MarkIntrinsicISizesDirty() on a flex item.
+ */
+ static void MarkCachedFlexMeasurementsDirty(nsIFrame* aItemFrame);
+
+ bool CanProvideLineIterator() const final { return true; }
+ nsILineIterator* GetLineIterator() final { return this; }
+ int32_t GetNumLines() const final;
+ bool IsLineIteratorFlowRTL() final;
+ mozilla::Result<LineInfo, nsresult> GetLine(int32_t aLineNumber) final;
+ int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) final;
+ NS_IMETHOD FindFrameAt(int32_t aLineNumber, nsPoint aPos,
+ nsIFrame** aFrameFound, bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) final;
+ NS_IMETHOD CheckLineOrder(int32_t aLine, bool* aIsReordered,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) final;
+
+ protected:
+ // Protected constructor & destructor
+ explicit nsFlexContainerFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {}
+
+ virtual ~nsFlexContainerFrame();
+
+ // Protected flex-container-specific methods / member-vars
+
+ /**
+ * This method does the bulk of the flex layout, implementing the algorithm
+ * described at: https://drafts.csswg.org/css-flexbox-1/#layout-algorithm
+ * (with a few initialization pieces happening in the caller, Reflow().
+ *
+ * (The logic behind the division of work between Reflow and DoFlexLayout is
+ * as follows: DoFlexLayout() begins at the step that we have to jump back
+ * to, if we find any visibility:collapse children, and Reflow() does
+ * everything before that point.)
+ *
+ * @param aTentativeContentBoxMainSize the "tentative" content-box main-size
+ * of the flex container; "tentative"
+ * because it may be unconstrained or may
+ * run off the page.
+ * @param aTentativeContentBoxCrossSize the "tentative" content-box cross-size
+ * of the flex container; "tentative"
+ * because it may be unconstrained or may
+ * run off the page.
+ */
+ struct FlexLayoutResult final {
+ // The flex lines of the flex container.
+ nsTArray<FlexLine> mLines;
+
+ // The absolutely-positioned flex children.
+ nsTArray<nsIFrame*> mPlaceholders;
+
+ bool mHasCollapsedItems = false;
+
+ // The final content-box main-size of the flex container as if there's no
+ // fragmentation.
+ nscoord mContentBoxMainSize = NS_UNCONSTRAINEDSIZE;
+
+ // The final content-box cross-size of the flex container as if there's no
+ // fragmentation.
+ nscoord mContentBoxCrossSize = NS_UNCONSTRAINEDSIZE;
+
+ // The flex container's ascent for the "first baseline" alignment, derived
+ // from any baseline-aligned flex items in the startmost (from the
+ // perspective of the flex container's WM) flex line, if any such items
+ // exist. Otherwise, nscoord_MIN.
+ //
+ // Note: this is a distance from the border-box block-start edge.
+ nscoord mAscent = NS_UNCONSTRAINEDSIZE;
+
+ // The flex container's ascent for the "last baseline" alignment, derived
+ // from any baseline-aligned flex items in the endmost (from the perspective
+ // of the flex container's WM) flex line, if any such items exist.
+ // Otherwise, nscoord_MIN.
+ //
+ // Note: this is a distance from the border-box block-end edge. It's
+ // different from the identically-named-member FlexItem::mAscentForLast,
+ // which is a distance from the item frame's border-box block-start edge.
+ nscoord mAscentForLast = NS_UNCONSTRAINEDSIZE;
+ };
+ FlexLayoutResult DoFlexLayout(
+ const ReflowInput& aReflowInput,
+ const nscoord aTentativeContentBoxMainSize,
+ const nscoord aTentativeContentBoxCrossSize,
+ const FlexboxAxisTracker& aAxisTracker, nscoord aMainGapSize,
+ nscoord aCrossGapSize, nsTArray<StrutInfo>& aStruts,
+ ComputedFlexContainerInfo* const aContainerInfo);
+
+ /**
+ * If our devtools have requested a ComputedFlexContainerInfo for this flex
+ * container, this method ensures that we have one (and if one already exists,
+ * this method reinitializes it to look like a freshly-created one).
+ *
+ * @return the pointer to a freshly created or reinitialized
+ * ComputedFlexContainerInfo if our devtools have requested it;
+ * otherwise nullptr.
+ */
+ ComputedFlexContainerInfo* CreateOrClearFlexContainerInfo();
+
+ /**
+ * Helpers for DoFlexLayout to computed fields in ComputedFlexContainerInfo.
+ */
+ static void CreateFlexLineAndFlexItemInfo(
+ ComputedFlexContainerInfo& aContainerInfo,
+ const nsTArray<FlexLine>& aLines);
+
+ static void ComputeFlexDirections(ComputedFlexContainerInfo& aContainerInfo,
+ const FlexboxAxisTracker& aAxisTracker);
+
+ static void UpdateFlexLineAndItemInfo(
+ ComputedFlexContainerInfo& aContainerInfo,
+ const nsTArray<FlexLine>& aLines);
+
+ /**
+ * Helper to query flex item's consumed block-size.
+ */
+ static nscoord FlexItemConsumedBSize(const FlexItem& aItem);
+
+#ifdef DEBUG
+ void SanityCheckAnonymousFlexItems() const;
+#endif // DEBUG
+
+ /**
+ * Construct a new FlexItem for the given child frame, directly at the end of
+ * aLine.
+ *
+ * Before returning, this method also processes the FlexItem to resolve its
+ * flex basis (including e.g. auto-height) as well as to resolve
+ * "min-height:auto", via ResolveAutoFlexBasisAndMinSize(). (Basically, the
+ * constructed FlexItem will be ready to participate in the "Resolve the
+ * Flexible Lengths" step of the Flex Layout Algorithm.)
+ * https://drafts.csswg.org/css-flexbox-1/#algo-flex
+ *
+ * Note that this method **does not** update aLine's main-size bookkeeping to
+ * account for the newly-constructed flex item. The caller is responsible for
+ * determining whether this line is a good fit for the new item. If so, the
+ * caller should update aLine's bookkeeping (via
+ * FlexLine::AddLastItemToMainSizeTotals), or move the new item to a new line.
+ */
+ void GenerateFlexItemForChild(FlexLine& aLine, nsIFrame* aChildFrame,
+ const ReflowInput& aParentReflowInput,
+ const FlexboxAxisTracker& aAxisTracker,
+ const nscoord aTentativeContentBoxCrossSize);
+
+ /**
+ * This method looks up cached block-axis measurements for a flex item, or
+ * does a measuring reflow and caches those measurements.
+ *
+ * This avoids exponential reflows - see the comment above the
+ * CachedBAxisMeasurement struct.
+ */
+ const CachedBAxisMeasurement& MeasureBSizeForFlexItem(
+ FlexItem& aItem, ReflowInput& aChildReflowInput);
+
+ /**
+ * This method performs a "measuring" reflow to get the content BSize of
+ * aFlexItem.Frame() (treating it as if it had a computed BSize of "auto"),
+ * and returns the resulting BSize measurement.
+ * (Helper for ResolveAutoFlexBasisAndMinSize().)
+ */
+ nscoord MeasureFlexItemContentBSize(FlexItem& aFlexItem,
+ bool aForceBResizeForMeasuringReflow,
+ const ReflowInput& aParentReflowInput);
+
+ /**
+ * This method resolves an "auto" flex-basis and/or min-main-size value
+ * on aFlexItem, if needed.
+ * (Helper for GenerateFlexItemForChild().)
+ */
+ void ResolveAutoFlexBasisAndMinSize(FlexItem& aFlexItem,
+ const ReflowInput& aItemReflowInput,
+ const FlexboxAxisTracker& aAxisTracker);
+
+ /**
+ * This method:
+ * - Creates FlexItems for all of our child frames (except placeholders).
+ * - Groups those FlexItems into FlexLines.
+ * - Returns those FlexLines in the outparam |aLines|.
+ *
+ * This corresponds to "Collect flex items into flex lines" step in the spec.
+ * https://drafts.csswg.org/css-flexbox-1/#algo-line-break
+ *
+ * For any child frames which are placeholders, this method will instead just
+ * append that child to the outparam |aPlaceholders| for separate handling.
+ * (Absolutely positioned children of a flex container are *not* flex items.)
+ */
+ void GenerateFlexLines(const ReflowInput& aReflowInput,
+ const nscoord aTentativeContentBoxMainSize,
+ const nscoord aTentativeContentBoxCrossSize,
+ const nsTArray<StrutInfo>& aStruts,
+ const FlexboxAxisTracker& aAxisTracker,
+ nscoord aMainGapSize,
+ nsTArray<nsIFrame*>& aPlaceholders,
+ nsTArray<FlexLine>& aLines, bool& aHasCollapsedItems);
+
+ /**
+ * Generates and returns a FlexLayoutResult that contains the FlexLines and
+ * some sizing metrics that should be used to lay out a particular flex
+ * container continuation (i.e. don't call this on the first-in-flow).
+ */
+ FlexLayoutResult GenerateFlexLayoutResult();
+
+ /**
+ * Resolves the content-box main-size of a flex container frame,
+ * primarily based on:
+ * - the "tentative" main size, taken from the reflow input ("tentative"
+ * because it may be unconstrained or may run off the page).
+ * - the sizes of our lines of flex items.
+ *
+ * We assume the available block-size is always *unconstrained* because this
+ * is called only in flex algorithm to measure the flex container's size
+ * without regards to pagination.
+ *
+ * Guaranteed to return a definite length, i.e. not NS_UNCONSTRAINEDSIZE,
+ * aside from cases with huge lengths which happen to compute to that value.
+ *
+ * This corresponds to "Determine the main size of the flex container" step in
+ * the spec. https://drafts.csswg.org/css-flexbox-1/#algo-main-container
+ *
+ * (Note: This function should be structurally similar to
+ * ComputeCrossSize().)
+ */
+ nscoord ComputeMainSize(const ReflowInput& aReflowInput,
+ const FlexboxAxisTracker& aAxisTracker,
+ const nscoord aTentativeContentBoxMainSize,
+ nsTArray<FlexLine>& aLines) const;
+
+ nscoord ComputeCrossSize(const ReflowInput& aReflowInput,
+ const FlexboxAxisTracker& aAxisTracker,
+ const nscoord aTentativeContentBoxCrossSize,
+ nscoord aSumLineCrossSizes, bool* aIsDefinite) const;
+
+ /**
+ * Compute the size of the available space that we'll give to our children to
+ * reflow into. In particular, compute the available size that we would give
+ * to a hypothetical child placed at the IStart/BStart corner of this flex
+ * container's content-box.
+ *
+ * @param aReflowInput the flex container's reflow input.
+ * @param aBorderPadding the border and padding of this frame with the
+ * assumption that this is the last fragment.
+ *
+ * @return the size of the available space for our children to reflow into.
+ */
+ mozilla::LogicalSize ComputeAvailableSizeForItems(
+ const ReflowInput& aReflowInput,
+ const mozilla::LogicalMargin& aBorderPadding) const;
+
+ void SizeItemInCrossAxis(ReflowInput& aChildReflowInput, FlexItem& aItem);
+
+ /**
+ * This method computes the metrics to be reported via the flex container's
+ * ReflowOutput & nsReflowStatus output parameters in Reflow().
+ *
+ * @param aContentBoxSize the final content-box size for the flex container as
+ * a whole, converted from the flex container's
+ * main/cross sizes. The main/cross sizes are computed
+ * by DoFlexLayout() if this frame is the
+ * first-in-flow, or are the stored ones in
+ * SharedFlexData if this frame is a not the
+ * first-in-flow.
+ * @param aBorderPadding the border and padding for this frame (possibly with
+ * some sides skipped as-appropriate, if we're in a
+ * continuation chain).
+ * @param aConsumedBSize the sum of our content-box block-size consumed by our
+ * prev-in-flows.
+ * @param aMayNeedNextInFlow true if we may need a next-in-flow because our
+ * effective content-box block-size exceeds the
+ * available block-size.
+ * @param aMaxBlockEndEdgeOfChildren the maximum block-end edge of the
+ * children of this fragment in this frame's
+ * coordinate space (as returned by
+ * ReflowChildren()).
+ * @param aChildrenStatus the reflow status of children (as returned by
+ * ReflowChildren()).
+ * @param aFlr the result returned by DoFlexLayout.
+ * Note: aFlr is mostly an "input" parameter, but we use
+ * aFlr.mAscent as an "in/out" parameter; it's initially the
+ * "tentative" flex container ascent computed in DoFlexLayout; or
+ * nscoord_MIN if the ascent hasn't been established yet. If the
+ * latter, this will be updated with an ascent derived from the
+ * (WM-relative) startmost flex item (if there are any flex
+ * items). Similar for aFlr.mAscentForLast.
+ */
+ void PopulateReflowOutput(
+ ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus, const mozilla::LogicalSize& aContentBoxSize,
+ const mozilla::LogicalMargin& aBorderPadding,
+ const nscoord aConsumedBSize, const bool aMayNeedNextInFlow,
+ const nscoord aMaxBlockEndEdgeOfChildren,
+ const nsReflowStatus& aChildrenStatus,
+ const FlexboxAxisTracker& aAxisTracker, FlexLayoutResult& aFlr);
+
+ /**
+ * Perform a final Reflow for our child frames.
+ *
+ * @param aContainerSize this frame's tentative physical border-box size, used
+ * only for logical to physical coordinate conversion.
+ * @param aAvailableSizeForItems the size of the available space for our
+ * children to reflow into.
+ * @param aBorderPadding the border and padding for this frame (possibly with
+ * some sides skipped as-appropriate, if we're in a
+ * continuation chain).
+ * @param aSumOfPrevInFlowsChildrenBlockSize See the comment for
+ * SumOfChildrenBlockSizeProperty.
+ * @param aFlr the result returned by DoFlexLayout.
+ * @param aFragmentData See the comment for PerFragmentFlexData.
+ * Note: aFragmentData is an "in/out" parameter. It is
+ * initialized by the data stored in our prev-in-flow's
+ * PerFragmentFlexData::Prop(); its fields will then be
+ * updated and become our PerFragmentFlexData.
+ * @return nscoord the maximum block-end edge of children of this fragment in
+ * flex container's coordinate space.
+ * @return nsReflowStatus the reflow status of children (i.e. flex items). If
+ * any child had an incomplete reflow status, then this
+ * will be Incomplete. Otherwise, if any child had an
+ * overflow-incomplete reflow status, this will be
+ * OverflowIncomplete.
+ */
+ std::tuple<nscoord, nsReflowStatus> ReflowChildren(
+ const ReflowInput& aReflowInput, const nsSize& aContainerSize,
+ const mozilla::LogicalSize& aAvailableSizeForItems,
+ const mozilla::LogicalMargin& aBorderPadding,
+ const FlexboxAxisTracker& aAxisTracker, FlexLayoutResult& aFlr,
+ PerFragmentFlexData& aFragmentData);
+
+ /**
+ * Moves the given flex item's frame to the given LogicalPosition (modulo any
+ * relative positioning).
+ *
+ * This can be used in cases where we've already done a "measuring reflow"
+ * for the flex item at the correct size, and hence can skip its final reflow
+ * (but still need to move it to the right final position).
+ *
+ * @param aItem The flex item whose frame should be moved.
+ * @param aFramePos The position where the flex item's frame should
+ * be placed. (pre-relative positioning)
+ * @param aContainerSize The flex container's size (required by some methods
+ * that we call, to interpret aFramePos correctly).
+ */
+ void MoveFlexItemToFinalPosition(const FlexItem& aItem,
+ const mozilla::LogicalPoint& aFramePos,
+ const nsSize& aContainerSize);
+ /**
+ * Helper-function to reflow a child frame, at its final position determined
+ * by flex layout.
+ *
+ * @param aAxisTracker A FlexboxAxisTracker with the flex container's axes.
+ * @param aReflowInput The flex container's reflow input.
+ * @param aItem The flex item to be reflowed.
+ * @param aFramePos The position where the flex item's frame should
+ * be placed. (pre-relative positioning)
+ * @param aAvailableSize The available size to reflow the child frame (in the
+ * child frame's writing-mode).
+ * @param aContainerSize The flex container's size (required by some methods
+ * that we call, to interpret aFramePos correctly).
+ * @return the child frame's reflow status.
+ */
+ nsReflowStatus ReflowFlexItem(const FlexboxAxisTracker& aAxisTracker,
+ const ReflowInput& aReflowInput,
+ const FlexItem& aItem,
+ const mozilla::LogicalPoint& aFramePos,
+ const mozilla::LogicalSize& aAvailableSize,
+ const nsSize& aContainerSize);
+
+ /**
+ * Helper-function to perform a "dummy reflow" on all our nsPlaceholderFrame
+ * children, at the container's content-box origin.
+ *
+ * This doesn't actually represent the static position of the placeholders'
+ * out-of-flow (OOF) frames -- we can't compute that until we've reflowed the
+ * OOF, because (depending on the CSS Align properties) the static position
+ * may be influenced by the OOF's size. So for now, we just co-opt the
+ * placeholder to store the flex container's logical content-box origin, and
+ * we defer to nsAbsoluteContainingBlock to determine the OOF's actual static
+ * position (using this origin, the OOF's size, and the CSS Align
+ * properties).
+ *
+ * @param aReflowInput The flex container's reflow input.
+ * @param aPlaceholders An array of all the flex container's
+ * nsPlaceholderFrame children.
+ * @param aContentBoxOrigin The flex container's logical content-box
+ * origin (in its own coordinate space).
+ * @param aContainerSize The flex container's size (required by some
+ * reflow methods to interpret positions correctly).
+ */
+ void ReflowPlaceholders(const ReflowInput& aReflowInput,
+ nsTArray<nsIFrame*>& aPlaceholders,
+ const mozilla::LogicalPoint& aContentBoxOrigin,
+ const nsSize& aContainerSize);
+
+ /**
+ * Helper for GetMinISize / GetPrefISize.
+ */
+ nscoord IntrinsicISize(gfxContext* aRenderingContext,
+ mozilla::IntrinsicISizeType aType);
+
+ /**
+ * Cached values to optimize GetMinISize/GetPrefISize.
+ */
+ nscoord mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ nscoord mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+
+ /**
+ * Cached baselines computed in our last reflow to optimize
+ * GetNaturalBaselineBOffset().
+ */
+ // Note: the first baseline is a distance from our border-box block-start
+ // edge.
+ nscoord mFirstBaseline = NS_INTRINSIC_ISIZE_UNKNOWN;
+ // Note: the last baseline is a distance from our border-box block-end edge.
+ nscoord mLastBaseline = NS_INTRINSIC_ISIZE_UNKNOWN;
+};
+
+#endif /* nsFlexContainerFrame_h___ */
diff --git a/layout/generic/nsFloatManager.cpp b/layout/generic/nsFloatManager.cpp
new file mode 100644
index 0000000000..b6612ca6f5
--- /dev/null
+++ b/layout/generic/nsFloatManager.cpp
@@ -0,0 +1,3010 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* class that manages rules for positioning floats */
+
+#include "nsFloatManager.h"
+
+#include <algorithm>
+#include <initializer_list>
+
+#include "gfxContext.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ReflowInput.h"
+#include "mozilla/ShapeUtils.h"
+#include "nsBlockFrame.h"
+#include "nsDeviceContext.h"
+#include "nsError.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsImageRenderer.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+using namespace mozilla::gfx;
+
+int32_t nsFloatManager::sCachedFloatManagerCount = 0;
+void* nsFloatManager::sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE];
+
+/////////////////////////////////////////////////////////////////////////////
+// nsFloatManager
+
+nsFloatManager::nsFloatManager(PresShell* aPresShell, WritingMode aWM)
+ :
+#ifdef DEBUG
+ mWritingMode(aWM),
+#endif
+ mLineLeft(0),
+ mBlockStart(0),
+ mFloatDamage(aPresShell),
+ mPushedLeftFloatPastBreak(false),
+ mPushedRightFloatPastBreak(false),
+ mSplitLeftFloatAcrossBreak(false),
+ mSplitRightFloatAcrossBreak(false) {
+ MOZ_COUNT_CTOR(nsFloatManager);
+}
+
+nsFloatManager::~nsFloatManager() { MOZ_COUNT_DTOR(nsFloatManager); }
+
+// static
+void* nsFloatManager::operator new(size_t aSize) noexcept(true) {
+ if (sCachedFloatManagerCount > 0) {
+ // We have cached unused instances of this class, return a cached
+ // instance in stead of always creating a new one.
+ return sCachedFloatManagers[--sCachedFloatManagerCount];
+ }
+
+ // The cache is empty, this means we have to create a new instance using
+ // the global |operator new|.
+ return moz_xmalloc(aSize);
+}
+
+void nsFloatManager::operator delete(void* aPtr, size_t aSize) {
+ if (!aPtr) return;
+ // This float manager is no longer used, if there's still room in
+ // the cache we'll cache this float manager, unless the layout
+ // module was already shut down.
+
+ if (sCachedFloatManagerCount < NS_FLOAT_MANAGER_CACHE_SIZE &&
+ sCachedFloatManagerCount >= 0) {
+ // There's still space in the cache for more instances, put this
+ // instance in the cache in stead of deleting it.
+
+ sCachedFloatManagers[sCachedFloatManagerCount++] = aPtr;
+ return;
+ }
+
+ // The cache is full, or the layout module has been shut down,
+ // delete this float manager.
+ free(aPtr);
+}
+
+/* static */
+void nsFloatManager::Shutdown() {
+ // The layout module is being shut down, clean up the cache and
+ // disable further caching.
+
+ int32_t i;
+
+ for (i = 0; i < sCachedFloatManagerCount; i++) {
+ void* floatManager = sCachedFloatManagers[i];
+ if (floatManager) free(floatManager);
+ }
+
+ // Disable further caching.
+ sCachedFloatManagerCount = -1;
+}
+
+#define CHECK_BLOCK_AND_LINE_DIR(aWM) \
+ NS_ASSERTION((aWM).GetBlockDir() == mWritingMode.GetBlockDir() && \
+ (aWM).IsLineInverted() == mWritingMode.IsLineInverted(), \
+ "incompatible writing modes")
+
+nsFlowAreaRect nsFloatManager::GetFlowArea(
+ WritingMode aWM, nscoord aBCoord, nscoord aBSize,
+ BandInfoType aBandInfoType, ShapeType aShapeType, LogicalRect aContentArea,
+ SavedState* aState, const nsSize& aContainerSize) const {
+ CHECK_BLOCK_AND_LINE_DIR(aWM);
+ NS_ASSERTION(aBSize >= 0, "unexpected max block size");
+ NS_ASSERTION(aContentArea.ISize(aWM) >= 0,
+ "unexpected content area inline size");
+
+ nscoord blockStart = aBCoord + mBlockStart;
+ if (blockStart < nscoord_MIN) {
+ NS_WARNING("bad value");
+ blockStart = nscoord_MIN;
+ }
+
+ // Determine the last float that we should consider.
+ uint32_t floatCount;
+ if (aState) {
+ // Use the provided state.
+ floatCount = aState->mFloatInfoCount;
+ MOZ_ASSERT(floatCount <= mFloats.Length(), "bad state");
+ } else {
+ // Use our current state.
+ floatCount = mFloats.Length();
+ }
+
+ // If there are no floats at all, or we're below the last one, return
+ // quickly.
+ if (floatCount == 0 || (mFloats[floatCount - 1].mLeftBEnd <= blockStart &&
+ mFloats[floatCount - 1].mRightBEnd <= blockStart)) {
+ return nsFlowAreaRect(aWM, aContentArea.IStart(aWM), aBCoord,
+ aContentArea.ISize(aWM), aBSize,
+ nsFlowAreaRectFlags::NoFlags);
+ }
+
+ nscoord blockEnd;
+ if (aBSize == nscoord_MAX) {
+ // This warning (and the two below) are possible to hit on pages
+ // with really large objects.
+ NS_WARNING_ASSERTION(aBandInfoType == BandInfoType::BandFromPoint,
+ "bad height");
+ blockEnd = nscoord_MAX;
+ } else {
+ blockEnd = blockStart + aBSize;
+ if (blockEnd < blockStart || blockEnd > nscoord_MAX) {
+ NS_WARNING("bad value");
+ blockEnd = nscoord_MAX;
+ }
+ }
+ nscoord lineLeft = mLineLeft + aContentArea.LineLeft(aWM, aContainerSize);
+ nscoord lineRight = mLineLeft + aContentArea.LineRight(aWM, aContainerSize);
+ if (lineRight < lineLeft) {
+ NS_WARNING("bad value");
+ lineRight = lineLeft;
+ }
+
+ // Walk backwards through the floats until we either hit the front of
+ // the list or we're above |blockStart|.
+ bool haveFloats = false;
+ bool mayWiden = false;
+ for (uint32_t i = floatCount; i > 0; --i) {
+ const FloatInfo& fi = mFloats[i - 1];
+ if (fi.mLeftBEnd <= blockStart && fi.mRightBEnd <= blockStart) {
+ // There aren't any more floats that could intersect this band.
+ break;
+ }
+ if (fi.IsEmpty(aShapeType)) {
+ // Ignore empty float areas.
+ // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
+ continue;
+ }
+
+ nscoord floatBStart = fi.BStart(aShapeType);
+ nscoord floatBEnd = fi.BEnd(aShapeType);
+ if (blockStart < floatBStart &&
+ aBandInfoType == BandInfoType::BandFromPoint) {
+ // This float is below our band. Shrink our band's height if needed.
+ if (floatBStart < blockEnd) {
+ blockEnd = floatBStart;
+ }
+ }
+ // If blockStart == blockEnd (which happens only with WidthWithinHeight),
+ // we include floats that begin at our 0-height vertical area. We
+ // need to do this to satisfy the invariant that a
+ // WidthWithinHeight call is at least as narrow on both sides as a
+ // BandFromPoint call beginning at its blockStart.
+ else if (blockStart < floatBEnd &&
+ (floatBStart < blockEnd ||
+ (floatBStart == blockEnd && blockStart == blockEnd))) {
+ // This float is in our band.
+
+ // Shrink our band's width if needed.
+ StyleFloat floatStyle = fi.mFrame->StyleDisplay()->mFloat;
+
+ // When aBandInfoType is BandFromPoint, we're only intended to
+ // consider a point along the y axis rather than a band.
+ const nscoord bandBlockEnd =
+ aBandInfoType == BandInfoType::BandFromPoint ? blockStart : blockEnd;
+ if (floatStyle == StyleFloat::Left) {
+ // A left float
+ nscoord lineRightEdge =
+ fi.LineRight(aShapeType, blockStart, bandBlockEnd);
+ if (lineRightEdge > lineLeft) {
+ lineLeft = lineRightEdge;
+ // Only set haveFloats to true if the float is inside our
+ // containing block. This matches the spec for what some
+ // callers want and disagrees for other callers, so we should
+ // probably provide better information at some point.
+ haveFloats = true;
+
+ // Our area may widen in the block direction if this float may
+ // narrow in the block direction.
+ mayWiden = mayWiden || fi.MayNarrowInBlockDirection(aShapeType);
+ }
+ } else {
+ // A right float
+ nscoord lineLeftEdge =
+ fi.LineLeft(aShapeType, blockStart, bandBlockEnd);
+ if (lineLeftEdge < lineRight) {
+ lineRight = lineLeftEdge;
+ // See above.
+ haveFloats = true;
+ mayWiden = mayWiden || fi.MayNarrowInBlockDirection(aShapeType);
+ }
+ }
+
+ // Shrink our band's height if needed.
+ if (floatBEnd < blockEnd &&
+ aBandInfoType == BandInfoType::BandFromPoint) {
+ blockEnd = floatBEnd;
+ }
+ }
+ }
+
+ nscoord blockSize =
+ (blockEnd == nscoord_MAX) ? nscoord_MAX : (blockEnd - blockStart);
+ // convert back from LineLeft/Right to IStart
+ nscoord inlineStart =
+ aWM.IsBidiLTR()
+ ? lineLeft - mLineLeft
+ : mLineLeft - lineRight + LogicalSize(aWM, aContainerSize).ISize(aWM);
+
+ nsFlowAreaRectFlags flags =
+ (haveFloats ? nsFlowAreaRectFlags::HasFloats
+ : nsFlowAreaRectFlags::NoFlags) |
+ (mayWiden ? nsFlowAreaRectFlags::MayWiden : nsFlowAreaRectFlags::NoFlags);
+ // Some callers clamp the inline size of nsFlowAreaRect to be nonnegative
+ // "for compatibility with nsSpaceManager". So, we set a flag here to record
+ // the fact that the ISize is actually negative, so that downstream code can
+ // realize that there's no place here where we could put a float-avoiding
+ // block (even one with ISize of 0).
+ if (lineRight - lineLeft < 0) {
+ flags |= nsFlowAreaRectFlags::ISizeIsActuallyNegative;
+ }
+
+ return nsFlowAreaRect(aWM, inlineStart, blockStart - mBlockStart,
+ lineRight - lineLeft, blockSize, flags);
+}
+
+void nsFloatManager::AddFloat(nsIFrame* aFloatFrame,
+ const LogicalRect& aMarginRect, WritingMode aWM,
+ const nsSize& aContainerSize) {
+ CHECK_BLOCK_AND_LINE_DIR(aWM);
+ NS_ASSERTION(aMarginRect.ISize(aWM) >= 0, "negative inline size!");
+ NS_ASSERTION(aMarginRect.BSize(aWM) >= 0, "negative block size!");
+
+ FloatInfo info(aFloatFrame, mLineLeft, mBlockStart, aMarginRect, aWM,
+ aContainerSize);
+
+ // Set mLeftBEnd and mRightBEnd.
+ if (HasAnyFloats()) {
+ FloatInfo& tail = mFloats[mFloats.Length() - 1];
+ info.mLeftBEnd = tail.mLeftBEnd;
+ info.mRightBEnd = tail.mRightBEnd;
+ } else {
+ info.mLeftBEnd = nscoord_MIN;
+ info.mRightBEnd = nscoord_MIN;
+ }
+ StyleFloat floatStyle = aFloatFrame->StyleDisplay()->mFloat;
+ MOZ_ASSERT(floatStyle == StyleFloat::Left || floatStyle == StyleFloat::Right,
+ "Unexpected float style!");
+ nscoord& sideBEnd =
+ floatStyle == StyleFloat::Left ? info.mLeftBEnd : info.mRightBEnd;
+ nscoord thisBEnd = info.BEnd();
+ if (thisBEnd > sideBEnd) sideBEnd = thisBEnd;
+
+ mFloats.AppendElement(std::move(info));
+}
+
+// static
+LogicalRect nsFloatManager::CalculateRegionFor(WritingMode aWM,
+ nsIFrame* aFloat,
+ const LogicalMargin& aMargin,
+ const nsSize& aContainerSize) {
+ // We consider relatively positioned frames at their original position.
+ LogicalRect region(aWM,
+ nsRect(aFloat->GetNormalPosition(), aFloat->GetSize()),
+ aContainerSize);
+
+ // Float region includes its margin
+ region.Inflate(aWM, aMargin);
+
+ // Don't store rectangles with negative margin-box width or height in
+ // the float manager; it can't deal with them.
+ if (region.ISize(aWM) < 0) {
+ // Preserve the right margin-edge for left floats and the left
+ // margin-edge for right floats
+ const nsStyleDisplay* display = aFloat->StyleDisplay();
+ StyleFloat floatStyle = display->mFloat;
+ if ((StyleFloat::Left == floatStyle) == aWM.IsBidiLTR()) {
+ region.IStart(aWM) = region.IEnd(aWM);
+ }
+ region.ISize(aWM) = 0;
+ }
+ if (region.BSize(aWM) < 0) {
+ region.BSize(aWM) = 0;
+ }
+ return region;
+}
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(FloatRegionProperty, nsMargin)
+
+LogicalRect nsFloatManager::GetRegionFor(WritingMode aWM, nsIFrame* aFloat,
+ const nsSize& aContainerSize) {
+ LogicalRect region = aFloat->GetLogicalRect(aWM, aContainerSize);
+ void* storedRegion = aFloat->GetProperty(FloatRegionProperty());
+ if (storedRegion) {
+ nsMargin margin = *static_cast<nsMargin*>(storedRegion);
+ region.Inflate(aWM, LogicalMargin(aWM, margin));
+ }
+ return region;
+}
+
+void nsFloatManager::StoreRegionFor(WritingMode aWM, nsIFrame* aFloat,
+ const LogicalRect& aRegion,
+ const nsSize& aContainerSize) {
+ nsRect region = aRegion.GetPhysicalRect(aWM, aContainerSize);
+ nsRect rect = aFloat->GetRect();
+ if (region.IsEqualEdges(rect)) {
+ aFloat->RemoveProperty(FloatRegionProperty());
+ } else {
+ nsMargin* storedMargin = aFloat->GetProperty(FloatRegionProperty());
+ if (!storedMargin) {
+ storedMargin = new nsMargin();
+ aFloat->SetProperty(FloatRegionProperty(), storedMargin);
+ }
+ *storedMargin = region - rect;
+ }
+}
+
+nsresult nsFloatManager::RemoveTrailingRegions(nsIFrame* aFrameList) {
+ if (!aFrameList) {
+ return NS_OK;
+ }
+ // This could be a good bit simpler if we could guarantee that the
+ // floats given were at the end of our list, so we could just search
+ // for the head of aFrameList. (But we can't;
+ // layout/reftests/bugs/421710-1.html crashes.)
+ nsTHashSet<nsIFrame*> frameSet(1);
+
+ for (nsIFrame* f = aFrameList; f; f = f->GetNextSibling()) {
+ frameSet.Insert(f);
+ }
+
+ uint32_t newLength = mFloats.Length();
+ while (newLength > 0) {
+ if (!frameSet.Contains(mFloats[newLength - 1].mFrame)) {
+ break;
+ }
+ --newLength;
+ }
+ mFloats.TruncateLength(newLength);
+
+#ifdef DEBUG
+ for (uint32_t i = 0; i < mFloats.Length(); ++i) {
+ NS_ASSERTION(
+ !frameSet.Contains(mFloats[i].mFrame),
+ "Frame region deletion was requested but we couldn't delete it");
+ }
+#endif
+
+ return NS_OK;
+}
+
+void nsFloatManager::PushState(SavedState* aState) {
+ MOZ_ASSERT(aState, "Need a place to save state");
+
+ // This is a cheap push implementation, which
+ // only saves the (x,y) and last frame in the mFrameInfoMap
+ // which is enough info to get us back to where we should be
+ // when pop is called.
+ //
+ // This push/pop mechanism is used to undo any
+ // floats that were added during the unconstrained reflow
+ // in nsBlockReflowContext::DoReflowBlock(). (See bug 96736)
+ //
+ // It should also be noted that the state for mFloatDamage is
+ // intentionally not saved or restored in PushState() and PopState(),
+ // since that could lead to bugs where damage is missed/dropped when
+ // we move from position A to B (during the intermediate incremental
+ // reflow mentioned above) and then from B to C during the subsequent
+ // reflow. In the typical case A and C will be the same, but not always.
+ // Allowing mFloatDamage to accumulate the damage incurred during both
+ // reflows ensures that nothing gets missed.
+ aState->mLineLeft = mLineLeft;
+ aState->mBlockStart = mBlockStart;
+ aState->mPushedLeftFloatPastBreak = mPushedLeftFloatPastBreak;
+ aState->mPushedRightFloatPastBreak = mPushedRightFloatPastBreak;
+ aState->mSplitLeftFloatAcrossBreak = mSplitLeftFloatAcrossBreak;
+ aState->mSplitRightFloatAcrossBreak = mSplitRightFloatAcrossBreak;
+ aState->mFloatInfoCount = mFloats.Length();
+}
+
+void nsFloatManager::PopState(SavedState* aState) {
+ MOZ_ASSERT(aState, "No state to restore?");
+
+ mLineLeft = aState->mLineLeft;
+ mBlockStart = aState->mBlockStart;
+ mPushedLeftFloatPastBreak = aState->mPushedLeftFloatPastBreak;
+ mPushedRightFloatPastBreak = aState->mPushedRightFloatPastBreak;
+ mSplitLeftFloatAcrossBreak = aState->mSplitLeftFloatAcrossBreak;
+ mSplitRightFloatAcrossBreak = aState->mSplitRightFloatAcrossBreak;
+
+ NS_ASSERTION(aState->mFloatInfoCount <= mFloats.Length(),
+ "somebody misused PushState/PopState");
+ mFloats.TruncateLength(aState->mFloatInfoCount);
+}
+
+nscoord nsFloatManager::LowestFloatBStart() const {
+ if (mPushedLeftFloatPastBreak || mPushedRightFloatPastBreak) {
+ return nscoord_MAX;
+ }
+ if (!HasAnyFloats()) {
+ return nscoord_MIN;
+ }
+ return mFloats[mFloats.Length() - 1].BStart() - mBlockStart;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+void DebugListFloatManager(const nsFloatManager* aFloatManager) {
+ aFloatManager->List(stdout);
+}
+
+nsresult nsFloatManager::List(FILE* out) const {
+ if (!HasAnyFloats()) return NS_OK;
+
+ for (uint32_t i = 0; i < mFloats.Length(); ++i) {
+ const FloatInfo& fi = mFloats[i];
+ fprintf_stderr(out,
+ "Float %u: frame=%p rect={%d,%d,%d,%d} BEnd={l:%d, r:%d}\n",
+ i, static_cast<void*>(fi.mFrame), fi.LineLeft(), fi.BStart(),
+ fi.ISize(), fi.BSize(), fi.mLeftBEnd, fi.mRightBEnd);
+ }
+ return NS_OK;
+}
+#endif
+
+nscoord nsFloatManager::ClearFloats(nscoord aBCoord,
+ StyleClear aClearType) const {
+ if (!HasAnyFloats()) {
+ return aBCoord;
+ }
+
+ nscoord blockEnd = aBCoord + mBlockStart;
+
+ const FloatInfo& tail = mFloats[mFloats.Length() - 1];
+ switch (aClearType) {
+ case StyleClear::Both:
+ blockEnd = std::max(blockEnd, tail.mLeftBEnd);
+ blockEnd = std::max(blockEnd, tail.mRightBEnd);
+ break;
+ case StyleClear::Left:
+ blockEnd = std::max(blockEnd, tail.mLeftBEnd);
+ break;
+ case StyleClear::Right:
+ blockEnd = std::max(blockEnd, tail.mRightBEnd);
+ break;
+ default:
+ // Do nothing
+ break;
+ }
+
+ blockEnd -= mBlockStart;
+
+ return blockEnd;
+}
+
+bool nsFloatManager::ClearContinues(StyleClear aClearType) const {
+ return ((mPushedLeftFloatPastBreak || mSplitLeftFloatAcrossBreak) &&
+ (aClearType == StyleClear::Both || aClearType == StyleClear::Left)) ||
+ ((mPushedRightFloatPastBreak || mSplitRightFloatAcrossBreak) &&
+ (aClearType == StyleClear::Both || aClearType == StyleClear::Right));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// ShapeInfo is an abstract class for implementing all the shapes in CSS
+// Shapes Module. A subclass needs to override all the methods to adjust
+// the flow area with respect to its shape.
+//
+class nsFloatManager::ShapeInfo {
+ public:
+ virtual ~ShapeInfo() = default;
+
+ virtual nscoord LineLeft(const nscoord aBStart,
+ const nscoord aBEnd) const = 0;
+ virtual nscoord LineRight(const nscoord aBStart,
+ const nscoord aBEnd) const = 0;
+ virtual nscoord BStart() const = 0;
+ virtual nscoord BEnd() const = 0;
+ virtual bool IsEmpty() const = 0;
+
+ // Does this shape possibly get inline narrower in the BStart() to BEnd()
+ // span when proceeding in the block direction? This is false for unrounded
+ // rectangles that span all the way to BEnd(), but could be true for other
+ // shapes. Note that we don't care if the BEnd() falls short of the margin
+ // rect -- the ShapeInfo can only affect float behavior in the span between
+ // BStart() and BEnd().
+ virtual bool MayNarrowInBlockDirection() const = 0;
+
+ // Translate the current origin by the specified offsets.
+ virtual void Translate(nscoord aLineLeft, nscoord aBlockStart) = 0;
+
+ static LogicalRect ComputeShapeBoxRect(StyleShapeBox, nsIFrame* const aFrame,
+ const LogicalRect& aMarginRect,
+ WritingMode aWM);
+
+ // Convert the LogicalRect to the special logical coordinate space used
+ // in float manager.
+ static nsRect ConvertToFloatLogical(const LogicalRect& aRect, WritingMode aWM,
+ const nsSize& aContainerSize) {
+ return nsRect(aRect.LineLeft(aWM, aContainerSize), aRect.BStart(aWM),
+ aRect.ISize(aWM), aRect.BSize(aWM));
+ }
+
+ static UniquePtr<ShapeInfo> CreateShapeBox(nsIFrame* const aFrame,
+ nscoord aShapeMargin,
+ const LogicalRect& aShapeBoxRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ static UniquePtr<ShapeInfo> CreateBasicShape(
+ const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
+ nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect,
+ const LogicalRect& aMarginRect, WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ static UniquePtr<ShapeInfo> CreateInset(const StyleBasicShape& aBasicShape,
+ nscoord aShapeMargin,
+ nsIFrame* aFrame,
+ const LogicalRect& aShapeBoxRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ static UniquePtr<ShapeInfo> CreateCircleOrEllipse(
+ const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
+ nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect, WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ static UniquePtr<ShapeInfo> CreatePolygon(const StyleBasicShape& aBasicShape,
+ nscoord aShapeMargin,
+ nsIFrame* const aFrame,
+ const LogicalRect& aShapeBoxRect,
+ const LogicalRect& aMarginRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ static UniquePtr<ShapeInfo> CreateImageShape(const StyleImage& aShapeImage,
+ float aShapeImageThreshold,
+ nscoord aShapeMargin,
+ nsIFrame* const aFrame,
+ const LogicalRect& aMarginRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ protected:
+ // Compute the minimum line-axis difference between the bounding shape
+ // box and its rounded corner within the given band (block-axis region).
+ // This is used as a helper function to compute the LineRight() and
+ // LineLeft(). See the picture in the implementation for an example.
+ // RadiusL and RadiusB stand for radius on the line-axis and block-axis.
+ //
+ // Returns radius-x diff on the line-axis, or 0 if there's no rounded
+ // corner within the given band.
+ static nscoord ComputeEllipseLineInterceptDiff(
+ const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
+ const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
+ const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
+ const nscoord aBandBStart, const nscoord aBandBEnd);
+
+ static nscoord XInterceptAtY(const nscoord aY, const nscoord aRadiusX,
+ const nscoord aRadiusY);
+
+ // Convert the physical point to the special logical coordinate space
+ // used in float manager.
+ static nsPoint ConvertToFloatLogical(const nsPoint& aPoint, WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ // Convert the half corner radii (nscoord[8]) to the special logical
+ // coordinate space used in float manager.
+ static UniquePtr<nscoord[]> ConvertToFloatLogical(const nscoord aRadii[8],
+ WritingMode aWM);
+
+ // Some ShapeInfo subclasses may define their float areas in intervals.
+ // Each interval is a rectangle that is one device pixel deep in the block
+ // axis. The values are stored as block edges in the y coordinates,
+ // and inline edges as the x coordinates. Interval arrays should be sorted
+ // on increasing y values. This function uses a binary search to find the
+ // first interval that contains aTargetY. If no such interval exists, this
+ // function returns aIntervals.Length().
+ static size_t MinIntervalIndexContainingY(const nsTArray<nsRect>& aIntervals,
+ const nscoord aTargetY);
+
+ // This interval function is designed to handle the arguments to ::LineLeft()
+ // and LineRight() and interpret them for the supplied aIntervals.
+ static nscoord LineEdge(const nsTArray<nsRect>& aIntervals,
+ const nscoord aBStart, const nscoord aBEnd,
+ bool aIsLineLeft);
+
+ // These types, constants, and functions are useful for ShapeInfos that
+ // allocate a distance field. Efficient distance field calculations use
+ // integer values that are 5X the Euclidean distance. MAX_MARGIN_5X is the
+ // largest possible margin that we can calculate (in 5X integer dev pixels),
+ // given these constraints.
+ typedef uint16_t dfType;
+ static const dfType MAX_CHAMFER_VALUE;
+ static const dfType MAX_MARGIN;
+ static const dfType MAX_MARGIN_5X;
+
+ // This function returns a typed, overflow-safe value of aShapeMargin in
+ // 5X integer dev pixels.
+ static dfType CalcUsedShapeMargin5X(nscoord aShapeMargin,
+ int32_t aAppUnitsPerDevPixel);
+};
+
+const nsFloatManager::ShapeInfo::dfType
+ nsFloatManager::ShapeInfo::MAX_CHAMFER_VALUE = 11;
+
+const nsFloatManager::ShapeInfo::dfType nsFloatManager::ShapeInfo::MAX_MARGIN =
+ (std::numeric_limits<dfType>::max() - MAX_CHAMFER_VALUE) / 5;
+
+const nsFloatManager::ShapeInfo::dfType
+ nsFloatManager::ShapeInfo::MAX_MARGIN_5X = MAX_MARGIN * 5;
+
+/////////////////////////////////////////////////////////////////////////////
+// EllipseShapeInfo
+//
+// Implements shape-outside: circle() and shape-outside: ellipse().
+//
+class nsFloatManager::EllipseShapeInfo final
+ : public nsFloatManager::ShapeInfo {
+ public:
+ // Construct the float area using math to calculate the shape boundary.
+ // This is the fast path and should be used when shape-margin is negligible,
+ // or when the two values of aRadii are roughly equal. Those two conditions
+ // are defined by ShapeMarginIsNegligible() and RadiiAreRoughlyEqual(). In
+ // those cases, we can conveniently represent the entire float area using
+ // an ellipse.
+ EllipseShapeInfo(const nsPoint& aCenter, const nsSize& aRadii,
+ nscoord aShapeMargin);
+
+ // Construct the float area using rasterization to calculate the shape
+ // boundary. This constructor accounts for the fact that applying
+ // 'shape-margin' to an ellipse produces a shape that is not mathematically
+ // representable as an ellipse.
+ EllipseShapeInfo(const nsPoint& aCenter, const nsSize& aRadii,
+ nscoord aShapeMargin, int32_t aAppUnitsPerDevPixel);
+
+ static bool ShapeMarginIsNegligible(nscoord aShapeMargin) {
+ // For now, only return true for a shape-margin of 0. In the future, if
+ // we want to enable use of the fast-path constructor more often, this
+ // limit could be increased;
+ static const nscoord SHAPE_MARGIN_NEGLIGIBLE_MAX(0);
+ return aShapeMargin <= SHAPE_MARGIN_NEGLIGIBLE_MAX;
+ }
+
+ static bool RadiiAreRoughlyEqual(const nsSize& aRadii) {
+ // For now, only return true when we are exactly equal. In the future, if
+ // we want to enable use of the fast-path constructor more often, this
+ // could be generalized to allow radii that are in some close proportion
+ // to each other.
+ return aRadii.width == aRadii.height;
+ }
+ nscoord LineEdge(const nscoord aBStart, const nscoord aBEnd,
+ bool aLeft) const;
+ nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord BStart() const override {
+ return mCenter.y - mRadii.height - mShapeMargin;
+ }
+ nscoord BEnd() const override {
+ return mCenter.y + mRadii.height + mShapeMargin;
+ }
+ bool IsEmpty() const override {
+ // An EllipseShapeInfo is never empty, because an ellipse or circle with
+ // a zero radius acts like a point, and an ellipse with one zero radius
+ // acts like a line.
+ return false;
+ }
+ bool MayNarrowInBlockDirection() const override { return true; }
+
+ void Translate(nscoord aLineLeft, nscoord aBlockStart) override {
+ mCenter.MoveBy(aLineLeft, aBlockStart);
+
+ for (nsRect& interval : mIntervals) {
+ interval.MoveBy(aLineLeft, aBlockStart);
+ }
+ }
+
+ private:
+ // The position of the center of the ellipse. The coordinate space is the
+ // same as FloatInfo::mRect.
+ nsPoint mCenter;
+ // The radii of the ellipse in app units. The width and height represent
+ // the line-axis and block-axis radii of the ellipse.
+ nsSize mRadii;
+ // The shape-margin of the ellipse in app units. If this value is greater
+ // than zero, then we calculate the bounds of the ellipse + margin using
+ // numerical methods and store the values in mIntervals.
+ nscoord mShapeMargin;
+
+ // An interval is slice of the float area defined by this EllipseShapeInfo.
+ // Each interval is a rectangle that is one pixel deep in the block
+ // axis. The values are stored as block edges in the y coordinates,
+ // and inline edges as the x coordinates.
+
+ // The intervals are stored in ascending order on y.
+ nsTArray<nsRect> mIntervals;
+};
+
+nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
+ const nsSize& aRadii,
+ nscoord aShapeMargin)
+ : mCenter(aCenter),
+ mRadii(aRadii),
+ mShapeMargin(
+ 0) // We intentionally ignore the value of aShapeMargin here.
+{
+ MOZ_ASSERT(
+ RadiiAreRoughlyEqual(aRadii) || ShapeMarginIsNegligible(aShapeMargin),
+ "This constructor should only be called when margin is "
+ "negligible or radii are roughly equal.");
+
+ // We add aShapeMargin into the radii, and we earlier stored a mShapeMargin
+ // of zero.
+ mRadii.width += aShapeMargin;
+ mRadii.height += aShapeMargin;
+}
+
+nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
+ const nsSize& aRadii,
+ nscoord aShapeMargin,
+ int32_t aAppUnitsPerDevPixel)
+ : mCenter(aCenter), mRadii(aRadii), mShapeMargin(aShapeMargin) {
+ if (RadiiAreRoughlyEqual(aRadii) || ShapeMarginIsNegligible(aShapeMargin)) {
+ // Mimic the behavior of the simple constructor, by adding aShapeMargin
+ // into the radii, and then storing mShapeMargin of zero.
+ mRadii.width += mShapeMargin;
+ mRadii.height += mShapeMargin;
+ mShapeMargin = 0;
+ return;
+ }
+
+ // We have to calculate a distance field from the ellipse edge, then build
+ // intervals based on pixels with less than aShapeMargin distance to an
+ // edge pixel.
+
+ // mCenter and mRadii have already been translated into logical coordinates.
+ // x = inline, y = block. Due to symmetry, we only need to calculate the
+ // distance field for one quadrant of the ellipse. We choose the positive-x,
+ // positive-y quadrant (the lower right quadrant in horizontal-tb writing
+ // mode). We choose this quadrant because it allows us to traverse our
+ // distance field in memory order, which is more cache efficient.
+ // When we apply these intervals in LineLeft() and LineRight(), we
+ // account for block ranges that hit other quadrants, or hit multiple
+ // quadrants.
+
+ // Given this setup, computing the distance field is a one-pass O(n)
+ // operation that runs from block top-to-bottom, inline left-to-right. We
+ // use a chamfer 5-7-11 5x5 matrix to compute minimum distance to an edge
+ // pixel. This integer math computation is reasonably close to the true
+ // Euclidean distance. The distances will be approximately 5x the true
+ // distance, quantized in integer units. The 5x is factored away in the
+ // comparison which builds the intervals.
+ dfType usedMargin5X =
+ CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
+
+ // Calculate the bounds of one quadrant of the ellipse, in integer device
+ // pixels. These bounds are equal to the rectangle defined by the radii,
+ // plus the shape-margin value in both dimensions.
+ const LayoutDeviceIntSize bounds =
+ LayoutDevicePixel::FromAppUnitsRounded(mRadii, aAppUnitsPerDevPixel) +
+ LayoutDeviceIntSize(usedMargin5X / 5, usedMargin5X / 5);
+
+ // Since our distance field is computed with a 5x5 neighborhood, but only
+ // looks in the negative block and negative inline directions, it is
+ // effectively a 3x3 neighborhood. We need to expand our distance field
+ // outwards by a further 2 pixels in both axes (on the minimum block edge
+ // and the minimum inline edge). We call this edge area the expanded region.
+
+ static const uint32_t iExpand = 2;
+ static const uint32_t bExpand = 2;
+
+ // Clamp the size of our distance field sizes to prevent multiplication
+ // overflow.
+ static const uint32_t DF_SIDE_MAX =
+ floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
+ const uint32_t iSize = std::min(bounds.width + iExpand, DF_SIDE_MAX);
+ const uint32_t bSize = std::min(bounds.height + bExpand, DF_SIDE_MAX);
+ auto df = MakeUniqueFallible<dfType[]>(iSize * bSize);
+ if (!df) {
+ // Without a distance field, we can't reason about the float area.
+ return;
+ }
+
+ // Single pass setting distance field, in positive block direction, three
+ // cases:
+ // 1) Expanded region pixel: set to MAX_MARGIN_5X.
+ // 2) Pixel within the ellipse: set to 0.
+ // 3) Other pixel: set to minimum neighborhood distance value, computed
+ // with 5-7-11 chamfer.
+
+ for (uint32_t b = 0; b < bSize; ++b) {
+ bool bIsInExpandedRegion(b < bExpand);
+ nscoord bInAppUnits = (b - bExpand) * aAppUnitsPerDevPixel;
+ bool bIsMoreThanEllipseBEnd(bInAppUnits > mRadii.height);
+
+ // Find the i intercept of the ellipse edge for this block row, and
+ // adjust it to compensate for the expansion of the inline dimension.
+ // If we're in the expanded region, or if we're using a b that's more
+ // than the bEnd of the ellipse, the intercept is nscoord_MIN.
+ // We have one other special case to consider: when the ellipse has no
+ // height. In that case we treat the bInAppUnits == 0 case as
+ // intercepting at the width of the ellipse. All other cases solve
+ // the intersection mathematically.
+ const int32_t iIntercept =
+ (bIsInExpandedRegion || bIsMoreThanEllipseBEnd)
+ ? nscoord_MIN
+ : iExpand + NSAppUnitsToIntPixels(
+ (!!mRadii.height || bInAppUnits)
+ ? XInterceptAtY(bInAppUnits, mRadii.width,
+ mRadii.height)
+ : mRadii.width,
+ aAppUnitsPerDevPixel);
+
+ // Set iMax in preparation for this block row.
+ int32_t iMax = iIntercept;
+
+ for (uint32_t i = 0; i < iSize; ++i) {
+ const uint32_t index = i + b * iSize;
+ MOZ_ASSERT(index < (iSize * bSize),
+ "Our distance field index should be in-bounds.");
+
+ // Handle our three cases, in order.
+ if (i < iExpand || bIsInExpandedRegion) {
+ // Case 1: Expanded reqion pixel.
+ df[index] = MAX_MARGIN_5X;
+ } else if ((int32_t)i <= iIntercept) {
+ // Case 2: Pixel within the ellipse, or just outside the edge of it.
+ // Having a positive height indicates that there's an area we can
+ // be inside of.
+ df[index] = (!!mRadii.height) ? 0 : 5;
+ } else {
+ // Case 3: Other pixel.
+
+ // Backward-looking neighborhood distance from target pixel X
+ // with chamfer 5-7-11 looks like:
+ //
+ // +--+--+--+
+ // | |11| |
+ // +--+--+--+
+ // |11| 7| 5|
+ // +--+--+--+
+ // | | 5| X|
+ // +--+--+--+
+ //
+ // X should be set to the minimum of the values of all of the numbered
+ // neighbors summed with the value in that chamfer cell.
+ MOZ_ASSERT(index - iSize - 2 < (iSize * bSize) &&
+ index - (iSize * 2) - 1 < (iSize * bSize),
+ "Our distance field most extreme indices should be "
+ "in-bounds.");
+
+ // clang-format off
+ df[index] = std::min<dfType>(df[index - 1] + 5,
+ std::min<dfType>(df[index - iSize] + 5,
+ std::min<dfType>(df[index - iSize - 1] + 7,
+ std::min<dfType>(df[index - iSize - 2] + 11,
+ df[index - (iSize * 2) - 1] + 11))));
+ // clang-format on
+
+ // Check the df value and see if it's less than or equal to the
+ // usedMargin5X value.
+ if (df[index] <= usedMargin5X) {
+ MOZ_ASSERT(iMax < (int32_t)i);
+ iMax = i;
+ } else {
+ // Since we're computing the bottom-right quadrant, there's no way
+ // for a later i value in this row to be within the usedMargin5X
+ // value. Likewise, every row beyond us will encounter this
+ // condition with an i value less than or equal to our i value now.
+ // Since our chamfer only looks upward and leftward, we can stop
+ // calculating for the rest of the row, because the distance field
+ // values there will never be looked at in a later row's chamfer
+ // calculation.
+ break;
+ }
+ }
+ }
+
+ // It's very likely, though not guaranteed that we will find an pixel
+ // within the shape-margin distance for each block row. This may not
+ // always be true due to rounding errors.
+ if (iMax > nscoord_MIN) {
+ // Origin for this interval is at the center of the ellipse, adjusted
+ // in the positive block direction by bInAppUnits.
+ nsPoint origin(aCenter.x, aCenter.y + bInAppUnits);
+ // Size is an inline iMax plus 1 (to account for the whole pixel) dev
+ // pixels, by 1 block dev pixel. We convert this to app units.
+ nsSize size((iMax - iExpand + 1) * aAppUnitsPerDevPixel,
+ aAppUnitsPerDevPixel);
+ mIntervals.AppendElement(nsRect(origin, size));
+ }
+ }
+}
+
+nscoord nsFloatManager::EllipseShapeInfo::LineEdge(const nscoord aBStart,
+ const nscoord aBEnd,
+ bool aIsLineLeft) const {
+ // If no mShapeMargin, just compute the edge using math.
+ if (mShapeMargin == 0) {
+ nscoord lineDiff = ComputeEllipseLineInterceptDiff(
+ BStart(), BEnd(), mRadii.width, mRadii.height, mRadii.width,
+ mRadii.height, aBStart, aBEnd);
+ return mCenter.x + (aIsLineLeft ? (-mRadii.width + lineDiff)
+ : (mRadii.width - lineDiff));
+ }
+
+ // We are checking against our intervals. Make sure we have some.
+ if (mIntervals.IsEmpty()) {
+ NS_WARNING("With mShapeMargin > 0, we can't proceed without intervals.");
+ return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
+ }
+
+ // Map aBStart and aBEnd into our intervals. Our intervals are calculated
+ // for the lower-right quadrant (in terms of horizontal-tb writing mode).
+ // If aBStart and aBEnd span the center of the ellipse, then we know we
+ // are at the maximum displacement from the center.
+ bool bStartIsAboveCenter = (aBStart < mCenter.y);
+ bool bEndIsBelowOrAtCenter = (aBEnd >= mCenter.y);
+ if (bStartIsAboveCenter && bEndIsBelowOrAtCenter) {
+ return mCenter.x + (aIsLineLeft ? (-mRadii.width - mShapeMargin)
+ : (mRadii.width + mShapeMargin));
+ }
+
+ // aBStart and aBEnd don't span the center. Since the intervals are
+ // strictly wider approaching the center (the start of the mIntervals
+ // array), we only need to find the interval at the block value closest to
+ // the center. We find the min of aBStart, aBEnd, and their reflections --
+ // whichever two of them are within the lower-right quadrant. When we
+ // reflect from the upper-right quadrant to the lower-right, we have to
+ // subtract 1 from the reflection, to account that block values are always
+ // addressed from the leading block edge.
+
+ // The key example is when we check with aBStart == aBEnd at the top of the
+ // intervals. That block line would be considered contained in the
+ // intervals (though it has no height), but its reflection would not be
+ // within the intervals unless we subtract 1.
+ nscoord bSmallestWithinIntervals = std::min(
+ bStartIsAboveCenter ? aBStart + (mCenter.y - aBStart) * 2 - 1 : aBStart,
+ bEndIsBelowOrAtCenter ? aBEnd : aBEnd + (mCenter.y - aBEnd) * 2 - 1);
+
+ MOZ_ASSERT(bSmallestWithinIntervals >= mCenter.y &&
+ bSmallestWithinIntervals < BEnd(),
+ "We should have a block value within the float area.");
+
+ size_t index =
+ MinIntervalIndexContainingY(mIntervals, bSmallestWithinIntervals);
+ if (index >= mIntervals.Length()) {
+ // This indicates that our intervals don't cover the block value
+ // bSmallestWithinIntervals. This can happen when rounding error in the
+ // distance field calculation resulted in the last block pixel row not
+ // contributing to the float area. As long as we're within one block pixel
+ // past the last interval, this is an expected outcome.
+#ifdef DEBUG
+ nscoord onePixelPastLastInterval =
+ mIntervals[mIntervals.Length() - 1].YMost() +
+ mIntervals[mIntervals.Length() - 1].Height();
+ NS_WARNING_ASSERTION(bSmallestWithinIntervals < onePixelPastLastInterval,
+ "We should have found a matching interval for this "
+ "block value.");
+#endif
+ return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
+ }
+
+ // The interval is storing the line right value. If aIsLineLeft is true,
+ // return the line right value reflected about the center. Since this is
+ // an inline measurement, it's just checking the distance to an edge, and
+ // not a collision with a specific pixel. For that reason, we don't need
+ // to subtract 1 from the reflection, as we did with the block reflection.
+ nscoord iLineRight = mIntervals[index].XMost();
+ return aIsLineLeft ? iLineRight - (iLineRight - mCenter.x) * 2 : iLineRight;
+}
+
+nscoord nsFloatManager::EllipseShapeInfo::LineLeft(const nscoord aBStart,
+ const nscoord aBEnd) const {
+ return LineEdge(aBStart, aBEnd, true);
+}
+
+nscoord nsFloatManager::EllipseShapeInfo::LineRight(const nscoord aBStart,
+ const nscoord aBEnd) const {
+ return LineEdge(aBStart, aBEnd, false);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// RoundedBoxShapeInfo
+//
+// Implements shape-outside: <shape-box> and shape-outside: inset().
+//
+class nsFloatManager::RoundedBoxShapeInfo final
+ : public nsFloatManager::ShapeInfo {
+ public:
+ RoundedBoxShapeInfo(const nsRect& aRect, UniquePtr<nscoord[]> aRadii)
+ : mRect(aRect), mRadii(std::move(aRadii)), mShapeMargin(0) {}
+
+ RoundedBoxShapeInfo(const nsRect& aRect, UniquePtr<nscoord[]> aRadii,
+ nscoord aShapeMargin, int32_t aAppUnitsPerDevPixel);
+
+ nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord BStart() const override { return mRect.y; }
+ nscoord BEnd() const override { return mRect.YMost(); }
+ bool IsEmpty() const override {
+ // A RoundedBoxShapeInfo is never empty, because if it is collapsed to
+ // zero area, it acts like a point. If it is collapsed further, to become
+ // inside-out, it acts like a rect in the same shape as the inside-out
+ // rect.
+ return false;
+ }
+ bool MayNarrowInBlockDirection() const override {
+ // Only possible to narrow if there are non-null mRadii.
+ return !!mRadii;
+ }
+
+ void Translate(nscoord aLineLeft, nscoord aBlockStart) override {
+ mRect.MoveBy(aLineLeft, aBlockStart);
+
+ if (mShapeMargin > 0) {
+ MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalTopRightCorner &&
+ mLogicalBottomLeftCorner && mLogicalBottomRightCorner,
+ "If we have positive shape-margin, we should have corners.");
+ mLogicalTopLeftCorner->Translate(aLineLeft, aBlockStart);
+ mLogicalTopRightCorner->Translate(aLineLeft, aBlockStart);
+ mLogicalBottomLeftCorner->Translate(aLineLeft, aBlockStart);
+ mLogicalBottomRightCorner->Translate(aLineLeft, aBlockStart);
+ }
+ }
+
+ static bool EachCornerHasBalancedRadii(const nscoord* aRadii) {
+ return (aRadii[eCornerTopLeftX] == aRadii[eCornerTopLeftY] &&
+ aRadii[eCornerTopRightX] == aRadii[eCornerTopRightY] &&
+ aRadii[eCornerBottomLeftX] == aRadii[eCornerBottomLeftY] &&
+ aRadii[eCornerBottomRightX] == aRadii[eCornerBottomRightY]);
+ }
+
+ private:
+ // The rect of the rounded box shape in the float manager's coordinate
+ // space.
+ nsRect mRect;
+ // The half corner radii of the reference box. It's an nscoord[8] array
+ // in the float manager's coordinate space. If there are no radii, it's
+ // nullptr.
+ const UniquePtr<nscoord[]> mRadii;
+
+ // A shape-margin value extends the boundaries of the float area. When our
+ // first constructor is used, it is for the creation of rounded boxes that
+ // can ignore shape-margin -- either because it was specified as zero or
+ // because the box shape and radii can be inflated to account for it. When
+ // our second constructor is used, we store the shape-margin value here.
+ const nscoord mShapeMargin;
+
+ // If our second constructor is called (which implies mShapeMargin > 0),
+ // we will construct EllipseShapeInfo objects for each corner. We use the
+ // float logical naming here, where LogicalTopLeftCorner means the BStart
+ // LineLeft corner, and similarly for the other corners.
+ UniquePtr<EllipseShapeInfo> mLogicalTopLeftCorner;
+ UniquePtr<EllipseShapeInfo> mLogicalTopRightCorner;
+ UniquePtr<EllipseShapeInfo> mLogicalBottomLeftCorner;
+ UniquePtr<EllipseShapeInfo> mLogicalBottomRightCorner;
+};
+
+nsFloatManager::RoundedBoxShapeInfo::RoundedBoxShapeInfo(
+ const nsRect& aRect, UniquePtr<nscoord[]> aRadii, nscoord aShapeMargin,
+ int32_t aAppUnitsPerDevPixel)
+ : mRect(aRect), mRadii(std::move(aRadii)), mShapeMargin(aShapeMargin) {
+ MOZ_ASSERT(mShapeMargin > 0 && !EachCornerHasBalancedRadii(mRadii.get()),
+ "Slow constructor should only be used for for shape-margin > 0 "
+ "and radii with elliptical corners.");
+
+ // Before we inflate mRect by mShapeMargin, construct each of our corners.
+ // If we do it in this order, it's a bit simpler to calculate the center
+ // of each of the corners.
+ mLogicalTopLeftCorner = MakeUnique<EllipseShapeInfo>(
+ nsPoint(mRect.X() + mRadii[eCornerTopLeftX],
+ mRect.Y() + mRadii[eCornerTopLeftY]),
+ nsSize(mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY]), mShapeMargin,
+ aAppUnitsPerDevPixel);
+
+ mLogicalTopRightCorner = MakeUnique<EllipseShapeInfo>(
+ nsPoint(mRect.XMost() - mRadii[eCornerTopRightX],
+ mRect.Y() + mRadii[eCornerTopRightY]),
+ nsSize(mRadii[eCornerTopRightX], mRadii[eCornerTopRightY]), mShapeMargin,
+ aAppUnitsPerDevPixel);
+
+ mLogicalBottomLeftCorner = MakeUnique<EllipseShapeInfo>(
+ nsPoint(mRect.X() + mRadii[eCornerBottomLeftX],
+ mRect.YMost() - mRadii[eCornerBottomLeftY]),
+ nsSize(mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY]),
+ mShapeMargin, aAppUnitsPerDevPixel);
+
+ mLogicalBottomRightCorner = MakeUnique<EllipseShapeInfo>(
+ nsPoint(mRect.XMost() - mRadii[eCornerBottomRightX],
+ mRect.YMost() - mRadii[eCornerBottomRightY]),
+ nsSize(mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY]),
+ mShapeMargin, aAppUnitsPerDevPixel);
+
+ // Now we inflate our mRect by mShapeMargin.
+ mRect.Inflate(mShapeMargin);
+}
+
+nscoord nsFloatManager::RoundedBoxShapeInfo::LineLeft(
+ const nscoord aBStart, const nscoord aBEnd) const {
+ if (mShapeMargin == 0) {
+ if (!mRadii) {
+ return mRect.x;
+ }
+
+ nscoord lineLeftDiff = ComputeEllipseLineInterceptDiff(
+ mRect.y, mRect.YMost(), mRadii[eCornerTopLeftX],
+ mRadii[eCornerTopLeftY], mRadii[eCornerBottomLeftX],
+ mRadii[eCornerBottomLeftY], aBStart, aBEnd);
+ return mRect.x + lineLeftDiff;
+ }
+
+ MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalBottomLeftCorner,
+ "If we have positive shape-margin, we should have corners.");
+
+ // Determine if aBEnd is within our top corner.
+ if (aBEnd < mLogicalTopLeftCorner->BEnd()) {
+ return mLogicalTopLeftCorner->LineLeft(aBStart, aBEnd);
+ }
+
+ // Determine if aBStart is within our bottom corner.
+ if (aBStart >= mLogicalBottomLeftCorner->BStart()) {
+ return mLogicalBottomLeftCorner->LineLeft(aBStart, aBEnd);
+ }
+
+ // Either aBStart or aBEnd or both are within the flat part of our left
+ // edge. Because we've already inflated our mRect to encompass our
+ // mShapeMargin, we can just return the edge.
+ return mRect.X();
+}
+
+nscoord nsFloatManager::RoundedBoxShapeInfo::LineRight(
+ const nscoord aBStart, const nscoord aBEnd) const {
+ if (mShapeMargin == 0) {
+ if (!mRadii) {
+ return mRect.XMost();
+ }
+
+ nscoord lineRightDiff = ComputeEllipseLineInterceptDiff(
+ mRect.y, mRect.YMost(), mRadii[eCornerTopRightX],
+ mRadii[eCornerTopRightY], mRadii[eCornerBottomRightX],
+ mRadii[eCornerBottomRightY], aBStart, aBEnd);
+ return mRect.XMost() - lineRightDiff;
+ }
+
+ MOZ_ASSERT(mLogicalTopRightCorner && mLogicalBottomRightCorner,
+ "If we have positive shape-margin, we should have corners.");
+
+ // Determine if aBEnd is within our top corner.
+ if (aBEnd < mLogicalTopRightCorner->BEnd()) {
+ return mLogicalTopRightCorner->LineRight(aBStart, aBEnd);
+ }
+
+ // Determine if aBStart is within our bottom corner.
+ if (aBStart >= mLogicalBottomRightCorner->BStart()) {
+ return mLogicalBottomRightCorner->LineRight(aBStart, aBEnd);
+ }
+
+ // Either aBStart or aBEnd or both are within the flat part of our right
+ // edge. Because we've already inflated our mRect to encompass our
+ // mShapeMargin, we can just return the edge.
+ return mRect.XMost();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// PolygonShapeInfo
+//
+// Implements shape-outside: polygon().
+//
+class nsFloatManager::PolygonShapeInfo final
+ : public nsFloatManager::ShapeInfo {
+ public:
+ explicit PolygonShapeInfo(nsTArray<nsPoint>&& aVertices);
+ PolygonShapeInfo(nsTArray<nsPoint>&& aVertices, nscoord aShapeMargin,
+ int32_t aAppUnitsPerDevPixel, const nsRect& aMarginRect);
+
+ nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord BStart() const override { return mBStart; }
+ nscoord BEnd() const override { return mBEnd; }
+ bool IsEmpty() const override {
+ // A PolygonShapeInfo is never empty, because the parser prevents us from
+ // creating a shape with no vertices. If we only have 1 vertex, the
+ // shape acts like a point. With 2 non-coincident vertices, the shape
+ // acts like a line.
+ return false;
+ }
+ bool MayNarrowInBlockDirection() const override { return true; }
+
+ void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
+
+ private:
+ // Helper method for determining the mBStart and mBEnd based on the
+ // vertices' y extent.
+ void ComputeExtent();
+
+ // Helper method for implementing LineLeft() and LineRight().
+ nscoord ComputeLineIntercept(
+ const nscoord aBStart, const nscoord aBEnd,
+ nscoord (*aCompareOp)(std::initializer_list<nscoord>),
+ const nscoord aLineInterceptInitialValue) const;
+
+ // Given a horizontal line y, and two points p1 and p2 forming a line
+ // segment L. Solve x for the intersection of y and L. This method
+ // assumes y and L do intersect, and L is *not* horizontal.
+ static nscoord XInterceptAtY(const nscoord aY, const nsPoint& aP1,
+ const nsPoint& aP2);
+
+ // The vertices of the polygon in the float manager's coordinate space.
+ nsTArray<nsPoint> mVertices;
+
+ // An interval is slice of the float area defined by this PolygonShapeInfo.
+ // These are only generated and used in float area calculations for
+ // shape-margin > 0. Each interval is a rectangle that is one device pixel
+ // deep in the block axis. The values are stored as block edges in the y
+ // coordinates, and inline edges as the x coordinates.
+
+ // The intervals are stored in ascending order on y.
+ nsTArray<nsRect> mIntervals;
+
+ // Computed block start and block end value of the polygon shape. These
+ // initial values are set to correct values in ComputeExtent(), which is
+ // called from all constructors. Afterwards, mBStart is guaranteed to be
+ // less than or equal to mBEnd.
+ nscoord mBStart = nscoord_MAX;
+ nscoord mBEnd = nscoord_MIN;
+};
+
+nsFloatManager::PolygonShapeInfo::PolygonShapeInfo(
+ nsTArray<nsPoint>&& aVertices)
+ : mVertices(std::move(aVertices)) {
+ ComputeExtent();
+}
+
+nsFloatManager::PolygonShapeInfo::PolygonShapeInfo(
+ nsTArray<nsPoint>&& aVertices, nscoord aShapeMargin,
+ int32_t aAppUnitsPerDevPixel, const nsRect& aMarginRect)
+ : mVertices(std::move(aVertices)) {
+ MOZ_ASSERT(aShapeMargin > 0,
+ "This constructor should only be used for a "
+ "polygon with a positive shape-margin.");
+
+ ComputeExtent();
+
+ // With a positive aShapeMargin, we have to calculate a distance
+ // field from the opaque pixels, then build intervals based on
+ // them being within aShapeMargin distance to an opaque pixel.
+
+ // Roughly: for each pixel in the margin box, we need to determine the
+ // distance to the nearest opaque image-pixel. If that distance is less
+ // than aShapeMargin, we consider this margin-box pixel as being part of
+ // the float area.
+
+ // Computing the distance field is a two-pass O(n) operation.
+ // We use a chamfer 5-7-11 5x5 matrix to compute minimum distance
+ // to an opaque pixel. This integer math computation is reasonably
+ // close to the true Euclidean distance. The distances will be
+ // approximately 5x the true distance, quantized in integer units.
+ // The 5x is factored away in the comparison used in the final
+ // pass which builds the intervals.
+ dfType usedMargin5X =
+ CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
+
+ // Allocate our distance field. The distance field has to cover
+ // the entire aMarginRect, since aShapeMargin could bleed into it.
+ // Conveniently, our vertices have been converted into this same space,
+ // so if we cover the aMarginRect, we cover all the vertices.
+ const LayoutDeviceIntSize marginRectDevPixels =
+ LayoutDevicePixel::FromAppUnitsRounded(aMarginRect.Size(),
+ aAppUnitsPerDevPixel);
+
+ // Since our distance field is computed with a 5x5 neighborhood,
+ // we need to expand our distance field by a further 4 pixels in
+ // both axes, 2 on the leading edge and 2 on the trailing edge.
+ // We call this edge area the "expanded region".
+ static const uint32_t kiExpansionPerSide = 2;
+ static const uint32_t kbExpansionPerSide = 2;
+
+ // Clamp the size of our distance field sizes to prevent multiplication
+ // overflow.
+ static const uint32_t DF_SIDE_MAX =
+ floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
+
+ // Clamp the margin plus 2X the expansion values between expansion + 1 and
+ // DF_SIDE_MAX. This ensures that the distance field allocation doesn't
+ // overflow during multiplication, and the reverse iteration doesn't
+ // underflow.
+ const uint32_t iSize =
+ std::max(std::min(marginRectDevPixels.width + (kiExpansionPerSide * 2),
+ DF_SIDE_MAX),
+ kiExpansionPerSide + 1);
+ const uint32_t bSize =
+ std::max(std::min(marginRectDevPixels.height + (kbExpansionPerSide * 2),
+ DF_SIDE_MAX),
+ kbExpansionPerSide + 1);
+
+ // Since the margin-box size is CSS controlled, and large values will
+ // generate large iSize and bSize values, we do a fallible allocation for
+ // the distance field. If allocation fails, we early exit and layout will
+ // be wrong, but we'll avoid aborting from OOM.
+ auto df = MakeUniqueFallible<dfType[]>(iSize * bSize);
+ if (!df) {
+ // Without a distance field, we can't reason about the float area.
+ return;
+ }
+
+ // First pass setting distance field, starting at top-left, three cases:
+ // 1) Expanded region pixel: set to MAX_MARGIN_5X.
+ // 2) Pixel within the polygon: set to 0.
+ // 3) Other pixel: set to minimum backward-looking neighborhood
+ // distance value, computed with 5-7-11 chamfer.
+
+ for (uint32_t b = 0; b < bSize; ++b) {
+ // Find the left and right i intercepts of the polygon edge for this
+ // block row, and adjust them to compensate for the expansion of the
+ // inline dimension. If we're in the expanded region, or if we're using
+ // a b that's less than the bStart of the polygon, the intercepts are
+ // the nscoord min and max limits.
+ nscoord bInAppUnits = (b - kbExpansionPerSide) * aAppUnitsPerDevPixel;
+ bool bIsInExpandedRegion(b < kbExpansionPerSide ||
+ b >= bSize - kbExpansionPerSide);
+
+ // We now figure out the i values that correspond to the left edge and
+ // the right edge of the polygon at one-dev-pixel-thick strip of b. We
+ // have a ComputeLineIntercept function that takes and returns app unit
+ // coordinates in the space of aMarginRect. So to pass in b values, we
+ // first have to add the aMarginRect.y value. And for the values that we
+ // get out, we have to subtract away the aMarginRect.x value before
+ // converting the app units to dev pixels.
+ nscoord bInAppUnitsMarginRect = bInAppUnits + aMarginRect.y;
+ bool bIsLessThanPolygonBStart(bInAppUnitsMarginRect < mBStart);
+ bool bIsMoreThanPolygonBEnd(bInAppUnitsMarginRect > mBEnd);
+
+ const int32_t iLeftEdge =
+ (bIsInExpandedRegion || bIsLessThanPolygonBStart ||
+ bIsMoreThanPolygonBEnd)
+ ? nscoord_MAX
+ : kiExpansionPerSide +
+ NSAppUnitsToIntPixels(
+ ComputeLineIntercept(
+ bInAppUnitsMarginRect,
+ bInAppUnitsMarginRect + aAppUnitsPerDevPixel,
+ std::min<nscoord>, nscoord_MAX) -
+ aMarginRect.x,
+ aAppUnitsPerDevPixel);
+
+ const int32_t iRightEdge =
+ (bIsInExpandedRegion || bIsLessThanPolygonBStart ||
+ bIsMoreThanPolygonBEnd)
+ ? nscoord_MIN
+ : kiExpansionPerSide +
+ NSAppUnitsToIntPixels(
+ ComputeLineIntercept(
+ bInAppUnitsMarginRect,
+ bInAppUnitsMarginRect + aAppUnitsPerDevPixel,
+ std::max<nscoord>, nscoord_MIN) -
+ aMarginRect.x,
+ aAppUnitsPerDevPixel);
+
+ for (uint32_t i = 0; i < iSize; ++i) {
+ const uint32_t index = i + b * iSize;
+ MOZ_ASSERT(index < (iSize * bSize),
+ "Our distance field index should be in-bounds.");
+
+ // Handle our three cases, in order.
+ if (i < kiExpansionPerSide || i >= iSize - kiExpansionPerSide ||
+ bIsInExpandedRegion) {
+ // Case 1: Expanded pixel.
+ df[index] = MAX_MARGIN_5X;
+ } else if ((int32_t)i >= iLeftEdge && (int32_t)i <= iRightEdge) {
+ // Case 2: Polygon pixel, either inside or just adjacent to the right
+ // edge. We need this special distinction to detect a space between
+ // edges that is less than one dev pixel.
+ df[index] = (int32_t)i < iRightEdge ? 0 : 5;
+ } else {
+ // Case 3: Other pixel.
+
+ // Backward-looking neighborhood distance from target pixel X
+ // with chamfer 5-7-11 looks like:
+ //
+ // +--+--+--+--+--+
+ // | |11| |11| |
+ // +--+--+--+--+--+
+ // |11| 7| 5| 7|11|
+ // +--+--+--+--+--+
+ // | | 5| X| | |
+ // +--+--+--+--+--+
+ //
+ // X should be set to the minimum of MAX_MARGIN_5X and the
+ // values of all of the numbered neighbors summed with the
+ // value in that chamfer cell.
+ MOZ_ASSERT(index - (iSize * 2) - 1 < (iSize * bSize) &&
+ index - iSize - 2 < (iSize * bSize),
+ "Our distance field most extreme indices should be "
+ "in-bounds.");
+
+ // clang-format off
+ df[index] = std::min<dfType>(MAX_MARGIN_5X,
+ std::min<dfType>(df[index - (iSize * 2) - 1] + 11,
+ std::min<dfType>(df[index - (iSize * 2) + 1] + 11,
+ std::min<dfType>(df[index - iSize - 2] + 11,
+ std::min<dfType>(df[index - iSize - 1] + 7,
+ std::min<dfType>(df[index - iSize] + 5,
+ std::min<dfType>(df[index - iSize + 1] + 7,
+ std::min<dfType>(df[index - iSize + 2] + 11,
+ df[index - 1] + 5))))))));
+ // clang-format on
+ }
+ }
+ }
+
+ // Okay, time for the second pass. This pass is in reverse order from
+ // the first pass. All of our opaque pixels have been set to 0, and all
+ // of our expanded region pixels have been set to MAX_MARGIN_5X. Other
+ // pixels have been set to some value between those two (inclusive) but
+ // this hasn't yet taken into account the neighbors that were processed
+ // after them in the first pass. This time we reverse iterate so we can
+ // apply the forward-looking chamfer.
+
+ // This time, we constrain our outer and inner loop to ignore the
+ // expanded region pixels. For each pixel we iterate, we set the df value
+ // to the minimum forward-looking neighborhood distance value, computed
+ // with a 5-7-11 chamfer. We also check each df value against the
+ // usedMargin5X threshold, and use that to set the iMin and iMax values
+ // for the interval we'll create for that block axis value (b).
+
+ // At the end of each row, if any of the other pixels had a value less
+ // than usedMargin5X, we create an interval.
+ for (uint32_t b = bSize - kbExpansionPerSide - 1; b >= kbExpansionPerSide;
+ --b) {
+ // iMin tracks the first df pixel and iMax the last df pixel whose
+ // df[] value is less than usedMargin5X. Set iMin and iMax in
+ // preparation for this row or column.
+ int32_t iMin = iSize;
+ int32_t iMax = -1;
+
+ for (uint32_t i = iSize - kiExpansionPerSide - 1; i >= kiExpansionPerSide;
+ --i) {
+ const uint32_t index = i + b * iSize;
+ MOZ_ASSERT(index < (iSize * bSize),
+ "Our distance field index should be in-bounds.");
+
+ // Only apply the chamfer calculation if the df value is not
+ // already 0, since the chamfer can only reduce the value.
+ if (df[index]) {
+ // Forward-looking neighborhood distance from target pixel X
+ // with chamfer 5-7-11 looks like:
+ //
+ // +--+--+--+--+--+
+ // | | | X| 5| |
+ // +--+--+--+--+--+
+ // |11| 7| 5| 7|11|
+ // +--+--+--+--+--+
+ // | |11| |11| |
+ // +--+--+--+--+--+
+ //
+ // X should be set to the minimum of its current value and
+ // the values of all of the numbered neighbors summed with
+ // the value in that chamfer cell.
+ MOZ_ASSERT(index + (iSize * 2) + 1 < (iSize * bSize) &&
+ index + iSize + 2 < (iSize * bSize),
+ "Our distance field most extreme indices should be "
+ "in-bounds.");
+
+ // clang-format off
+ df[index] = std::min<dfType>(df[index],
+ std::min<dfType>(df[index + (iSize * 2) + 1] + 11,
+ std::min<dfType>(df[index + (iSize * 2) - 1] + 11,
+ std::min<dfType>(df[index + iSize + 2] + 11,
+ std::min<dfType>(df[index + iSize + 1] + 7,
+ std::min<dfType>(df[index + iSize] + 5,
+ std::min<dfType>(df[index + iSize - 1] + 7,
+ std::min<dfType>(df[index + iSize - 2] + 11,
+ df[index + 1] + 5))))))));
+ // clang-format on
+ }
+
+ // Finally, we can check the df value and see if it's less than
+ // or equal to the usedMargin5X value.
+ if (df[index] <= usedMargin5X) {
+ if (iMax == -1) {
+ iMax = i;
+ }
+ MOZ_ASSERT(iMin > (int32_t)i);
+ iMin = i;
+ }
+ }
+
+ if (iMax != -1) {
+ // Our interval values, iMin, iMax, and b are all calculated from
+ // the expanded region, which is based on the margin rect. To create
+ // our interval, we have to subtract kiExpansionPerSide from iMin and
+ // iMax, and subtract kbExpansionPerSide from b to account for the
+ // expanded region edges. This produces coords that are relative to
+ // our margin-rect.
+
+ // Origin for this interval is at the aMarginRect origin, adjusted in
+ // the block direction by b in app units, and in the inline direction
+ // by iMin in app units.
+ nsPoint origin(
+ aMarginRect.x + (iMin - kiExpansionPerSide) * aAppUnitsPerDevPixel,
+ aMarginRect.y + (b - kbExpansionPerSide) * aAppUnitsPerDevPixel);
+
+ // Size is the difference in iMax and iMin, plus 1 (to account for the
+ // whole pixel) dev pixels, by 1 block dev pixel. We don't bother
+ // subtracting kiExpansionPerSide from iMin and iMax in this case
+ // because we only care about the distance between them. We convert
+ // everything to app units.
+ nsSize size((iMax - iMin + 1) * aAppUnitsPerDevPixel,
+ aAppUnitsPerDevPixel);
+
+ mIntervals.AppendElement(nsRect(origin, size));
+ }
+ }
+
+ // Reverse the intervals keep the array sorted on the block direction.
+ mIntervals.Reverse();
+
+ // Adjust our extents by aShapeMargin. This may cause overflow of some
+ // kind if aShapeMargin is large, so we do some clamping to maintain the
+ // invariant mBStart <= mBEnd.
+ mBStart = std::min(mBStart, mBStart - aShapeMargin);
+ mBEnd = std::max(mBEnd, mBEnd + aShapeMargin);
+}
+
+nscoord nsFloatManager::PolygonShapeInfo::LineLeft(const nscoord aBStart,
+ const nscoord aBEnd) const {
+ // Use intervals if we have them.
+ if (!mIntervals.IsEmpty()) {
+ return LineEdge(mIntervals, aBStart, aBEnd, true);
+ }
+
+ // We want the line-left-most inline-axis coordinate where the
+ // (block-axis) aBStart/aBEnd band crosses a line segment of the polygon.
+ // To get that, we start as line-right as possible (at nscoord_MAX). Then
+ // we iterate each line segment to compute its intersection point with the
+ // band (if any) and using std::min() successively to get the smallest
+ // inline-coordinates among those intersection points.
+ //
+ // Note: std::min<nscoord> means the function std::min() with template
+ // parameter nscoord, not the minimum value of nscoord.
+ return ComputeLineIntercept(aBStart, aBEnd, std::min<nscoord>, nscoord_MAX);
+}
+
+nscoord nsFloatManager::PolygonShapeInfo::LineRight(const nscoord aBStart,
+ const nscoord aBEnd) const {
+ // Use intervals if we have them.
+ if (!mIntervals.IsEmpty()) {
+ return LineEdge(mIntervals, aBStart, aBEnd, false);
+ }
+
+ // Similar to LineLeft(). Though here, we want the line-right-most
+ // inline-axis coordinate, so we instead start at nscoord_MIN and use
+ // std::max() to get the biggest inline-coordinate among those
+ // intersection points.
+ return ComputeLineIntercept(aBStart, aBEnd, std::max<nscoord>, nscoord_MIN);
+}
+
+void nsFloatManager::PolygonShapeInfo::ComputeExtent() {
+ // mBStart and mBEnd are the lower and the upper bounds of all the
+ // vertex.y, respectively. The vertex.y is actually on the block-axis of
+ // the float manager's writing mode.
+ for (const nsPoint& vertex : mVertices) {
+ mBStart = std::min(mBStart, vertex.y);
+ mBEnd = std::max(mBEnd, vertex.y);
+ }
+
+ MOZ_ASSERT(mBStart <= mBEnd,
+ "Start of float area should be less than "
+ "or equal to the end.");
+}
+
+nscoord nsFloatManager::PolygonShapeInfo::ComputeLineIntercept(
+ const nscoord aBStart, const nscoord aBEnd,
+ nscoord (*aCompareOp)(std::initializer_list<nscoord>),
+ const nscoord aLineInterceptInitialValue) const {
+ MOZ_ASSERT(aBStart <= aBEnd,
+ "The band's block start is greater than its block end?");
+
+ const size_t len = mVertices.Length();
+ nscoord lineIntercept = aLineInterceptInitialValue;
+
+ // We have some special treatment of horizontal lines between vertices.
+ // Generally, we can ignore the impact of the horizontal lines since their
+ // endpoints will be included in the lines preceeding or following them.
+ // However, it's possible the polygon is entirely a horizontal line,
+ // possibly built from more than one horizontal segment. In such a case,
+ // we need to have the horizontal line(s) contribute to the line intercepts.
+ // We do this by accepting horizontal lines until we find a non-horizontal
+ // line, after which all further horizontal lines are ignored.
+ bool canIgnoreHorizontalLines = false;
+
+ // Iterate each line segment {p0, p1}, {p1, p2}, ..., {pn, p0}.
+ for (size_t i = 0; i < len; ++i) {
+ const nsPoint* smallYVertex = &mVertices[i];
+ const nsPoint* bigYVertex = &mVertices[(i + 1) % len];
+
+ // Swap the two points to satisfy the requirement for calling
+ // XInterceptAtY.
+ if (smallYVertex->y > bigYVertex->y) {
+ std::swap(smallYVertex, bigYVertex);
+ }
+
+ // Generally, we need to ignore line segments that either don't intersect
+ // the band, or merely touch it. However, if the polygon has no block extent
+ // (it is a point, or a horizontal line), and the band touches the line
+ // segment, we let that line segment through.
+ if ((aBStart >= bigYVertex->y || aBEnd <= smallYVertex->y) &&
+ !(mBStart == mBEnd && aBStart == bigYVertex->y)) {
+ // Skip computing the intercept if the band doesn't intersect the
+ // line segment.
+ continue;
+ }
+
+ nscoord bStartLineIntercept;
+ nscoord bEndLineIntercept;
+
+ if (smallYVertex->y == bigYVertex->y) {
+ // The line is horizontal; see if we can ignore it.
+ if (canIgnoreHorizontalLines) {
+ continue;
+ }
+
+ // For a horizontal line that we can't ignore, we treat the two x value
+ // ends as the bStartLineIntercept and bEndLineIntercept. It doesn't
+ // matter which is applied to which, because they'll both be applied
+ // to aCompareOp.
+ bStartLineIntercept = smallYVertex->x;
+ bEndLineIntercept = bigYVertex->x;
+ } else {
+ // This is not a horizontal line. We can now ignore all future
+ // horizontal lines.
+ canIgnoreHorizontalLines = true;
+
+ bStartLineIntercept =
+ aBStart <= smallYVertex->y
+ ? smallYVertex->x
+ : XInterceptAtY(aBStart, *smallYVertex, *bigYVertex);
+ bEndLineIntercept =
+ aBEnd >= bigYVertex->y
+ ? bigYVertex->x
+ : XInterceptAtY(aBEnd, *smallYVertex, *bigYVertex);
+ }
+
+ // If either new intercept is more extreme than lineIntercept (per
+ // aCompareOp), then update lineIntercept to that value.
+ lineIntercept =
+ aCompareOp({lineIntercept, bStartLineIntercept, bEndLineIntercept});
+ }
+
+ return lineIntercept;
+}
+
+void nsFloatManager::PolygonShapeInfo::Translate(nscoord aLineLeft,
+ nscoord aBlockStart) {
+ for (nsPoint& vertex : mVertices) {
+ vertex.MoveBy(aLineLeft, aBlockStart);
+ }
+ for (nsRect& interval : mIntervals) {
+ interval.MoveBy(aLineLeft, aBlockStart);
+ }
+ mBStart += aBlockStart;
+ mBEnd += aBlockStart;
+}
+
+/* static */
+nscoord nsFloatManager::PolygonShapeInfo::XInterceptAtY(const nscoord aY,
+ const nsPoint& aP1,
+ const nsPoint& aP2) {
+ // Solve for x in the linear equation: x = x1 + (y-y1) * (x2-x1) / (y2-y1),
+ // where aP1 = (x1, y1) and aP2 = (x2, y2).
+
+ MOZ_ASSERT(aP1.y <= aY && aY <= aP2.y,
+ "This function won't work if the horizontal line at aY and "
+ "the line segment (aP1, aP2) do not intersect!");
+
+ MOZ_ASSERT(aP1.y != aP2.y,
+ "A horizontal line segment results in dividing by zero error!");
+
+ return aP1.x + (aY - aP1.y) * (aP2.x - aP1.x) / (aP2.y - aP1.y);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// ImageShapeInfo
+//
+// Implements shape-outside: <image>
+//
+class nsFloatManager::ImageShapeInfo final : public nsFloatManager::ShapeInfo {
+ public:
+ ImageShapeInfo(uint8_t* aAlphaPixels, int32_t aStride,
+ const LayoutDeviceIntSize& aImageSize,
+ int32_t aAppUnitsPerDevPixel, float aShapeImageThreshold,
+ nscoord aShapeMargin, const nsRect& aContentRect,
+ const nsRect& aMarginRect, WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
+ nscoord BStart() const override { return mBStart; }
+ nscoord BEnd() const override { return mBEnd; }
+ bool IsEmpty() const override { return mIntervals.IsEmpty(); }
+ bool MayNarrowInBlockDirection() const override { return true; }
+
+ void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
+
+ private:
+ // An interval is slice of the float area defined by this ImageShapeInfo.
+ // Each interval is a rectangle that is one pixel deep in the block
+ // axis. The values are stored as block edges in the y coordinates,
+ // and inline edges as the x coordinates.
+
+ // The intervals are stored in ascending order on y.
+ nsTArray<nsRect> mIntervals;
+
+ nscoord mBStart = nscoord_MAX;
+ nscoord mBEnd = nscoord_MIN;
+
+ // CreateInterval transforms the supplied aIMin and aIMax and aB
+ // values into an interval that respects the writing mode. An
+ // aOffsetFromContainer can be provided if the aIMin, aIMax, aB
+ // values were generated relative to something other than the container
+ // rect (such as the content rect or margin rect).
+ void CreateInterval(int32_t aIMin, int32_t aIMax, int32_t aB,
+ int32_t aAppUnitsPerDevPixel,
+ const nsPoint& aOffsetFromContainer, WritingMode aWM,
+ const nsSize& aContainerSize);
+};
+
+nsFloatManager::ImageShapeInfo::ImageShapeInfo(
+ uint8_t* aAlphaPixels, int32_t aStride,
+ const LayoutDeviceIntSize& aImageSize, int32_t aAppUnitsPerDevPixel,
+ float aShapeImageThreshold, nscoord aShapeMargin,
+ const nsRect& aContentRect, const nsRect& aMarginRect, WritingMode aWM,
+ const nsSize& aContainerSize) {
+ MOZ_ASSERT(aShapeImageThreshold >= 0.0 && aShapeImageThreshold <= 1.0,
+ "The computed value of shape-image-threshold is wrong!");
+
+ const uint8_t threshold = NSToIntFloor(aShapeImageThreshold * 255);
+
+ MOZ_ASSERT(aImageSize.width >= 0 && aImageSize.height >= 0,
+ "Image size must be non-negative for our math to work.");
+ const uint32_t w = aImageSize.width;
+ const uint32_t h = aImageSize.height;
+
+ if (aShapeMargin <= 0) {
+ // Without a positive aShapeMargin, all we have to do is a
+ // direct threshold comparison of the alpha pixels.
+ // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
+
+ // Scan the pixels in a double loop. For horizontal writing modes, we do
+ // this row by row, from top to bottom. For vertical writing modes, we do
+ // column by column, from left to right. We define the two loops
+ // generically, then figure out the rows and cols within the inner loop.
+ const uint32_t bSize = aWM.IsVertical() ? w : h;
+ const uint32_t iSize = aWM.IsVertical() ? h : w;
+ for (uint32_t b = 0; b < bSize; ++b) {
+ // iMin and max store the start and end of the float area for the row
+ // or column represented by this iteration of the outer loop.
+ int32_t iMin = -1;
+ int32_t iMax = -1;
+
+ for (uint32_t i = 0; i < iSize; ++i) {
+ const uint32_t col = aWM.IsVertical() ? b : i;
+ const uint32_t row = aWM.IsVertical() ? i : b;
+ const uint32_t index = col + row * aStride;
+
+ // Determine if the alpha pixel at this row and column has a value
+ // greater than the threshold. If it does, update our iMin and iMax
+ // values to track the edges of the float area for this row or column.
+ // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
+ const uint8_t alpha = aAlphaPixels[index];
+ if (alpha > threshold) {
+ if (iMin == -1) {
+ iMin = i;
+ }
+ MOZ_ASSERT(iMax < (int32_t)i);
+ iMax = i;
+ }
+ }
+
+ // At the end of a row or column; did we find something?
+ if (iMin != -1) {
+ // We need to supply an offset of the content rect top left, since
+ // our col and row have been calculated from the content rect,
+ // instead of the margin rect (against which floats are applied).
+ CreateInterval(iMin, iMax, b, aAppUnitsPerDevPixel,
+ aContentRect.TopLeft(), aWM, aContainerSize);
+ }
+ }
+
+ if (aWM.IsVerticalRL()) {
+ // vertical-rl or sideways-rl.
+ // Because we scan the columns from left to right, we need to reverse
+ // the array so that it's sorted (in ascending order) on the block
+ // direction.
+ mIntervals.Reverse();
+ }
+ } else {
+ // With a positive aShapeMargin, we have to calculate a distance
+ // field from the opaque pixels, then build intervals based on
+ // them being within aShapeMargin distance to an opaque pixel.
+
+ // Roughly: for each pixel in the margin box, we need to determine the
+ // distance to the nearest opaque image-pixel. If that distance is less
+ // than aShapeMargin, we consider this margin-box pixel as being part of
+ // the float area.
+
+ // Computing the distance field is a two-pass O(n) operation.
+ // We use a chamfer 5-7-11 5x5 matrix to compute minimum distance
+ // to an opaque pixel. This integer math computation is reasonably
+ // close to the true Euclidean distance. The distances will be
+ // approximately 5x the true distance, quantized in integer units.
+ // The 5x is factored away in the comparison used in the final
+ // pass which builds the intervals.
+ dfType usedMargin5X =
+ CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
+
+ // Allocate our distance field. The distance field has to cover
+ // the entire aMarginRect, since aShapeMargin could bleed into it,
+ // beyond the content rect covered by aAlphaPixels. To make this work,
+ // we calculate a dfOffset value which is the top left of the content
+ // rect relative to the margin rect.
+ nsPoint offsetPoint = aContentRect.TopLeft() - aMarginRect.TopLeft();
+ LayoutDeviceIntPoint dfOffset = LayoutDevicePixel::FromAppUnitsRounded(
+ offsetPoint, aAppUnitsPerDevPixel);
+
+ // Since our distance field is computed with a 5x5 neighborhood,
+ // we need to expand our distance field by a further 4 pixels in
+ // both axes, 2 on the leading edge and 2 on the trailing edge.
+ // We call this edge area the "expanded region".
+
+ // Our expansion amounts need to be the same for our math to work.
+ static uint32_t kExpansionPerSide = 2;
+ // Since dfOffset will be used in comparisons against expanded region
+ // pixel values, it's convenient to add expansion amounts to dfOffset in
+ // both axes, to simplify comparison math later.
+ dfOffset.x += kExpansionPerSide;
+ dfOffset.y += kExpansionPerSide;
+
+ // In all these calculations, we purposely ignore aStride, because
+ // we don't have to replicate the packing that we received in
+ // aAlphaPixels. When we need to convert from df coordinates to
+ // alpha coordinates, we do that with math based on row and col.
+ const LayoutDeviceIntSize marginRectDevPixels =
+ LayoutDevicePixel::FromAppUnitsRounded(aMarginRect.Size(),
+ aAppUnitsPerDevPixel);
+
+ // Clamp the size of our distance field sizes to prevent multiplication
+ // overflow.
+ static const uint32_t DF_SIDE_MAX =
+ floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
+
+ // Clamp the margin plus 2X the expansion values between expansion + 1
+ // and DF_SIDE_MAX. This ensures that the distance field allocation
+ // doesn't overflow during multiplication, and the reverse iteration
+ // doesn't underflow.
+ const uint32_t wEx =
+ std::max(std::min(marginRectDevPixels.width + (kExpansionPerSide * 2),
+ DF_SIDE_MAX),
+ kExpansionPerSide + 1);
+ const uint32_t hEx =
+ std::max(std::min(marginRectDevPixels.height + (kExpansionPerSide * 2),
+ DF_SIDE_MAX),
+ kExpansionPerSide + 1);
+
+ // Since the margin-box size is CSS controlled, and large values will
+ // generate large wEx and hEx values, we do a falliable allocation for
+ // the distance field. If allocation fails, we early exit and layout will
+ // be wrong, but we'll avoid aborting from OOM.
+ auto df = MakeUniqueFallible<dfType[]>(wEx * hEx);
+ if (!df) {
+ // Without a distance field, we can't reason about the float area.
+ return;
+ }
+
+ const uint32_t bSize = aWM.IsVertical() ? wEx : hEx;
+ const uint32_t iSize = aWM.IsVertical() ? hEx : wEx;
+
+ // First pass setting distance field, starting at top-left, three cases:
+ // 1) Expanded region pixel: set to MAX_MARGIN_5X.
+ // 2) Image pixel with alpha greater than threshold: set to 0.
+ // 3) Other pixel: set to minimum backward-looking neighborhood
+ // distance value, computed with 5-7-11 chamfer.
+
+ // Scan the pixels in a double loop. For horizontal writing modes, we do
+ // this row by row, from top to bottom. For vertical writing modes, we do
+ // column by column, from left to right. We define the two loops
+ // generically, then figure out the rows and cols within the inner loop.
+ for (uint32_t b = 0; b < bSize; ++b) {
+ for (uint32_t i = 0; i < iSize; ++i) {
+ const uint32_t col = aWM.IsVertical() ? b : i;
+ const uint32_t row = aWM.IsVertical() ? i : b;
+ const uint32_t index = col + row * wEx;
+ MOZ_ASSERT(index < (wEx * hEx),
+ "Our distance field index should be in-bounds.");
+
+ // Handle our three cases, in order.
+ if (col < kExpansionPerSide || col >= wEx - kExpansionPerSide ||
+ row < kExpansionPerSide || row >= hEx - kExpansionPerSide) {
+ // Case 1: Expanded pixel.
+ df[index] = MAX_MARGIN_5X;
+ } else if ((int32_t)col >= dfOffset.x &&
+ (int32_t)col < (dfOffset.x + aImageSize.width) &&
+ (int32_t)row >= dfOffset.y &&
+ (int32_t)row < (dfOffset.y + aImageSize.height) &&
+ aAlphaPixels[col - dfOffset.x.value +
+ (row - dfOffset.y.value) * aStride] >
+ threshold) {
+ // Case 2: Image pixel that is opaque.
+ DebugOnly<uint32_t> alphaIndex =
+ col - dfOffset.x.value + (row - dfOffset.y.value) * aStride;
+ MOZ_ASSERT(alphaIndex < (aStride * h),
+ "Our aAlphaPixels index should be in-bounds.");
+
+ df[index] = 0;
+ } else {
+ // Case 3: Other pixel.
+ if (aWM.IsVertical()) {
+ // Column-by-column, starting at the left, each column
+ // top-to-bottom.
+ // Backward-looking neighborhood distance from target pixel X
+ // with chamfer 5-7-11 looks like:
+ //
+ // +--+--+--+
+ // | |11| | | +
+ // +--+--+--+ | /|
+ // |11| 7| 5| | / |
+ // +--+--+--+ | / V
+ // | | 5| X| |/
+ // +--+--+--+ +
+ // |11| 7| |
+ // +--+--+--+
+ // | |11| |
+ // +--+--+--+
+ //
+ // X should be set to the minimum of MAX_MARGIN_5X and the
+ // values of all of the numbered neighbors summed with the
+ // value in that chamfer cell.
+ MOZ_ASSERT(index - wEx - 2 < (iSize * bSize) &&
+ index + wEx - 2 < (iSize * bSize) &&
+ index - (wEx * 2) - 1 < (iSize * bSize),
+ "Our distance field most extreme indices should be "
+ "in-bounds.");
+
+ // clang-format off
+ df[index] = std::min<dfType>(MAX_MARGIN_5X,
+ std::min<dfType>(df[index - wEx - 2] + 11,
+ std::min<dfType>(df[index + wEx - 2] + 11,
+ std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
+ std::min<dfType>(df[index - wEx - 1] + 7,
+ std::min<dfType>(df[index - 1] + 5,
+ std::min<dfType>(df[index + wEx - 1] + 7,
+ std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
+ df[index - wEx] + 5))))))));
+ // clang-format on
+ } else {
+ // Row-by-row, starting at the top, each row left-to-right.
+ // Backward-looking neighborhood distance from target pixel X
+ // with chamfer 5-7-11 looks like:
+ //
+ // +--+--+--+--+--+
+ // | |11| |11| | ----+
+ // +--+--+--+--+--+ /
+ // |11| 7| 5| 7|11| /
+ // +--+--+--+--+--+ /
+ // | | 5| X| | | +-->
+ // +--+--+--+--+--+
+ //
+ // X should be set to the minimum of MAX_MARGIN_5X and the
+ // values of all of the numbered neighbors summed with the
+ // value in that chamfer cell.
+ MOZ_ASSERT(index - (wEx * 2) - 1 < (iSize * bSize) &&
+ index - wEx - 2 < (iSize * bSize),
+ "Our distance field most extreme indices should be "
+ "in-bounds.");
+
+ // clang-format off
+ df[index] = std::min<dfType>(MAX_MARGIN_5X,
+ std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
+ std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
+ std::min<dfType>(df[index - wEx - 2] + 11,
+ std::min<dfType>(df[index - wEx - 1] + 7,
+ std::min<dfType>(df[index - wEx] + 5,
+ std::min<dfType>(df[index - wEx + 1] + 7,
+ std::min<dfType>(df[index - wEx + 2] + 11,
+ df[index - 1] + 5))))))));
+ // clang-format on
+ }
+ }
+ }
+ }
+
+ // Okay, time for the second pass. This pass is in reverse order from
+ // the first pass. All of our opaque pixels have been set to 0, and all
+ // of our expanded region pixels have been set to MAX_MARGIN_5X. Other
+ // pixels have been set to some value between those two (inclusive) but
+ // this hasn't yet taken into account the neighbors that were processed
+ // after them in the first pass. This time we reverse iterate so we can
+ // apply the forward-looking chamfer.
+
+ // This time, we constrain our outer and inner loop to ignore the
+ // expanded region pixels. For each pixel we iterate, we set the df value
+ // to the minimum forward-looking neighborhood distance value, computed
+ // with a 5-7-11 chamfer. We also check each df value against the
+ // usedMargin5X threshold, and use that to set the iMin and iMax values
+ // for the interval we'll create for that block axis value (b).
+
+ // At the end of each row (or column in vertical writing modes),
+ // if any of the other pixels had a value less than usedMargin5X,
+ // we create an interval. Note: "bSize - kExpansionPerSide - 1" is the
+ // index of the final row of pixels before the trailing expanded region.
+ for (uint32_t b = bSize - kExpansionPerSide - 1; b >= kExpansionPerSide;
+ --b) {
+ // iMin tracks the first df pixel and iMax the last df pixel whose
+ // df[] value is less than usedMargin5X. Set iMin and iMax in
+ // preparation for this row or column.
+ int32_t iMin = iSize;
+ int32_t iMax = -1;
+
+ // Note: "iSize - kExpansionPerSide - 1" is the index of the final row
+ // of pixels before the trailing expanded region.
+ for (uint32_t i = iSize - kExpansionPerSide - 1; i >= kExpansionPerSide;
+ --i) {
+ const uint32_t col = aWM.IsVertical() ? b : i;
+ const uint32_t row = aWM.IsVertical() ? i : b;
+ const uint32_t index = col + row * wEx;
+ MOZ_ASSERT(index < (wEx * hEx),
+ "Our distance field index should be in-bounds.");
+
+ // Only apply the chamfer calculation if the df value is not
+ // already 0, since the chamfer can only reduce the value.
+ if (df[index]) {
+ if (aWM.IsVertical()) {
+ // Column-by-column, starting at the right, each column
+ // bottom-to-top.
+ // Forward-looking neighborhood distance from target pixel X
+ // with chamfer 5-7-11 looks like:
+ //
+ // +--+--+--+
+ // | |11| | +
+ // +--+--+--+ /|
+ // | | 7|11| A / |
+ // +--+--+--+ | / |
+ // | X| 5| | |/ |
+ // +--+--+--+ + |
+ // | 5| 7|11|
+ // +--+--+--+
+ // | |11| |
+ // +--+--+--+
+ //
+ // X should be set to the minimum of its current value and
+ // the values of all of the numbered neighbors summed with
+ // the value in that chamfer cell.
+ MOZ_ASSERT(index + wEx + 2 < (wEx * hEx) &&
+ index + (wEx * 2) + 1 < (wEx * hEx) &&
+ index - (wEx * 2) + 1 < (wEx * hEx),
+ "Our distance field most extreme indices should be "
+ "in-bounds.");
+
+ // clang-format off
+ df[index] = std::min<dfType>(df[index],
+ std::min<dfType>(df[index + wEx + 2] + 11,
+ std::min<dfType>(df[index - wEx + 2] + 11,
+ std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
+ std::min<dfType>(df[index + wEx + 1] + 7,
+ std::min<dfType>(df[index + 1] + 5,
+ std::min<dfType>(df[index - wEx + 1] + 7,
+ std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
+ df[index + wEx] + 5))))))));
+ // clang-format on
+ } else {
+ // Row-by-row, starting at the bottom, each row right-to-left.
+ // Forward-looking neighborhood distance from target pixel X
+ // with chamfer 5-7-11 looks like:
+ //
+ // +--+--+--+--+--+
+ // | | | X| 5| | <--+
+ // +--+--+--+--+--+ /
+ // |11| 7| 5| 7|11| /
+ // +--+--+--+--+--+ /
+ // | |11| |11| | +----
+ // +--+--+--+--+--+
+ //
+ // X should be set to the minimum of its current value and
+ // the values of all of the numbered neighbors summed with
+ // the value in that chamfer cell.
+ MOZ_ASSERT(index + (wEx * 2) + 1 < (wEx * hEx) &&
+ index + wEx + 2 < (wEx * hEx),
+ "Our distance field most extreme indices should be "
+ "in-bounds.");
+
+ // clang-format off
+ df[index] = std::min<dfType>(df[index],
+ std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
+ std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
+ std::min<dfType>(df[index + wEx + 2] + 11,
+ std::min<dfType>(df[index + wEx + 1] + 7,
+ std::min<dfType>(df[index + wEx] + 5,
+ std::min<dfType>(df[index + wEx - 1] + 7,
+ std::min<dfType>(df[index + wEx - 2] + 11,
+ df[index + 1] + 5))))))));
+ // clang-format on
+ }
+ }
+
+ // Finally, we can check the df value and see if it's less than
+ // or equal to the usedMargin5X value.
+ if (df[index] <= usedMargin5X) {
+ if (iMax == -1) {
+ iMax = i;
+ }
+ MOZ_ASSERT(iMin > (int32_t)i);
+ iMin = i;
+ }
+ }
+
+ if (iMax != -1) {
+ // Our interval values, iMin, iMax, and b are all calculated from
+ // the expanded region, which is based on the margin rect. To create
+ // our interval, we have to subtract kExpansionPerSide from (iMin,
+ // iMax, and b) to account for the expanded region edges. This
+ // produces coords that are relative to our margin-rect, so we pass
+ // in aMarginRect.TopLeft() to make CreateInterval convert to our
+ // container's coordinate space.
+ CreateInterval(iMin - kExpansionPerSide, iMax - kExpansionPerSide,
+ b - kExpansionPerSide, aAppUnitsPerDevPixel,
+ aMarginRect.TopLeft(), aWM, aContainerSize);
+ }
+ }
+
+ if (!aWM.IsVerticalRL()) {
+ // Anything other than vertical-rl or sideways-rl.
+ // Because we assembled our intervals on the bottom-up pass,
+ // they are reversed for most writing modes. Reverse them to
+ // keep the array sorted on the block direction.
+ mIntervals.Reverse();
+ }
+ }
+
+ if (!mIntervals.IsEmpty()) {
+ mBStart = mIntervals[0].Y();
+ mBEnd = mIntervals.LastElement().YMost();
+ }
+}
+
+void nsFloatManager::ImageShapeInfo::CreateInterval(
+ int32_t aIMin, int32_t aIMax, int32_t aB, int32_t aAppUnitsPerDevPixel,
+ const nsPoint& aOffsetFromContainer, WritingMode aWM,
+ const nsSize& aContainerSize) {
+ // Store an interval as an nsRect with our inline axis values stored in x
+ // and our block axis values stored in y. The position is dependent on
+ // the writing mode, but the size is the same for all writing modes.
+
+ // Size is the difference in inline axis edges stored as x, and one
+ // block axis pixel stored as y. For the inline axis, we add 1 to aIMax
+ // because we want to capture the far edge of the last pixel.
+ nsSize size(((aIMax + 1) - aIMin) * aAppUnitsPerDevPixel,
+ aAppUnitsPerDevPixel);
+
+ // Since we started our scanning of the image pixels from the top left,
+ // the interval position starts from the origin of the content rect,
+ // converted to logical coordinates.
+ nsPoint origin =
+ ConvertToFloatLogical(aOffsetFromContainer, aWM, aContainerSize);
+
+ // Depending on the writing mode, we now move the origin.
+ if (aWM.IsVerticalRL()) {
+ // vertical-rl or sideways-rl.
+ // These writing modes proceed from the top right, and each interval
+ // moves in a positive inline direction and negative block direction.
+ // That means that the intervals will be reversed after all have been
+ // constructed. We add 1 to aB to capture the end of the block axis pixel.
+ origin.MoveBy(aIMin * aAppUnitsPerDevPixel,
+ (aB + 1) * -aAppUnitsPerDevPixel);
+ } else if (aWM.IsSidewaysLR()) {
+ // This writing mode proceeds from the bottom left, and each interval
+ // moves in a negative inline direction and a positive block direction.
+ // We add 1 to aIMax to capture the end of the inline axis pixel.
+ origin.MoveBy((aIMax + 1) * -aAppUnitsPerDevPixel,
+ aB * aAppUnitsPerDevPixel);
+ } else {
+ // horizontal-tb or vertical-lr.
+ // These writing modes proceed from the top left and each interval
+ // moves in a positive step in both inline and block directions.
+ origin.MoveBy(aIMin * aAppUnitsPerDevPixel, aB * aAppUnitsPerDevPixel);
+ }
+
+ mIntervals.AppendElement(nsRect(origin, size));
+}
+
+nscoord nsFloatManager::ImageShapeInfo::LineLeft(const nscoord aBStart,
+ const nscoord aBEnd) const {
+ return LineEdge(mIntervals, aBStart, aBEnd, true);
+}
+
+nscoord nsFloatManager::ImageShapeInfo::LineRight(const nscoord aBStart,
+ const nscoord aBEnd) const {
+ return LineEdge(mIntervals, aBStart, aBEnd, false);
+}
+
+void nsFloatManager::ImageShapeInfo::Translate(nscoord aLineLeft,
+ nscoord aBlockStart) {
+ for (nsRect& interval : mIntervals) {
+ interval.MoveBy(aLineLeft, aBlockStart);
+ }
+
+ mBStart += aBlockStart;
+ mBEnd += aBlockStart;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// FloatInfo
+
+nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame, nscoord aLineLeft,
+ nscoord aBlockStart,
+ const LogicalRect& aMarginRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize)
+ : mFrame(aFrame),
+ mLeftBEnd(nscoord_MIN),
+ mRightBEnd(nscoord_MIN),
+ mRect(ShapeInfo::ConvertToFloatLogical(aMarginRect, aWM, aContainerSize) +
+ nsPoint(aLineLeft, aBlockStart)) {
+ MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
+ using ShapeOutsideType = StyleShapeOutside::Tag;
+
+ if (IsEmpty()) {
+ // Per spec, a float area defined by a shape is clipped to the float’s
+ // margin box. Therefore, no need to create a shape info if the float's
+ // margin box is empty, since a float area can only be smaller than the
+ // margin box.
+
+ // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
+ return;
+ }
+
+ const nsStyleDisplay* styleDisplay = mFrame->StyleDisplay();
+ const auto& shapeOutside = styleDisplay->mShapeOutside;
+
+ nscoord shapeMargin = shapeOutside.IsNone()
+ ? 0
+ : nsLayoutUtils::ResolveToLength<true>(
+ styleDisplay->mShapeMargin,
+ LogicalSize(aWM, aContainerSize).ISize(aWM));
+
+ switch (shapeOutside.tag) {
+ case ShapeOutsideType::None:
+ // No need to create shape info.
+ return;
+
+ case ShapeOutsideType::Image: {
+ float shapeImageThreshold = styleDisplay->mShapeImageThreshold;
+ mShapeInfo = ShapeInfo::CreateImageShape(
+ shapeOutside.AsImage(), shapeImageThreshold, shapeMargin, mFrame,
+ aMarginRect, aWM, aContainerSize);
+ if (!mShapeInfo) {
+ // Image is not ready, or fails to load, etc.
+ return;
+ }
+
+ break;
+ }
+
+ case ShapeOutsideType::Box: {
+ // Initialize <shape-box>'s reference rect.
+ LogicalRect shapeBoxRect = ShapeInfo::ComputeShapeBoxRect(
+ shapeOutside.AsBox(), mFrame, aMarginRect, aWM);
+ mShapeInfo = ShapeInfo::CreateShapeBox(mFrame, shapeMargin, shapeBoxRect,
+ aWM, aContainerSize);
+ break;
+ }
+
+ case ShapeOutsideType::Shape: {
+ const auto& shape = *shapeOutside.AsShape()._0;
+ // Initialize <shape-box>'s reference rect.
+ LogicalRect shapeBoxRect = ShapeInfo::ComputeShapeBoxRect(
+ shapeOutside.AsShape()._1, mFrame, aMarginRect, aWM);
+ mShapeInfo =
+ ShapeInfo::CreateBasicShape(shape, shapeMargin, mFrame, shapeBoxRect,
+ aMarginRect, aWM, aContainerSize);
+ break;
+ }
+ }
+
+ MOZ_ASSERT(mShapeInfo,
+ "All shape-outside values except none should have mShapeInfo!");
+
+ // Translate the shape to the same origin as nsFloatManager.
+ mShapeInfo->Translate(aLineLeft, aBlockStart);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsFloatManager::FloatInfo::FloatInfo(FloatInfo&& aOther)
+ : mFrame(std::move(aOther.mFrame)),
+ mLeftBEnd(std::move(aOther.mLeftBEnd)),
+ mRightBEnd(std::move(aOther.mRightBEnd)),
+ mRect(std::move(aOther.mRect)),
+ mShapeInfo(std::move(aOther.mShapeInfo)) {
+ MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
+}
+
+nsFloatManager::FloatInfo::~FloatInfo() {
+ MOZ_COUNT_DTOR(nsFloatManager::FloatInfo);
+}
+#endif
+
+nscoord nsFloatManager::FloatInfo::LineLeft(ShapeType aShapeType,
+ const nscoord aBStart,
+ const nscoord aBEnd) const {
+ if (aShapeType == ShapeType::Margin) {
+ return LineLeft();
+ }
+
+ MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
+ if (!mShapeInfo) {
+ return LineLeft();
+ }
+ // Clip the flow area to the margin-box because
+ // https://drafts.csswg.org/css-shapes-1/#relation-to-box-model-and-float-behavior
+ // says "When a shape is used to define a float area, the shape is clipped
+ // to the float’s margin box."
+ return std::max(LineLeft(), mShapeInfo->LineLeft(aBStart, aBEnd));
+}
+
+nscoord nsFloatManager::FloatInfo::LineRight(ShapeType aShapeType,
+ const nscoord aBStart,
+ const nscoord aBEnd) const {
+ if (aShapeType == ShapeType::Margin) {
+ return LineRight();
+ }
+
+ MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
+ if (!mShapeInfo) {
+ return LineRight();
+ }
+ // Clip the flow area to the margin-box. See LineLeft().
+ return std::min(LineRight(), mShapeInfo->LineRight(aBStart, aBEnd));
+}
+
+nscoord nsFloatManager::FloatInfo::BStart(ShapeType aShapeType) const {
+ if (aShapeType == ShapeType::Margin) {
+ return BStart();
+ }
+
+ MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
+ if (!mShapeInfo) {
+ return BStart();
+ }
+ // Clip the flow area to the margin-box. See LineLeft().
+ return std::max(BStart(), mShapeInfo->BStart());
+}
+
+nscoord nsFloatManager::FloatInfo::BEnd(ShapeType aShapeType) const {
+ if (aShapeType == ShapeType::Margin) {
+ return BEnd();
+ }
+
+ MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
+ if (!mShapeInfo) {
+ return BEnd();
+ }
+ // Clip the flow area to the margin-box. See LineLeft().
+ return std::min(BEnd(), mShapeInfo->BEnd());
+}
+
+bool nsFloatManager::FloatInfo::IsEmpty(ShapeType aShapeType) const {
+ if (aShapeType == ShapeType::Margin) {
+ return IsEmpty();
+ }
+
+ MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
+ if (!mShapeInfo) {
+ return IsEmpty();
+ }
+ return mShapeInfo->IsEmpty();
+}
+
+bool nsFloatManager::FloatInfo::MayNarrowInBlockDirection(
+ ShapeType aShapeType) const {
+ // This function mirrors the cases of the three argument versions of
+ // LineLeft() and LineRight(). This function returns true if and only if
+ // either of those functions could possibly return "narrower" values with
+ // increasing aBStart values. "Narrower" means closer to the far end of
+ // the float shape.
+ if (aShapeType == ShapeType::Margin) {
+ return false;
+ }
+
+ MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
+ if (!mShapeInfo) {
+ return false;
+ }
+
+ return mShapeInfo->MayNarrowInBlockDirection();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// ShapeInfo
+
+/* static */
+LogicalRect nsFloatManager::ShapeInfo::ComputeShapeBoxRect(
+ StyleShapeBox aBox, nsIFrame* const aFrame, const LogicalRect& aMarginRect,
+ WritingMode aWM) {
+ LogicalRect rect = aMarginRect;
+
+ switch (aBox) {
+ case StyleShapeBox::ContentBox:
+ rect.Deflate(aWM, aFrame->GetLogicalUsedPadding(aWM));
+ [[fallthrough]];
+ case StyleShapeBox::PaddingBox:
+ rect.Deflate(aWM, aFrame->GetLogicalUsedBorder(aWM));
+ [[fallthrough]];
+ case StyleShapeBox::BorderBox:
+ rect.Deflate(aWM, aFrame->GetLogicalUsedMargin(aWM));
+ break;
+ case StyleShapeBox::MarginBox:
+ // Do nothing. rect is already a margin rect.
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown shape box");
+ break;
+ }
+
+ return rect;
+}
+
+/* static */ UniquePtr<nsFloatManager::ShapeInfo>
+nsFloatManager::ShapeInfo::CreateShapeBox(nsIFrame* const aFrame,
+ nscoord aShapeMargin,
+ const LogicalRect& aShapeBoxRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize) {
+ nsRect logicalShapeBoxRect =
+ ConvertToFloatLogical(aShapeBoxRect, aWM, aContainerSize);
+
+ // Inflate logicalShapeBoxRect by aShapeMargin.
+ logicalShapeBoxRect.Inflate(aShapeMargin);
+
+ nscoord physicalRadii[8];
+ bool hasRadii = aFrame->GetShapeBoxBorderRadii(physicalRadii);
+ if (!hasRadii) {
+ return MakeUnique<RoundedBoxShapeInfo>(logicalShapeBoxRect,
+ UniquePtr<nscoord[]>());
+ }
+
+ // Add aShapeMargin to each of the radii.
+ for (nscoord& r : physicalRadii) {
+ r += aShapeMargin;
+ }
+
+ return MakeUnique<RoundedBoxShapeInfo>(
+ logicalShapeBoxRect, ConvertToFloatLogical(physicalRadii, aWM));
+}
+
+/* static */ UniquePtr<nsFloatManager::ShapeInfo>
+nsFloatManager::ShapeInfo::CreateBasicShape(const StyleBasicShape& aBasicShape,
+ nscoord aShapeMargin,
+ nsIFrame* const aFrame,
+ const LogicalRect& aShapeBoxRect,
+ const LogicalRect& aMarginRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize) {
+ switch (aBasicShape.tag) {
+ case StyleBasicShape::Tag::Polygon:
+ return CreatePolygon(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect,
+ aMarginRect, aWM, aContainerSize);
+ case StyleBasicShape::Tag::Circle:
+ case StyleBasicShape::Tag::Ellipse:
+ return CreateCircleOrEllipse(aBasicShape, aShapeMargin, aFrame,
+ aShapeBoxRect, aWM, aContainerSize);
+ case StyleBasicShape::Tag::Rect:
+ return CreateInset(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect, aWM,
+ aContainerSize);
+ case StyleBasicShape::Tag::Path:
+ MOZ_ASSERT_UNREACHABLE("Unsupported basic shape");
+ }
+ return nullptr;
+}
+
+/* static */ UniquePtr<nsFloatManager::ShapeInfo>
+nsFloatManager::ShapeInfo::CreateInset(const StyleBasicShape& aBasicShape,
+ nscoord aShapeMargin, nsIFrame* aFrame,
+ const LogicalRect& aShapeBoxRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize) {
+ // Use physical coordinates to compute inset() because the top, right,
+ // bottom and left offsets are physical.
+ // https://drafts.csswg.org/css-shapes-1/#funcdef-inset
+ nsRect physicalShapeBoxRect =
+ aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
+ const nsRect insetRect = ShapeUtils::ComputeInsetRect(
+ aBasicShape.AsRect().rect, physicalShapeBoxRect);
+
+ nsRect logicalInsetRect = ConvertToFloatLogical(
+ LogicalRect(aWM, insetRect, aContainerSize), aWM, aContainerSize);
+ nscoord physicalRadii[8];
+ bool hasRadii = ShapeUtils::ComputeRectRadii(aBasicShape.AsRect().round,
+ physicalShapeBoxRect, insetRect,
+ physicalRadii);
+
+ // With a zero shape-margin, we will be able to use the fast constructor.
+ if (aShapeMargin == 0) {
+ if (!hasRadii) {
+ return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
+ UniquePtr<nscoord[]>());
+ }
+ return MakeUnique<RoundedBoxShapeInfo>(
+ logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM));
+ }
+
+ // With a positive shape-margin, we might still be able to use the fast
+ // constructor. With no radii, we can build a rounded box by inflating
+ // logicalInsetRect, and supplying aShapeMargin as the radius for all
+ // corners.
+ if (!hasRadii) {
+ logicalInsetRect.Inflate(aShapeMargin);
+ auto logicalRadii = MakeUnique<nscoord[]>(8);
+ for (int32_t i = 0; i < 8; ++i) {
+ logicalRadii[i] = aShapeMargin;
+ }
+ return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
+ std::move(logicalRadii));
+ }
+
+ // If we have radii, and they have balanced/equal corners, we can inflate
+ // both logicalInsetRect and all the radii and use the fast constructor.
+ if (RoundedBoxShapeInfo::EachCornerHasBalancedRadii(physicalRadii)) {
+ logicalInsetRect.Inflate(aShapeMargin);
+ for (nscoord& r : physicalRadii) {
+ r += aShapeMargin;
+ }
+ return MakeUnique<RoundedBoxShapeInfo>(
+ logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM));
+ }
+
+ // With positive shape-margin and elliptical radii, we have to use the
+ // slow constructor.
+ nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
+ int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
+ return MakeUnique<RoundedBoxShapeInfo>(
+ logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM), aShapeMargin,
+ appUnitsPerDevPixel);
+}
+
+/* static */ UniquePtr<nsFloatManager::ShapeInfo>
+nsFloatManager::ShapeInfo::CreateCircleOrEllipse(
+ const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
+ nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect, WritingMode aWM,
+ const nsSize& aContainerSize) {
+ // Use physical coordinates to compute the center of circle() or ellipse()
+ // since the <position> keywords such as 'left', 'top', etc. are physical.
+ // https://drafts.csswg.org/css-shapes-1/#funcdef-ellipse
+ nsRect physicalShapeBoxRect =
+ aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
+ nsPoint physicalCenter = ShapeUtils::ComputeCircleOrEllipseCenter(
+ aBasicShape, physicalShapeBoxRect);
+ nsPoint logicalCenter =
+ ConvertToFloatLogical(physicalCenter, aWM, aContainerSize);
+
+ // Compute the circle or ellipse radii.
+ nsSize radii;
+ if (aBasicShape.IsCircle()) {
+ nscoord radius = ShapeUtils::ComputeCircleRadius(
+ aBasicShape, physicalCenter, physicalShapeBoxRect);
+ // Circles can use the three argument, math constructor for
+ // EllipseShapeInfo.
+ radii = nsSize(radius, radius);
+ return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
+ }
+
+ MOZ_ASSERT(aBasicShape.IsEllipse());
+ nsSize physicalRadii = ShapeUtils::ComputeEllipseRadii(
+ aBasicShape, physicalCenter, physicalShapeBoxRect);
+ LogicalSize logicalRadii(aWM, physicalRadii);
+ radii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
+
+ // If radii are close to the same value, or if aShapeMargin is small
+ // enough (as specified in css pixels), then we can use the three argument
+ // constructor for EllipseShapeInfo, which uses math for a more efficient
+ // method of float area computation.
+ if (EllipseShapeInfo::ShapeMarginIsNegligible(aShapeMargin) ||
+ EllipseShapeInfo::RadiiAreRoughlyEqual(radii)) {
+ return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
+ }
+
+ // We have to use the full constructor for EllipseShapeInfo. This
+ // computes the float area using a rasterization method.
+ nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
+ int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
+ return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin,
+ appUnitsPerDevPixel);
+}
+
+/* static */ UniquePtr<nsFloatManager::ShapeInfo>
+nsFloatManager::ShapeInfo::CreatePolygon(const StyleBasicShape& aBasicShape,
+ nscoord aShapeMargin,
+ nsIFrame* const aFrame,
+ const LogicalRect& aShapeBoxRect,
+ const LogicalRect& aMarginRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize) {
+ // Use physical coordinates to compute each (xi, yi) vertex because CSS
+ // represents them using physical coordinates.
+ // https://drafts.csswg.org/css-shapes-1/#funcdef-polygon
+ nsRect physicalShapeBoxRect =
+ aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
+
+ // Get physical vertices.
+ nsTArray<nsPoint> vertices =
+ ShapeUtils::ComputePolygonVertices(aBasicShape, physicalShapeBoxRect);
+
+ // Convert all the physical vertices to logical.
+ for (nsPoint& vertex : vertices) {
+ vertex = ConvertToFloatLogical(vertex, aWM, aContainerSize);
+ }
+
+ if (aShapeMargin == 0) {
+ return MakeUnique<PolygonShapeInfo>(std::move(vertices));
+ }
+
+ nsRect marginRect = ConvertToFloatLogical(aMarginRect, aWM, aContainerSize);
+
+ // We have to use the full constructor for PolygonShapeInfo. This
+ // computes the float area using a rasterization method.
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ return MakeUnique<PolygonShapeInfo>(std::move(vertices), aShapeMargin,
+ appUnitsPerDevPixel, marginRect);
+}
+
+/* static */ UniquePtr<nsFloatManager::ShapeInfo>
+nsFloatManager::ShapeInfo::CreateImageShape(const StyleImage& aShapeImage,
+ float aShapeImageThreshold,
+ nscoord aShapeMargin,
+ nsIFrame* const aFrame,
+ const LogicalRect& aMarginRect,
+ WritingMode aWM,
+ const nsSize& aContainerSize) {
+ MOZ_ASSERT(&aShapeImage == &aFrame->StyleDisplay()->mShapeOutside.AsImage(),
+ "aFrame should be the frame that we got aShapeImage from");
+
+ nsImageRenderer imageRenderer(aFrame, &aShapeImage,
+ nsImageRenderer::FLAG_SYNC_DECODE_IMAGES);
+
+ if (!imageRenderer.PrepareImage()) {
+ // The image is not ready yet. Boost its loading priority since it will
+ // affect layout.
+ if (imgRequestProxy* req = aShapeImage.GetImageRequest()) {
+ req->BoostPriority(imgIRequest::CATEGORY_SIZE_QUERY);
+ }
+ return nullptr;
+ }
+
+ nsRect contentRect = aFrame->GetContentRect();
+
+ // Create a draw target and draw shape image on it.
+ nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
+ int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
+ LayoutDeviceIntSize contentSizeInDevPixels =
+ LayoutDeviceIntSize::FromAppUnitsRounded(contentRect.Size(),
+ appUnitsPerDevPixel);
+
+ // Use empty CSSSizeOrRatio to force set the preferred size as the frame's
+ // content box size.
+ imageRenderer.SetPreferredSize(CSSSizeOrRatio(), contentRect.Size());
+
+ RefPtr<gfx::DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
+ contentSizeInDevPixels.ToUnknownSize(), gfx::SurfaceFormat::A8);
+ if (!drawTarget) {
+ return nullptr;
+ }
+
+ gfxContext context(drawTarget);
+
+ ImgDrawResult result =
+ imageRenderer.DrawShapeImage(aFrame->PresContext(), context);
+
+ if (result != ImgDrawResult::SUCCESS) {
+ return nullptr;
+ }
+
+ // Retrieve the pixel image buffer to create the image shape info.
+ RefPtr<SourceSurface> sourceSurface = drawTarget->Snapshot();
+ RefPtr<DataSourceSurface> dataSourceSurface = sourceSurface->GetDataSurface();
+ DataSourceSurface::ScopedMap map(dataSourceSurface, DataSourceSurface::READ);
+
+ if (!map.IsMapped()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(sourceSurface->GetSize() == contentSizeInDevPixels.ToUnknownSize(),
+ "Who changes the size?");
+
+ nsRect marginRect = aMarginRect.GetPhysicalRect(aWM, aContainerSize);
+
+ uint8_t* alphaPixels = map.GetData();
+ int32_t stride = map.GetStride();
+
+ // NOTE: ImageShapeInfo constructor does not keep a persistent copy of
+ // alphaPixels; it's only used during the constructor to compute pixel ranges.
+ return MakeUnique<ImageShapeInfo>(alphaPixels, stride, contentSizeInDevPixels,
+ appUnitsPerDevPixel, aShapeImageThreshold,
+ aShapeMargin, contentRect, marginRect, aWM,
+ aContainerSize);
+}
+
+/* static */
+nscoord nsFloatManager::ShapeInfo::ComputeEllipseLineInterceptDiff(
+ const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
+ const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
+ const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
+ const nscoord aBandBStart, const nscoord aBandBEnd) {
+ // An example for the band intersecting with the top right corner of an
+ // ellipse with writing-mode horizontal-tb.
+ //
+ // lineIntercept lineDiff
+ // | |
+ // +---------------------------------|-------|-+---- aShapeBoxBStart
+ // | ##########^ | | |
+ // | ##############|#### | | |
+ // +---------#################|######|-------|-+---- aBandBStart
+ // | ###################|######|## | |
+ // | aBStartCornerRadiusB |######|### | |
+ // | ######################|######|##### | |
+ // +---#######################|<-----------><->^---- aBandBEnd
+ // | ########################|############## |
+ // | ########################|############## |---- b
+ // | #########################|############### |
+ // | ######################## v<-------------->v
+ // |###################### aBStartCornerRadiusL|
+ // |###########################################|
+ // |###########################################|
+ // |###########################################|
+ // |###########################################|
+ // | ######################################### |
+ // | ######################################### |
+ // | ####################################### |
+ // | ####################################### |
+ // | ##################################### |
+ // | ################################### |
+ // | ############################### |
+ // | ############################# |
+ // | ######################### |
+ // | ################### |
+ // | ########### |
+ // +-------------------------------------------+----- aShapeBoxBEnd
+
+ NS_ASSERTION(aShapeBoxBStart <= aShapeBoxBEnd, "Bad shape box coordinates!");
+ NS_ASSERTION(aBandBStart <= aBandBEnd, "Bad band coordinates!");
+
+ nscoord lineDiff = 0;
+
+ // If the band intersects both the block-start and block-end corners, we
+ // don't need to enter either branch because the correct lineDiff is 0.
+ if (aBStartCornerRadiusB > 0 && aBandBEnd >= aShapeBoxBStart &&
+ aBandBEnd <= aShapeBoxBStart + aBStartCornerRadiusB) {
+ // The band intersects only the block-start corner.
+ nscoord b = aBStartCornerRadiusB - (aBandBEnd - aShapeBoxBStart);
+ nscoord lineIntercept =
+ XInterceptAtY(b, aBStartCornerRadiusL, aBStartCornerRadiusB);
+ lineDiff = aBStartCornerRadiusL - lineIntercept;
+ } else if (aBEndCornerRadiusB > 0 &&
+ aBandBStart >= aShapeBoxBEnd - aBEndCornerRadiusB &&
+ aBandBStart <= aShapeBoxBEnd) {
+ // The band intersects only the block-end corner.
+ nscoord b = aBEndCornerRadiusB - (aShapeBoxBEnd - aBandBStart);
+ nscoord lineIntercept =
+ XInterceptAtY(b, aBEndCornerRadiusL, aBEndCornerRadiusB);
+ lineDiff = aBEndCornerRadiusL - lineIntercept;
+ }
+
+ return lineDiff;
+}
+
+/* static */
+nscoord nsFloatManager::ShapeInfo::XInterceptAtY(const nscoord aY,
+ const nscoord aRadiusX,
+ const nscoord aRadiusY) {
+ // Solve for x in the ellipse equation (x/radiusX)^2 + (y/radiusY)^2 = 1.
+ MOZ_ASSERT(aRadiusY > 0);
+ const auto ratioY = aY / static_cast<double>(aRadiusY);
+ MOZ_ASSERT(ratioY <= 1, "Why is position y outside of the radius on y-axis?");
+ return NSToCoordTrunc(aRadiusX * std::sqrt(1 - ratioY * ratioY));
+}
+
+/* static */
+nsPoint nsFloatManager::ShapeInfo::ConvertToFloatLogical(
+ const nsPoint& aPoint, WritingMode aWM, const nsSize& aContainerSize) {
+ LogicalPoint logicalPoint(aWM, aPoint, aContainerSize);
+ return nsPoint(logicalPoint.LineRelative(aWM, aContainerSize),
+ logicalPoint.B(aWM));
+}
+
+/* static */ UniquePtr<nscoord[]>
+nsFloatManager::ShapeInfo::ConvertToFloatLogical(const nscoord aRadii[8],
+ WritingMode aWM) {
+ UniquePtr<nscoord[]> logicalRadii(new nscoord[8]);
+
+ // Get the physical side for line-left and line-right since border radii
+ // are on the physical axis.
+ Side lineLeftSide =
+ aWM.PhysicalSide(aWM.LogicalSideForLineRelativeDir(eLineRelativeDirLeft));
+ logicalRadii[eCornerTopLeftX] =
+ aRadii[SideToHalfCorner(lineLeftSide, true, false)];
+ logicalRadii[eCornerTopLeftY] =
+ aRadii[SideToHalfCorner(lineLeftSide, true, true)];
+ logicalRadii[eCornerBottomLeftX] =
+ aRadii[SideToHalfCorner(lineLeftSide, false, false)];
+ logicalRadii[eCornerBottomLeftY] =
+ aRadii[SideToHalfCorner(lineLeftSide, false, true)];
+
+ Side lineRightSide = aWM.PhysicalSide(
+ aWM.LogicalSideForLineRelativeDir(eLineRelativeDirRight));
+ logicalRadii[eCornerTopRightX] =
+ aRadii[SideToHalfCorner(lineRightSide, false, false)];
+ logicalRadii[eCornerTopRightY] =
+ aRadii[SideToHalfCorner(lineRightSide, false, true)];
+ logicalRadii[eCornerBottomRightX] =
+ aRadii[SideToHalfCorner(lineRightSide, true, false)];
+ logicalRadii[eCornerBottomRightY] =
+ aRadii[SideToHalfCorner(lineRightSide, true, true)];
+
+ if (aWM.IsLineInverted()) {
+ // When IsLineInverted() is true, i.e. aWM is vertical-lr,
+ // line-over/line-under are inverted from block-start/block-end. So the
+ // relationship reverses between which corner comes first going
+ // clockwise, and which corner is block-start versus block-end. We need
+ // to swap the values stored in top and bottom corners.
+ std::swap(logicalRadii[eCornerTopLeftX], logicalRadii[eCornerBottomLeftX]);
+ std::swap(logicalRadii[eCornerTopLeftY], logicalRadii[eCornerBottomLeftY]);
+ std::swap(logicalRadii[eCornerTopRightX],
+ logicalRadii[eCornerBottomRightX]);
+ std::swap(logicalRadii[eCornerTopRightY],
+ logicalRadii[eCornerBottomRightY]);
+ }
+
+ return logicalRadii;
+}
+
+/* static */
+size_t nsFloatManager::ShapeInfo::MinIntervalIndexContainingY(
+ const nsTArray<nsRect>& aIntervals, const nscoord aTargetY) {
+ // Perform a binary search to find the minimum index of an interval
+ // that contains aTargetY. If no such interval exists, return a value
+ // equal to the number of intervals.
+ size_t startIdx = 0;
+ size_t endIdx = aIntervals.Length();
+ while (startIdx < endIdx) {
+ size_t midIdx = startIdx + (endIdx - startIdx) / 2;
+ if (aIntervals[midIdx].ContainsY(aTargetY)) {
+ return midIdx;
+ }
+ nscoord midY = aIntervals[midIdx].Y();
+ if (midY < aTargetY) {
+ startIdx = midIdx + 1;
+ } else {
+ endIdx = midIdx;
+ }
+ }
+
+ return endIdx;
+}
+
+/* static */
+nscoord nsFloatManager::ShapeInfo::LineEdge(const nsTArray<nsRect>& aIntervals,
+ const nscoord aBStart,
+ const nscoord aBEnd,
+ bool aIsLineLeft) {
+ MOZ_ASSERT(aBStart <= aBEnd,
+ "The band's block start is greater than its block end?");
+
+ // Find all the intervals whose rects overlap the aBStart to
+ // aBEnd range, and find the most constraining inline edge
+ // depending on the value of aLeft.
+
+ // Since the intervals are stored in block-axis order, we need
+ // to find the first interval that overlaps aBStart and check
+ // succeeding intervals until we get past aBEnd.
+
+ nscoord lineEdge = aIsLineLeft ? nscoord_MAX : nscoord_MIN;
+
+ size_t intervalCount = aIntervals.Length();
+ for (size_t i = MinIntervalIndexContainingY(aIntervals, aBStart);
+ i < intervalCount; ++i) {
+ // We can always get the bCoord from the intervals' mLineLeft,
+ // since the y() coordinate is duplicated in both points in the
+ // interval.
+ auto& interval = aIntervals[i];
+ nscoord bCoord = interval.Y();
+ if (bCoord >= aBEnd) {
+ break;
+ }
+ // Get the edge from the interval point indicated by aLeft.
+ if (aIsLineLeft) {
+ lineEdge = std::min(lineEdge, interval.X());
+ } else {
+ lineEdge = std::max(lineEdge, interval.XMost());
+ }
+ }
+
+ return lineEdge;
+}
+
+/* static */ nsFloatManager::ShapeInfo::dfType
+nsFloatManager::ShapeInfo::CalcUsedShapeMargin5X(nscoord aShapeMargin,
+ int32_t aAppUnitsPerDevPixel) {
+ // Our distance field has to be able to hold values equal to the
+ // maximum shape-margin value that we care about faithfully rendering,
+ // times 5. A 16-bit unsigned int can represent up to ~ 65K which means
+ // we can handle a margin up to ~ 13K device pixels. That's good enough
+ // for practical usage. Any supplied shape-margin value higher than this
+ // maximum will be clamped.
+ static const float MAX_MARGIN_5X_FLOAT = (float)MAX_MARGIN_5X;
+
+ // Convert aShapeMargin to dev pixels, convert that into 5x-dev-pixel
+ // space, then clamp to MAX_MARGIN_5X_FLOAT.
+ float shapeMarginDevPixels5X =
+ 5.0f * NSAppUnitsToFloatPixels(aShapeMargin, aAppUnitsPerDevPixel);
+ NS_WARNING_ASSERTION(shapeMarginDevPixels5X <= MAX_MARGIN_5X_FLOAT,
+ "shape-margin is too large and is being clamped.");
+
+ // We calculate a minimum in float space, which takes care of any overflow
+ // or infinity that may have occurred earlier from multiplication of
+ // too-large aShapeMargin values.
+ float usedMargin5XFloat =
+ std::min(shapeMarginDevPixels5X, MAX_MARGIN_5X_FLOAT);
+ return (dfType)NSToIntRound(usedMargin5XFloat);
+}
+
+//----------------------------------------------------------------------
+
+nsAutoFloatManager::~nsAutoFloatManager() {
+ // Restore the old float manager in the reflow input if necessary.
+ if (mNew) {
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyFloatManager) {
+ printf("restoring old float manager %p\n", mOld);
+ }
+#endif
+
+ mReflowInput.mFloatManager = mOld;
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyFloatManager) {
+ if (mOld) {
+ mReflowInput.mFrame->ListTag(stdout);
+ printf(": float manager %p after reflow\n", mOld);
+ mOld->List(stdout);
+ }
+ }
+#endif
+ }
+}
+
+void nsAutoFloatManager::CreateFloatManager(nsPresContext* aPresContext) {
+ MOZ_ASSERT(!mNew, "Redundant call to CreateFloatManager!");
+
+ // Create a new float manager and install it in the reflow
+ // input. `Remember' the old float manager so we can restore it
+ // later.
+ mNew = MakeUnique<nsFloatManager>(aPresContext->PresShell(),
+ mReflowInput.GetWritingMode());
+
+#ifdef DEBUG
+ if (nsBlockFrame::gNoisyFloatManager) {
+ printf("constructed new float manager %p (replacing %p)\n", mNew.get(),
+ mReflowInput.mFloatManager);
+ }
+#endif
+
+ // Set the float manager in the existing reflow input.
+ mOld = mReflowInput.mFloatManager;
+ mReflowInput.mFloatManager = mNew.get();
+}
diff --git a/layout/generic/nsFloatManager.h b/layout/generic/nsFloatManager.h
new file mode 100644
index 0000000000..f947567383
--- /dev/null
+++ b/layout/generic/nsFloatManager.h
@@ -0,0 +1,464 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* class that manages rules for positioning floats */
+
+#ifndef nsFloatManager_h_
+#define nsFloatManager_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WritingModes.h"
+#include "nsCoord.h"
+#include "nsFrameList.h" // for DEBUG_FRAME_DUMP
+#include "nsIntervalSet.h"
+#include "nsPoint.h"
+#include "nsTArray.h"
+
+class nsIFrame;
+class nsPresContext;
+namespace mozilla {
+struct ReflowInput;
+class PresShell;
+} // namespace mozilla
+
+enum class nsFlowAreaRectFlags : uint32_t {
+ NoFlags = 0,
+ HasFloats = 1 << 0,
+ MayWiden = 1 << 1,
+ ISizeIsActuallyNegative = 1 << 2,
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsFlowAreaRectFlags)
+
+/**
+ * The available space for content not occupied by floats is divided
+ * into a sequence of rectangles in the block direction. However, we
+ * need to know not only the rectangle, but also whether it was reduced
+ * (from the content rectangle) by floats that actually intruded into
+ * the content rectangle. If it has been reduced by floats, then we also
+ * track whether the flow area might widen as the floats narrow in the
+ * block direction.
+ */
+struct nsFlowAreaRect {
+ mozilla::LogicalRect mRect;
+
+ nsFlowAreaRectFlags mAreaFlags;
+
+ nsFlowAreaRect(mozilla::WritingMode aWritingMode, nscoord aICoord,
+ nscoord aBCoord, nscoord aISize, nscoord aBSize,
+ nsFlowAreaRectFlags aAreaFlags)
+ : mRect(aWritingMode, aICoord, aBCoord, aISize, aBSize),
+ mAreaFlags(aAreaFlags) {}
+
+ bool HasFloats() const {
+ return (bool)(mAreaFlags & nsFlowAreaRectFlags::HasFloats);
+ }
+ bool MayWiden() const {
+ return (bool)(mAreaFlags & nsFlowAreaRectFlags::MayWiden);
+ }
+ bool ISizeIsActuallyNegative() const {
+ return (bool)(mAreaFlags & nsFlowAreaRectFlags::ISizeIsActuallyNegative);
+ }
+};
+
+#define NS_FLOAT_MANAGER_CACHE_SIZE 64
+
+/**
+ * nsFloatManager is responsible for implementing CSS's rules for
+ * positioning floats. An nsFloatManager object is created during reflow for
+ * any block with NS_BLOCK_BFC_STATE_BITS. During reflow, the float manager for
+ * the nearest such ancestor block is found in ReflowInput::mFloatManager.
+ *
+ * According to the line-relative mappings in CSS Writing Modes spec [1],
+ * line-right and line-left are calculated with respect to the writing mode
+ * of the containing block of the floats. All the writing modes passed to
+ * nsFloatManager methods should be the containing block's writing mode.
+ *
+ * However, according to the abstract-to-physical mappings table [2], the
+ * 'direction' property of the containing block doesn't affect the
+ * interpretation of line-right and line-left. We actually implement this by
+ * passing in the writing mode of the block formatting context (BFC), i.e.
+ * the of BlockReflowState's writing mode.
+ *
+ * nsFloatManager uses a special logical coordinate space with inline
+ * coordinates on the line-axis and block coordinates on the block-axis
+ * based on the writing mode of the block formatting context. All the
+ * physical types like nsRect, nsPoint, etc. use this coordinate space. See
+ * FloatInfo::mRect for an example.
+ *
+ * [1] https://drafts.csswg.org/css-writing-modes/#line-mappings
+ * [2] https://drafts.csswg.org/css-writing-modes/#logical-to-physical
+ */
+class nsFloatManager {
+ public:
+ explicit nsFloatManager(mozilla::PresShell* aPresShell,
+ mozilla::WritingMode aWM);
+ ~nsFloatManager();
+
+ void* operator new(size_t aSize) noexcept(true);
+ void operator delete(void* aPtr, size_t aSize);
+
+ static void Shutdown();
+
+ /**
+ * Get float region stored on the frame. (Defaults to mRect if it's
+ * not there.) The float region is the area impacted by this float;
+ * the coordinates are relative to the containing block frame.
+ */
+ static mozilla::LogicalRect GetRegionFor(mozilla::WritingMode aWM,
+ nsIFrame* aFloatFrame,
+ const nsSize& aContainerSize);
+ /**
+ * Calculate the float region for this frame using aMargin and the
+ * frame's mRect. The region includes the margins around the float,
+ * but doesn't include the relative offsets.
+ * Note that if the frame is or has a continuation, aMargin's top
+ * and/or bottom must be zeroed by the caller.
+ */
+ static mozilla::LogicalRect CalculateRegionFor(
+ mozilla::WritingMode aWM, nsIFrame* aFloatFrame,
+ const mozilla::LogicalMargin& aMargin, const nsSize& aContainerSize);
+ /**
+ * Store the float region on the frame. The region is stored
+ * as a delta against the mRect, so repositioning the frame will
+ * also reposition the float region.
+ */
+ static void StoreRegionFor(mozilla::WritingMode aWM, nsIFrame* aFloat,
+ const mozilla::LogicalRect& aRegion,
+ const nsSize& aContainerSize);
+
+ // Structure that stores the current state of a float manager for
+ // Save/Restore purposes.
+ struct SavedState {
+ explicit SavedState()
+ : mFloatInfoCount(0),
+ mLineLeft(0),
+ mBlockStart(0),
+ mPushedLeftFloatPastBreak(false),
+ mPushedRightFloatPastBreak(false),
+ mSplitLeftFloatAcrossBreak(false),
+ mSplitRightFloatAcrossBreak(false) {}
+
+ private:
+ uint32_t mFloatInfoCount;
+ nscoord mLineLeft, mBlockStart;
+ bool mPushedLeftFloatPastBreak;
+ bool mPushedRightFloatPastBreak;
+ bool mSplitLeftFloatAcrossBreak;
+ bool mSplitRightFloatAcrossBreak;
+
+ friend class nsFloatManager;
+ };
+
+ /**
+ * Translate the current origin by the specified offsets. This
+ * creates a new local coordinate space relative to the current
+ * coordinate space.
+ */
+ void Translate(nscoord aLineLeft, nscoord aBlockStart) {
+ mLineLeft += aLineLeft;
+ mBlockStart += aBlockStart;
+ }
+
+ /**
+ * Returns the current translation from local coordinate space to
+ * world coordinate space. This represents the accumulated calls to
+ * Translate().
+ */
+ void GetTranslation(nscoord& aLineLeft, nscoord& aBlockStart) const {
+ aLineLeft = mLineLeft;
+ aBlockStart = mBlockStart;
+ }
+
+ /**
+ * Get information about the area available to content that flows
+ * around floats. Two different types of space can be requested:
+ * BandFromPoint: returns the band containing block-dir coordinate
+ * |aBCoord| (though actually with the top truncated to begin at
+ * aBCoord), but up to at most |aBSize| (which may be nscoord_MAX).
+ * This will return the tallest rectangle whose block start is
+ * |aBCoord| and in which there are no changes in what floats are
+ * on the sides of that rectangle, but will limit the block size
+ * of the rectangle to |aBSize|. The inline start and end edges
+ * of the rectangle give the area available for line boxes in that
+ * space. The inline size of this resulting rectangle will not be
+ * negative.
+ * WidthWithinHeight: This returns a rectangle whose block start
+ * is aBCoord and whose block size is exactly aBSize. Its inline
+ * start and end edges give the corresponding edges of the space
+ * that can be used for line boxes *throughout* that space. (It
+ * is possible that more inline space could be used in part of the
+ * space if a float begins or ends in it.) The inline size of the
+ * resulting rectangle can be negative.
+ *
+ * ShapeType can be used to request two different types of flow areas.
+ * (This is the float area defined in CSS Shapes Module Level 1 §1.4):
+ * Margin: uses the float element's margin-box to request the flow area.
+ * ShapeOutside: uses the float element's shape-outside value to request
+ * the float area.
+ *
+ * @param aBCoord [in] block-dir coordinate for block start of available space
+ * desired, which are positioned relative to the current translation.
+ * @param aBSize [in] see above
+ * @param aContentArea [in] an nsRect representing the content area
+ * @param aState [in] If null, use the current state, otherwise, do
+ * computation based only on floats present in the given
+ * saved state.
+ * @return An nsFlowAreaRect whose:
+ * mRect is the resulting rectangle for line boxes. It will not
+ * extend beyond aContentArea's inline bounds, but may be
+ * narrower when floats are present.
+ * mHasFloats is whether there are floats at the sides of the
+ * return value including those that do not reduce the line box
+ * inline size at all (because they are entirely in the margins)
+ */
+ enum class BandInfoType { BandFromPoint, WidthWithinHeight };
+ enum class ShapeType { Margin, ShapeOutside };
+ nsFlowAreaRect GetFlowArea(mozilla::WritingMode aWM, nscoord aBCoord,
+ nscoord aBSize, BandInfoType aBandInfoType,
+ ShapeType aShapeType,
+ mozilla::LogicalRect aContentArea,
+ SavedState* aState,
+ const nsSize& aContainerSize) const;
+
+ /**
+ * Add a float that comes after all floats previously added. Its
+ * block start must be even with or below the top of all previous
+ * floats.
+ *
+ * aMarginRect is relative to the current translation. The caller
+ * must ensure aMarginRect.height >= 0 and aMarginRect.width >= 0.
+ */
+ void AddFloat(nsIFrame* aFloatFrame, const mozilla::LogicalRect& aMarginRect,
+ mozilla::WritingMode aWM, const nsSize& aContainerSize);
+
+ /**
+ * Notify that we tried to place a float that could not fit at all and
+ * had to be pushed to the next page/column? (If so, we can't place
+ * any more floats in this page/column because of the rule that the
+ * top of a float cannot be above the top of an earlier float. It
+ * also means that any clear needs to continue to the next column.)
+ */
+ void SetPushedLeftFloatPastBreak() { mPushedLeftFloatPastBreak = true; }
+ void SetPushedRightFloatPastBreak() { mPushedRightFloatPastBreak = true; }
+
+ /**
+ * Notify that we split a float, with part of it needing to be pushed
+ * to the next page/column. (This means that any 'clear' needs to
+ * continue to the next page/column.)
+ */
+ void SetSplitLeftFloatAcrossBreak() { mSplitLeftFloatAcrossBreak = true; }
+ void SetSplitRightFloatAcrossBreak() { mSplitRightFloatAcrossBreak = true; }
+
+ /**
+ * Remove the regions associated with this floating frame and its
+ * next-sibling list. Some of the frames may never have been added;
+ * we just skip those. This is not fully general; it only works as
+ * long as the N frames to be removed are the last N frames to have
+ * been added; if there's a frame in the middle of them that should
+ * not be removed, YOU LOSE.
+ */
+ nsresult RemoveTrailingRegions(nsIFrame* aFrameList);
+
+ bool HasAnyFloats() const { return !mFloats.IsEmpty(); }
+
+ /**
+ * Methods for dealing with the propagation of float damage during
+ * reflow.
+ */
+ bool HasFloatDamage() const { return !mFloatDamage.IsEmpty(); }
+
+ void IncludeInDamage(nscoord aIntervalBegin, nscoord aIntervalEnd) {
+ mFloatDamage.IncludeInterval(aIntervalBegin + mBlockStart,
+ aIntervalEnd + mBlockStart);
+ }
+
+ bool IntersectsDamage(nscoord aIntervalBegin, nscoord aIntervalEnd) const {
+ return mFloatDamage.Intersects(aIntervalBegin + mBlockStart,
+ aIntervalEnd + mBlockStart);
+ }
+
+ /**
+ * Saves the current state of the float manager into aState.
+ */
+ void PushState(SavedState* aState);
+
+ /**
+ * Restores the float manager to the saved state.
+ *
+ * These states must be managed using stack discipline. PopState can only
+ * be used after PushState has been used to save the state, and it can only
+ * be used once --- although it can be omitted; saved states can be ignored.
+ * States must be popped in the reverse order they were pushed. A
+ * call to PopState invalidates any saved states Pushed after the
+ * state passed to PopState was pushed.
+ */
+ void PopState(SavedState* aState);
+
+ /**
+ * Get the block start of the last float placed into the float
+ * manager, to enforce the rule that a float can't be above an earlier
+ * float. Returns the minimum nscoord value if there are no floats.
+ *
+ * The result is relative to the current translation.
+ */
+ nscoord LowestFloatBStart() const;
+
+ /**
+ * Return the coordinate of the lowest float matching aClearType in
+ * this float manager. Returns aBCoord if there are no matching
+ * floats.
+ *
+ * Both aBCoord and the result are relative to the current translation.
+ */
+ nscoord ClearFloats(nscoord aBCoord, mozilla::StyleClear aClearType) const;
+
+ /**
+ * Checks if clear would pass into the floats' BFC's next-in-flow,
+ * i.e. whether floats affecting this clear have continuations.
+ */
+ bool ClearContinues(mozilla::StyleClear aClearType) const;
+
+ void AssertStateMatches(SavedState* aState) const {
+ NS_ASSERTION(
+ aState->mLineLeft == mLineLeft && aState->mBlockStart == mBlockStart &&
+ aState->mPushedLeftFloatPastBreak == mPushedLeftFloatPastBreak &&
+ aState->mPushedRightFloatPastBreak == mPushedRightFloatPastBreak &&
+ aState->mSplitLeftFloatAcrossBreak == mSplitLeftFloatAcrossBreak &&
+ aState->mSplitRightFloatAcrossBreak ==
+ mSplitRightFloatAcrossBreak &&
+ aState->mFloatInfoCount == mFloats.Length(),
+ "float manager state should match saved state");
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ /**
+ * Dump the state of the float manager out to a file.
+ */
+ nsresult List(FILE* out) const;
+#endif
+
+ private:
+ class ShapeInfo;
+ class RoundedBoxShapeInfo;
+ class EllipseShapeInfo;
+ class PolygonShapeInfo;
+ class ImageShapeInfo;
+
+ struct FloatInfo {
+ nsIFrame* const mFrame;
+ // The lowest block-ends of left/right floats up to and including
+ // this one.
+ nscoord mLeftBEnd, mRightBEnd;
+
+ FloatInfo(nsIFrame* aFrame, nscoord aLineLeft, nscoord aBlockStart,
+ const mozilla::LogicalRect& aMarginRect, mozilla::WritingMode aWM,
+ const nsSize& aContainerSize);
+
+ nscoord LineLeft() const { return mRect.x; }
+ nscoord LineRight() const { return mRect.XMost(); }
+ nscoord ISize() const { return mRect.width; }
+ nscoord BStart() const { return mRect.y; }
+ nscoord BEnd() const { return mRect.YMost(); }
+ nscoord BSize() const { return mRect.height; }
+ bool IsEmpty() const { return mRect.IsEmpty(); }
+
+ // aBStart and aBEnd are the starting and ending coordinate of a band.
+ // LineLeft() and LineRight() return the innermost line-left extent and
+ // line-right extent within the given band, respectively.
+ nscoord LineLeft(ShapeType aShapeType, const nscoord aBStart,
+ const nscoord aBEnd) const;
+ nscoord LineRight(ShapeType aShapeType, const nscoord aBStart,
+ const nscoord aBEnd) const;
+ nscoord BStart(ShapeType aShapeType) const;
+ nscoord BEnd(ShapeType aShapeType) const;
+ bool IsEmpty(ShapeType aShapeType) const;
+ bool MayNarrowInBlockDirection(ShapeType aShapeType) const;
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ FloatInfo(FloatInfo&& aOther);
+ ~FloatInfo();
+#endif
+
+ // NB! This is really a logical rect in a writing mode suitable for
+ // placing floats, which is not necessarily the actual writing mode
+ // either of the block which created the float manager or the block
+ // that is calling the float manager. The inline coordinates are in
+ // the line-relative axis of the float manager and its block
+ // coordinates are in the float manager's block direction.
+ nsRect mRect;
+ // Pointer to a concrete subclass of ShapeInfo or null, which means that
+ // there is no shape-outside.
+ mozilla::UniquePtr<ShapeInfo> mShapeInfo;
+ };
+
+#ifdef DEBUG
+ // Store the writing mode from the block frame which establishes the block
+ // formatting context (BFC) when the nsFloatManager is created.
+ mozilla::WritingMode mWritingMode;
+#endif
+
+ // Translation from local to global coordinate space.
+ nscoord mLineLeft, mBlockStart;
+ // We use 11 here in order to fill up the jemalloc allocatoed chunk nicely,
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1362876#c6.
+ AutoTArray<FloatInfo, 11> mFloats;
+ nsIntervalSet mFloatDamage;
+
+ // Did we try to place a float that could not fit at all and had to be
+ // pushed to the next page/column? If so, we can't place any more
+ // floats in this page/column because of the rule that the top of a
+ // float cannot be above the top of an earlier float. And we also
+ // need to apply this information to 'clear', and thus need to
+ // separate left and right floats.
+ bool mPushedLeftFloatPastBreak;
+ bool mPushedRightFloatPastBreak;
+
+ // Did we split a float, with part of it needing to be pushed to the
+ // next page/column. This means that any 'clear' needs to continue to
+ // the next page/column.
+ bool mSplitLeftFloatAcrossBreak;
+ bool mSplitRightFloatAcrossBreak;
+
+ static int32_t sCachedFloatManagerCount;
+ static void* sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE];
+
+ nsFloatManager(const nsFloatManager&) = delete;
+ void operator=(const nsFloatManager&) = delete;
+};
+
+/**
+ * A helper class to manage maintenance of the float manager during
+ * nsBlockFrame::Reflow. It automatically restores the old float
+ * manager in the reflow input when the object goes out of scope.
+ */
+class nsAutoFloatManager {
+ using ReflowInput = mozilla::ReflowInput;
+
+ public:
+ explicit nsAutoFloatManager(ReflowInput& aReflowInput)
+ : mReflowInput(aReflowInput), mOld(nullptr) {}
+
+ ~nsAutoFloatManager();
+
+ /**
+ * Create a new float manager for the specified frame. This will
+ * `remember' the old float manager, and install the new float
+ * manager in the reflow input.
+ */
+ void CreateFloatManager(nsPresContext* aPresContext);
+
+ protected:
+ ReflowInput& mReflowInput;
+ mozilla::UniquePtr<nsFloatManager> mNew;
+
+ // A non-owning pointer, which points to the object owned by
+ // nsAutoFloatManager::mNew.
+ nsFloatManager* mOld;
+};
+
+#endif /* !defined(nsFloatManager_h_) */
diff --git a/layout/generic/nsFontInflationData.cpp b/layout/generic/nsFontInflationData.cpp
new file mode 100644
index 0000000000..b510a62b0c
--- /dev/null
+++ b/layout/generic/nsFontInflationData.cpp
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Per-block-formatting-context manager of font size inflation for pan and zoom
+ * UI. */
+
+#include "nsFontInflationData.h"
+#include "FrameProperties.h"
+#include "nsTextControlFrame.h"
+#include "nsListControlFrame.h"
+#include "nsComboboxControlFrame.h"
+#include "mozilla/dom/Text.h" // for inline nsINode::AsText() definition
+#include "mozilla/PresShell.h"
+#include "mozilla/ReflowInput.h"
+#include "nsTextFrameUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(FontInflationDataProperty,
+ nsFontInflationData)
+
+/* static */ nsFontInflationData* nsFontInflationData::FindFontInflationDataFor(
+ const nsIFrame* aFrame) {
+ // We have one set of font inflation data per block formatting context.
+ const nsIFrame* bfc = FlowRootFor(aFrame);
+ NS_ASSERTION(bfc->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
+ "should have found a flow root");
+ MOZ_ASSERT(aFrame->GetWritingMode().IsVertical() ==
+ bfc->GetWritingMode().IsVertical(),
+ "current writing mode should match that of our flow root");
+
+ return bfc->GetProperty(FontInflationDataProperty());
+}
+
+/* static */
+bool nsFontInflationData::UpdateFontInflationDataISizeFor(
+ const ReflowInput& aReflowInput) {
+ nsIFrame* bfc = aReflowInput.mFrame;
+ NS_ASSERTION(bfc->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
+ "should have been given a flow root");
+ nsFontInflationData* data = bfc->GetProperty(FontInflationDataProperty());
+ bool oldInflationEnabled;
+ nscoord oldUsableISize;
+ if (data) {
+ oldUsableISize = data->mUsableISize;
+ oldInflationEnabled = data->mInflationEnabled;
+ } else {
+ data = new nsFontInflationData(bfc);
+ bfc->SetProperty(FontInflationDataProperty(), data);
+ oldUsableISize = -1;
+ oldInflationEnabled = true; /* not relevant */
+ }
+
+ data->UpdateISize(aReflowInput);
+
+ if (oldInflationEnabled != data->mInflationEnabled) return true;
+
+ return oldInflationEnabled && oldUsableISize != data->mUsableISize;
+}
+
+/* static */
+void nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame* aBFCFrame) {
+ NS_ASSERTION(aBFCFrame->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
+ "should have been given a flow root");
+
+ nsFontInflationData* data =
+ aBFCFrame->GetProperty(FontInflationDataProperty());
+ if (data) {
+ data->MarkTextDirty();
+ }
+}
+
+nsFontInflationData::nsFontInflationData(nsIFrame* aBFCFrame)
+ : mBFCFrame(aBFCFrame),
+ mUsableISize(0),
+ mTextAmount(0),
+ mTextThreshold(0),
+ mInflationEnabled(false),
+ mTextDirty(true) {}
+
+/**
+ * Find the closest common ancestor between aFrame1 and aFrame2, except
+ * treating the parent of a frame as the first-in-flow of its parent (so
+ * the result doesn't change when breaking changes).
+ *
+ * aKnownCommonAncestor is a known common ancestor of both.
+ */
+static nsIFrame* NearestCommonAncestorFirstInFlow(
+ nsIFrame* aFrame1, nsIFrame* aFrame2, nsIFrame* aKnownCommonAncestor) {
+ aFrame1 = aFrame1->FirstInFlow();
+ aFrame2 = aFrame2->FirstInFlow();
+ aKnownCommonAncestor = aKnownCommonAncestor->FirstInFlow();
+
+ AutoTArray<nsIFrame*, 32> ancestors1, ancestors2;
+ for (nsIFrame* f = aFrame1; f != aKnownCommonAncestor;
+ (f = f->GetParent()) && (f = f->FirstInFlow())) {
+ ancestors1.AppendElement(f);
+ }
+ for (nsIFrame* f = aFrame2; f != aKnownCommonAncestor;
+ (f = f->GetParent()) && (f = f->FirstInFlow())) {
+ ancestors2.AppendElement(f);
+ }
+
+ nsIFrame* result = aKnownCommonAncestor;
+ uint32_t i1 = ancestors1.Length(), i2 = ancestors2.Length();
+ while (i1-- != 0 && i2-- != 0) {
+ if (ancestors1[i1] != ancestors2[i2]) {
+ break;
+ }
+ result = ancestors1[i1];
+ }
+
+ return result;
+}
+
+static nscoord ComputeDescendantISize(const ReflowInput& aAncestorReflowInput,
+ nsIFrame* aDescendantFrame) {
+ nsIFrame* ancestorFrame = aAncestorReflowInput.mFrame->FirstInFlow();
+ if (aDescendantFrame == ancestorFrame) {
+ return aAncestorReflowInput.ComputedISize();
+ }
+
+ AutoTArray<nsIFrame*, 16> frames;
+ for (nsIFrame* f = aDescendantFrame; f != ancestorFrame;
+ f = f->GetParent()->FirstInFlow()) {
+ frames.AppendElement(f);
+ }
+
+ // This ignores the inline-size contributions made by scrollbars, though in
+ // reality we don't have any scrollbars on the sorts of devices on
+ // which we use font inflation, so it's not a problem. But it may
+ // occasionally cause problems when writing tests on desktop.
+
+ uint32_t len = frames.Length();
+ ReflowInput* reflowInputs =
+ static_cast<ReflowInput*>(moz_xmalloc(sizeof(ReflowInput) * len));
+ nsPresContext* presContext = aDescendantFrame->PresContext();
+ for (uint32_t i = 0; i < len; ++i) {
+ const ReflowInput& parentReflowInput =
+ (i == 0) ? aAncestorReflowInput : reflowInputs[i - 1];
+ nsIFrame* frame = frames[len - i - 1];
+ WritingMode wm = frame->GetWritingMode();
+ LogicalSize availSize = parentReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ MOZ_ASSERT(frame->GetParent()->FirstInFlow() ==
+ parentReflowInput.mFrame->FirstInFlow(),
+ "bad logic in this function");
+ new (reflowInputs + i)
+ ReflowInput(presContext, parentReflowInput, frame, availSize);
+ }
+
+ MOZ_ASSERT(reflowInputs[len - 1].mFrame == aDescendantFrame,
+ "bad logic in this function");
+ nscoord result = reflowInputs[len - 1].ComputedISize();
+
+ for (uint32_t i = len; i-- != 0;) {
+ reflowInputs[i].~ReflowInput();
+ }
+ free(reflowInputs);
+
+ return result;
+}
+
+void nsFontInflationData::UpdateISize(const ReflowInput& aReflowInput) {
+ nsIFrame* bfc = aReflowInput.mFrame;
+ NS_ASSERTION(bfc->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT),
+ "must be block formatting context");
+
+ nsIFrame* firstInflatableDescendant =
+ FindEdgeInflatableFrameIn(bfc, eFromStart);
+ if (!firstInflatableDescendant) {
+ mTextAmount = 0;
+ mTextThreshold = 0; // doesn't matter
+ mTextDirty = false;
+ mInflationEnabled = false;
+ return;
+ }
+ nsIFrame* lastInflatableDescendant = FindEdgeInflatableFrameIn(bfc, eFromEnd);
+ MOZ_ASSERT(!firstInflatableDescendant == !lastInflatableDescendant,
+ "null-ness should match; NearestCommonAncestorFirstInFlow"
+ " will crash when passed null");
+
+ // Particularly when we're computing for the root BFC, the inline-size of
+ // nca might differ significantly for the inline-size of bfc.
+ nsIFrame* nca = NearestCommonAncestorFirstInFlow(
+ firstInflatableDescendant, lastInflatableDescendant, bfc);
+ while (!nca->IsContainerForFontSizeInflation()) {
+ nca = nca->GetParent()->FirstInFlow();
+ }
+
+ nscoord newNCAISize = ComputeDescendantISize(aReflowInput, nca);
+
+ // See comment above "font.size.inflation.lineThreshold" in
+ // modules/libpref/src/init/StaticPrefList.yaml .
+ PresShell* presShell = bfc->PresShell();
+ uint32_t lineThreshold = presShell->FontSizeInflationLineThreshold();
+ nscoord newTextThreshold = (newNCAISize * lineThreshold) / 100;
+
+ if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
+ // Because we truncate our scan when we hit sufficient text, we now
+ // need to rescan.
+ mTextDirty = true;
+ }
+
+ // Font inflation increases the font size for a given flow root so that the
+ // text is legible when we've zoomed such that the respective nearest common
+ // ancestor's (NCA) full inline-size (ISize) fills the screen. We assume how-
+ // ever that we don't want to zoom out further than the root iframe's ISize
+ // (i.e. the viewport for a top-level document, or the containing iframe
+ // otherwise), since in some cases zooming out further might not even be
+ // possible or make sense.
+ // Hence the ISize assumed to be usable for displaying text is limited to the
+ // visible area.
+ nsPresContext* presContext = bfc->PresContext();
+ MOZ_ASSERT(
+ bfc->GetWritingMode().IsVertical() == nca->GetWritingMode().IsVertical(),
+ "writing mode of NCA should match that of its flow root");
+ nscoord iFrameISize = bfc->GetWritingMode().IsVertical()
+ ? presContext->GetVisibleArea().height
+ : presContext->GetVisibleArea().width;
+ mUsableISize = std::min(iFrameISize, newNCAISize);
+ mTextThreshold = newTextThreshold;
+ mInflationEnabled = mTextAmount >= mTextThreshold;
+}
+
+/* static */ nsIFrame* nsFontInflationData::FindEdgeInflatableFrameIn(
+ nsIFrame* aFrame, SearchDirection aDirection) {
+ // NOTE: This function has a similar structure to ScanTextIn!
+
+ // FIXME: Should probably only scan the text that's actually going to
+ // be inflated!
+
+ nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
+ if (fcf) {
+ return aFrame;
+ }
+
+ // FIXME: aDirection!
+ AutoTArray<FrameChildList, 4> lists;
+ aFrame->GetChildLists(&lists);
+ for (uint32_t i = 0, len = lists.Length(); i < len; ++i) {
+ const nsFrameList& list =
+ lists[(aDirection == eFromStart) ? i : len - i - 1].mList;
+ for (nsIFrame* kid = (aDirection == eFromStart) ? list.FirstChild()
+ : list.LastChild();
+ kid; kid = (aDirection == eFromStart) ? kid->GetNextSibling()
+ : kid->GetPrevSibling()) {
+ if (kid->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
+ // Goes in a different set of inflation data.
+ continue;
+ }
+
+ if (kid->IsTextFrame()) {
+ nsIContent* content = kid->GetContent();
+ if (content && kid == content->GetPrimaryFrame()) {
+ uint32_t len = nsTextFrameUtils::
+ ComputeApproximateLengthWithWhitespaceCompression(
+ content->AsText(), kid->StyleText());
+ if (len != 0) {
+ return kid;
+ }
+ }
+ } else {
+ nsIFrame* kidResult = FindEdgeInflatableFrameIn(kid, aDirection);
+ if (kidResult) {
+ return kidResult;
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+void nsFontInflationData::ScanText() {
+ mTextDirty = false;
+ mTextAmount = 0;
+ ScanTextIn(mBFCFrame);
+ mInflationEnabled = mTextAmount >= mTextThreshold;
+}
+
+static uint32_t DoCharCountOfLargestOption(nsIFrame* aContainer) {
+ uint32_t result = 0;
+ for (nsIFrame* option : aContainer->PrincipalChildList()) {
+ uint32_t optionResult;
+ if (option->GetContent()->IsHTMLElement(nsGkAtoms::optgroup)) {
+ optionResult = DoCharCountOfLargestOption(option);
+ } else {
+ // REVIEW: Check the frame structure for this!
+ optionResult = 0;
+ for (nsIFrame* optionChild : option->PrincipalChildList()) {
+ if (optionChild->IsTextFrame()) {
+ optionResult += nsTextFrameUtils::
+ ComputeApproximateLengthWithWhitespaceCompression(
+ optionChild->GetContent()->AsText(),
+ optionChild->StyleText());
+ }
+ }
+ }
+ if (optionResult > result) {
+ result = optionResult;
+ }
+ }
+ return result;
+}
+
+static uint32_t CharCountOfLargestOption(nsIFrame* aListControlFrame) {
+ return DoCharCountOfLargestOption(
+ static_cast<nsListControlFrame*>(aListControlFrame)
+ ->GetOptionsContainer());
+}
+
+void nsFontInflationData::ScanTextIn(nsIFrame* aFrame) {
+ // NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
+
+ // FIXME: Should probably only scan the text that's actually going to
+ // be inflated!
+
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* kid : childList.mList) {
+ if (kid->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
+ // Goes in a different set of inflation data.
+ continue;
+ }
+
+ LayoutFrameType fType = kid->Type();
+ if (fType == LayoutFrameType::Text) {
+ nsIContent* content = kid->GetContent();
+ if (content && kid == content->GetPrimaryFrame()) {
+ uint32_t len = nsTextFrameUtils::
+ ComputeApproximateLengthWithWhitespaceCompression(
+ content->AsText(), kid->StyleText());
+ if (len != 0) {
+ nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
+ if (fontSize > 0) {
+ mTextAmount += fontSize * len;
+ }
+ }
+ }
+ } else if (fType == LayoutFrameType::TextInput) {
+ // We don't want changes to the amount of text in a text input
+ // to change what we count towards inflation.
+ nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
+ int32_t charCount = static_cast<nsTextControlFrame*>(kid)->GetCols();
+ mTextAmount += charCount * fontSize;
+ } else if (fType == LayoutFrameType::ComboboxControl) {
+ // See textInputFrame above (with s/amount of text/selected option/).
+ // Don't just recurse down to the list control inside, since we
+ // need to exclude the display frame.
+ nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
+ int32_t charCount = static_cast<nsComboboxControlFrame*>(kid)
+ ->CharCountOfLargestOptionForInflation();
+ mTextAmount += charCount * fontSize;
+ } else if (fType == LayoutFrameType::ListControl) {
+ // See textInputFrame above (with s/amount of text/selected option/).
+ nscoord fontSize = kid->StyleFont()->mFont.size.ToAppUnits();
+ int32_t charCount = CharCountOfLargestOption(kid);
+ mTextAmount += charCount * fontSize;
+ } else {
+ // recursive step
+ ScanTextIn(kid);
+ }
+
+ if (mTextAmount >= mTextThreshold) {
+ return;
+ }
+ }
+ }
+}
diff --git a/layout/generic/nsFontInflationData.h b/layout/generic/nsFontInflationData.h
new file mode 100644
index 0000000000..80a422fe32
--- /dev/null
+++ b/layout/generic/nsFontInflationData.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Per-block-formatting-context manager of font size inflation for pan and zoom
+ * UI. */
+
+#ifndef nsFontInflationData_h_
+#define nsFontInflationData_h_
+
+#include "nsContainerFrame.h"
+
+class nsFontInflationData {
+ using ReflowInput = mozilla::ReflowInput;
+
+ public:
+ static nsFontInflationData* FindFontInflationDataFor(const nsIFrame* aFrame);
+
+ // Returns whether the usable width changed (which requires the
+ // caller to mark its descendants dirty)
+ static bool UpdateFontInflationDataISizeFor(const ReflowInput& aReflowInput);
+
+ static void MarkFontInflationDataTextDirty(nsIFrame* aFrame);
+
+ bool InflationEnabled() {
+ if (mTextDirty) {
+ ScanText();
+ }
+ return mInflationEnabled;
+ }
+
+ nscoord UsableISize() const { return mUsableISize; }
+
+ private:
+ explicit nsFontInflationData(nsIFrame* aBFCFrame);
+
+ nsFontInflationData(const nsFontInflationData&) = delete;
+ void operator=(const nsFontInflationData&) = delete;
+
+ void UpdateISize(const ReflowInput& aReflowInput);
+ enum SearchDirection { eFromStart, eFromEnd };
+ static nsIFrame* FindEdgeInflatableFrameIn(nsIFrame* aFrame,
+ SearchDirection aDirection);
+
+ void MarkTextDirty() { mTextDirty = true; }
+ void ScanText();
+ // Scan text in the subtree rooted at aFrame. Increment mTextAmount
+ // by multiplying the number of characters found by the font size
+ // (yielding the inline-size that would be occupied by the characters if
+ // they were all em squares). But stop scanning if mTextAmount
+ // crosses mTextThreshold.
+ void ScanTextIn(nsIFrame* aFrame);
+
+ static const nsIFrame* FlowRootFor(const nsIFrame* aFrame) {
+ while (!aFrame->HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
+ aFrame = aFrame->GetParent();
+ }
+ return aFrame;
+ }
+
+ nsIFrame* mBFCFrame;
+ nscoord mUsableISize;
+ nscoord mTextAmount, mTextThreshold;
+ bool mInflationEnabled; // for this BFC
+ bool mTextDirty;
+};
+
+#endif /* !defined(nsFontInflationData_h_) */
diff --git a/layout/generic/nsFrameList.cpp b/layout/generic/nsFrameList.cpp
new file mode 100644
index 0000000000..dee80066ed
--- /dev/null
+++ b/layout/generic/nsFrameList.cpp
@@ -0,0 +1,487 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFrameList.h"
+
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/PresShell.h"
+#include "nsBidiPresUtils.h"
+#include "nsContainerFrame.h"
+#include "nsGkAtoms.h"
+#include "nsILineIterator.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace detail {
+const AlignedFrameListBytes gEmptyFrameListBytes = {0};
+} // namespace detail
+} // namespace mozilla
+
+void* nsFrameList::operator new(size_t sz, mozilla::PresShell* aPresShell) {
+ return aPresShell->AllocateByObjectID(eArenaObjectID_nsFrameList, sz);
+}
+
+void nsFrameList::Delete(mozilla::PresShell* aPresShell) {
+ MOZ_ASSERT(this != &EmptyList(), "Shouldn't Delete() this list");
+ NS_ASSERTION(IsEmpty(), "Shouldn't Delete() a non-empty list");
+
+ aPresShell->FreeByObjectID(eArenaObjectID_nsFrameList, this);
+}
+
+void nsFrameList::DestroyFrames(FrameDestroyContext& aContext) {
+ while (nsIFrame* frame = RemoveLastChild()) {
+ frame->Destroy(aContext);
+ }
+ MOZ_ASSERT(!mFirstChild && !mLastChild, "We should've destroyed all frames!");
+}
+
+void nsFrameList::RemoveFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "null ptr");
+#ifdef DEBUG_FRAME_LIST
+ // ContainsFrame is O(N)
+ MOZ_ASSERT(ContainsFrame(aFrame), "wrong list");
+#endif
+
+ nsIFrame* nextFrame = aFrame->GetNextSibling();
+ if (aFrame == mFirstChild) {
+ mFirstChild = nextFrame;
+ aFrame->SetNextSibling(nullptr);
+ if (!nextFrame) {
+ mLastChild = nullptr;
+ }
+ } else {
+ nsIFrame* prevSibling = aFrame->GetPrevSibling();
+ NS_ASSERTION(prevSibling && prevSibling->GetNextSibling() == aFrame,
+ "Broken frame linkage");
+ prevSibling->SetNextSibling(nextFrame);
+ aFrame->SetNextSibling(nullptr);
+ if (!nextFrame) {
+ mLastChild = prevSibling;
+ }
+ }
+}
+
+nsFrameList nsFrameList::TakeFramesAfter(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return std::move(*this);
+ }
+
+ MOZ_ASSERT(ContainsFrame(aFrame), "aFrame is not on this list!");
+
+ nsIFrame* newFirstChild = aFrame->GetNextSibling();
+ if (!newFirstChild) {
+ return nsFrameList();
+ }
+
+ nsIFrame* newLastChild = mLastChild;
+ mLastChild = aFrame;
+ mLastChild->SetNextSibling(nullptr);
+ return nsFrameList(newFirstChild, newLastChild);
+}
+
+nsIFrame* nsFrameList::RemoveFirstChild() {
+ if (mFirstChild) {
+ nsIFrame* firstChild = mFirstChild;
+ RemoveFrame(firstChild);
+ return firstChild;
+ }
+ return nullptr;
+}
+
+nsIFrame* nsFrameList::RemoveLastChild() {
+ if (mLastChild) {
+ nsIFrame* lastChild = mLastChild;
+ RemoveFrame(lastChild);
+ return lastChild;
+ }
+ return nullptr;
+}
+
+void nsFrameList::DestroyFrame(FrameDestroyContext& aContext,
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "null ptr");
+ RemoveFrame(aFrame);
+ aFrame->Destroy(aContext);
+}
+
+nsFrameList::Slice nsFrameList::InsertFrames(nsContainerFrame* aParent,
+ nsIFrame* aPrevSibling,
+ nsFrameList&& aFrameList) {
+ MOZ_ASSERT(aFrameList.NotEmpty(), "Unexpected empty list");
+
+ if (aParent) {
+ aFrameList.ApplySetParent(aParent);
+ }
+
+ NS_ASSERTION(IsEmpty() || FirstChild()->GetParent() ==
+ aFrameList.FirstChild()->GetParent(),
+ "frame to add has different parent");
+ NS_ASSERTION(!aPrevSibling || aPrevSibling->GetParent() ==
+ aFrameList.FirstChild()->GetParent(),
+ "prev sibling has different parent");
+#ifdef DEBUG_FRAME_LIST
+ // ContainsFrame is O(N)
+ NS_ASSERTION(!aPrevSibling || ContainsFrame(aPrevSibling),
+ "prev sibling is not on this list");
+#endif
+
+ nsIFrame* firstNewFrame = aFrameList.FirstChild();
+ nsIFrame* nextSibling;
+ if (aPrevSibling) {
+ nextSibling = aPrevSibling->GetNextSibling();
+ aPrevSibling->SetNextSibling(firstNewFrame);
+ } else {
+ nextSibling = mFirstChild;
+ mFirstChild = firstNewFrame;
+ }
+
+ nsIFrame* lastNewFrame = aFrameList.LastChild();
+ lastNewFrame->SetNextSibling(nextSibling);
+ if (!nextSibling) {
+ mLastChild = lastNewFrame;
+ }
+
+ VerifyList();
+
+ aFrameList.Clear();
+ return Slice(firstNewFrame, nextSibling);
+}
+
+nsFrameList nsFrameList::TakeFramesBefore(nsIFrame* aFrame) {
+ if (!aFrame) {
+ // We handed over the whole list.
+ return std::move(*this);
+ }
+
+ MOZ_ASSERT(ContainsFrame(aFrame), "aFrame is not on this list!");
+
+ if (aFrame == mFirstChild) {
+ // aFrame is our first child. Nothing to extract.
+ return nsFrameList();
+ }
+
+ // Extract all previous siblings of aFrame as a new list.
+ nsIFrame* prev = aFrame->GetPrevSibling();
+ nsIFrame* newFirstChild = mFirstChild;
+ nsIFrame* newLastChild = prev;
+
+ prev->SetNextSibling(nullptr);
+ mFirstChild = aFrame;
+
+ return nsFrameList(newFirstChild, newLastChild);
+}
+
+nsIFrame* nsFrameList::FrameAt(int32_t aIndex) const {
+ MOZ_ASSERT(aIndex >= 0, "invalid arg");
+ if (aIndex < 0) return nullptr;
+ nsIFrame* frame = mFirstChild;
+ while ((aIndex-- > 0) && frame) {
+ frame = frame->GetNextSibling();
+ }
+ return frame;
+}
+
+int32_t nsFrameList::IndexOf(nsIFrame* aFrame) const {
+ int32_t count = 0;
+ for (nsIFrame* f = mFirstChild; f; f = f->GetNextSibling()) {
+ if (f == aFrame) return count;
+ ++count;
+ }
+ return -1;
+}
+
+bool nsFrameList::ContainsFrame(const nsIFrame* aFrame) const {
+ MOZ_ASSERT(aFrame, "null ptr");
+
+ nsIFrame* frame = mFirstChild;
+ while (frame) {
+ if (frame == aFrame) {
+ return true;
+ }
+ frame = frame->GetNextSibling();
+ }
+ return false;
+}
+
+int32_t nsFrameList::GetLength() const {
+ int32_t count = 0;
+ nsIFrame* frame = mFirstChild;
+ while (frame) {
+ count++;
+ frame = frame->GetNextSibling();
+ }
+ return count;
+}
+
+void nsFrameList::ApplySetParent(nsContainerFrame* aParent) const {
+ NS_ASSERTION(aParent, "null ptr");
+
+ for (nsIFrame* f = FirstChild(); f; f = f->GetNextSibling()) {
+ f->SetParent(aParent);
+ }
+}
+
+/* static */
+void nsFrameList::UnhookFrameFromSiblings(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->GetPrevSibling() && aFrame->GetNextSibling());
+ nsIFrame* const nextSibling = aFrame->GetNextSibling();
+ nsIFrame* const prevSibling = aFrame->GetPrevSibling();
+ aFrame->SetNextSibling(nullptr);
+ prevSibling->SetNextSibling(nextSibling);
+ MOZ_ASSERT(!aFrame->GetPrevSibling() && !aFrame->GetNextSibling());
+}
+
+#ifdef DEBUG_FRAME_DUMP
+void nsFrameList::List(FILE* out) const {
+ fprintf_stderr(out, "<\n");
+ for (nsIFrame* frame = mFirstChild; frame; frame = frame->GetNextSibling()) {
+ frame->List(out, " ");
+ }
+ fprintf_stderr(out, ">\n");
+}
+#endif
+
+nsIFrame* nsFrameList::GetPrevVisualFor(nsIFrame* aFrame) const {
+ if (!mFirstChild) return nullptr;
+
+ nsIFrame* parent = mFirstChild->GetParent();
+ if (!parent) return aFrame ? aFrame->GetPrevSibling() : LastChild();
+
+ mozilla::intl::BidiDirection paraDir =
+ nsBidiPresUtils::ParagraphDirection(mFirstChild);
+
+ AutoAssertNoDomMutations guard;
+ nsILineIterator* iter = parent->GetLineIterator();
+ if (!iter) {
+ // Parent is not a block Frame
+ if (parent->IsLineFrame()) {
+ // Line frames are not bidi-splittable, so need to consider bidi
+ // reordering
+ if (paraDir == mozilla::intl::BidiDirection::LTR) {
+ return nsBidiPresUtils::GetFrameToLeftOf(aFrame, mFirstChild, -1);
+ } else { // RTL
+ return nsBidiPresUtils::GetFrameToRightOf(aFrame, mFirstChild, -1);
+ }
+ } else {
+ // Just get the next or prev sibling, depending on block and frame
+ // direction.
+ if (nsBidiPresUtils::IsFrameInParagraphDirection(mFirstChild)) {
+ return aFrame ? aFrame->GetPrevSibling() : LastChild();
+ } else {
+ return aFrame ? aFrame->GetNextSibling() : mFirstChild;
+ }
+ }
+ }
+
+ // Parent is a block frame, so use the LineIterator to find the previous
+ // visual sibling on this line, or the last one on the previous line.
+
+ int32_t thisLine;
+ if (aFrame) {
+ thisLine = iter->FindLineContaining(aFrame);
+ if (thisLine < 0) return nullptr;
+ } else {
+ thisLine = iter->GetNumLines();
+ }
+
+ nsIFrame* frame = nullptr;
+
+ if (aFrame) {
+ auto line = iter->GetLine(thisLine).unwrap();
+
+ if (paraDir == mozilla::intl::BidiDirection::LTR) {
+ frame = nsBidiPresUtils::GetFrameToLeftOf(aFrame, line.mFirstFrameOnLine,
+ line.mNumFramesOnLine);
+ } else { // RTL
+ frame = nsBidiPresUtils::GetFrameToRightOf(aFrame, line.mFirstFrameOnLine,
+ line.mNumFramesOnLine);
+ }
+ }
+
+ if (!frame && thisLine > 0) {
+ // Get the last frame of the previous line
+ auto line = iter->GetLine(thisLine - 1).unwrap();
+
+ if (paraDir == mozilla::intl::BidiDirection::LTR) {
+ frame = nsBidiPresUtils::GetFrameToLeftOf(nullptr, line.mFirstFrameOnLine,
+ line.mNumFramesOnLine);
+ } else { // RTL
+ frame = nsBidiPresUtils::GetFrameToRightOf(
+ nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine);
+ }
+ }
+ return frame;
+}
+
+nsIFrame* nsFrameList::GetNextVisualFor(nsIFrame* aFrame) const {
+ if (!mFirstChild) return nullptr;
+
+ nsIFrame* parent = mFirstChild->GetParent();
+ if (!parent) return aFrame ? aFrame->GetPrevSibling() : mFirstChild;
+
+ mozilla::intl::BidiDirection paraDir =
+ nsBidiPresUtils::ParagraphDirection(mFirstChild);
+
+ AutoAssertNoDomMutations guard;
+ nsILineIterator* iter = parent->GetLineIterator();
+ if (!iter) {
+ // Parent is not a block Frame
+ if (parent->IsLineFrame()) {
+ // Line frames are not bidi-splittable, so need to consider bidi
+ // reordering
+ if (paraDir == mozilla::intl::BidiDirection::LTR) {
+ return nsBidiPresUtils::GetFrameToRightOf(aFrame, mFirstChild, -1);
+ } else { // RTL
+ return nsBidiPresUtils::GetFrameToLeftOf(aFrame, mFirstChild, -1);
+ }
+ } else {
+ // Just get the next or prev sibling, depending on block and frame
+ // direction.
+ if (nsBidiPresUtils::IsFrameInParagraphDirection(mFirstChild)) {
+ return aFrame ? aFrame->GetNextSibling() : mFirstChild;
+ } else {
+ return aFrame ? aFrame->GetPrevSibling() : LastChild();
+ }
+ }
+ }
+
+ // Parent is a block frame, so use the LineIterator to find the next visual
+ // sibling on this line, or the first one on the next line.
+
+ int32_t thisLine;
+ if (aFrame) {
+ thisLine = iter->FindLineContaining(aFrame);
+ if (thisLine < 0) return nullptr;
+ } else {
+ thisLine = -1;
+ }
+
+ nsIFrame* frame = nullptr;
+
+ if (aFrame) {
+ auto line = iter->GetLine(thisLine).unwrap();
+
+ if (paraDir == mozilla::intl::BidiDirection::LTR) {
+ frame = nsBidiPresUtils::GetFrameToRightOf(aFrame, line.mFirstFrameOnLine,
+ line.mNumFramesOnLine);
+ } else { // RTL
+ frame = nsBidiPresUtils::GetFrameToLeftOf(aFrame, line.mFirstFrameOnLine,
+ line.mNumFramesOnLine);
+ }
+ }
+
+ int32_t numLines = iter->GetNumLines();
+ if (!frame && thisLine < numLines - 1) {
+ // Get the first frame of the next line
+ auto line = iter->GetLine(thisLine + 1).unwrap();
+
+ if (paraDir == mozilla::intl::BidiDirection::LTR) {
+ frame = nsBidiPresUtils::GetFrameToRightOf(
+ nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine);
+ } else { // RTL
+ frame = nsBidiPresUtils::GetFrameToLeftOf(nullptr, line.mFirstFrameOnLine,
+ line.mNumFramesOnLine);
+ }
+ }
+ return frame;
+}
+
+#ifdef DEBUG_FRAME_LIST
+void nsFrameList::VerifyList() const {
+ NS_ASSERTION((mFirstChild == nullptr) == (mLastChild == nullptr),
+ "bad list state");
+
+ if (IsEmpty()) {
+ return;
+ }
+
+ // Simple algorithm to find a loop in a linked list -- advance pointers
+ // through it at speeds of 1 and 2, and if they ever get to be equal bail
+ NS_ASSERTION(!mFirstChild->GetPrevSibling(), "bad prev sibling pointer");
+ nsIFrame *first = mFirstChild, *second = mFirstChild;
+ for (;;) {
+ first = first->GetNextSibling();
+ second = second->GetNextSibling();
+ if (!second) {
+ break;
+ }
+ NS_ASSERTION(second->GetPrevSibling()->GetNextSibling() == second,
+ "bad prev sibling pointer");
+ second = second->GetNextSibling();
+ if (first == second) {
+ // Loop detected! Since second advances faster, they can't both be null;
+ // we would have broken out of the loop long ago.
+ NS_ERROR("loop in frame list. This will probably hang soon.");
+ return;
+ }
+ if (!second) {
+ break;
+ }
+ NS_ASSERTION(second->GetPrevSibling()->GetNextSibling() == second,
+ "bad prev sibling pointer");
+ }
+
+ NS_ASSERTION(mLastChild == nsLayoutUtils::GetLastSibling(mFirstChild),
+ "bogus mLastChild");
+ // XXX we should also assert that all GetParent() are either null or
+ // the same non-null value, but nsCSSFrameConstructor::nsFrameItems
+ // prevents that, e.g. table captions.
+}
+#endif
+
+namespace mozilla {
+
+#ifdef DEBUG_FRAME_DUMP
+const char* ChildListName(FrameChildListID aListID) {
+ switch (aListID) {
+ case FrameChildListID::Principal:
+ return "";
+ case FrameChildListID::Popup:
+ return "PopupList";
+ case FrameChildListID::Caption:
+ return "CaptionList";
+ case FrameChildListID::ColGroup:
+ return "ColGroupList";
+ case FrameChildListID::Absolute:
+ return "AbsoluteList";
+ case FrameChildListID::Fixed:
+ return "FixedList";
+ case FrameChildListID::Overflow:
+ return "OverflowList";
+ case FrameChildListID::OverflowContainers:
+ return "OverflowContainersList";
+ case FrameChildListID::ExcessOverflowContainers:
+ return "ExcessOverflowContainersList";
+ case FrameChildListID::OverflowOutOfFlow:
+ return "OverflowOutOfFlowList";
+ case FrameChildListID::Float:
+ return "FloatList";
+ case FrameChildListID::Bullet:
+ return "BulletList";
+ case FrameChildListID::PushedFloats:
+ return "PushedFloatsList";
+ case FrameChildListID::Backdrop:
+ return "BackdropList";
+ case FrameChildListID::NoReflowPrincipal:
+ return "NoReflowPrincipalList";
+ }
+
+ MOZ_ASSERT_UNREACHABLE("unknown list");
+ return "UNKNOWN_FRAME_CHILD_LIST";
+}
+#endif
+
+AutoFrameListPtr::~AutoFrameListPtr() {
+ if (mFrameList) {
+ mFrameList->Delete(mPresContext->PresShell());
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/generic/nsFrameList.h b/layout/generic/nsFrameList.h
new file mode 100644
index 0000000000..d22248dc4b
--- /dev/null
+++ b/layout/generic/nsFrameList.h
@@ -0,0 +1,497 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFrameList_h___
+#define nsFrameList_h___
+
+#include <stdio.h> /* for FILE* */
+#include "nsDebug.h"
+#include "nsTArray.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/FunctionTypeTraits.h"
+#include "mozilla/RefPtr.h"
+
+#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING) || defined(MOZ_LAYOUT_DEBUGGER)
+// DEBUG_FRAME_DUMP enables nsIFrame::List and related methods.
+// You can also define this in a non-DEBUG build if you need frame dumps.
+# define DEBUG_FRAME_DUMP 1
+#endif
+
+class nsContainerFrame;
+class nsIContent;
+class nsIFrame;
+class nsPresContext;
+
+namespace mozilla {
+
+struct FrameDestroyContext;
+
+class PresShell;
+class FrameChildList;
+enum class FrameChildListID {
+ // The individual concrete child lists.
+ Principal,
+ Popup,
+ Caption,
+ ColGroup,
+ Absolute,
+ Fixed,
+ Overflow,
+ OverflowContainers,
+ ExcessOverflowContainers,
+ OverflowOutOfFlow,
+ Float,
+ Bullet,
+ PushedFloats,
+ Backdrop,
+ // A special alias for FrameChildListID::Principal that suppress the reflow
+ // request that is normally done when manipulating child lists.
+ NoReflowPrincipal,
+};
+
+} // namespace mozilla
+
+// Uncomment this to enable expensive frame-list integrity checking
+// #define DEBUG_FRAME_LIST
+
+/**
+ * A class for managing a list of frames.
+ */
+class nsFrameList {
+ // Next()/Prev() need to know about nsIFrame. To make them inline, their
+ // implementations are in nsIFrame.h.
+ struct ForwardFrameTraversal final {
+ static inline nsIFrame* Next(nsIFrame*);
+ static inline nsIFrame* Prev(nsIFrame*);
+ };
+ struct BackwardFrameTraversal final {
+ static inline nsIFrame* Next(nsIFrame*);
+ static inline nsIFrame* Prev(nsIFrame*);
+ };
+
+ public:
+ template <typename FrameTraversal>
+ class Iterator;
+ class Slice;
+
+ using iterator = Iterator<ForwardFrameTraversal>;
+ using const_iterator = Iterator<ForwardFrameTraversal>;
+ using reverse_iterator = Iterator<BackwardFrameTraversal>;
+ using const_reverse_iterator = Iterator<BackwardFrameTraversal>;
+
+ nsFrameList() : mFirstChild(nullptr), mLastChild(nullptr) {}
+
+ nsFrameList(nsIFrame* aFirstFrame, nsIFrame* aLastFrame)
+ : mFirstChild(aFirstFrame), mLastChild(aLastFrame) {
+ VerifyList();
+ }
+
+ // nsFrameList is a move-only class by default. Use Clone() if you really want
+ // a copy of this list.
+ nsFrameList(const nsFrameList& aOther) = delete;
+ nsFrameList& operator=(const nsFrameList& aOther) = delete;
+ nsFrameList Clone() const { return nsFrameList(mFirstChild, mLastChild); }
+
+ /**
+ * Transfer frames in aOther to this list. aOther becomes empty after this
+ * operation.
+ */
+ nsFrameList(nsFrameList&& aOther)
+ : mFirstChild(aOther.mFirstChild), mLastChild(aOther.mLastChild) {
+ aOther.Clear();
+ VerifyList();
+ }
+ nsFrameList& operator=(nsFrameList&& aOther) {
+ if (this != &aOther) {
+ MOZ_ASSERT(IsEmpty(), "Assigning to a non-empty list will lose frames!");
+ mFirstChild = aOther.FirstChild();
+ mLastChild = aOther.LastChild();
+ aOther.Clear();
+ }
+ return *this;
+ }
+
+ /**
+ * Infallibly allocate a nsFrameList from the shell arena.
+ */
+ void* operator new(size_t sz, mozilla::PresShell* aPresShell);
+
+ /**
+ * Deallocate this list that was allocated from the shell arena.
+ * The list is required to be empty.
+ */
+ void Delete(mozilla::PresShell* aPresShell);
+
+ /**
+ * For each frame in this list: remove it from the list then call
+ * Destroy() on it with the passed context as an argument.
+ */
+ void DestroyFrames(mozilla::FrameDestroyContext&);
+
+ void Clear() { mFirstChild = mLastChild = nullptr; }
+
+ /**
+ * Append aFrameList to this list. If aParent is not null,
+ * reparents the newly added frames. Clears out aFrameList and
+ * returns a list slice represening the newly-appended frames.
+ */
+ Slice AppendFrames(nsContainerFrame* aParent, nsFrameList&& aFrameList) {
+ return InsertFrames(aParent, LastChild(), std::move(aFrameList));
+ }
+
+ /**
+ * Append aFrame to this list. If aParent is not null,
+ * reparents the newly added frame.
+ */
+ void AppendFrame(nsContainerFrame* aParent, nsIFrame* aFrame) {
+ AppendFrames(aParent, nsFrameList(aFrame, aFrame));
+ }
+
+ /**
+ * Take aFrame out of the frame list. This also disconnects aFrame
+ * from the sibling list. The frame must be non-null and present on
+ * this list.
+ */
+ void RemoveFrame(nsIFrame* aFrame);
+
+ /**
+ * Take all the frames before aFrame out of the frame list; aFrame and all the
+ * frames after it stay in this list. If aFrame is nullptr, remove the entire
+ * frame list.
+ * @param aFrame a frame in this frame list, or nullptr.
+ * @return the removed frames, if any.
+ */
+ [[nodiscard]] nsFrameList TakeFramesBefore(nsIFrame* aFrame);
+
+ /**
+ * Take all the frames after aFrame out of the frame list; aFrame and all the
+ * frames before it stay in this list. If aFrame is nullptr, removes the
+ * entire list.
+ * @param aFrame a frame in this list, or nullptr.
+ * @return the removed frames, if any.
+ */
+ [[nodiscard]] nsFrameList TakeFramesAfter(nsIFrame* aFrame);
+
+ /**
+ * Take the first (or last) child (if any) out of the frame list.
+ * @return the first (or last) child, or nullptr if the list is empty
+ */
+ nsIFrame* RemoveFirstChild();
+ nsIFrame* RemoveLastChild();
+
+ /**
+ * The following two functions are intended to be used in concert for
+ * removing a frame from its frame list when the set of possible frame
+ * lists is known in advance, but the exact frame list is unknown.
+ * aFrame must be non-null.
+ * Example use:
+ * bool removed = frameList1.StartRemoveFrame(aFrame) ||
+ * frameList2.ContinueRemoveFrame(aFrame) ||
+ * frameList3.ContinueRemoveFrame(aFrame);
+ * MOZ_ASSERT(removed);
+ *
+ * @note One of the frame lists MUST contain aFrame, if it's on some other
+ * frame list then the example above will likely lead to crashes.
+ * This function is O(1).
+ * @return true iff aFrame was removed from /some/ list, not necessarily
+ * this one. If it was removed from a different list then it is
+ * guaranteed that that list is still non-empty.
+ * (this method is implemented in nsIFrame.h to be able to inline)
+ */
+ inline bool StartRemoveFrame(nsIFrame* aFrame);
+
+ /**
+ * Precondition: StartRemoveFrame MUST be called before this.
+ * This function is O(1).
+ * @see StartRemoveFrame
+ * @return true iff aFrame was removed from this list
+ * (this method is implemented in nsIFrame.h to be able to inline)
+ */
+ inline bool ContinueRemoveFrame(nsIFrame* aFrame);
+
+ /**
+ * Take a frame out of the frame list and then destroy it.
+ * The frame must be non-null and present on this list.
+ */
+ void DestroyFrame(mozilla::FrameDestroyContext&, nsIFrame*);
+
+ /**
+ * Insert aFrame right after aPrevSibling, or prepend it to this
+ * list if aPrevSibling is null. If aParent is not null, also
+ * reparents newly-added frame. Note that this method always
+ * sets the frame's nextSibling pointer.
+ */
+ void InsertFrame(nsContainerFrame* aParent, nsIFrame* aPrevSibling,
+ nsIFrame* aFrame) {
+ InsertFrames(aParent, aPrevSibling, nsFrameList(aFrame, aFrame));
+ }
+
+ /**
+ * Inserts aFrameList into this list after aPrevSibling (at the beginning if
+ * aPrevSibling is null). If aParent is not null, reparents the newly added
+ * frames. Clears out aFrameList and returns a list slice representing the
+ * newly-inserted frames.
+ */
+ Slice InsertFrames(nsContainerFrame* aParent, nsIFrame* aPrevSibling,
+ nsFrameList&& aFrameList);
+
+ /**
+ * Split this list just before the first frame that matches aPredicate,
+ * and return a nsFrameList containing all the frames before it. The
+ * matched frame and all frames after it stay in this list. If no matched
+ * frame exists, all the frames are drained into the returned list, and
+ * this list ends up empty.
+ *
+ * aPredicate should be of this function signature: bool(nsIFrame*).
+ */
+ template <typename Predicate>
+ nsFrameList Split(Predicate&& aPredicate) {
+ static_assert(
+ std::is_same<
+ typename mozilla::FunctionTypeTraits<Predicate>::ReturnType,
+ bool>::value &&
+ mozilla::FunctionTypeTraits<Predicate>::arity == 1 &&
+ std::is_same<typename mozilla::FunctionTypeTraits<
+ Predicate>::template ParameterType<0>,
+ nsIFrame*>::value,
+ "aPredicate should be of this function signature: bool(nsIFrame*)");
+
+ for (nsIFrame* f : *this) {
+ if (aPredicate(f)) {
+ return TakeFramesBefore(f);
+ }
+ }
+ return std::move(*this);
+ }
+
+ nsIFrame* FirstChild() const { return mFirstChild; }
+
+ nsIFrame* LastChild() const { return mLastChild; }
+
+ nsIFrame* FrameAt(int32_t aIndex) const;
+ int32_t IndexOf(nsIFrame* aFrame) const;
+
+ bool IsEmpty() const { return nullptr == mFirstChild; }
+
+ bool NotEmpty() const { return nullptr != mFirstChild; }
+
+ /**
+ * Return true if aFrame is on this list.
+ * @note this method has O(n) time complexity over the length of the list
+ * XXXmats: ideally, we should make this function #ifdef DEBUG
+ */
+ bool ContainsFrame(const nsIFrame* aFrame) const;
+
+ /**
+ * Get the number of frames in this list. Note that currently the
+ * implementation has O(n) time complexity. Do not call it repeatedly in hot
+ * code.
+ * XXXmats: ideally, we should make this function #ifdef DEBUG
+ */
+ int32_t GetLength() const;
+
+ /**
+ * If this frame list has only one frame, return that frame.
+ * Otherwise, return null.
+ */
+ nsIFrame* OnlyChild() const {
+ if (FirstChild() == LastChild()) {
+ return FirstChild();
+ }
+ return nullptr;
+ }
+
+ /**
+ * Call SetParent(aParent) for each frame in this list.
+ * @param aParent the new parent frame, must be non-null
+ */
+ void ApplySetParent(nsContainerFrame* aParent) const;
+
+ /**
+ * If this frame list is non-empty then append it to aLists as the
+ * aListID child list.
+ */
+ inline void AppendIfNonempty(nsTArray<mozilla::FrameChildList>* aLists,
+ mozilla::FrameChildListID aListID) const {
+ if (NotEmpty()) {
+ aLists->EmplaceBack(*this, aListID);
+ }
+ }
+
+ /**
+ * Return the frame before this frame in visual order (after Bidi reordering).
+ * If aFrame is null, return the last frame in visual order.
+ */
+ nsIFrame* GetPrevVisualFor(nsIFrame* aFrame) const;
+
+ /**
+ * Return the frame after this frame in visual order (after Bidi reordering).
+ * If aFrame is null, return the first frame in visual order.
+ */
+ nsIFrame* GetNextVisualFor(nsIFrame* aFrame) const;
+
+#ifdef DEBUG_FRAME_DUMP
+ void List(FILE* out) const;
+#endif
+
+ static inline const nsFrameList& EmptyList();
+
+ /**
+ * A class representing a slice of a frame list.
+ */
+ class Slice {
+ public:
+ // Implicit on purpose, so that we can easily create Slice from nsFrameList
+ // via this impicit constructor.
+ MOZ_IMPLICIT Slice(const nsFrameList& aList)
+ : mStart(aList.FirstChild()), mEnd(nullptr) {}
+ Slice(nsIFrame* aStart, nsIFrame* aEnd) : mStart(aStart), mEnd(aEnd) {}
+
+ iterator begin() const { return iterator(mStart); }
+ const_iterator cbegin() const { return begin(); }
+ iterator end() const { return iterator(mEnd); }
+ const_iterator cend() const { return end(); }
+
+ private:
+ // Our starting frame.
+ nsIFrame* const mStart;
+
+ // The first frame that is NOT in the slice. May be null.
+ nsIFrame* const mEnd;
+ };
+
+ template <typename FrameTraversal>
+ class Iterator final {
+ public:
+ // It is disputable whether these type definitions are correct, since
+ // operator* doesn't return a reference at all. Also, the iterator_category
+ // can be at most std::input_iterator_tag (rather than
+ // std::bidrectional_iterator_tag, as it might seem), because it is a
+ // stashing iterator. See also, e.g.,
+ // https://stackoverflow.com/questions/50909701/what-should-be-iterator-category-for-a-stashing-iterator
+ using value_type = nsIFrame* const;
+ using pointer = value_type*;
+ using reference = value_type&;
+ using difference_type = ptrdiff_t;
+ using iterator_category = std::input_iterator_tag;
+
+ explicit constexpr Iterator(nsIFrame* aCurrent) : mCurrent(aCurrent) {}
+
+ nsIFrame* operator*() const { return mCurrent; }
+
+ Iterator& operator++() {
+ mCurrent = FrameTraversal::Next(mCurrent);
+ return *this;
+ }
+ Iterator& operator--() {
+ mCurrent = FrameTraversal::Prev(mCurrent);
+ return *this;
+ }
+
+ Iterator operator++(int) {
+ auto ret = *this;
+ ++*this;
+ return ret;
+ }
+ Iterator operator--(int) {
+ auto ret = *this;
+ --*this;
+ return ret;
+ }
+
+ bool operator==(const Iterator<FrameTraversal>& aOther) const {
+ return mCurrent == aOther.mCurrent;
+ }
+
+ bool operator!=(const Iterator<FrameTraversal>& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ nsIFrame* mCurrent;
+ };
+
+ iterator begin() const { return iterator(mFirstChild); }
+ const_iterator cbegin() const { return begin(); }
+ iterator end() const { return iterator(nullptr); }
+ const_iterator cend() const { return end(); }
+ reverse_iterator rbegin() const { return reverse_iterator(mLastChild); }
+ const_reverse_iterator crbegin() const { return rbegin(); }
+ reverse_iterator rend() const { return reverse_iterator(nullptr); }
+ const_reverse_iterator crend() const { return rend(); }
+
+ private:
+ void operator delete(void*) = delete;
+
+#ifdef DEBUG_FRAME_LIST
+ void VerifyList() const;
+#else
+ void VerifyList() const {}
+#endif
+
+ protected:
+ /**
+ * Disconnect aFrame from its siblings. This must only be called if aFrame
+ * is NOT the first or last sibling, because otherwise its nsFrameList will
+ * have a stale mFirst/LastChild pointer. This precondition is asserted.
+ * This function is O(1).
+ */
+ static void UnhookFrameFromSiblings(nsIFrame* aFrame);
+
+ nsIFrame* mFirstChild;
+ nsIFrame* mLastChild;
+};
+
+namespace mozilla {
+
+#ifdef DEBUG_FRAME_DUMP
+extern const char* ChildListName(FrameChildListID aListID);
+#endif
+
+using FrameChildListIDs = EnumSet<FrameChildListID>;
+
+class FrameChildList {
+ public:
+ FrameChildList(const nsFrameList& aList, FrameChildListID aID)
+ : mList(aList.Clone()), mID(aID) {}
+ nsFrameList mList;
+ FrameChildListID mID;
+};
+
+/**
+ * Simple "auto_ptr" for nsFrameLists allocated from the shell arena.
+ * The frame list given to the constructor will be deallocated (if non-null)
+ * in the destructor. The frame list must then be empty.
+ */
+class MOZ_RAII AutoFrameListPtr final {
+ public:
+ AutoFrameListPtr(nsPresContext* aPresContext, nsFrameList* aFrameList)
+ : mPresContext(aPresContext), mFrameList(aFrameList) {}
+ ~AutoFrameListPtr();
+ operator nsFrameList*() const { return mFrameList; }
+ nsFrameList* operator->() const { return mFrameList; }
+
+ private:
+ nsPresContext* mPresContext;
+ nsFrameList* mFrameList;
+};
+
+namespace detail {
+union AlignedFrameListBytes {
+ void* ptr;
+ char bytes[sizeof(nsFrameList)];
+};
+extern const AlignedFrameListBytes gEmptyFrameListBytes;
+} // namespace detail
+
+} // namespace mozilla
+
+/* static */ inline const nsFrameList& nsFrameList::EmptyList() {
+ return *reinterpret_cast<const nsFrameList*>(
+ &mozilla::detail::gEmptyFrameListBytes);
+}
+
+#endif /* nsFrameList_h___ */
diff --git a/layout/generic/nsFrameSelection.cpp b/layout/generic/nsFrameSelection.cpp
new file mode 100644
index 0000000000..002d26c4fa
--- /dev/null
+++ b/layout/generic/nsFrameSelection.cpp
@@ -0,0 +1,3113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of nsFrameSelection
+ */
+
+#include "nsFrameSelection.h"
+
+#include "ErrorList.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_bidi.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsFrameTraversal.h"
+#include "nsString.h"
+#include "nsISelectionListener.h"
+#include "nsContentCID.h"
+#include "nsDeviceContext.h"
+#include "nsIContent.h"
+#include "nsRange.h"
+#include "nsITableCellLayout.h"
+#include "nsTArray.h"
+#include "nsTableWrapperFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsTextFragment.h"
+#include <algorithm>
+#include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
+
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsLayoutCID.h"
+#include "nsBidiPresUtils.h"
+#include "nsTextFrame.h"
+
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+
+#include "mozilla/PresShell.h"
+#include "nsPresContext.h"
+#include "nsCaret.h"
+
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+
+// notifications
+#include "mozilla/dom/Document.h"
+
+#include "nsISelectionController.h" //for the enums
+#include "nsCopySupport.h"
+#include "nsIClipboard.h"
+#include "nsIFrameInlines.h"
+
+#include "nsError.h"
+#include "mozilla/AutoCopyListener.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Highlight.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/StaticRange.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/SelectionBinding.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsFocusManager.h"
+#include "nsPIDOMWindow.h"
+
+#include "SelectionMovementUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static LazyLogModule sFrameSelectionLog("FrameSelection");
+
+// #define DEBUG_TABLE 1
+
+/**
+ * Add cells to the selection inside of the given cells range.
+ *
+ * @param aTable [in] HTML table element
+ * @param aStartRowIndex [in] row index where the cells range starts
+ * @param aStartColumnIndex [in] column index where the cells range starts
+ * @param aEndRowIndex [in] row index where the cells range ends
+ * @param aEndColumnIndex [in] column index where the cells range ends
+ */
+static nsresult AddCellsToSelection(const nsIContent* aTableContent,
+ int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex,
+ Selection& aNormalSelection);
+
+static nsAtom* GetTag(nsINode* aNode);
+
+static nsINode* GetClosestInclusiveTableCellAncestor(nsINode* aDomNode);
+MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult CreateAndAddRange(
+ nsINode* aContainer, int32_t aOffset, Selection& aNormalSelection);
+static nsresult SelectCellElement(nsIContent* aCellElement,
+ Selection& aNormalSelection);
+
+#ifdef XP_MACOSX
+static nsresult UpdateSelectionCacheOnRepaintSelection(Selection* aSel);
+#endif // XP_MACOSX
+
+#ifdef PRINT_RANGE
+static void printRange(nsRange* aDomRange);
+# define DEBUG_OUT_RANGE(x) printRange(x)
+#else
+# define DEBUG_OUT_RANGE(x)
+#endif // PRINT_RANGE
+
+/******************************************************************************
+ * mozilla::PeekOffsetStruct
+ ******************************************************************************/
+
+// #define DEBUG_SELECTION // uncomment for printf describing every collapse and
+// extend. #define DEBUG_NAVIGATION
+
+// #define DEBUG_TABLE_SELECTION 1
+
+namespace mozilla {
+
+PeekOffsetStruct::PeekOffsetStruct(nsSelectionAmount aAmount,
+ nsDirection aDirection, int32_t aStartOffset,
+ nsPoint aDesiredCaretPos,
+ const PeekOffsetOptions aOptions,
+ EWordMovementType aWordMovementType)
+ : mAmount(aAmount),
+ mDirection(aDirection),
+ mStartOffset(aStartOffset),
+ mDesiredCaretPos(aDesiredCaretPos),
+ mWordMovementType(aWordMovementType),
+ mOptions(aOptions),
+ mResultFrame(nullptr),
+ mContentOffset(0),
+ mAttach(CaretAssociationHint::Before) {}
+
+} // namespace mozilla
+
+// Array which contains index of each SelecionType in Selection::mDOMSelections.
+// For avoiding using if nor switch to retrieve the index, this needs to have
+// -1 for SelectionTypes which won't be created its Selection instance.
+static const int8_t kIndexOfSelections[] = {
+ -1, // SelectionType::eInvalid
+ -1, // SelectionType::eNone
+ 0, // SelectionType::eNormal
+ 1, // SelectionType::eSpellCheck
+ 2, // SelectionType::eIMERawClause
+ 3, // SelectionType::eIMESelectedRawClause
+ 4, // SelectionType::eIMEConvertedClause
+ 5, // SelectionType::eIMESelectedClause
+ 6, // SelectionType::eAccessibility
+ 7, // SelectionType::eFind
+ 8, // SelectionType::eURLSecondary
+ 9, // SelectionType::eURLStrikeout
+ -1, // SelectionType::eHighlight
+};
+
+inline int8_t GetIndexFromSelectionType(SelectionType aSelectionType) {
+ // The enum value of eInvalid is -1 and the others are sequential value
+ // starting from 0. Therefore, |SelectionType + 1| is the index of
+ // kIndexOfSelections.
+ return kIndexOfSelections[static_cast<int8_t>(aSelectionType) + 1];
+}
+
+/*
+The limiter is used specifically for the text areas and textfields
+In that case it is the DIV tag that is anonymously created for the text
+areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
+BR node the limiter will be the parent and the offset will point before or
+after the BR node. In the case of the text node the parent content is
+the text node itself and the offset will be the exact character position.
+The offset is not important to check for validity. Simply look at the
+passed in content. If it equals the limiter then the selection point is valid.
+If its parent it the limiter then the point is also valid. In the case of
+NO limiter all points are valid since you are in a topmost iframe. (browser
+or composer)
+*/
+bool nsFrameSelection::IsValidSelectionPoint(nsINode* aNode) const {
+ if (!aNode) {
+ return false;
+ }
+
+ nsIContent* limiter = GetLimiter();
+ if (limiter && limiter != aNode && limiter != aNode->GetParent()) {
+ // if newfocus == the limiter. that's ok. but if not there and not parent
+ // bad
+ return false; // not in the right content. tLimiter said so
+ }
+
+ limiter = GetAncestorLimiter();
+ return !limiter || aNode->IsInclusiveDescendantOf(limiter);
+}
+
+namespace mozilla {
+struct MOZ_RAII AutoPrepareFocusRange {
+ AutoPrepareFocusRange(Selection* aSelection,
+ const bool aMultiRangeSelection) {
+ MOZ_ASSERT(aSelection);
+ MOZ_ASSERT(aSelection->GetType() == SelectionType::eNormal);
+
+ if (aSelection->mStyledRanges.mRanges.Length() <= 1) {
+ return;
+ }
+
+ if (aSelection->mFrameSelection->IsUserSelectionReason()) {
+ mUserSelect.emplace(aSelection);
+ }
+
+ nsTArray<StyledRange>& ranges = aSelection->mStyledRanges.mRanges;
+ if (!aSelection->mUserInitiated || aMultiRangeSelection) {
+ // Scripted command or the user is starting a new explicit multi-range
+ // selection.
+ for (StyledRange& entry : ranges) {
+ MOZ_ASSERT(entry.mRange->IsDynamicRange());
+ entry.mRange->AsDynamicRange()->SetIsGenerated(false);
+ }
+ return;
+ }
+
+ if (!IsAnchorRelativeOperation(
+ aSelection->mFrameSelection->mSelectionChangeReasons)) {
+ return;
+ }
+
+ // This operation is against the anchor but our current mAnchorFocusRange
+ // represents the focus in a multi-range selection. The anchor from a user
+ // perspective is the most distant generated range on the opposite side.
+ // Find that range and make it the mAnchorFocusRange.
+ nsRange* const newAnchorFocusRange =
+ FindGeneratedRangeMostDistantFromAnchor(*aSelection);
+
+ if (!newAnchorFocusRange) {
+ // There are no generated ranges - that's fine.
+ return;
+ }
+
+ // Setup the new mAnchorFocusRange and mark the old one as generated.
+ if (aSelection->mAnchorFocusRange) {
+ aSelection->mAnchorFocusRange->SetIsGenerated(true);
+ }
+
+ newAnchorFocusRange->SetIsGenerated(false);
+ aSelection->mAnchorFocusRange = newAnchorFocusRange;
+
+ RemoveGeneratedRanges(*aSelection);
+
+ if (aSelection->mFrameSelection) {
+ aSelection->mFrameSelection->InvalidateDesiredCaretPos();
+ }
+ }
+
+ private:
+ static nsRange* FindGeneratedRangeMostDistantFromAnchor(
+ const Selection& aSelection) {
+ const nsTArray<StyledRange>& ranges = aSelection.mStyledRanges.mRanges;
+ const size_t len = ranges.Length();
+ nsRange* result{nullptr};
+ if (aSelection.GetDirection() == eDirNext) {
+ for (size_t i = 0; i < len; ++i) {
+ // This function is only called for selections with type == eNormal.
+ // (see MOZ_ASSERT in constructor).
+ // Therefore, all ranges must be dynamic.
+ if (ranges[i].mRange->AsDynamicRange()->IsGenerated()) {
+ result = ranges[i].mRange->AsDynamicRange();
+ break;
+ }
+ }
+ } else {
+ size_t i = len;
+ while (i--) {
+ if (ranges[i].mRange->AsDynamicRange()->IsGenerated()) {
+ result = ranges[i].mRange->AsDynamicRange();
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static void RemoveGeneratedRanges(Selection& aSelection) {
+ RefPtr<nsPresContext> presContext = aSelection.GetPresContext();
+ nsTArray<StyledRange>& ranges = aSelection.mStyledRanges.mRanges;
+ size_t i = ranges.Length();
+ while (i--) {
+ // This function is only called for selections with type == eNormal.
+ // (see MOZ_ASSERT in constructor).
+ // Therefore, all ranges must be dynamic.
+ if (!ranges[i].mRange->IsDynamicRange()) {
+ continue;
+ }
+ nsRange* range = ranges[i].mRange->AsDynamicRange();
+ if (range->IsGenerated()) {
+ range->UnregisterSelection(aSelection);
+ aSelection.SelectFrames(presContext, *range, false);
+ ranges.RemoveElementAt(i);
+ }
+ }
+ }
+
+ /**
+ * @aParam aSelectionChangeReasons can be multiple of the reasons defined in
+ nsISelectionListener.idl.
+ */
+ static bool IsAnchorRelativeOperation(const int16_t aSelectionChangeReasons) {
+ return aSelectionChangeReasons &
+ (nsISelectionListener::DRAG_REASON |
+ nsISelectionListener::MOUSEDOWN_REASON |
+ nsISelectionListener::MOUSEUP_REASON |
+ nsISelectionListener::COLLAPSETOSTART_REASON);
+ }
+
+ Maybe<Selection::AutoUserInitiated> mUserSelect;
+};
+
+} // namespace mozilla
+
+////////////BEGIN nsFrameSelection methods
+
+template Result<RefPtr<nsRange>, nsresult>
+nsFrameSelection::CreateRangeExtendedToSomewhere(
+ nsDirection aDirection, const nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle);
+template Result<RefPtr<StaticRange>, nsresult>
+nsFrameSelection::CreateRangeExtendedToSomewhere(
+ nsDirection aDirection, const nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle);
+
+nsFrameSelection::nsFrameSelection(PresShell* aPresShell, nsIContent* aLimiter,
+ const bool aAccessibleCaretEnabled) {
+ for (size_t i = 0; i < ArrayLength(mDomSelections); i++) {
+ mDomSelections[i] = new Selection(kPresentSelectionTypes[i], this);
+ }
+
+#ifdef XP_MACOSX
+ // On macOS, cache the current selection to send to service menu of macOS.
+ bool enableAutoCopy = true;
+ AutoCopyListener::Init(nsIClipboard::kSelectionCache);
+#else // #ifdef XP_MACOSX
+ // Check to see if the auto-copy pref is enabled and make the normal
+ // Selection notifies auto-copy listener of its changes.
+ bool enableAutoCopy = AutoCopyListener::IsPrefEnabled();
+ if (enableAutoCopy) {
+ AutoCopyListener::Init(nsIClipboard::kSelectionClipboard);
+ }
+#endif // #ifdef XP_MACOSX #else
+
+ if (enableAutoCopy) {
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ if (mDomSelections[index]) {
+ mDomSelections[index]->NotifyAutoCopy();
+ }
+ }
+
+ mPresShell = aPresShell;
+ mDragState = false;
+ mLimiters.mLimiter = aLimiter;
+
+ // This should only ever be initialized on the main thread, so we are OK here.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+
+ mAccessibleCaretEnabled = aAccessibleCaretEnabled;
+ if (mAccessibleCaretEnabled) {
+ mDomSelections[index]->MaybeNotifyAccessibleCaretEventHub(aPresShell);
+ }
+
+ if (mDomSelections[index]) {
+ mDomSelections[index]->EnableSelectionChangeEvent();
+ }
+}
+
+nsFrameSelection::~nsFrameSelection() = default;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)
+ for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) {
+ tmp->mDomSelections[i] = nullptr;
+ }
+ tmp->mHighlightSelections.Clear();
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(
+ mTableSelection.mClosestInclusiveTableCellAncestor)
+ tmp->mTableSelection.mMode = TableSelectionMode::None;
+ tmp->mTableSelection.mDragSelectingCells = false;
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mStartSelectedCell)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mEndSelectedCell)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mAppendStartSelectedCell)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mUnselectCellOnMouseUp)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainedRange.mRange)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters.mLimiter)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters.mAncestorLimiter)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)
+ if (tmp->mPresShell && tmp->mPresShell->GetDocument() &&
+ nsCCUncollectableMarker::InGeneration(
+ cb, tmp->mPresShell->GetDocument()->GetMarkedCCGeneration())) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+ for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i])
+ }
+
+ for (const auto& value : tmp->mHighlightSelections) {
+ CycleCollectionNoteChild(cb, value.second().get(),
+ "mHighlightSelections[]");
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
+ mTableSelection.mClosestInclusiveTableCellAncestor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mStartSelectedCell)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mEndSelectedCell)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mAppendStartSelectedCell)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mUnselectCellOnMouseUp)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainedRange.mRange)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters.mLimiter)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters.mAncestorLimiter)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+bool nsFrameSelection::Caret::IsVisualMovement(
+ bool aContinueSelection, CaretMovementStyle aMovementStyle) const {
+ int32_t movementFlag = StaticPrefs::bidi_edit_caret_movement_style();
+ return aMovementStyle == eVisual ||
+ (aMovementStyle == eUsePrefStyle &&
+ (movementFlag == 1 || (movementFlag == 2 && !aContinueSelection)));
+}
+
+// Get the x (or y, in vertical writing mode) position requested
+// by the Key Handling for line-up/down
+nsresult nsFrameSelection::DesiredCaretPos::FetchPos(
+ nsPoint& aDesiredCaretPos, const PresShell& aPresShell,
+ Selection& aNormalSelection) const {
+ MOZ_ASSERT(aNormalSelection.GetType() == SelectionType::eNormal);
+
+ if (mIsSet) {
+ aDesiredCaretPos = mValue;
+ return NS_OK;
+ }
+
+ RefPtr<nsCaret> caret = aPresShell.GetCaret();
+ if (!caret) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ caret->SetSelection(&aNormalSelection);
+
+ nsRect coord;
+ nsIFrame* caretFrame = caret->GetGeometry(&coord);
+ if (!caretFrame) {
+ return NS_ERROR_FAILURE;
+ }
+ nsPoint viewOffset(0, 0);
+ nsView* view = nullptr;
+ caretFrame->GetOffsetFromView(viewOffset, &view);
+ if (view) {
+ coord += viewOffset;
+ }
+ aDesiredCaretPos = coord.TopLeft();
+ return NS_OK;
+}
+
+void nsFrameSelection::InvalidateDesiredCaretPos() // do not listen to
+ // mDesiredCaretPos.mValue;
+ // you must get another.
+{
+ mDesiredCaretPos.Invalidate();
+}
+
+void nsFrameSelection::DesiredCaretPos::Invalidate() { mIsSet = false; }
+
+void nsFrameSelection::DesiredCaretPos::Set(const nsPoint& aPos) {
+ mValue = aPos;
+ mIsSet = true;
+}
+
+nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
+ nsIFrame* aFrame, const nsPoint& aPoint, nsIFrame** aRetFrame,
+ nsPoint& aRetPoint) const {
+ //
+ // The whole point of this method is to return a frame and point that
+ // that lie within the same valid subtree as the anchor node's frame,
+ // for use with the method GetContentAndOffsetsFromPoint().
+ //
+ // A valid subtree is defined to be one where all the content nodes in
+ // the tree have a valid parent-child relationship.
+ //
+ // If the anchor frame and aFrame are in the same subtree, aFrame will
+ // be returned in aRetFrame. If they are in different subtrees, we
+ // return the frame for the root of the subtree.
+ //
+
+ if (!aFrame || !aRetFrame) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ *aRetFrame = aFrame;
+ aRetPoint = aPoint;
+
+ //
+ // Get the frame and content for the selection's anchor point!
+ //
+
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ if (!mDomSelections[index]) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsIContent> anchorContent =
+ do_QueryInterface(mDomSelections[index]->GetAnchorNode());
+ if (!anchorContent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ //
+ // Now find the root of the subtree containing the anchor's content.
+ //
+
+ NS_ENSURE_STATE(mPresShell);
+ RefPtr<PresShell> presShell = mPresShell;
+ nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(presShell);
+ NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED);
+
+ //
+ // Now find the root of the subtree containing aFrame's content.
+ //
+
+ nsCOMPtr<nsIContent> content = aFrame->GetContent();
+
+ if (content) {
+ nsIContent* contentRoot = content->GetSelectionRootContent(presShell);
+ NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
+
+ if (anchorRoot == contentRoot) {
+ // If the aFrame's content isn't the capturing content, it should be
+ // a descendant. At this time, we can return simply.
+ nsIContent* capturedContent = PresShell::GetCapturingContent();
+ if (capturedContent != content) {
+ return NS_OK;
+ }
+
+ // Find the frame under the mouse cursor with the root frame.
+ // At this time, don't use the anchor's frame because it may not have
+ // fixed positioned frames.
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame);
+ nsIFrame* cursorFrame =
+ nsLayoutUtils::GetFrameForPoint(RelativeTo{rootFrame}, ptInRoot);
+
+ // If the mouse cursor in on a frame which is descendant of same
+ // selection root, we can expand the selection to the frame.
+ if (cursorFrame && cursorFrame->PresShell() == presShell) {
+ nsCOMPtr<nsIContent> cursorContent = cursorFrame->GetContent();
+ NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE);
+ nsIContent* cursorContentRoot =
+ cursorContent->GetSelectionRootContent(presShell);
+ NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED);
+ if (cursorContentRoot == anchorRoot) {
+ *aRetFrame = cursorFrame;
+ aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame);
+ return NS_OK;
+ }
+ }
+ // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
+ // cursor is out of the window), we should use the frame of the anchor
+ // root.
+ }
+ }
+
+ //
+ // When we can't find a frame which is under the mouse cursor and has a same
+ // selection root as the anchor node's, we should return the selection root
+ // frame.
+ //
+
+ *aRetFrame = anchorRoot->GetPrimaryFrame();
+
+ if (!*aRetFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ //
+ // Now make sure that aRetPoint is converted to the same coordinate
+ // system used by aRetFrame.
+ //
+
+ aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
+
+ return NS_OK;
+}
+
+void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint(
+ mozilla::intl::BidiEmbeddingLevel aLevel) {
+ // If the current level is undefined, we have just inserted new text.
+ // In this case, we don't want to reset the keyboard language
+ mCaret.mBidiLevel = aLevel;
+
+ RefPtr<nsCaret> caret;
+ if (mPresShell && (caret = mPresShell->GetCaret())) {
+ caret->SchedulePaint();
+ }
+}
+
+mozilla::intl::BidiEmbeddingLevel nsFrameSelection::GetCaretBidiLevel() const {
+ return mCaret.mBidiLevel;
+}
+
+void nsFrameSelection::UndefineCaretBidiLevel() {
+ mCaret.mBidiLevel = mozilla::intl::BidiEmbeddingLevel(mCaret.mBidiLevel |
+ BIDI_LEVEL_UNDEFINED);
+}
+
+#ifdef PRINT_RANGE
+void printRange(nsRange* aDomRange) {
+ if (!aDomRange) {
+ printf("NULL Range\n");
+ }
+ nsINode* startNode = aDomRange->GetStartContainer();
+ nsINode* endNode = aDomRange->GetEndContainer();
+ int32_t startOffset = aDomRange->StartOffset();
+ int32_t endOffset = aDomRange->EndOffset();
+
+ printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
+ (unsigned long)aDomRange, (unsigned long)startNode, (long)startOffset,
+ (unsigned long)endNode, (long)endOffset);
+}
+#endif /* PRINT_RANGE */
+
+static nsAtom* GetTag(nsINode* aNode) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
+ if (!content) {
+ MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()");
+ return nullptr;
+ }
+
+ return content->NodeInfo()->NameAtom();
+}
+
+/**
+ * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
+ */
+static nsINode* GetClosestInclusiveTableCellAncestor(nsINode* aDomNode) {
+ if (!aDomNode) return nullptr;
+ nsINode* current = aDomNode;
+ // Start with current node and look for a table cell
+ while (current) {
+ nsAtom* tag = GetTag(current);
+ if (tag == nsGkAtoms::td || tag == nsGkAtoms::th) return current;
+ current = current->GetParent();
+ }
+ return nullptr;
+}
+
+static nsDirection GetCaretDirection(const nsIFrame& aFrame,
+ nsDirection aDirection,
+ bool aVisualMovement) {
+ const mozilla::intl::BidiDirection paragraphDirection =
+ nsBidiPresUtils::ParagraphDirection(&aFrame);
+ return (aVisualMovement &&
+ paragraphDirection == mozilla::intl::BidiDirection::RTL)
+ ? nsDirection(1 - aDirection)
+ : aDirection;
+}
+
+nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
+ bool aContinueSelection,
+ const nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle) {
+ NS_ENSURE_STATE(mPresShell);
+ // Flush out layout, since we need it to be up to date to do caret
+ // positioning.
+ OwningNonNull<PresShell> presShell(*mPresShell);
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ if (!mPresShell) {
+ return NS_OK;
+ }
+
+ nsPresContext* context = mPresShell->GetPresContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ const RefPtr<Selection> sel = mDomSelections[index];
+ if (!sel) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ int32_t scrollFlags = Selection::SCROLL_FOR_CARET_MOVE;
+ if (sel->IsEditorSelection()) {
+ // If caret moves in editor, it should cause scrolling even if it's in
+ // overflow: hidden;.
+ scrollFlags |= Selection::SCROLL_OVERFLOW_HIDDEN;
+ }
+
+ const bool doCollapse = [&] {
+ if (sel->IsCollapsed() || aContinueSelection) {
+ return false;
+ }
+ if (aAmount > eSelectLine) {
+ return false;
+ }
+ int32_t caretStyle = StaticPrefs::layout_selection_caret_style();
+ return caretStyle == 2 || (caretStyle == 0 && aAmount != eSelectLine);
+ }();
+
+ if (doCollapse) {
+ if (aDirection == eDirPrevious) {
+ SetChangeReasons(nsISelectionListener::COLLAPSETOSTART_REASON);
+ mCaret.mHint = CaretAssociationHint::After;
+ } else {
+ SetChangeReasons(nsISelectionListener::COLLAPSETOEND_REASON);
+ mCaret.mHint = CaretAssociationHint::Before;
+ }
+ } else {
+ SetChangeReasons(nsISelectionListener::KEYPRESS_REASON);
+ }
+
+ mCaretMoveAmount = aAmount;
+
+ AutoPrepareFocusRange prep(sel, false);
+
+ // we must keep this around and revalidate it when its just UP/DOWN
+ nsPoint desiredPos(0, 0);
+
+ if (aAmount == eSelectLine) {
+ nsresult result = mDesiredCaretPos.FetchPos(desiredPos, *mPresShell, *sel);
+ if (NS_FAILED(result)) {
+ return result;
+ }
+ mDesiredCaretPos.Set(desiredPos);
+ }
+
+ bool visualMovement =
+ mCaret.IsVisualMovement(aContinueSelection, aMovementStyle);
+ const PrimaryFrameData frameForFocus =
+ sel->GetPrimaryFrameForCaretAtFocusNode(visualMovement);
+ if (!frameForFocus.mFrame) {
+ return NS_ERROR_FAILURE;
+ }
+ if (visualMovement) {
+ // FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode.
+ // Therefore, this may not be intended by the original author.
+ SetHint(frameForFocus.mHint);
+ }
+
+ Result<bool, nsresult> isIntraLineCaretMove =
+ SelectionMovementUtils::IsIntraLineCaretMove(aAmount);
+ nsDirection direction{aDirection};
+ if (isIntraLineCaretMove.isErr()) {
+ return isIntraLineCaretMove.unwrapErr();
+ }
+ if (isIntraLineCaretMove.inspect()) {
+ // Forget old caret position for moving caret to different line since
+ // caret position may be changed.
+ mDesiredCaretPos.Invalidate();
+ direction =
+ GetCaretDirection(*frameForFocus.mFrame, aDirection, visualMovement);
+ }
+
+ if (doCollapse) {
+ const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
+ if (anchorFocusRange) {
+ RefPtr<nsINode> node;
+ uint32_t offset;
+ if (visualMovement &&
+ nsBidiPresUtils::IsReversedDirectionFrame(frameForFocus.mFrame)) {
+ direction = nsDirection(1 - direction);
+ }
+ if (direction == eDirPrevious) {
+ node = anchorFocusRange->GetStartContainer();
+ offset = anchorFocusRange->StartOffset();
+ } else {
+ node = anchorFocusRange->GetEndContainer();
+ offset = anchorFocusRange->EndOffset();
+ }
+ sel->CollapseInLimiter(node, offset);
+ }
+ sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
+ ScrollAxis(), ScrollAxis(), scrollFlags);
+ return NS_OK;
+ }
+
+ CaretAssociationHint tHint(
+ mCaret.mHint); // temporary variable so we dont set
+ // mCaret.mHint until it is necessary
+
+ Result<PeekOffsetStruct, nsresult> result = PeekOffsetForCaretMove(
+ direction, aContinueSelection, aAmount, aMovementStyle, desiredPos);
+ nsresult rv;
+ if (result.isOk() && result.inspect().mResultContent) {
+ const PeekOffsetStruct& pos = result.inspect();
+ nsIFrame* theFrame;
+ int32_t frameStart, frameEnd;
+
+ if (aAmount <= eSelectWordNoSpace) {
+ // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
+ // not set pos.mAttachForward, so determine the hint here based on the
+ // result frame and offset: If we're at the end of a text frame, set the
+ // hint to ASSOCIATE_BEFORE to indicate that we want the caret displayed
+ // at the end of this frame, not at the beginning of the next one.
+ theFrame = pos.mResultFrame;
+ std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
+ if (frameEnd == pos.mContentOffset && !(frameStart == 0 && frameEnd == 0))
+ tHint = CaretAssociationHint::Before;
+ else
+ tHint = CaretAssociationHint::After;
+ } else {
+ // For up/down and home/end, pos.mResultFrame might not be set correctly,
+ // or not at all. In these cases, get the frame based on the content and
+ // hint returned by PeekOffset().
+ tHint = pos.mAttach;
+ theFrame = SelectionMovementUtils::GetFrameForNodeOffset(
+ pos.mResultContent, pos.mContentOffset, tHint);
+ if (!theFrame) return NS_ERROR_FAILURE;
+
+ std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
+ }
+
+ if (context->BidiEnabled()) {
+ switch (aAmount) {
+ case eSelectBeginLine:
+ case eSelectEndLine: {
+ // In Bidi contexts, PeekOffset calculates pos.mContentOffset
+ // differently depending on whether the movement is visual or logical.
+ // For visual movement, pos.mContentOffset depends on the direction-
+ // ality of the first/last frame on the line (theFrame), and the caret
+ // directionality must correspond.
+ FrameBidiData bidiData = theFrame->GetBidiData();
+ SetCaretBidiLevelAndMaybeSchedulePaint(
+ visualMovement ? bidiData.embeddingLevel : bidiData.baseLevel);
+ break;
+ }
+ default:
+ // If the current position is not a frame boundary, it's enough just
+ // to take the Bidi level of the current frame
+ if ((pos.mContentOffset != frameStart &&
+ pos.mContentOffset != frameEnd) ||
+ eSelectLine == aAmount) {
+ SetCaretBidiLevelAndMaybeSchedulePaint(
+ theFrame->GetEmbeddingLevel());
+ } else {
+ BidiLevelFromMove(mPresShell, pos.mResultContent,
+ pos.mContentOffset, aAmount, tHint);
+ }
+ }
+ }
+ // "pos" is on the stack, so pos.mResultContent has stack lifetime, so using
+ // MOZ_KnownLive is ok.
+ const FocusMode focusMode = aContinueSelection
+ ? FocusMode::kExtendSelection
+ : FocusMode::kCollapseToNewPoint;
+ rv = TakeFocus(MOZ_KnownLive(*pos.mResultContent), pos.mContentOffset,
+ pos.mContentOffset, tHint, focusMode);
+ } else if (aAmount <= eSelectWordNoSpace && direction == eDirNext &&
+ !aContinueSelection) {
+ // Collapse selection if PeekOffset failed, we either
+ // 1. bumped into the BRFrame, bug 207623
+ // 2. had select-all in a text input (DIV range), bug 352759.
+ bool isBRFrame = frameForFocus.mFrame->IsBrFrame();
+ RefPtr<nsINode> node = sel->GetFocusNode();
+ sel->CollapseInLimiter(node, sel->FocusOffset());
+ // Note: 'frameForFocus.mFrame' might be dead here.
+ if (!isBRFrame) {
+ mCaret.mHint = CaretAssociationHint::Before; // We're now at the end of
+ // the frame to the left.
+ }
+ rv = NS_OK;
+ } else {
+ rv = result.isErr() ? result.unwrapErr() : NS_OK;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
+ ScrollAxis(), ScrollAxis(), scrollFlags);
+ }
+
+ return rv;
+}
+
+Result<PeekOffsetStruct, nsresult> nsFrameSelection::PeekOffsetForCaretMove(
+ nsDirection aDirection, bool aContinueSelection,
+ const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle,
+ const nsPoint& aDesiredCaretPos) const {
+ if (!mPresShell) {
+ return Err(NS_ERROR_NULL_POINTER);
+ }
+
+ Selection* selection =
+ mDomSelections[GetIndexFromSelectionType(SelectionType::eNormal)];
+ if (!selection) {
+ return Err(NS_ERROR_NULL_POINTER);
+ }
+
+ nsIContent* content = nsIContent::FromNodeOrNull(selection->GetFocusNode());
+ if (!content) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(mPresShell->GetDocument() == content->GetComposedDoc());
+
+ const bool visualMovement =
+ mCaret.IsVisualMovement(aContinueSelection, aMovementStyle);
+
+ PeekOffsetOptions options;
+ // set data using mLimiters.mLimiter to stop on scroll views. If we have a
+ // limiter then we stop peeking when we hit scrollable views. If no limiter
+ // then just let it go ahead
+ if (mLimiters.mLimiter) {
+ options += PeekOffsetOption::StopAtScroller;
+ }
+ if (visualMovement) {
+ options += PeekOffsetOption::Visual;
+ }
+ if (aContinueSelection) {
+ options += PeekOffsetOption::Extend;
+ }
+ if (selection->IsEditorSelection()) {
+ options += PeekOffsetOption::ForceEditableRegion;
+ }
+
+ return SelectionMovementUtils::PeekOffsetForCaretMove(
+ content, selection->FocusOffset(), aDirection, GetHint(),
+ GetCaretBidiLevel(), aAmount, aDesiredCaretPos, options);
+}
+
+nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels(
+ nsIContent* aNode, uint32_t aContentOffset, bool aJumpLines) const {
+ return SelectionMovementUtils::GetPrevNextBidiLevels(
+ aNode, aContentOffset, mCaret.mHint, aJumpLines);
+}
+
+nsresult nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) {
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ if (!mDomSelections[index]) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mMaintainedRange.MaintainAnchorFocusRange(*mDomSelections[index], aAmount);
+
+ return NS_OK;
+}
+
+void nsFrameSelection::BidiLevelFromMove(PresShell* aPresShell,
+ nsIContent* aNode,
+ uint32_t aContentOffset,
+ nsSelectionAmount aAmount,
+ CaretAssociationHint aHint) {
+ switch (aAmount) {
+ // Movement within the line: the new cursor Bidi level is the level of the
+ // last character moved over
+ case eSelectCharacter:
+ case eSelectCluster:
+ case eSelectWord:
+ case eSelectWordNoSpace:
+ case eSelectBeginLine:
+ case eSelectEndLine:
+ case eSelectNoAmount: {
+ nsPrevNextBidiLevels levels =
+ SelectionMovementUtils::GetPrevNextBidiLevels(aNode, aContentOffset,
+ aHint, false);
+
+ SetCaretBidiLevelAndMaybeSchedulePaint(
+ aHint == CaretAssociationHint::Before ? levels.mLevelBefore
+ : levels.mLevelAfter);
+ break;
+ }
+ /*
+ // Up and Down: the new cursor Bidi level is the smaller of the two
+ surrounding characters case eSelectLine: case eSelectParagraph:
+ GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame,
+ &secondFrame, &firstLevel, &secondLevel);
+ aPresShell->SetCaretBidiLevelAndMaybeSchedulePaint(std::min(firstLevel,
+ secondLevel)); break;
+ */
+
+ default:
+ UndefineCaretBidiLevel();
+ }
+}
+
+void nsFrameSelection::BidiLevelFromClick(nsIContent* aNode,
+ uint32_t aContentOffset) {
+ nsIFrame* clickInFrame = nullptr;
+ clickInFrame = SelectionMovementUtils::GetFrameForNodeOffset(
+ aNode, aContentOffset, mCaret.mHint);
+ if (!clickInFrame) return;
+
+ SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame->GetEmbeddingLevel());
+}
+
+void nsFrameSelection::MaintainedRange::AdjustNormalSelection(
+ const nsIContent* aContent, const int32_t aOffset,
+ Selection& aNormalSelection) const {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ if (!mRange || !aContent) {
+ return;
+ }
+
+ nsINode* rangeStartNode = mRange->GetStartContainer();
+ nsINode* rangeEndNode = mRange->GetEndContainer();
+ const uint32_t rangeStartOffset = mRange->StartOffset();
+ const uint32_t rangeEndOffset = mRange->EndOffset();
+
+ NS_ASSERTION(aOffset >= 0, "aOffset should not be negative");
+ const Maybe<int32_t> relToStart =
+ nsContentUtils::ComparePoints_AllowNegativeOffsets(
+ rangeStartNode, rangeStartOffset, aContent, aOffset);
+ if (NS_WARN_IF(!relToStart)) {
+ // Potentially handle this properly when Selection across Shadow DOM
+ // boundary is implemented
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
+ return;
+ }
+
+ const Maybe<int32_t> relToEnd =
+ nsContentUtils::ComparePoints_AllowNegativeOffsets(
+ rangeEndNode, rangeEndOffset, aContent, aOffset);
+ if (NS_WARN_IF(!relToEnd)) {
+ // Potentially handle this properly when Selection across Shadow DOM
+ // boundary is implemented
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
+ return;
+ }
+
+ // If aContent/aOffset is inside (or at the edge of) the maintained
+ // selection, or if it is on the "anchor" side of the maintained selection,
+ // we need to do something.
+ if ((*relToStart <= 0 && *relToEnd >= 0) ||
+ (*relToStart > 0 && aNormalSelection.GetDirection() == eDirNext) ||
+ (*relToEnd < 0 && aNormalSelection.GetDirection() == eDirPrevious)) {
+ // Set the current range to the maintained range.
+ aNormalSelection.ReplaceAnchorFocusRange(mRange);
+ // Set the direction of the selection so that the anchor will be on the
+ // far side of the maintained selection, relative to aContent/aOffset.
+ aNormalSelection.SetDirection(*relToStart > 0 ? eDirPrevious : eDirNext);
+ }
+}
+
+void nsFrameSelection::MaintainedRange::AdjustContentOffsets(
+ nsIFrame::ContentOffsets& aOffsets, StopAtScroller aStopAtScroller) const {
+ // Adjust offsets according to maintained amount
+ if (mRange && mAmount != eSelectNoAmount) {
+ nsINode* rangenode = mRange->GetStartContainer();
+ int32_t rangeOffset = mRange->StartOffset();
+ const Maybe<int32_t> relativePosition = nsContentUtils::ComparePoints(
+ rangenode, rangeOffset, aOffsets.content, aOffsets.offset);
+ if (NS_WARN_IF(!relativePosition)) {
+ // Potentially handle this properly when Selection across Shadow DOM
+ // boundary is implemented
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
+ return;
+ }
+
+ nsDirection direction = *relativePosition > 0 ? eDirPrevious : eDirNext;
+ nsSelectionAmount amount = mAmount;
+ if (amount == eSelectBeginLine && direction == eDirNext) {
+ amount = eSelectEndLine;
+ }
+
+ uint32_t offset;
+ nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset(
+ aOffsets.content, aOffsets.offset, CaretAssociationHint::After,
+ &offset);
+
+ PeekOffsetOptions peekOffsetOptions{};
+ if (aStopAtScroller == StopAtScroller::Yes) {
+ peekOffsetOptions += PeekOffsetOption::StopAtScroller;
+ }
+ if (frame && amount == eSelectWord && direction == eDirPrevious) {
+ // To avoid selecting the previous word when at start of word,
+ // first move one character forward.
+ PeekOffsetStruct charPos(eSelectCharacter, eDirNext,
+ static_cast<int32_t>(offset), nsPoint(0, 0),
+ peekOffsetOptions);
+ if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) {
+ frame = charPos.mResultFrame;
+ offset = charPos.mContentOffset;
+ }
+ }
+
+ PeekOffsetStruct pos(amount, direction, static_cast<int32_t>(offset),
+ nsPoint(0, 0), peekOffsetOptions);
+ if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) {
+ aOffsets.content = pos.mResultContent;
+ aOffsets.offset = pos.mContentOffset;
+ }
+ }
+}
+
+void nsFrameSelection::MaintainedRange::MaintainAnchorFocusRange(
+ const Selection& aNormalSelection, const nsSelectionAmount aAmount) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ mAmount = aAmount;
+
+ const nsRange* anchorFocusRange = aNormalSelection.GetAnchorFocusRange();
+ if (anchorFocusRange && aAmount != eSelectNoAmount) {
+ mRange = anchorFocusRange->CloneRange();
+ return;
+ }
+
+ mRange = nullptr;
+}
+
+nsresult nsFrameSelection::HandleClick(nsIContent* aNewFocus,
+ uint32_t aContentOffset,
+ uint32_t aContentEndOffset,
+ const FocusMode aFocusMode,
+ CaretAssociationHint aHint) {
+ if (!aNewFocus) return NS_ERROR_INVALID_ARG;
+
+ if (MOZ_LOG_TEST(sFrameSelectionLog, LogLevel::Debug)) {
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,
+ ("%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i",
+ __FUNCTION__,
+ mDomSelections[index] ? mDomSelections[index].get() : nullptr,
+ aNewFocus, aContentOffset, aContentEndOffset,
+ static_cast<int>(aFocusMode)));
+ }
+
+ mDesiredCaretPos.Invalidate();
+
+ if (aFocusMode != FocusMode::kExtendSelection) {
+ mMaintainedRange.mRange = nullptr;
+ if (!IsValidSelectionPoint(aNewFocus)) {
+ mLimiters.mAncestorLimiter = nullptr;
+ }
+ }
+
+ // Don't take focus when dragging off of a table
+ if (!mTableSelection.mDragSelectingCells) {
+ BidiLevelFromClick(aNewFocus, aContentOffset);
+ SetChangeReasons(nsISelectionListener::MOUSEDOWN_REASON +
+ nsISelectionListener::DRAG_REASON);
+
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ RefPtr<Selection> selection = mDomSelections[index];
+ MOZ_ASSERT(selection);
+
+ if (aFocusMode == FocusMode::kExtendSelection) {
+ mMaintainedRange.AdjustNormalSelection(aNewFocus, aContentOffset,
+ *selection);
+ }
+
+ AutoPrepareFocusRange prep(selection,
+ aFocusMode == FocusMode::kMultiRangeSelection);
+ return TakeFocus(*aNewFocus, aContentOffset, aContentEndOffset, aHint,
+ aFocusMode);
+ }
+
+ return NS_OK;
+}
+
+void nsFrameSelection::HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint) {
+ if (!aFrame || !mPresShell) {
+ return;
+ }
+
+ nsresult result;
+ nsIFrame* newFrame = 0;
+ nsPoint newPoint;
+
+ result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame,
+ newPoint);
+ if (NS_FAILED(result)) return;
+ if (!newFrame) return;
+
+ nsIFrame::ContentOffsets offsets =
+ newFrame->GetContentOffsetsFromPoint(newPoint);
+ if (!offsets.content) return;
+
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ RefPtr<Selection> selection = mDomSelections[index];
+ if (newFrame->IsSelected() && selection) {
+ // `MOZ_KnownLive` required because of
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1636889.
+ mMaintainedRange.AdjustNormalSelection(MOZ_KnownLive(offsets.content),
+ offsets.offset, *selection);
+ }
+
+ mMaintainedRange.AdjustContentOffsets(
+ offsets, mLimiters.mLimiter ? MaintainedRange::StopAtScroller::Yes
+ : MaintainedRange::StopAtScroller::No);
+
+ // TODO: no click has happened, rename `HandleClick`.
+ HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */, offsets.offset,
+ offsets.offset, FocusMode::kExtendSelection, offsets.associate);
+}
+
+nsresult nsFrameSelection::StartAutoScrollTimer(nsIFrame* aFrame,
+ const nsPoint& aPoint,
+ uint32_t aDelay) {
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ if (!mDomSelections[index]) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ RefPtr<Selection> selection = mDomSelections[index];
+ return selection->StartAutoScrollTimer(aFrame, aPoint, aDelay);
+}
+
+void nsFrameSelection::StopAutoScrollTimer() {
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ if (!mDomSelections[index]) {
+ return;
+ }
+
+ mDomSelections[index]->StopAutoScrollTimer();
+}
+
+// static
+nsINode* nsFrameSelection::TableSelection::IsContentInActivelyEditableTableCell(
+ nsPresContext* aContext, nsIContent* aContent) {
+ if (!aContext) {
+ return nullptr;
+ }
+
+ RefPtr<HTMLEditor> htmlEditor = nsContentUtils::GetHTMLEditor(aContext);
+ if (!htmlEditor) {
+ return nullptr;
+ }
+
+ nsINode* inclusiveTableCellAncestor =
+ GetClosestInclusiveTableCellAncestor(aContent);
+ if (!inclusiveTableCellAncestor) {
+ return nullptr;
+ }
+
+ const Element* editingHost = htmlEditor->ComputeEditingHost();
+ if (!editingHost) {
+ return nullptr;
+ }
+
+ const bool editableCell =
+ inclusiveTableCellAncestor->IsInclusiveDescendantOf(editingHost);
+ return editableCell ? inclusiveTableCellAncestor : nullptr;
+}
+
+namespace {
+struct ParentAndOffset {
+ explicit ParentAndOffset(const nsINode& aNode)
+ : mParent{aNode.GetParent()},
+ mOffset{mParent ? mParent->ComputeIndexOf_Deprecated(&aNode) : 0} {}
+
+ nsINode* mParent;
+
+ // 0, if there's no parent.
+ int32_t mOffset;
+};
+
+} // namespace
+/**
+hard to go from nodes to frames, easy the other way!
+ */
+nsresult nsFrameSelection::TakeFocus(nsIContent& aNewFocus,
+ uint32_t aContentOffset,
+ uint32_t aContentEndOffset,
+ CaretAssociationHint aHint,
+ const FocusMode aFocusMode) {
+ NS_ENSURE_STATE(mPresShell);
+
+ if (!IsValidSelectionPoint(&aNewFocus)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_LOG(sFrameSelectionLog, LogLevel::Verbose,
+ ("%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i",
+ __FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset,
+ static_cast<int>(aHint), static_cast<int>(aFocusMode)));
+
+ mPresShell->FrameSelectionWillTakeFocus(*this);
+
+ // Clear all table selection data
+ mTableSelection.mMode = TableSelectionMode::None;
+ mTableSelection.mDragSelectingCells = false;
+ mTableSelection.mStartSelectedCell = nullptr;
+ mTableSelection.mEndSelectedCell = nullptr;
+ mTableSelection.mAppendStartSelectedCell = nullptr;
+ mCaret.mHint = aHint;
+
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ if (!mDomSelections[index]) return NS_ERROR_NULL_POINTER;
+
+ Maybe<Selection::AutoUserInitiated> userSelect;
+ if (IsUserSelectionReason()) {
+ userSelect.emplace(mDomSelections[index]);
+ }
+
+ // traverse through document and unselect crap here
+ switch (aFocusMode) {
+ case FocusMode::kCollapseToNewPoint:
+ [[fallthrough]];
+ case FocusMode::kMultiRangeSelection: {
+ // single click? setting cursor down
+ const Batching saveBatching =
+ mBatching; // hack to use the collapse code.
+ mBatching.mCounter = 1;
+
+ RefPtr<Selection> selection = mDomSelections[index];
+
+ if (aFocusMode == FocusMode::kMultiRangeSelection) {
+ // Remove existing collapsed ranges as there's no point in having
+ // non-anchor/focus collapsed ranges.
+ selection->RemoveCollapsedRanges();
+
+ ErrorResult error;
+ RefPtr<nsRange> newRange = nsRange::Create(
+ &aNewFocus, aContentOffset, &aNewFocus, aContentOffset, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+ MOZ_ASSERT(newRange);
+ selection->AddRangeAndSelectFramesAndNotifyListeners(*newRange,
+ IgnoreErrors());
+ } else {
+ bool oldDesiredPosSet =
+ mDesiredCaretPos.mIsSet; // need to keep old desired
+ // position if it was set.
+ selection->CollapseInLimiter(&aNewFocus, aContentOffset);
+ mDesiredCaretPos.mIsSet =
+ oldDesiredPosSet; // now reset desired pos back.
+ }
+
+ mBatching = saveBatching;
+
+ if (aContentEndOffset != aContentOffset) {
+ selection->Extend(&aNewFocus, aContentEndOffset);
+ }
+
+ // find out if we are inside a table. if so, find out which one and which
+ // cell once we do that, the next time we get a takefocus, check the
+ // parent tree. if we are no longer inside same table ,cell then switch to
+ // table selection mode. BUT only do this in an editor
+
+ NS_ENSURE_STATE(mPresShell);
+ RefPtr<nsPresContext> context = mPresShell->GetPresContext();
+ mTableSelection.mClosestInclusiveTableCellAncestor = nullptr;
+ if (nsINode* inclusiveTableCellAncestor =
+ TableSelection::IsContentInActivelyEditableTableCell(
+ context, &aNewFocus)) {
+ mTableSelection.mClosestInclusiveTableCellAncestor =
+ inclusiveTableCellAncestor;
+ MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,
+ ("%s: Collapsing into new cell", __FUNCTION__));
+ }
+
+ break;
+ }
+ case FocusMode::kExtendSelection: {
+ // Now update the range list:
+ nsINode* inclusiveTableCellAncestor =
+ GetClosestInclusiveTableCellAncestor(&aNewFocus);
+ if (mTableSelection.mClosestInclusiveTableCellAncestor &&
+ inclusiveTableCellAncestor &&
+ inclusiveTableCellAncestor !=
+ mTableSelection
+ .mClosestInclusiveTableCellAncestor) // switch to cell
+ // selection mode
+ {
+ MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,
+ ("%s: moving into new cell", __FUNCTION__));
+
+ WidgetMouseEvent event(false, eVoidEvent, nullptr,
+ WidgetMouseEvent::eReal);
+
+ // Start selecting in the cell we were in before
+ ParentAndOffset parentAndOffset{
+ *mTableSelection.mClosestInclusiveTableCellAncestor};
+ if (parentAndOffset.mParent) {
+ const nsresult result = HandleTableSelection(
+ parentAndOffset.mParent, parentAndOffset.mOffset,
+ TableSelectionMode::Cell, &event);
+ if (NS_WARN_IF(NS_FAILED(result))) {
+ return result;
+ }
+ }
+
+ // Find the parent of this new cell and extend selection to it
+ parentAndOffset = ParentAndOffset{*inclusiveTableCellAncestor};
+
+ // XXXX We need to REALLY get the current key shift state
+ // (we'd need to add event listener -- let's not bother for now)
+ event.mModifiers &= ~MODIFIER_SHIFT; // aContinueSelection;
+ if (parentAndOffset.mParent) {
+ mTableSelection.mClosestInclusiveTableCellAncestor =
+ inclusiveTableCellAncestor;
+ // Continue selection into next cell
+ const nsresult result = HandleTableSelection(
+ parentAndOffset.mParent, parentAndOffset.mOffset,
+ TableSelectionMode::Cell, &event);
+ if (NS_WARN_IF(NS_FAILED(result))) {
+ return result;
+ }
+ }
+ } else {
+ RefPtr<Selection> selection = mDomSelections[index];
+ // XXXX Problem: Shift+click in browser is appending text selection to
+ // selected table!!!
+ // is this the place to erase selected cells ?????
+ uint32_t offset =
+ (selection->GetDirection() == eDirNext &&
+ aContentEndOffset > aContentOffset) // didn't go far enough
+ ? aContentEndOffset // this will only redraw the diff
+ : aContentOffset;
+ selection->Extend(&aNewFocus, offset);
+ }
+ break;
+ }
+ }
+
+ // Don't notify selection listeners if batching is on:
+ if (IsBatching()) {
+ return NS_OK;
+ }
+
+ // Be aware, the Selection instance may be destroyed after this call.
+ return NotifySelectionListeners(SelectionType::eNormal);
+}
+
+UniquePtr<SelectionDetails> nsFrameSelection::LookUpSelection(
+ nsIContent* aContent, int32_t aContentOffset, int32_t aContentLength,
+ bool aSlowCheck) const {
+ if (!aContent || !mPresShell) {
+ return nullptr;
+ }
+
+ // TODO: Layout should use `uint32_t` for handling offset in DOM nodes
+ // (for example: bug 1735262)
+ MOZ_ASSERT(aContentOffset >= 0);
+ MOZ_ASSERT(aContentLength >= 0);
+ if (MOZ_UNLIKELY(aContentOffset < 0) || MOZ_UNLIKELY(aContentLength < 0)) {
+ return nullptr;
+ }
+
+ UniquePtr<SelectionDetails> details;
+
+ for (size_t j = 0; j < ArrayLength(mDomSelections); j++) {
+ if (mDomSelections[j]) {
+ details = mDomSelections[j]->LookUpSelection(
+ aContent, static_cast<uint32_t>(aContentOffset),
+ static_cast<uint32_t>(aContentLength), std::move(details),
+ kPresentSelectionTypes[j], aSlowCheck);
+ }
+ }
+
+ // This may seem counter intuitive at first. Highlight selections need to be
+ // iterated from back to front:
+ //
+ // - `mHighlightSelections` is ordered by insertion, i.e. if two or more
+ // highlights overlap, the latest must take precedence.
+ // - however, the `LookupSelection()` algorithm reverses the order by setting
+ // the current `details` as `mNext`.
+ for (const auto& iter : Reversed(mHighlightSelections)) {
+ details = iter.second()->LookUpSelection(
+ aContent, static_cast<uint32_t>(aContentOffset),
+ static_cast<uint32_t>(aContentLength), std::move(details),
+ SelectionType::eHighlight, aSlowCheck);
+ }
+
+ return details;
+}
+
+void nsFrameSelection::SetDragState(bool aState) {
+ if (mDragState == aState) return;
+
+ mDragState = aState;
+
+ if (!mDragState) {
+ mTableSelection.mDragSelectingCells = false;
+ // Notify that reason is mouse up.
+ SetChangeReasons(nsISelectionListener::MOUSEUP_REASON);
+
+ // flag is set to false in `NotifySelectionListeners`.
+ // since this function call is part of click event, this would immediately
+ // reset the flag, rendering it useless.
+ AutoRestore<bool> restoreIsDoubleClickSelectionFlag(
+ mIsDoubleClickSelection);
+ // Be aware, the Selection instance may be destroyed after this call.
+ NotifySelectionListeners(SelectionType::eNormal);
+ }
+}
+
+Selection* nsFrameSelection::GetSelection(SelectionType aSelectionType) const {
+ int8_t index = GetIndexFromSelectionType(aSelectionType);
+ if (index < 0) return nullptr;
+
+ return mDomSelections[index];
+}
+
+void nsFrameSelection::AddHighlightSelection(
+ nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight) {
+ RefPtr<Selection> selection =
+ aHighlight.CreateHighlightSelection(aHighlightName, this);
+ if (auto iter =
+ std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
+ [&aHighlightName](auto const& aElm) {
+ return aElm.first() == aHighlightName;
+ });
+ iter != mHighlightSelections.end()) {
+ iter->second() = std::move(selection);
+ } else {
+ mHighlightSelections.AppendElement(
+ CompactPair<RefPtr<nsAtom>, RefPtr<Selection>>(aHighlightName,
+ std::move(selection)));
+ }
+}
+
+void nsFrameSelection::RemoveHighlightSelection(nsAtom* aHighlightName) {
+ if (auto iter =
+ std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
+ [&aHighlightName](auto const& aElm) {
+ return aElm.first() == aHighlightName;
+ });
+ iter != mHighlightSelections.end()) {
+ RefPtr<Selection> selection = iter->second();
+ selection->RemoveAllRanges(IgnoreErrors());
+ mHighlightSelections.RemoveElementAt(iter);
+ }
+}
+
+void nsFrameSelection::AddHighlightSelectionRange(
+ nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight,
+ mozilla::dom::AbstractRange& aRange) {
+ if (auto iter =
+ std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
+ [&aHighlightName](auto const& aElm) {
+ return aElm.first() == aHighlightName;
+ });
+ iter != mHighlightSelections.end()) {
+ RefPtr<Selection> selection = iter->second();
+ selection->AddHighlightRangeAndSelectFramesAndNotifyListeners(aRange);
+ } else {
+ // if the selection does not exist yet, add all of its ranges and exit.
+ RefPtr<Selection> selection =
+ aHighlight.CreateHighlightSelection(aHighlightName, this);
+ mHighlightSelections.AppendElement(
+ CompactPair<RefPtr<nsAtom>, RefPtr<Selection>>(aHighlightName,
+ std::move(selection)));
+ }
+}
+
+void nsFrameSelection::RemoveHighlightSelectionRange(
+ nsAtom* aHighlightName, mozilla::dom::AbstractRange& aRange) {
+ if (auto iter =
+ std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
+ [&aHighlightName](auto const& aElm) {
+ return aElm.first() == aHighlightName;
+ });
+ iter != mHighlightSelections.end()) {
+ // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
+ RefPtr<Selection> selection = iter->second();
+ selection->RemoveRangeAndUnselectFramesAndNotifyListeners(aRange,
+ IgnoreErrors());
+ }
+}
+
+nsresult nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType,
+ SelectionRegion aRegion,
+ int16_t aFlags) const {
+ int8_t index = GetIndexFromSelectionType(aSelectionType);
+ if (index < 0) return NS_ERROR_INVALID_ARG;
+
+ if (!mDomSelections[index]) return NS_ERROR_NULL_POINTER;
+
+ ScrollAxis verticalScroll = ScrollAxis();
+ int32_t flags = Selection::SCROLL_DO_FLUSH;
+ if (aFlags & nsISelectionController::SCROLL_SYNCHRONOUS) {
+ flags |= Selection::SCROLL_SYNCHRONOUS;
+ } else if (aFlags & nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY) {
+ flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY;
+ }
+ if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) {
+ flags |= Selection::SCROLL_OVERFLOW_HIDDEN;
+ }
+ if (aFlags & nsISelectionController::SCROLL_CENTER_VERTICALLY) {
+ verticalScroll =
+ ScrollAxis(WhereToScroll::Center, WhenToScroll::IfNotFullyVisible);
+ }
+ if (aFlags & nsISelectionController::SCROLL_FOR_CARET_MOVE) {
+ flags |= Selection::SCROLL_FOR_CARET_MOVE;
+ }
+
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ RefPtr<Selection> sel = mDomSelections[index];
+ return sel->ScrollIntoView(aRegion, verticalScroll, ScrollAxis(), flags);
+}
+
+nsresult nsFrameSelection::RepaintSelection(SelectionType aSelectionType) {
+ int8_t index = GetIndexFromSelectionType(aSelectionType);
+ if (index < 0) return NS_ERROR_INVALID_ARG;
+ if (!mDomSelections[index]) return NS_ERROR_NULL_POINTER;
+ NS_ENSURE_STATE(mPresShell);
+
+// On macOS, update the selection cache to the new active selection
+// aka the current selection.
+#ifdef XP_MACOSX
+ // Check that we're in the an active window and, if this is Web content,
+ // in the frontmost tab.
+ Document* doc = mPresShell->GetDocument();
+ if (doc && IsInActiveTab(doc) && aSelectionType == SelectionType::eNormal) {
+ UpdateSelectionCacheOnRepaintSelection(mDomSelections[index]);
+ }
+#endif
+ return mDomSelections[index]->Repaint(mPresShell->GetPresContext());
+}
+
+nsIFrame* nsFrameSelection::GetFrameToPageSelect() const {
+ if (NS_WARN_IF(!mPresShell)) {
+ return nullptr;
+ }
+
+ nsIFrame* rootFrameToSelect;
+ if (mLimiters.mLimiter) {
+ rootFrameToSelect = mLimiters.mLimiter->GetPrimaryFrame();
+ if (NS_WARN_IF(!rootFrameToSelect)) {
+ return nullptr;
+ }
+ } else if (mLimiters.mAncestorLimiter) {
+ rootFrameToSelect = mLimiters.mAncestorLimiter->GetPrimaryFrame();
+ if (NS_WARN_IF(!rootFrameToSelect)) {
+ return nullptr;
+ }
+ } else {
+ rootFrameToSelect = mPresShell->GetRootScrollFrame();
+ if (NS_WARN_IF(!rootFrameToSelect)) {
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIContent> contentToSelect = mPresShell->GetContentForScrolling();
+ if (contentToSelect) {
+ // If there is selected content, look for nearest and vertical scrollable
+ // parent under the root frame.
+ for (nsIFrame* frame = contentToSelect->GetPrimaryFrame();
+ frame && frame != rootFrameToSelect; frame = frame->GetParent()) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
+ if (!scrollableFrame) {
+ continue;
+ }
+ ScrollStyles scrollStyles = scrollableFrame->GetScrollStyles();
+ if (scrollStyles.mVertical == StyleOverflow::Hidden) {
+ continue;
+ }
+ layers::ScrollDirections directions =
+ scrollableFrame->GetAvailableScrollingDirections();
+ if (directions.contains(layers::ScrollDirection::eVertical)) {
+ // If there is sub scrollable frame, let's use its page size to select.
+ return frame;
+ }
+ }
+ }
+ // Otherwise, i.e., there is no scrollable frame or only the root frame is
+ // scrollable, let's return the root frame because Shift + PageUp/PageDown
+ // should expand the selection in the root content even if it's not
+ // scrollable.
+ return rootFrameToSelect;
+}
+
+nsresult nsFrameSelection::PageMove(bool aForward, bool aExtend,
+ nsIFrame* aFrame,
+ SelectionIntoView aSelectionIntoView) {
+ MOZ_ASSERT(aFrame);
+
+ // expected behavior for PageMove is to scroll AND move the caret
+ // and remain relative position of the caret in view. see Bug 4302.
+
+ // Get the scrollable frame. If aFrame is not scrollable, this is nullptr.
+ nsIScrollableFrame* scrollableFrame = aFrame->GetScrollTargetFrame();
+ // Get the scrolled frame. If aFrame is not scrollable, this is aFrame
+ // itself.
+ nsIFrame* scrolledFrame =
+ scrollableFrame ? scrollableFrame->GetScrolledFrame() : aFrame;
+ if (!scrolledFrame) {
+ return NS_OK;
+ }
+
+ // find out where the caret is.
+ // we should know mDesiredCaretPos.mValue value of nsFrameSelection, but I
+ // havent seen that behavior in other windows applications yet.
+ RefPtr<Selection> selection = GetSelection(SelectionType::eNormal);
+ if (!selection) {
+ return NS_OK;
+ }
+
+ nsRect caretPos;
+ nsIFrame* caretFrame = nsCaret::GetGeometry(selection, &caretPos);
+ if (!caretFrame) {
+ return NS_OK;
+ }
+
+ // If the scrolled frame is outside of current selection limiter,
+ // we need to scroll the frame but keep moving selection in the limiter.
+ nsIFrame* frameToClick = scrolledFrame;
+ if (!IsValidSelectionPoint(scrolledFrame->GetContent())) {
+ frameToClick = GetFrameToPageSelect();
+ if (NS_WARN_IF(!frameToClick)) {
+ return NS_OK;
+ }
+ }
+
+ if (scrollableFrame) {
+ // If there is a scrollable frame, adjust pseudo-click position with page
+ // scroll amount.
+ // XXX This may scroll more than one page if ScrollSelectionIntoView is
+ // called later because caret may not fully visible. E.g., if
+ // clicking line will be visible only half height with scrolling
+ // the frame, ScrollSelectionIntoView additionally scrolls to show
+ // the caret entirely.
+ if (aForward) {
+ caretPos.y += scrollableFrame->GetPageScrollAmount().height;
+ } else {
+ caretPos.y -= scrollableFrame->GetPageScrollAmount().height;
+ }
+ } else {
+ // Otherwise, adjust pseudo-click position with the frame size.
+ if (aForward) {
+ caretPos.y += frameToClick->GetSize().height;
+ } else {
+ caretPos.y -= frameToClick->GetSize().height;
+ }
+ }
+
+ caretPos += caretFrame->GetOffsetTo(frameToClick);
+
+ // get a content at desired location
+ nsPoint desiredPoint;
+ desiredPoint.x = caretPos.x;
+ desiredPoint.y = caretPos.y + caretPos.height / 2;
+ nsIFrame::ContentOffsets offsets =
+ frameToClick->GetContentOffsetsFromPoint(desiredPoint);
+
+ if (!offsets.content) {
+ // XXX Do we need to handle ScrollSelectionIntoView in this case?
+ return NS_OK;
+ }
+
+ // First, place the caret.
+ bool selectionChanged;
+ {
+ // We don't want any script to run until we check whether selection is
+ // modified by HandleClick.
+ SelectionBatcher ensureNoSelectionChangeNotifications(selection,
+ __FUNCTION__);
+
+ RangeBoundary oldAnchor = selection->AnchorRef();
+ RangeBoundary oldFocus = selection->FocusRef();
+
+ const FocusMode focusMode =
+ aExtend ? FocusMode::kExtendSelection : FocusMode::kCollapseToNewPoint;
+ HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */,
+ offsets.offset, offsets.offset, focusMode,
+ CaretAssociationHint::After);
+
+ selectionChanged = selection->AnchorRef() != oldAnchor ||
+ selection->FocusRef() != oldFocus;
+ }
+
+ bool doScrollSelectionIntoView = !(
+ aSelectionIntoView == SelectionIntoView::IfChanged && !selectionChanged);
+
+ // Then, scroll the given frame one page.
+ if (scrollableFrame) {
+ // If we'll call ScrollSelectionIntoView later and selection wasn't
+ // changed and we scroll outside of selection limiter, we shouldn't use
+ // smooth scroll here because nsIScrollableFrame uses normal runnable,
+ // but ScrollSelectionIntoView uses early runner and it cancels the
+ // pending smooth scroll. Therefore, if we used smooth scroll in such
+ // case, ScrollSelectionIntoView would scroll to show caret instead of
+ // page scroll of an element outside selection limiter.
+ ScrollMode scrollMode = doScrollSelectionIntoView && !selectionChanged &&
+ scrolledFrame != frameToClick
+ ? ScrollMode::Instant
+ : ScrollMode::Smooth;
+ scrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
+ ScrollUnit::PAGES, scrollMode);
+ }
+
+ // Finally, scroll selection into view if requested.
+ if (!doScrollSelectionIntoView) {
+ return NS_OK;
+ }
+ return ScrollSelectionIntoView(
+ SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS |
+ nsISelectionController::SCROLL_FOR_CARET_MOVE);
+}
+
+nsresult nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount,
+ bool aExtend) {
+ NS_ENSURE_STATE(mPresShell);
+ // Flush out layout, since we need it to be up to date to do caret
+ // positioning.
+ OwningNonNull<PresShell> presShell(*mPresShell);
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ if (!mPresShell) {
+ return NS_OK;
+ }
+
+ // Check that parameters are safe
+ if (aDirection < 0 || aDirection > 3 || aAmount < 0 || aAmount > 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPresContext* context = mPresShell->GetPresContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ RefPtr<Selection> sel = mDomSelections[index];
+ if (!sel) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // Map the abstract movement amounts (0-1) to direction-specific
+ // selection units.
+ static const nsSelectionAmount inlineAmount[] = {eSelectCluster, eSelectWord};
+ static const nsSelectionAmount blockPrevAmount[] = {eSelectLine,
+ eSelectBeginLine};
+ static const nsSelectionAmount blockNextAmount[] = {eSelectLine,
+ eSelectEndLine};
+
+ struct PhysicalToLogicalMapping {
+ nsDirection direction;
+ const nsSelectionAmount* amounts;
+ };
+ static const PhysicalToLogicalMapping verticalLR[4] = {
+ {eDirPrevious, blockPrevAmount}, // left
+ {eDirNext, blockNextAmount}, // right
+ {eDirPrevious, inlineAmount}, // up
+ {eDirNext, inlineAmount} // down
+ };
+ static const PhysicalToLogicalMapping verticalRL[4] = {
+ {eDirNext, blockNextAmount},
+ {eDirPrevious, blockPrevAmount},
+ {eDirPrevious, inlineAmount},
+ {eDirNext, inlineAmount}};
+ static const PhysicalToLogicalMapping horizontal[4] = {
+ {eDirPrevious, inlineAmount},
+ {eDirNext, inlineAmount},
+ {eDirPrevious, blockPrevAmount},
+ {eDirNext, blockNextAmount}};
+
+ WritingMode wm;
+ const PrimaryFrameData frameForFocus =
+ sel->GetPrimaryFrameForCaretAtFocusNode(true);
+ if (frameForFocus.mFrame) {
+ // FYI: Setting the caret association hint was done during a call of
+ // GetPrimaryFrameForCaretAtFocusNode. Therefore, this may not be intended
+ // by the original author.
+ sel->GetFrameSelection()->SetHint(frameForFocus.mHint);
+
+ if (!frameForFocus.mFrame->Style()->IsTextCombined()) {
+ wm = frameForFocus.mFrame->GetWritingMode();
+ } else {
+ // Using different direction for horizontal-in-vertical would
+ // make it hard to navigate via keyboard. Inherit the moving
+ // direction from its parent.
+ MOZ_ASSERT(frameForFocus.mFrame->IsTextFrame());
+ wm = frameForFocus.mFrame->GetParent()->GetWritingMode();
+ MOZ_ASSERT(wm.IsVertical(),
+ "Text combined "
+ "can only appear in vertical text");
+ }
+ }
+
+ const PhysicalToLogicalMapping& mapping =
+ wm.IsVertical()
+ ? wm.IsVerticalLR() ? verticalLR[aDirection] : verticalRL[aDirection]
+ : horizontal[aDirection];
+
+ nsresult rv =
+ MoveCaret(mapping.direction, aExtend, mapping.amounts[aAmount], eVisual);
+ if (NS_FAILED(rv)) {
+ // If we tried to do a line move, but couldn't move in the given direction,
+ // then we'll "promote" this to a line-edge move instead.
+ if (mapping.amounts[aAmount] == eSelectLine) {
+ rv = MoveCaret(mapping.direction, aExtend, mapping.amounts[aAmount + 1],
+ eVisual);
+ }
+ // And if it was a next-word move that failed (which can happen when
+ // eat_space_to_next_word is true, see bug 1153237), then just move forward
+ // to the line-edge.
+ else if (mapping.amounts[aAmount] == eSelectWord &&
+ mapping.direction == eDirNext) {
+ rv = MoveCaret(eDirNext, aExtend, eSelectEndLine, eVisual);
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsFrameSelection::CharacterMove(bool aForward, bool aExtend) {
+ return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectCluster,
+ eUsePrefStyle);
+}
+
+nsresult nsFrameSelection::WordMove(bool aForward, bool aExtend) {
+ return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectWord,
+ eUsePrefStyle);
+}
+
+nsresult nsFrameSelection::LineMove(bool aForward, bool aExtend) {
+ return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectLine,
+ eUsePrefStyle);
+}
+
+nsresult nsFrameSelection::IntraLineMove(bool aForward, bool aExtend) {
+ if (aForward) {
+ return MoveCaret(eDirNext, aExtend, eSelectEndLine, eLogical);
+ } else {
+ return MoveCaret(eDirPrevious, aExtend, eSelectBeginLine, eLogical);
+ }
+}
+
+template <typename RangeType>
+Result<RefPtr<RangeType>, nsresult>
+nsFrameSelection::CreateRangeExtendedToSomewhere(
+ nsDirection aDirection, const nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle) {
+ MOZ_ASSERT(aDirection == eDirNext || aDirection == eDirPrevious);
+ MOZ_ASSERT(aAmount == eSelectCharacter || aAmount == eSelectCluster ||
+ aAmount == eSelectWord || aAmount == eSelectBeginLine ||
+ aAmount == eSelectEndLine);
+ MOZ_ASSERT(aMovementStyle == eLogical || aMovementStyle == eVisual ||
+ aMovementStyle == eUsePrefStyle);
+
+ if (!mPresShell) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+ OwningNonNull<PresShell> presShell(*mPresShell);
+ presShell->FlushPendingNotifications(FlushType::Layout);
+ if (!mPresShell) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ Selection* selection =
+ mDomSelections[GetIndexFromSelectionType(SelectionType::eNormal)];
+ if (!selection || selection->RangeCount() != 1) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ RefPtr<const nsRange> firstRange = selection->GetRangeAt(0);
+ if (!firstRange || !firstRange->IsPositioned()) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ Result<PeekOffsetStruct, nsresult> result = PeekOffsetForCaretMove(
+ aDirection, true, aAmount, aMovementStyle, nsPoint(0, 0));
+ if (result.isErr()) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ const PeekOffsetStruct& pos = result.inspect();
+ RefPtr<RangeType> range;
+ if (NS_WARN_IF(!pos.mResultContent)) {
+ return range;
+ }
+ if (aDirection == eDirPrevious) {
+ range = RangeType::Create(
+ RawRangeBoundary(pos.mResultContent, pos.mContentOffset),
+ firstRange->EndRef(), IgnoreErrors());
+ } else {
+ range = RangeType::Create(
+ firstRange->StartRef(),
+ RawRangeBoundary(pos.mResultContent, pos.mContentOffset),
+ IgnoreErrors());
+ }
+ return range;
+}
+
+//////////END FRAMESELECTION
+
+LazyLogModule gBatchLog("SelectionBatch");
+
+void nsFrameSelection::StartBatchChanges(const char* aRequesterFuncName) {
+ MOZ_LOG(gBatchLog, LogLevel::Info,
+ ("%p%snsFrameSelection::StartBatchChanges(%s)", this,
+ std::string((mBatching.mCounter + 1) * 2, ' ').c_str(),
+ aRequesterFuncName));
+ mBatching.mCounter++;
+}
+
+void nsFrameSelection::EndBatchChanges(const char* aRequesterFuncName,
+ int16_t aReasons) {
+ MOZ_LOG(gBatchLog, LogLevel::Info,
+ ("%p%snsFrameSelection::EndBatchChanges (%s, %s)", this,
+ std::string(mBatching.mCounter * 2, ' ').c_str(), aRequesterFuncName,
+ SelectionChangeReasonsToCString(aReasons).get()));
+ MOZ_ASSERT(mBatching.mCounter > 0, "Bad mBatching.mCounter");
+ mBatching.mCounter--;
+
+ if (mBatching.mCounter == 0 && mBatching.mChangesDuringBatching) {
+ AddChangeReasons(aReasons);
+ mCaretMoveAmount = eSelectNoAmount;
+ mBatching.mChangesDuringBatching = false;
+ // Be aware, the Selection instance may be destroyed after this call.
+ NotifySelectionListeners(SelectionType::eNormal);
+ }
+}
+
+nsresult nsFrameSelection::NotifySelectionListeners(
+ SelectionType aSelectionType) {
+ int8_t index = GetIndexFromSelectionType(aSelectionType);
+ if (index >= 0 && mDomSelections[index]) {
+ RefPtr<Selection> selection = mDomSelections[index];
+ selection->NotifySelectionListeners();
+ mCaretMoveAmount = eSelectNoAmount;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+// Start of Table Selection methods
+
+static bool IsCell(nsIContent* aContent) {
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
+}
+
+// static
+nsITableCellLayout* nsFrameSelection::GetCellLayout(
+ const nsIContent* aCellContent) {
+ nsITableCellLayout* cellLayoutObject =
+ do_QueryFrame(aCellContent->GetPrimaryFrame());
+ return cellLayoutObject;
+}
+
+nsresult nsFrameSelection::ClearNormalSelection() {
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ RefPtr<Selection> selection = mDomSelections[index];
+ if (!selection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ ErrorResult err;
+ selection->RemoveAllRanges(err);
+ return err.StealNSResult();
+}
+
+static nsIContent* GetFirstSelectedContent(const nsRange* aRange) {
+ if (!aRange) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(aRange->GetStartContainer(), "Must have start parent!");
+ MOZ_ASSERT(aRange->GetStartContainer()->IsElement(), "Unexpected parent");
+
+ return aRange->GetChildAtStartOffset();
+}
+
+// Table selection support.
+// TODO: Separate table methods into a separate nsITableSelection interface
+nsresult nsFrameSelection::HandleTableSelection(nsINode* aParentContent,
+ int32_t aContentOffset,
+ TableSelectionMode aTarget,
+ WidgetMouseEvent* aMouseEvent) {
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ RefPtr<Selection> selection = mDomSelections[index];
+ if (!selection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return mTableSelection.HandleSelection(aParentContent, aContentOffset,
+ aTarget, aMouseEvent, mDragState,
+ *selection);
+}
+
+nsresult nsFrameSelection::TableSelection::HandleSelection(
+ nsINode* aParentContent, int32_t aContentOffset, TableSelectionMode aTarget,
+ WidgetMouseEvent* aMouseEvent, bool aDragState,
+ Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER);
+
+ if (aDragState && mDragSelectingCells &&
+ aTarget == TableSelectionMode::Table) {
+ // We were selecting cells and user drags mouse in table border or inbetween
+ // cells,
+ // just do nothing
+ return NS_OK;
+ }
+
+ RefPtr<nsIContent> childContent =
+ aParentContent->GetChildAt_Deprecated(aContentOffset);
+
+ // When doing table selection, always set the direction to next so
+ // we can be sure that anchorNode's offset always points to the
+ // selected cell
+ aNormalSelection.SetDirection(eDirNext);
+
+ // Stack-class to wrap all table selection changes in
+ // BeginBatchChanges() / EndBatchChanges()
+ SelectionBatcher selectionBatcher(&aNormalSelection, __FUNCTION__);
+
+ if (aDragState && mDragSelectingCells) {
+ return HandleDragSelecting(aTarget, childContent, aMouseEvent,
+ aNormalSelection);
+ }
+
+ return HandleMouseUpOrDown(aTarget, aDragState, childContent, aParentContent,
+ aContentOffset, aMouseEvent, aNormalSelection);
+}
+
+class nsFrameSelection::TableSelection::RowAndColumnRelation {
+ public:
+ static Result<RowAndColumnRelation, nsresult> Create(
+ const nsIContent* aFirst, const nsIContent* aSecond) {
+ RowAndColumnRelation result;
+
+ nsresult errorResult =
+ GetCellIndexes(aFirst, result.mFirst.mRow, result.mFirst.mColumn);
+ if (NS_FAILED(errorResult)) {
+ return Err(errorResult);
+ }
+
+ errorResult =
+ GetCellIndexes(aSecond, result.mSecond.mRow, result.mSecond.mColumn);
+ if (NS_FAILED(errorResult)) {
+ return Err(errorResult);
+ }
+
+ return result;
+ }
+
+ bool IsSameColumn() const { return mFirst.mColumn == mSecond.mColumn; }
+
+ bool IsSameRow() const { return mFirst.mRow == mSecond.mRow; }
+
+ private:
+ RowAndColumnRelation() = default;
+
+ struct RowAndColumn {
+ int32_t mRow = 0;
+ int32_t mColumn = 0;
+ };
+
+ RowAndColumn mFirst;
+ RowAndColumn mSecond;
+};
+
+nsresult nsFrameSelection::TableSelection::HandleDragSelecting(
+ TableSelectionMode aTarget, nsIContent* aChildContent,
+ const WidgetMouseEvent* aMouseEvent, Selection& aNormalSelection) {
+ // We are drag-selecting
+ if (aTarget != TableSelectionMode::Table) {
+ // If dragging in the same cell as last event, do nothing
+ if (mEndSelectedCell == aChildContent) {
+ return NS_OK;
+ }
+
+#ifdef DEBUG_TABLE_SELECTION
+ printf(
+ " mStartSelectedCell = %p, "
+ "mEndSelectedCell = %p, aChildContent = %p "
+ "\n",
+ mStartSelectedCell.get(), mEndSelectedCell.get(), aChildContent);
+#endif
+ // aTarget can be any "cell mode",
+ // so we can easily drag-select rows and columns
+ // Once we are in row or column mode,
+ // we can drift into any cell to stay in that mode
+ // even if aTarget = TableSelectionMode::Cell
+
+ if (mMode == TableSelectionMode::Row ||
+ mMode == TableSelectionMode::Column) {
+ if (mEndSelectedCell) {
+ Result<RowAndColumnRelation, nsresult> rowAndColumnRelation =
+ RowAndColumnRelation::Create(mEndSelectedCell, aChildContent);
+
+ if (rowAndColumnRelation.isErr()) {
+ return rowAndColumnRelation.unwrapErr();
+ }
+
+ if ((mMode == TableSelectionMode::Row &&
+ rowAndColumnRelation.inspect().IsSameRow()) ||
+ (mMode == TableSelectionMode::Column &&
+ rowAndColumnRelation.inspect().IsSameColumn())) {
+ return NS_OK;
+ }
+ }
+#ifdef DEBUG_TABLE_SELECTION
+ printf(" Dragged into a new column or row\n");
+#endif
+ // Continue dragging row or column selection
+
+ return SelectRowOrColumn(aChildContent, aNormalSelection);
+ }
+ if (mMode == TableSelectionMode::Cell) {
+#ifdef DEBUG_TABLE_SELECTION
+ printf("HandleTableSelection: Dragged into a new cell\n");
+#endif
+ // Trick for quick selection of rows and columns
+ // Hold down shift, then start selecting in one direction
+ // If next cell dragged into is in same row, select entire row,
+ // if next cell is in same column, select entire column
+ if (mStartSelectedCell && aMouseEvent->IsShift()) {
+ Result<RowAndColumnRelation, nsresult> rowAndColumnRelation =
+ RowAndColumnRelation::Create(mStartSelectedCell, aChildContent);
+ if (rowAndColumnRelation.isErr()) {
+ return rowAndColumnRelation.unwrapErr();
+ }
+
+ if (rowAndColumnRelation.inspect().IsSameRow() ||
+ rowAndColumnRelation.inspect().IsSameColumn()) {
+ // Force new selection block
+ mStartSelectedCell = nullptr;
+ aNormalSelection.RemoveAllRanges(IgnoreErrors());
+
+ if (rowAndColumnRelation.inspect().IsSameRow()) {
+ mMode = TableSelectionMode::Row;
+ } else {
+ mMode = TableSelectionMode::Column;
+ }
+
+ return SelectRowOrColumn(aChildContent, aNormalSelection);
+ }
+ }
+
+ // Reselect block of cells to new end location
+ return SelectBlockOfCells(mStartSelectedCell, aChildContent,
+ aNormalSelection);
+ }
+ }
+ // Do nothing if dragging in table, but outside a cell
+ return NS_OK;
+}
+
+nsresult nsFrameSelection::TableSelection::HandleMouseUpOrDown(
+ TableSelectionMode aTarget, bool aDragState, nsIContent* aChildContent,
+ nsINode* aParentContent, int32_t aContentOffset,
+ const WidgetMouseEvent* aMouseEvent, Selection& aNormalSelection) {
+ nsresult result = NS_OK;
+ // Not dragging -- mouse event is down or up
+ if (aDragState) {
+#ifdef DEBUG_TABLE_SELECTION
+ printf("HandleTableSelection: Mouse down event\n");
+#endif
+ // Clear cell we stored in mouse-down
+ mUnselectCellOnMouseUp = nullptr;
+
+ if (aTarget == TableSelectionMode::Cell) {
+ bool isSelected = false;
+
+ // Check if we have other selected cells
+ nsIContent* previousCellNode =
+ GetFirstSelectedContent(GetFirstCellRange(aNormalSelection));
+ if (previousCellNode) {
+ // We have at least 1 other selected cell
+
+ // Check if new cell is already selected
+ nsIFrame* cellFrame = aChildContent->GetPrimaryFrame();
+ if (!cellFrame) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ isSelected = cellFrame->IsSelected();
+ } else {
+ // No cells selected -- remove non-cell selection
+ aNormalSelection.RemoveAllRanges(IgnoreErrors());
+ }
+ mDragSelectingCells = true; // Signal to start drag-cell-selection
+ mMode = aTarget;
+ // Set start for new drag-selection block (not appended)
+ mStartSelectedCell = aChildContent;
+ // The initial block end is same as the start
+ mEndSelectedCell = aChildContent;
+
+ if (isSelected) {
+ // Remember this cell to (possibly) unselect it on mouseup
+ mUnselectCellOnMouseUp = aChildContent;
+#ifdef DEBUG_TABLE_SELECTION
+ printf(
+ "HandleTableSelection: Saving "
+ "mUnselectCellOnMouseUp\n");
+#endif
+ } else {
+ // Select an unselected cell
+ // but first remove existing selection if not in same table
+ if (previousCellNode &&
+ !IsInSameTable(previousCellNode, aChildContent)) {
+ aNormalSelection.RemoveAllRanges(IgnoreErrors());
+ // Reset selection mode that is cleared in RemoveAllRanges
+ mMode = aTarget;
+ }
+
+ return ::SelectCellElement(aChildContent, aNormalSelection);
+ }
+
+ return NS_OK;
+ }
+ if (aTarget == TableSelectionMode::Table) {
+ // TODO: We currently select entire table when clicked between cells,
+ // should we restrict to only around border?
+ // *** How do we get location data for cell and click?
+ mDragSelectingCells = false;
+ mStartSelectedCell = nullptr;
+ mEndSelectedCell = nullptr;
+
+ // Remove existing selection and select the table
+ aNormalSelection.RemoveAllRanges(IgnoreErrors());
+ return CreateAndAddRange(aParentContent, aContentOffset,
+ aNormalSelection);
+ }
+ if (aTarget == TableSelectionMode::Row ||
+ aTarget == TableSelectionMode::Column) {
+#ifdef DEBUG_TABLE_SELECTION
+ printf("aTarget == %d\n", aTarget);
+#endif
+
+ // Start drag-selecting mode so multiple rows/cols can be selected
+ // Note: Currently, nsIFrame::GetDataForTableSelection
+ // will never call us for row or column selection on mouse down
+ mDragSelectingCells = true;
+
+ // Force new selection block
+ mStartSelectedCell = nullptr;
+ aNormalSelection.RemoveAllRanges(IgnoreErrors());
+ // Always do this AFTER RemoveAllRanges
+ mMode = aTarget;
+
+ return SelectRowOrColumn(aChildContent, aNormalSelection);
+ }
+ } else {
+#ifdef DEBUG_TABLE_SELECTION
+ printf(
+ "HandleTableSelection: Mouse UP event. "
+ "mDragSelectingCells=%d, "
+ "mStartSelectedCell=%p\n",
+ mDragSelectingCells, mStartSelectedCell.get());
+#endif
+ // First check if we are extending a block selection
+ const uint32_t rangeCount = aNormalSelection.RangeCount();
+
+ if (rangeCount > 0 && aMouseEvent->IsShift() && mAppendStartSelectedCell &&
+ mAppendStartSelectedCell != aChildContent) {
+ // Shift key is down: append a block selection
+ mDragSelectingCells = false;
+
+ return SelectBlockOfCells(mAppendStartSelectedCell, aChildContent,
+ aNormalSelection);
+ }
+
+ if (mDragSelectingCells) {
+ mAppendStartSelectedCell = mStartSelectedCell;
+ }
+
+ mDragSelectingCells = false;
+ mStartSelectedCell = nullptr;
+ mEndSelectedCell = nullptr;
+
+ // Any other mouseup actions require that Ctrl or Cmd key is pressed
+ // else stop table selection mode
+ bool doMouseUpAction = false;
+#ifdef XP_MACOSX
+ doMouseUpAction = aMouseEvent->IsMeta();
+#else
+ doMouseUpAction = aMouseEvent->IsControl();
+#endif
+ if (!doMouseUpAction) {
+#ifdef DEBUG_TABLE_SELECTION
+ printf(
+ "HandleTableSelection: Ending cell selection on mouseup: "
+ "mAppendStartSelectedCell=%p\n",
+ mAppendStartSelectedCell.get());
+#endif
+ return NS_OK;
+ }
+ // Unselect a cell only if it wasn't
+ // just selected on mousedown
+ if (aChildContent == mUnselectCellOnMouseUp) {
+ // Scan ranges to find the cell to unselect (the selection range to
+ // remove)
+ // XXXbz it's really weird that this lives outside the loop, so once we
+ // find one we keep looking at it even if we find no more cells...
+ nsINode* previousCellParent = nullptr;
+#ifdef DEBUG_TABLE_SELECTION
+ printf(
+ "HandleTableSelection: Unselecting "
+ "mUnselectCellOnMouseUp; "
+ "rangeCount=%d\n",
+ rangeCount);
+#endif
+ for (const uint32_t i : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(aNormalSelection.RangeCount() == rangeCount);
+ // Strong reference, because sometimes we want to remove
+ // this range, and then we might be the only owner.
+ RefPtr<nsRange> range = aNormalSelection.GetRangeAt(i);
+ if (MOZ_UNLIKELY(!range)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsINode* container = range->GetStartContainer();
+ if (!container) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ int32_t offset = range->StartOffset();
+ // Be sure previous selection is a table cell
+ nsIContent* child = range->GetChildAtStartOffset();
+ if (child && IsCell(child)) {
+ previousCellParent = container;
+ }
+
+ // We're done if we didn't find parent of a previously-selected cell
+ if (!previousCellParent) {
+ break;
+ }
+
+ if (previousCellParent == aParentContent && offset == aContentOffset) {
+ // Cell is already selected
+ if (rangeCount == 1) {
+#ifdef DEBUG_TABLE_SELECTION
+ printf("HandleTableSelection: Unselecting single selected cell\n");
+#endif
+ // This was the only cell selected.
+ // Collapse to "normal" selection inside the cell
+ mStartSelectedCell = nullptr;
+ mEndSelectedCell = nullptr;
+ mAppendStartSelectedCell = nullptr;
+ // TODO: We need a "Collapse to just before deepest child" routine
+ // Even better, should we collapse to just after the LAST deepest
+ // child
+ // (i.e., at the end of the cell's contents)?
+ return aNormalSelection.CollapseInLimiter(aChildContent, 0);
+ }
+#ifdef DEBUG_TABLE_SELECTION
+ printf(
+ "HandleTableSelection: Removing cell from multi-cell "
+ "selection\n");
+#endif
+ // Unselecting the start of previous block
+ // XXX What do we use now!
+ if (aChildContent == mAppendStartSelectedCell) {
+ mAppendStartSelectedCell = nullptr;
+ }
+
+ // Deselect cell by removing its range from selection
+ ErrorResult err;
+ aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
+ *range, err);
+ return err.StealNSResult();
+ }
+ }
+ mUnselectCellOnMouseUp = nullptr;
+ }
+ }
+ return result;
+}
+
+nsresult nsFrameSelection::TableSelection::SelectBlockOfCells(
+ nsIContent* aStartCell, nsIContent* aEndCell, Selection& aNormalSelection) {
+ NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER);
+ mEndSelectedCell = aEndCell;
+
+ nsresult result = NS_OK;
+
+ // If new end cell is in a different table, do nothing
+ const RefPtr<const nsIContent> table = IsInSameTable(aStartCell, aEndCell);
+ if (!table) {
+ return NS_OK;
+ }
+
+ // Get starting and ending cells' location in the cellmap
+ int32_t startRowIndex, startColIndex, endRowIndex, endColIndex;
+ result = GetCellIndexes(aStartCell, startRowIndex, startColIndex);
+ if (NS_FAILED(result)) return result;
+ result = GetCellIndexes(aEndCell, endRowIndex, endColIndex);
+ if (NS_FAILED(result)) return result;
+
+ if (mDragSelectingCells) {
+ // Drag selecting: remove selected cells outside of new block limits
+ // TODO: `UnselectCells`'s return value shouldn't be ignored.
+ UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex,
+ true, aNormalSelection);
+ }
+
+ // Note that we select block in the direction of user's mouse dragging,
+ // which means start cell may be after the end cell in either row or column
+ return AddCellsToSelection(table, startRowIndex, startColIndex, endRowIndex,
+ endColIndex, aNormalSelection);
+}
+
+nsresult nsFrameSelection::TableSelection::UnselectCells(
+ const nsIContent* aTableContent, int32_t aStartRowIndex,
+ int32_t aStartColumnIndex, int32_t aEndRowIndex, int32_t aEndColumnIndex,
+ bool aRemoveOutsideOfCellRange, mozilla::dom::Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ nsTableWrapperFrame* tableFrame =
+ do_QueryFrame(aTableContent->GetPrimaryFrame());
+ if (!tableFrame) return NS_ERROR_FAILURE;
+
+ int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex);
+ int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex);
+ int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex);
+ int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex);
+
+ // Strong reference because we sometimes remove the range
+ RefPtr<nsRange> range = GetFirstCellRange(aNormalSelection);
+ nsIContent* cellNode = GetFirstSelectedContent(range);
+ MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range");
+
+ int32_t curRowIndex, curColIndex;
+ while (cellNode) {
+ nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
+ if (NS_FAILED(result)) return result;
+
+#ifdef DEBUG_TABLE_SELECTION
+ if (!range) printf("RemoveCellsToSelection -- range is null\n");
+#endif
+
+ if (range) {
+ if (aRemoveOutsideOfCellRange) {
+ if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex ||
+ curColIndex < minColIndex || curColIndex > maxColIndex) {
+ aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
+ *range, IgnoreErrors());
+ // Since we've removed the range, decrement pointer to next range
+ mSelectedCellIndex--;
+ }
+
+ } else {
+ // Remove cell from selection if it belongs to the given cells range or
+ // it is spanned onto the cells range.
+ nsTableCellFrame* cellFrame =
+ tableFrame->GetCellFrameAt(curRowIndex, curColIndex);
+
+ uint32_t origRowIndex = cellFrame->RowIndex();
+ uint32_t origColIndex = cellFrame->ColIndex();
+ uint32_t actualRowSpan =
+ tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex);
+ uint32_t actualColSpan =
+ tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex);
+ if (origRowIndex <= static_cast<uint32_t>(maxRowIndex) &&
+ maxRowIndex >= 0 &&
+ origRowIndex + actualRowSpan - 1 >=
+ static_cast<uint32_t>(minRowIndex) &&
+ origColIndex <= static_cast<uint32_t>(maxColIndex) &&
+ maxColIndex >= 0 &&
+ origColIndex + actualColSpan - 1 >=
+ static_cast<uint32_t>(minColIndex)) {
+ aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
+ *range, IgnoreErrors());
+ // Since we've removed the range, decrement pointer to next range
+ mSelectedCellIndex--;
+ }
+ }
+ }
+
+ range = GetNextCellRange(aNormalSelection);
+ cellNode = GetFirstSelectedContent(range);
+ MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range");
+ }
+
+ return NS_OK;
+}
+
+nsresult SelectCellElement(nsIContent* aCellElement,
+ Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ nsIContent* parent = aCellElement->GetParent();
+
+ // Get child offset
+ const int32_t offset = parent->ComputeIndexOf_Deprecated(aCellElement);
+
+ return CreateAndAddRange(parent, offset, aNormalSelection);
+}
+
+static nsresult AddCellsToSelection(const nsIContent* aTableContent,
+ int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex,
+ Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ nsTableWrapperFrame* tableFrame =
+ do_QueryFrame(aTableContent->GetPrimaryFrame());
+ if (!tableFrame) { // Check that |table| is a table.
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult result = NS_OK;
+ uint32_t row = aStartRowIndex;
+ while (true) {
+ uint32_t col = aStartColumnIndex;
+ while (true) {
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col);
+
+ // Skip cells that are spanned from previous locations or are already
+ // selected
+ if (cellFrame) {
+ uint32_t origRow = cellFrame->RowIndex();
+ uint32_t origCol = cellFrame->ColIndex();
+ if (origRow == row && origCol == col && !cellFrame->IsSelected()) {
+ result = SelectCellElement(cellFrame->GetContent(), aNormalSelection);
+ if (NS_FAILED(result)) {
+ return result;
+ }
+ }
+ }
+ // Done when we reach end column
+ if (col == static_cast<uint32_t>(aEndColumnIndex)) {
+ break;
+ }
+
+ if (aStartColumnIndex < aEndColumnIndex) {
+ col++;
+ } else {
+ col--;
+ }
+ }
+ if (row == static_cast<uint32_t>(aEndRowIndex)) {
+ break;
+ }
+
+ if (aStartRowIndex < aEndRowIndex) {
+ row++;
+ } else {
+ row--;
+ }
+ }
+ return result;
+}
+
+nsresult nsFrameSelection::RemoveCellsFromSelection(nsIContent* aTable,
+ int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex) {
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ const RefPtr<mozilla::dom::Selection> selection = mDomSelections[index];
+ if (!selection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return mTableSelection.UnselectCells(aTable, aStartRowIndex,
+ aStartColumnIndex, aEndRowIndex,
+ aEndColumnIndex, false, *selection);
+}
+
+nsresult nsFrameSelection::RestrictCellsToSelection(nsIContent* aTable,
+ int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex) {
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ const RefPtr<mozilla::dom::Selection> selection = mDomSelections[index];
+ if (!selection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return mTableSelection.UnselectCells(aTable, aStartRowIndex,
+ aStartColumnIndex, aEndRowIndex,
+ aEndColumnIndex, true, *selection);
+}
+
+Result<nsFrameSelection::TableSelection::FirstAndLastCell, nsresult>
+nsFrameSelection::TableSelection::FindFirstAndLastCellOfRowOrColumn(
+ const nsIContent& aCellContent) const {
+ const nsIContent* table = GetParentTable(&aCellContent);
+ if (!table) {
+ return Err(NS_ERROR_NULL_POINTER);
+ }
+
+ // Get table and cell layout interfaces to access
+ // cell data based on cellmap location
+ // Frames are not ref counted, so don't use an nsCOMPtr
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame());
+ if (!tableFrame) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ nsITableCellLayout* cellLayout = GetCellLayout(&aCellContent);
+ if (!cellLayout) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ // Get location of target cell:
+ int32_t rowIndex, colIndex;
+ nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex);
+ if (NS_FAILED(result)) {
+ return Err(result);
+ }
+
+ // Be sure we start at proper beginning
+ // (This allows us to select row or col given ANY cell!)
+ if (mMode == TableSelectionMode::Row) {
+ colIndex = 0;
+ }
+ if (mMode == TableSelectionMode::Column) {
+ rowIndex = 0;
+ }
+
+ FirstAndLastCell firstAndLastCell;
+ while (true) {
+ // Loop through all cells in column or row to find first and last
+ nsCOMPtr<nsIContent> curCellContent =
+ tableFrame->GetCellAt(rowIndex, colIndex);
+ if (!curCellContent) {
+ break;
+ }
+
+ if (!firstAndLastCell.mFirst) {
+ firstAndLastCell.mFirst = curCellContent;
+ }
+
+ firstAndLastCell.mLast = std::move(curCellContent);
+
+ // Move to next cell in cellmap, skipping spanned locations
+ if (mMode == TableSelectionMode::Row) {
+ colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
+ } else {
+ rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
+ }
+ }
+ return firstAndLastCell;
+}
+
+nsresult nsFrameSelection::TableSelection::SelectRowOrColumn(
+ nsIContent* aCellContent, Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ if (!aCellContent) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ Result<FirstAndLastCell, nsresult> firstAndLastCell =
+ FindFirstAndLastCellOfRowOrColumn(*aCellContent);
+ if (firstAndLastCell.isErr()) {
+ return firstAndLastCell.unwrapErr();
+ }
+
+ // Use SelectBlockOfCells:
+ // This will replace existing selection,
+ // but allow unselecting by dragging out of selected region
+ if (firstAndLastCell.inspect().mFirst && firstAndLastCell.inspect().mLast) {
+ nsresult rv{NS_OK};
+
+ if (!mStartSelectedCell) {
+ // We are starting a new block, so select the first cell
+ rv = ::SelectCellElement(firstAndLastCell.inspect().mFirst,
+ aNormalSelection);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mStartSelectedCell = firstAndLastCell.inspect().mFirst;
+ }
+
+ rv = SelectBlockOfCells(mStartSelectedCell,
+ firstAndLastCell.inspect().mLast, aNormalSelection);
+
+ // This gets set to the cell at end of row/col,
+ // but we need it to be the cell under cursor
+ mEndSelectedCell = aCellContent;
+ return rv;
+ }
+
+#if 0
+// This is a more efficient strategy that appends row to current selection,
+// but doesn't allow dragging OFF of an existing selection to unselect!
+ do {
+ // Loop through all cells in column or row
+ result = tableLayout->GetCellDataAt(rowIndex, colIndex,
+ getter_AddRefs(cellElement),
+ curRowIndex, curColIndex,
+ rowSpan, colSpan,
+ actualRowSpan, actualColSpan,
+ isSelected);
+ if (NS_FAILED(result)) return result;
+ // We're done when cell is not found
+ if (!cellElement) break;
+
+
+ // Check spans else we infinitely loop
+ NS_ASSERTION(actualColSpan, "actualColSpan is 0!");
+ NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!");
+
+ // Skip cells that are already selected or span from outside our region
+ if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex)
+ {
+ result = SelectCellElement(cellElement);
+ if (NS_FAILED(result)) return result;
+ }
+ // Move to next row or column in cellmap, skipping spanned locations
+ if (mMode == TableSelectionMode::Row)
+ colIndex += actualColSpan;
+ else
+ rowIndex += actualRowSpan;
+ }
+ while (cellElement);
+#endif
+
+ return NS_OK;
+}
+
+// static
+nsIContent* nsFrameSelection::GetFirstCellNodeInRange(const nsRange* aRange) {
+ if (!aRange) return nullptr;
+
+ nsIContent* childContent = aRange->GetChildAtStartOffset();
+ if (!childContent) return nullptr;
+ // Don't return node if not a cell
+ if (!IsCell(childContent)) return nullptr;
+
+ return childContent;
+}
+
+nsRange* nsFrameSelection::TableSelection::GetFirstCellRange(
+ const mozilla::dom::Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ nsRange* firstRange = aNormalSelection.GetRangeAt(0);
+ if (!GetFirstCellNodeInRange(firstRange)) {
+ return nullptr;
+ }
+
+ // Setup for next cell
+ mSelectedCellIndex = 1;
+
+ return firstRange;
+}
+
+nsRange* nsFrameSelection::TableSelection::GetNextCellRange(
+ const mozilla::dom::Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ nsRange* range =
+ aNormalSelection.GetRangeAt(AssertedCast<uint32_t>(mSelectedCellIndex));
+
+ // Get first node in next range of selection - test if it's a cell
+ if (!GetFirstCellNodeInRange(range)) {
+ return nullptr;
+ }
+
+ // Setup for next cell
+ mSelectedCellIndex++;
+
+ return range;
+}
+
+// static
+nsresult nsFrameSelection::GetCellIndexes(const nsIContent* aCell,
+ int32_t& aRowIndex,
+ int32_t& aColIndex) {
+ if (!aCell) return NS_ERROR_NULL_POINTER;
+
+ aColIndex = 0; // initialize out params
+ aRowIndex = 0;
+
+ nsITableCellLayout* cellLayoutObject = GetCellLayout(aCell);
+ if (!cellLayoutObject) return NS_ERROR_FAILURE;
+ return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
+}
+
+// static
+nsIContent* nsFrameSelection::IsInSameTable(const nsIContent* aContent1,
+ const nsIContent* aContent2) {
+ if (!aContent1 || !aContent2) return nullptr;
+
+ nsIContent* tableNode1 = GetParentTable(aContent1);
+ nsIContent* tableNode2 = GetParentTable(aContent2);
+
+ // Must be in the same table. Note that we want to return false for
+ // the test if both tables are null.
+ return (tableNode1 == tableNode2) ? tableNode1 : nullptr;
+}
+
+// static
+nsIContent* nsFrameSelection::GetParentTable(const nsIContent* aCell) {
+ if (!aCell) {
+ return nullptr;
+ }
+
+ for (nsIContent* parent = aCell->GetParent(); parent;
+ parent = parent->GetParent()) {
+ if (parent->IsHTMLElement(nsGkAtoms::table)) {
+ return parent;
+ }
+ }
+
+ return nullptr;
+}
+
+nsresult nsFrameSelection::SelectCellElement(nsIContent* aCellElement) {
+ const int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ const RefPtr<Selection> selection = mDomSelections[index];
+ if (!selection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return ::SelectCellElement(aCellElement, *selection);
+}
+
+nsresult CreateAndAddRange(nsINode* aContainer, int32_t aOffset,
+ Selection& aNormalSelection) {
+ MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
+
+ if (!aContainer) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // Set range around child at given offset
+ ErrorResult error;
+ RefPtr<nsRange> range =
+ nsRange::Create(aContainer, aOffset, aContainer, aOffset + 1, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+ MOZ_ASSERT(range);
+
+ ErrorResult err;
+ aNormalSelection.AddRangeAndSelectFramesAndNotifyListeners(*range, err);
+ return err.StealNSResult();
+}
+
+// End of Table Selection
+
+void nsFrameSelection::SetAncestorLimiter(nsIContent* aLimiter) {
+ if (mLimiters.mAncestorLimiter != aLimiter) {
+ mLimiters.mAncestorLimiter = aLimiter;
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ if (!mDomSelections[index]) return;
+
+ if (!IsValidSelectionPoint(mDomSelections[index]->GetFocusNode())) {
+ ClearNormalSelection();
+ if (mLimiters.mAncestorLimiter) {
+ SetChangeReasons(nsISelectionListener::NO_REASON);
+ nsCOMPtr<nsIContent> limiter(mLimiters.mAncestorLimiter);
+ const nsresult rv =
+ TakeFocus(*limiter, 0, 0, CaretAssociationHint::Before,
+ FocusMode::kCollapseToNewPoint);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ // TODO: in case of failure, propagate it to the callers.
+ }
+ }
+ }
+}
+
+void nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent) {
+ if (aMouseEvent) {
+ mDelayedMouseEvent.mIsValid = true;
+ mDelayedMouseEvent.mIsShift = aMouseEvent->IsShift();
+ mDelayedMouseEvent.mClickCount = aMouseEvent->mClickCount;
+ } else {
+ mDelayedMouseEvent.mIsValid = false;
+ }
+}
+
+void nsFrameSelection::DisconnectFromPresShell() {
+ if (mAccessibleCaretEnabled) {
+ int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
+ mDomSelections[index]->StopNotifyingAccessibleCaretEventHub();
+ }
+
+ StopAutoScrollTimer();
+ for (size_t i = 0; i < ArrayLength(mDomSelections); i++) {
+ mDomSelections[i]->Clear(nullptr);
+ }
+ mPresShell = nullptr;
+}
+
+#ifdef XP_MACOSX
+/**
+ * See Bug 1288453.
+ *
+ * Update the selection cache on repaint to handle when a pre-existing
+ * selection becomes active aka the current selection.
+ *
+ * 1. Change the current selection by click n dragging another selection.
+ * - Make a selection on content page. Make a selection in a text editor.
+ * - You can click n drag the content selection to make it active again.
+ * 2. Change the current selection when switching to a tab with a selection.
+ * - Make selection in tab.
+ * - Switching tabs will make its respective selection active.
+ *
+ * Therefore, we only update the selection cache on a repaint
+ * if the current selection being repainted is not an empty selection.
+ *
+ * If the current selection is empty. The current selection cache
+ * would be cleared by AutoCopyListener::OnSelectionChange().
+ */
+static nsresult UpdateSelectionCacheOnRepaintSelection(Selection* aSel) {
+ PresShell* presShell = aSel->GetPresShell();
+ if (!presShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<Document> aDoc = presShell->GetDocument();
+
+ if (aDoc && aSel && !aSel->IsCollapsed()) {
+ return nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
+ aSel, aDoc, nsIClipboard::kSelectionCache, false);
+ }
+
+ return NS_OK;
+}
+#endif // XP_MACOSX
+
+// mozilla::AutoCopyListener
+
+int16_t AutoCopyListener::sClipboardID = -1;
+
+/*
+ * What we do now:
+ * On every selection change, we copy to the clipboard anew, creating a
+ * HTML buffer, a transferable, an nsISupportsString and
+ * a huge mess every time. This is basically what
+ * nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() does to move the
+ * selection into the clipboard for Edit->Copy.
+ *
+ * What we should do, to make our end of the deal faster:
+ * Create a singleton transferable with our own magic converter. When selection
+ * changes (use a quick cache to detect ``real'' changes), we put the new
+ * Selection in the transferable. Our magic converter will take care of
+ * transferable->whatever-other-format when the time comes to actually
+ * hand over the clipboard contents.
+ *
+ * Other issues:
+ * - which X clipboard should we populate?
+ * - should we use a different one than Edit->Copy, so that inadvertant
+ * selections (or simple clicks, which currently cause a selection
+ * notification, regardless of if they're in the document which currently has
+ * selection!) don't lose the contents of the ``application''? Or should we
+ * just put some intelligence in the ``is this a real selection?'' code to
+ * protect our selection against clicks in other documents that don't create
+ * selections?
+ * - maybe we should just never clear the X clipboard? That would make this
+ * problem just go away, which is very tempting.
+ *
+ * On macOS,
+ * nsIClipboard::kSelectionCache is the flag for current selection cache.
+ * Set the current selection cache on the parent process in
+ * widget cocoa nsClipboard whenever selection changes.
+ */
+
+// static
+void AutoCopyListener::OnSelectionChange(Document* aDocument,
+ Selection& aSelection,
+ int16_t aReason) {
+ MOZ_ASSERT(IsValidClipboardID(sClipboardID));
+
+ // For now, we should prevent any updates caused by a call of Selection API.
+ // We should allow this in some cases later, though. See the valid usage in
+ // bug 1567160.
+ if (aReason & nsISelectionListener::JS_REASON) {
+ return;
+ }
+
+ if (sClipboardID == nsIClipboard::kSelectionCache) {
+ // Do nothing if this isn't in the active window and,
+ // in the case of Web content, in the frontmost tab.
+ if (!aDocument || !IsInActiveTab(aDocument)) {
+ return;
+ }
+ }
+
+ static const int16_t kResasonsToHandle =
+ nsISelectionListener::MOUSEUP_REASON |
+ nsISelectionListener::SELECTALL_REASON |
+ nsISelectionListener::KEYPRESS_REASON;
+ if (!(aReason & kResasonsToHandle)) {
+ return; // Don't care if we are still dragging.
+ }
+
+ if (!aDocument || aSelection.IsCollapsed()) {
+#ifdef DEBUG_CLIPBOARD
+ fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n");
+#endif
+ if (sClipboardID != nsIClipboard::kSelectionCache) {
+ // XXX Should we clear X clipboard?
+ return;
+ }
+
+ // If on macOS, clear the current selection transferable cached
+ // on the parent process (nsClipboard) when the selection is empty.
+ DebugOnly<nsresult> rv = nsCopySupport::ClearSelectionCache();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "nsCopySupport::ClearSelectionCache() failed");
+ return;
+ }
+
+ DebugOnly<nsresult> rv =
+ nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
+ &aSelection, aDocument, sClipboardID, false);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed");
+}
diff --git a/layout/generic/nsFrameSelection.h b/layout/generic/nsFrameSelection.h
new file mode 100644
index 0000000000..e8c55d53b5
--- /dev/null
+++ b/layout/generic/nsFrameSelection.h
@@ -0,0 +1,1140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFrameSelection_h___
+#define nsFrameSelection_h___
+
+#include <stdint.h>
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/CompactPair.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/dom/Highlight.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/Result.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIFrame.h"
+#include "nsIContent.h"
+#include "nsISelectionController.h"
+#include "nsISelectionListener.h"
+#include "nsITableCellLayout.h"
+#include "WordMovementType.h"
+#include "nsBidiPresUtils.h"
+
+class nsRange;
+
+#define BIDI_LEVEL_UNDEFINED mozilla::intl::BidiEmbeddingLevel(0x80)
+
+//----------------------------------------------------------------------
+
+// Selection interface
+
+struct SelectionDetails {
+ SelectionDetails()
+ : mStart(), mEnd(), mSelectionType(mozilla::SelectionType::eInvalid) {
+ MOZ_COUNT_CTOR(SelectionDetails);
+ }
+ MOZ_COUNTED_DTOR(SelectionDetails)
+
+ int32_t mStart;
+ int32_t mEnd;
+ mozilla::SelectionType mSelectionType;
+ mozilla::dom::HighlightSelectionData mHighlightData;
+ mozilla::TextRangeStyle mTextRangeStyle;
+ mozilla::UniquePtr<SelectionDetails> mNext;
+};
+
+struct SelectionCustomColors {
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNTED_DEFAULT_CTOR(SelectionCustomColors)
+ MOZ_COUNTED_DTOR(SelectionCustomColors)
+#endif
+ mozilla::Maybe<nscolor> mForegroundColor;
+ mozilla::Maybe<nscolor> mBackgroundColor;
+ mozilla::Maybe<nscolor> mAltForegroundColor;
+ mozilla::Maybe<nscolor> mAltBackgroundColor;
+};
+
+namespace mozilla {
+class PresShell;
+
+/** PeekOffsetStruct is used to group various arguments (both input and output)
+ * that are passed to nsIFrame::PeekOffset(). See below for the description of
+ * individual arguments.
+ */
+enum class PeekOffsetOption : uint16_t {
+ // Whether to allow jumping across line boundaries.
+ //
+ // Used with: eSelectCharacter, eSelectWord.
+ JumpLines,
+
+ // Whether we should preserve or trim spaces at begin/end of content
+ PreserveSpaces,
+
+ // Whether to stop when reaching a scroll view boundary.
+ //
+ // Used with: eSelectCharacter, eSelectWord, eSelectLine.
+ StopAtScroller,
+
+ // Whether to stop when reaching a placeholder frame.
+ StopAtPlaceholder,
+
+ // Whether the peeking is done in response to a keyboard action.
+ //
+ // Used with: eSelectWord.
+ IsKeyboardSelect,
+
+ // Whether bidi caret behavior is visual (set) or logical (unset).
+ //
+ // Used with: eSelectCharacter, eSelectWord, eSelectBeginLine, eSelectEndLine.
+ Visual,
+
+ // Whether the selection is being extended or moved.
+ Extend,
+
+ // If true, the offset has to end up in an editable node, otherwise we'll keep
+ // searching.
+ ForceEditableRegion,
+};
+
+using PeekOffsetOptions = EnumSet<PeekOffsetOption>;
+
+struct MOZ_STACK_CLASS PeekOffsetStruct {
+ PeekOffsetStruct(nsSelectionAmount aAmount, nsDirection aDirection,
+ int32_t aStartOffset, nsPoint aDesiredCaretPos,
+ // Passing by value here is intentional because EnumSet
+ // is optimized as uint*_t in opt builds.
+ const PeekOffsetOptions aOptions,
+ EWordMovementType aWordMovementType = eDefaultBehavior);
+
+ // Note: Most arguments (input and output) are only used with certain values
+ // of mAmount. These values are indicated for each argument below.
+ // Arguments with no such indication are used with all values of mAmount.
+
+ /*** Input arguments ***/
+ // Note: The value of some of the input arguments may be changed upon exit.
+
+ // The type of movement requested (by character, word, line, etc.)
+ nsSelectionAmount mAmount;
+
+ // eDirPrevious or eDirNext.
+ //
+ // Note for visual bidi movement:
+ // * eDirPrevious means 'left-then-up' if the containing block is LTR,
+ // 'right-then-up' if it is RTL.
+ // * eDirNext means 'right-then-down' if the containing block is LTR,
+ // 'left-then-down' if it is RTL.
+ // * Between paragraphs, eDirPrevious means "go to the visual end of
+ // the previous paragraph", and eDirNext means "go to the visual
+ // beginning of the next paragraph".
+ //
+ // Used with: eSelectCharacter, eSelectWord, eSelectLine, eSelectParagraph.
+ const nsDirection mDirection;
+
+ // Offset into the content of the current frame where the peek starts.
+ //
+ // Used with: eSelectCharacter, eSelectWord
+ int32_t mStartOffset;
+
+ // The desired inline coordinate for the caret (one of .x or .y will be used,
+ // depending on line's writing mode)
+ //
+ // Used with: eSelectLine.
+ const nsPoint mDesiredCaretPos;
+
+ // An enum that determines whether to prefer the start or end of a word or to
+ // use the default behavior, which is a combination of direction and the
+ // platform-based pref "layout.word_select.eat_space_to_next_word"
+ EWordMovementType mWordMovementType;
+
+ PeekOffsetOptions mOptions;
+
+ /*** Output arguments ***/
+
+ // Content reached as a result of the peek.
+ nsCOMPtr<nsIContent> mResultContent;
+
+ // Frame reached as a result of the peek.
+ //
+ // Used with: eSelectCharacter, eSelectWord.
+ nsIFrame* mResultFrame;
+
+ // Offset into content reached as a result of the peek.
+ int32_t mContentOffset;
+
+ // When the result position is between two frames, indicates which of the two
+ // frames the caret should be painted in. false means "the end of the frame
+ // logically before the caret", true means "the beginning of the frame
+ // logically after the caret".
+ //
+ // Used with: eSelectLine, eSelectBeginLine, eSelectEndLine.
+ CaretAssociationHint mAttach;
+};
+
+} // namespace mozilla
+
+struct nsPrevNextBidiLevels {
+ void SetData(nsIFrame* aFrameBefore, nsIFrame* aFrameAfter,
+ mozilla::intl::BidiEmbeddingLevel aLevelBefore,
+ mozilla::intl::BidiEmbeddingLevel aLevelAfter) {
+ mFrameBefore = aFrameBefore;
+ mFrameAfter = aFrameAfter;
+ mLevelBefore = aLevelBefore;
+ mLevelAfter = aLevelAfter;
+ }
+ nsIFrame* mFrameBefore;
+ nsIFrame* mFrameAfter;
+ mozilla::intl::BidiEmbeddingLevel mLevelBefore;
+ mozilla::intl::BidiEmbeddingLevel mLevelAfter;
+};
+
+namespace mozilla {
+class SelectionChangeEventDispatcher;
+namespace dom {
+class Highlight;
+class Selection;
+} // namespace dom
+
+/**
+ * Constants for places that want to handle table selections. These
+ * indicate what part of a table is being selected.
+ */
+enum class TableSelectionMode : uint32_t {
+ None, /* Nothing being selected; not valid in all cases. */
+ Cell, /* A cell is being selected. */
+ Row, /* A row is being selected. */
+ Column, /* A column is being selected. */
+ Table, /* A table (including cells and captions) is being selected. */
+ AllCells, /* All the cells in a table are being selected. */
+};
+
+} // namespace mozilla
+class nsIScrollableFrame;
+
+class nsFrameSelection final {
+ public:
+ using CaretAssociationHint = mozilla::CaretAssociationHint;
+
+ /*interfaces for addref and release and queryinterface*/
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsFrameSelection)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsFrameSelection)
+
+ enum class FocusMode {
+ kExtendSelection, /** Keep old anchor point. */
+ kCollapseToNewPoint, /** Collapses the Selection to the new point. */
+ kMultiRangeSelection, /** Keeps existing non-collapsed ranges and marks them
+ as generated. */
+ };
+
+ /**
+ * HandleClick will take the focus to the new frame at the new offset and
+ * will either extend the selection from the old anchor, or replace the old
+ * anchor. the old anchor and focus position may also be used to deselect
+ * things
+ *
+ * @param aNewfocus is the content that wants the focus
+ *
+ * @param aContentOffset is the content offset of the parent aNewFocus
+ *
+ * @param aContentOffsetEnd is the content offset of the parent aNewFocus and
+ * is specified different when you need to select to and include both start
+ * and end points
+ *
+ * @param aHint will tell the selection which direction geometrically to
+ * actually show the caret on. 1 = end of this line 0 = beginning of this line
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult HandleClick(nsIContent* aNewFocus,
+ uint32_t aContentOffset,
+ uint32_t aContentEndOffset,
+ FocusMode aFocusMode,
+ CaretAssociationHint aHint);
+
+ public:
+ /**
+ * Sets flag to true if a selection is created by doubleclick or
+ * long tapping a word.
+ *
+ * @param aIsDoubleClickSelection True if the selection is created by
+ * doubleclick or long tap over a word.
+ */
+ void SetIsDoubleClickSelection(bool aIsDoubleClickSelection) {
+ mIsDoubleClickSelection = aIsDoubleClickSelection;
+ }
+
+ /**
+ * Returns true if the selection was created by doubleclick or
+ * long tap over a word.
+ */
+ [[nodiscard]] bool IsDoubleClickSelection() const {
+ return mIsDoubleClickSelection;
+ }
+
+ /**
+ * HandleDrag extends the selection to contain the frame closest to aPoint.
+ *
+ * @param aPresContext is the context to use when figuring out what frame
+ * contains the point.
+ *
+ * @param aFrame is the parent of all frames to use when searching for the
+ * closest frame to the point.
+ *
+ * @param aPoint is relative to aFrame
+ */
+ MOZ_CAN_RUN_SCRIPT void HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint);
+
+ /**
+ * HandleTableSelection will set selection to a table, cell, etc
+ * depending on information contained in aFlags
+ *
+ * @param aParentContent is the paretent of either a table or cell that user
+ * clicked or dragged the mouse in
+ *
+ * @param aContentOffset is the offset of the table or cell
+ *
+ * @param aTarget indicates what to select
+ * * TableSelectionMode::Cell
+ * We should select a cell (content points to the cell)
+ * * TableSelectionMode::Row
+ * We should select a row (content points to any cell in row)
+ * * TableSelectionMode::Column
+ * We should select a row (content points to any cell in column)
+ * * TableSelectionMode::Table
+ * We should select a table (content points to the table)
+ * * TableSelectionMode::AllCells
+ * We should select all cells (content points to any cell in table)
+ *
+ * @param aMouseEvent passed in so we can get where event occurred
+ * and what keys are pressed
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ HandleTableSelection(nsINode* aParentContent, int32_t aContentOffset,
+ mozilla::TableSelectionMode aTarget,
+ mozilla::WidgetMouseEvent* aMouseEvent);
+
+ /**
+ * Add cell to the selection with `SelectionType::eNormal`.
+ *
+ * @param aCell [in] HTML td element.
+ */
+ nsresult SelectCellElement(nsIContent* aCell);
+
+ public:
+ /**
+ * Remove cells from selection inside of the given cell range.
+ *
+ * @param aTable [in] HTML table element
+ * @param aStartRowIndex [in] row index where the cells range starts
+ * @param aStartColumnIndex [in] column index where the cells range starts
+ * @param aEndRowIndex [in] row index where the cells range ends
+ * @param aEndColumnIndex [in] column index where the cells range ends
+ */
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult RemoveCellsFromSelection(nsIContent* aTable, int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex);
+
+ /**
+ * Remove cells from selection outside of the given cell range.
+ *
+ * @param aTable [in] HTML table element
+ * @param aStartRowIndex [in] row index where the cells range starts
+ * @param aStartColumnIndex [in] column index where the cells range starts
+ * @param aEndRowIndex [in] row index where the cells range ends
+ * @param aEndColumnIndex [in] column index where the cells range ends
+ */
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult RestrictCellsToSelection(nsIContent* aTable, int32_t aStartRowIndex,
+ int32_t aStartColumnIndex,
+ int32_t aEndRowIndex,
+ int32_t aEndColumnIndex);
+
+ /**
+ * StartAutoScrollTimer is responsible for scrolling frames so that
+ * aPoint is always visible, and for selecting any frame that contains
+ * aPoint. The timer will also reset itself to fire again if we have
+ * not scrolled to the end of the document.
+ *
+ * @param aFrame is the outermost frame to use when searching for
+ * the closest frame for the point, i.e. the frame that is capturing
+ * the mouse
+ *
+ * @param aPoint is relative to aFrame.
+ *
+ * @param aDelay is the timer's interval.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult StartAutoScrollTimer(nsIFrame* aFrame, const nsPoint& aPoint,
+ uint32_t aDelay);
+
+ /**
+ * Stops any active auto scroll timer.
+ */
+ void StopAutoScrollTimer();
+
+ /**
+ * Returns in frame coordinates the selection beginning and ending with the
+ * type of selection given
+ *
+ * @param aContent is the content asking
+ * @param aContentOffset is the starting content boundary
+ * @param aContentLength is the length of the content piece asking
+ * @param aSlowCheck will check using slow method with no shortcuts
+ */
+ mozilla::UniquePtr<SelectionDetails> LookUpSelection(nsIContent* aContent,
+ int32_t aContentOffset,
+ int32_t aContentLength,
+ bool aSlowCheck) const;
+
+ /**
+ * Sets the drag state to aState for resons of drag state.
+ *
+ * @param aState is the new state of drag
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetDragState(bool aState);
+
+ /**
+ * Gets the drag state to aState for resons of drag state.
+ *
+ * @param aState will hold the state of drag
+ */
+ bool GetDragState() const { return mDragState; }
+
+ /**
+ * If we are in table cell selection mode. aka ctrl click in table cell
+ */
+ bool IsInTableSelectionMode() const {
+ return mTableSelection.mMode != mozilla::TableSelectionMode::None;
+ }
+ void ClearTableCellSelection() {
+ mTableSelection.mMode = mozilla::TableSelectionMode::None;
+ }
+
+ /**
+ * No query interface for selection. must use this method now.
+ *
+ * @param aSelectionType The selection type what you want.
+ */
+ mozilla::dom::Selection* GetSelection(
+ mozilla::SelectionType aSelectionType) const;
+
+ /**
+ * @brief Adds a highlight selection for `aHighlight`.
+ */
+ MOZ_CAN_RUN_SCRIPT void AddHighlightSelection(
+ nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight);
+ /**
+ * @brief Removes the Highlight selection identified by `aHighlightName`.
+ */
+ MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelection(nsAtom* aHighlightName);
+
+ /**
+ * @brief Adds a new range to the highlight selection.
+ *
+ * If there is no highlight selection for the given highlight yet, it is
+ * created using |AddHighlightSelection|.
+ */
+ MOZ_CAN_RUN_SCRIPT void AddHighlightSelectionRange(
+ nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight,
+ mozilla::dom::AbstractRange& aRange);
+
+ /**
+ * @brief Removes a range from a highlight selection.
+ */
+ MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelectionRange(
+ nsAtom* aHighlightName, mozilla::dom::AbstractRange& aRange);
+ /**
+ * ScrollSelectionIntoView scrolls a region of the selection,
+ * so that it is visible in the scrolled view.
+ *
+ * @param aSelectionType the selection to scroll into view.
+ *
+ * @param aRegion the region inside the selection to scroll into view.
+ *
+ * @param aFlags the scroll flags. Valid bits include:
+ * * SCROLL_SYNCHRONOUS: when set, scrolls the selection into view
+ * before returning. If not set, posts a request which is processed
+ * at some point after the method returns.
+ * * SCROLL_FIRST_ANCESTOR_ONLY: if set, only the first ancestor will be
+ * scrolled into view.
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ ScrollSelectionIntoView(mozilla::SelectionType aSelectionType,
+ SelectionRegion aRegion, int16_t aFlags) const;
+
+ /**
+ * RepaintSelection repaints the selected frames that are inside the
+ * selection specified by aSelectionType.
+ *
+ * @param aSelectionType The selection type what you want to repaint.
+ */
+ nsresult RepaintSelection(mozilla::SelectionType aSelectionType);
+
+ bool IsValidSelectionPoint(nsINode* aNode) const;
+
+ /**
+ * GetFrameToPageSelect() returns a frame which is ancestor limit of
+ * per-page selection. The frame may not be scrollable. E.g.,
+ * when selection ancestor limit is set to a frame of an editing host of
+ * contenteditable element and it's not scrollable.
+ */
+ nsIFrame* GetFrameToPageSelect() const;
+
+ /**
+ * This method moves caret (if aExtend is false) or expands selection (if
+ * aExtend is true). Then, scrolls aFrame one page. Finally, this may
+ * call ScrollSelectionIntoView() for making focus of selection visible
+ * but depending on aSelectionIntoView value.
+ *
+ * @param aForward if true, scroll forward if not scroll backward
+ * @param aExtend if true, extend selection to the new point
+ * @param aFrame the frame to scroll or container of per-page selection.
+ * if aExtend is true and selection may have ancestor limit,
+ * should set result of GetFrameToPageSelect().
+ * @param aSelectionIntoView
+ * If IfChanged, this makes selection into view only when
+ * selection is modified by the call.
+ * If Yes, this makes selection into view always.
+ */
+ enum class SelectionIntoView { IfChanged, Yes };
+ MOZ_CAN_RUN_SCRIPT nsresult PageMove(bool aForward, bool aExtend,
+ nsIFrame* aFrame,
+ SelectionIntoView aSelectionIntoView);
+
+ void SetHint(CaretAssociationHint aHintRight) { mCaret.mHint = aHintRight; }
+ CaretAssociationHint GetHint() const { return mCaret.mHint; }
+
+ void SetCaretBidiLevelAndMaybeSchedulePaint(
+ mozilla::intl::BidiEmbeddingLevel aLevel);
+
+ /**
+ * GetCaretBidiLevel gets the caret bidi level.
+ */
+ mozilla::intl::BidiEmbeddingLevel GetCaretBidiLevel() const;
+
+ /**
+ * UndefineCaretBidiLevel sets the caret bidi level to "undefined".
+ */
+ void UndefineCaretBidiLevel();
+
+ /**
+ * PhysicalMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one unit
+ * 'aAmount' in the given aDirection.
+ * @param aDirection the direction to move the selection
+ * @param aAmount amount of movement (char/line; word/page; eol/doc)
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult PhysicalMove(int16_t aDirection,
+ int16_t aAmount,
+ bool aExtend);
+
+ /**
+ * CharacterMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one character
+ * left or right.
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult CharacterMove(bool aForward,
+ bool aExtend);
+
+ /**
+ * WordMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one word left or
+ * right.
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult WordMove(bool aForward, bool aExtend);
+
+ /**
+ * LineMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move one line up or
+ * down.
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult LineMove(bool aForward, bool aExtend);
+
+ /**
+ * IntraLineMove will generally be called from the nsiselectioncontroller
+ * implementations. the effect being the selection will move to beginning or
+ * end of line
+ * @param aForward move forward in document.
+ * @param aExtend continue selection
+ */
+ // TODO: replace with `MOZ_CAN_RUN_SCRIPT`.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult IntraLineMove(bool aForward,
+ bool aExtend);
+
+ /**
+ * CreateRangeExtendedToNextGraphemeClusterBoundary() returns range which is
+ * extended from normal selection range to start of next grapheme cluster
+ * boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToNextGraphemeClusterBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectCluster,
+ eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToPreviousCharacterBoundary() returns range which is
+ * extended from normal selection range to start of previous character
+ * boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToPreviousCharacterBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(
+ eDirPrevious, eSelectCharacter, eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToNextWordBoundary() returns range which is
+ * extended from normal selection range to start of next word boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToNextWordBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectWord,
+ eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToPreviousWordBoundary() returns range which is
+ * extended from normal selection range to start of previous word boundary.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToPreviousWordBoundary() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirPrevious, eSelectWord,
+ eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToPreviousHardLineBreak() returns range which is
+ * extended from normal selection range to previous hard line break.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToPreviousHardLineBreak() {
+ return CreateRangeExtendedToSomewhere<RangeType>(
+ eDirPrevious, eSelectBeginLine, eLogical);
+ }
+
+ /**
+ * CreateRangeExtendedToNextHardLineBreak() returns range which is extended
+ * from normal selection range to next hard line break.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToNextHardLineBreak() {
+ return CreateRangeExtendedToSomewhere<RangeType>(eDirNext, eSelectEndLine,
+ eLogical);
+ }
+
+ /** Sets/Gets The display selection enum.
+ */
+ void SetDisplaySelection(int16_t aState) { mDisplaySelection = aState; }
+ int16_t GetDisplaySelection() const { return mDisplaySelection; }
+
+ /**
+ * This method can be used to store the data received during a MouseDown
+ * event so that we can place the caret during the MouseUp event.
+ *
+ * @param aMouseEvent the event received by the selection MouseDown
+ * handling method. A nullptr value can be use to tell this method
+ * that any data is storing is no longer valid.
+ */
+ void SetDelayedCaretData(mozilla::WidgetMouseEvent* aMouseEvent);
+
+ /**
+ * Get the delayed MouseDown event data necessary to place the
+ * caret during MouseUp processing.
+ *
+ * @return a pointer to the event received
+ * by the selection during MouseDown processing. It can be nullptr
+ * if the data is no longer valid.
+ */
+ bool HasDelayedCaretData() const { return mDelayedMouseEvent.mIsValid; }
+ bool IsShiftDownInDelayedCaretData() const {
+ NS_ASSERTION(mDelayedMouseEvent.mIsValid, "No valid delayed caret data");
+ return mDelayedMouseEvent.mIsShift;
+ }
+ uint32_t GetClickCountInDelayedCaretData() const {
+ NS_ASSERTION(mDelayedMouseEvent.mIsValid, "No valid delayed caret data");
+ return mDelayedMouseEvent.mClickCount;
+ }
+
+ bool MouseDownRecorded() const {
+ return !GetDragState() && HasDelayedCaretData() &&
+ GetClickCountInDelayedCaretData() < 2;
+ }
+
+ /**
+ * Get the content node that limits the selection
+ *
+ * When searching up a nodes for parents, as in a text edit field
+ * in an browser page, we must stop at this node else we reach into the
+ * parent page, which is very bad!
+ */
+ nsIContent* GetLimiter() const { return mLimiters.mLimiter; }
+
+ nsIContent* GetAncestorLimiter() const { return mLimiters.mAncestorLimiter; }
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void SetAncestorLimiter(nsIContent* aLimiter);
+
+ /**
+ * GetPrevNextBidiLevels will return the frames and associated Bidi levels of
+ * the characters logically before and after a (collapsed) selection.
+ *
+ * @param aNode is the node containing the selection
+ * @param aContentOffset is the offset of the selection in the node
+ * @param aJumpLines
+ * If true, look across line boundaries.
+ * If false, behave as if there were base-level frames at line edges.
+ *
+ * @return A struct holding the before/after frame and the before/after
+ * level.
+ *
+ * At the beginning and end of each line there is assumed to be a frame with
+ * Bidi level equal to the paragraph embedding level.
+ *
+ * In these cases the before frame and after frame respectively will be
+ * nullptr.
+ */
+ nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
+ uint32_t aContentOffset,
+ bool aJumpLines) const;
+
+ /**
+ * MaintainSelection will track the normal selection as being "sticky".
+ * Dragging or extending selection will never allow for a subset
+ * (or the whole) of the maintained selection to become unselected.
+ * Primary use: double click selecting then dragging on second click
+ *
+ * @param aAmount the initial amount of text selected (word, line or
+ * paragraph). For "line", use eSelectBeginLine.
+ */
+ nsresult MaintainSelection(nsSelectionAmount aAmount = eSelectNoAmount);
+
+ MOZ_CAN_RUN_SCRIPT nsresult ConstrainFrameAndPointToAnchorSubtree(
+ nsIFrame* aFrame, const nsPoint& aPoint, nsIFrame** aRetFrame,
+ nsPoint& aRetPoint) const;
+
+ /**
+ * @param aPresShell is the parameter to be used for most of the other calls
+ * for callbacks etc
+ *
+ * @param aLimiter limits the selection to nodes with aLimiter parents
+ *
+ * @param aAccessibleCaretEnabled true if we should enable the accessible
+ * caret.
+ */
+ nsFrameSelection(mozilla::PresShell* aPresShell, nsIContent* aLimiter,
+ bool aAccessibleCaretEnabled);
+
+ /**
+ * @param aRequesterFuncName function name which wants to start the batch.
+ * This won't be stored nor exposed to selection listeners etc, used only for
+ * logging.
+ */
+ void StartBatchChanges(const char* aRequesterFuncName);
+
+ /**
+ * @param aRequesterFuncName function name which wants to end the batch.
+ * This won't be stored nor exposed to selection listeners etc, used only for
+ * logging.
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void EndBatchChanges(
+ const char* aRequesterFuncName,
+ int16_t aReasons = nsISelectionListener::NO_REASON);
+
+ mozilla::PresShell* GetPresShell() const { return mPresShell; }
+
+ void DisconnectFromPresShell();
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult ClearNormalSelection();
+
+ // Table selection support.
+ static nsITableCellLayout* GetCellLayout(const nsIContent* aCellContent);
+
+ private:
+ ~nsFrameSelection();
+
+ // TODO: in case an error is returned, it sometimes refers to a programming
+ // error, in other cases to runtime errors. This deserves to be cleaned up.
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ TakeFocus(nsIContent& aNewFocus, uint32_t aContentOffset,
+ uint32_t aContentEndOffset, CaretAssociationHint aHint,
+ FocusMode aFocusMode);
+
+ /**
+ * After moving the caret, its Bidi level is set according to the following
+ * rules:
+ *
+ * After moving over a character with left/right arrow, set to the Bidi level
+ * of the last moved over character. After Home and End, set to the paragraph
+ * embedding level. After up/down arrow, PageUp/Down, set to the lower level
+ * of the 2 surrounding characters. After mouse click, set to the level of the
+ * current frame.
+ *
+ * The following two methods use GetPrevNextBidiLevels to determine the new
+ * Bidi level. BidiLevelFromMove is called when the caret is moved in response
+ * to a keyboard event
+ *
+ * @param aPresShell is the presentation shell
+ * @param aNode is the content node
+ * @param aContentOffset is the new caret position, as an offset into aNode
+ * @param aAmount is the amount of the move that gave the caret its new
+ * position
+ * @param aHint is the hint indicating in what logical direction the caret
+ * moved
+ */
+ void BidiLevelFromMove(mozilla::PresShell* aPresShell, nsIContent* aNode,
+ uint32_t aContentOffset, nsSelectionAmount aAmount,
+ CaretAssociationHint aHint);
+ /**
+ * BidiLevelFromClick is called when the caret is repositioned by clicking the
+ * mouse
+ *
+ * @param aNode is the content node
+ * @param aContentOffset is the new caret position, as an offset into aNode
+ */
+ void BidiLevelFromClick(nsIContent* aNewFocus, uint32_t aContentOffset);
+
+ /**
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl.
+ */
+ void SetChangeReasons(int16_t aReasons) {
+ mSelectionChangeReasons = aReasons;
+ }
+
+ /**
+ * @param aReasons potentially multiple of the reasons defined in
+ * nsISelectionListener.idl.
+ */
+ void AddChangeReasons(int16_t aReasons) {
+ mSelectionChangeReasons |= aReasons;
+ }
+
+ /**
+ * @return potentially multiple of the reasons defined in
+ * nsISelectionListener.idl.
+ */
+ int16_t PopChangeReasons() {
+ int16_t retval = mSelectionChangeReasons;
+ mSelectionChangeReasons = nsISelectionListener::NO_REASON;
+ return retval;
+ }
+
+ nsSelectionAmount GetCaretMoveAmount() { return mCaretMoveAmount; }
+
+ bool IsUserSelectionReason() const {
+ return (mSelectionChangeReasons &
+ (nsISelectionListener::DRAG_REASON |
+ nsISelectionListener::MOUSEDOWN_REASON |
+ nsISelectionListener::MOUSEUP_REASON |
+ nsISelectionListener::KEYPRESS_REASON)) !=
+ nsISelectionListener::NO_REASON;
+ }
+
+ friend class mozilla::dom::Selection;
+ friend class mozilla::SelectionChangeEventDispatcher;
+ friend struct mozilla::AutoPrepareFocusRange;
+
+ /*HELPER METHODS*/
+ // Whether MoveCaret should use logical or visual movement,
+ // or follow the bidi.edit.caret_movement_style preference.
+ enum CaretMovementStyle { eLogical, eVisual, eUsePrefStyle };
+ MOZ_CAN_RUN_SCRIPT nsresult MoveCaret(nsDirection aDirection,
+ bool aContinueSelection,
+ nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle);
+
+ /**
+ * PeekOffsetForCaretMove() only peek offset for caret move from the focus
+ * point of the normal selection. I.e., won't change selection ranges nor
+ * bidi information.
+ */
+ mozilla::Result<mozilla::PeekOffsetStruct, nsresult> PeekOffsetForCaretMove(
+ nsDirection aDirection, bool aContinueSelection,
+ const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle,
+ const nsPoint& aDesiredCaretPos) const;
+
+ /**
+ * CreateRangeExtendedToSomewhere() is common method to implement
+ * CreateRangeExtendedTo*(). This method creates a range extended from
+ * normal selection range.
+ */
+ template <typename RangeType>
+ MOZ_CAN_RUN_SCRIPT mozilla::Result<RefPtr<RangeType>, nsresult>
+ CreateRangeExtendedToSomewhere(nsDirection aDirection,
+ const nsSelectionAmount aAmount,
+ CaretMovementStyle aMovementStyle);
+
+ void InvalidateDesiredCaretPos(); // do not listen to mDesiredCaretPos.mValue
+ // you must get another.
+
+ bool IsBatching() const { return mBatching.mCounter > 0; }
+
+ void SetChangesDuringBatchingFlag() {
+ MOZ_ASSERT(mBatching.mCounter > 0);
+
+ mBatching.mChangesDuringBatching = true;
+ }
+
+ // nsFrameSelection may get deleted when calling this,
+ // so remember to use nsCOMPtr when needed.
+ MOZ_CAN_RUN_SCRIPT
+ nsresult NotifySelectionListeners(mozilla::SelectionType aSelectionType);
+
+ static nsresult GetCellIndexes(const nsIContent* aCell, int32_t& aRowIndex,
+ int32_t& aColIndex);
+
+ static nsIContent* GetFirstCellNodeInRange(const nsRange* aRange);
+ // Returns non-null table if in same table, null otherwise
+ static nsIContent* IsInSameTable(const nsIContent* aContent1,
+ const nsIContent* aContent2);
+ // Might return null
+ static nsIContent* GetParentTable(const nsIContent* aCellNode);
+
+ ////////////BEGIN nsFrameSelection members
+
+ RefPtr<mozilla::dom::Selection>
+ mDomSelections[sizeof(mozilla::kPresentSelectionTypes) /
+ sizeof(mozilla::SelectionType)];
+
+ nsTArray<
+ mozilla::CompactPair<RefPtr<nsAtom>, RefPtr<mozilla::dom::Selection>>>
+ mHighlightSelections;
+
+ struct TableSelection {
+ // Get our first range, if its first selected node is a cell. If this does
+ // not return null, then the first node in the returned range is a cell
+ // (according to GetFirstCellNodeInRange).
+ nsRange* GetFirstCellRange(const mozilla::dom::Selection& aNormalSelection);
+
+ // Get our next range, if its first selected node is a cell. If this does
+ // not return null, then the first node in the returned range is a cell
+ // (according to GetFirstCellNodeInRange).
+ nsRange* GetNextCellRange(const mozilla::dom::Selection& aNormalSelection);
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ HandleSelection(nsINode* aParentContent, int32_t aContentOffset,
+ mozilla::TableSelectionMode aTarget,
+ mozilla::WidgetMouseEvent* aMouseEvent, bool aDragState,
+ mozilla::dom::Selection& aNormalSelection);
+
+ /**
+ * @return the closest inclusive table cell ancestor
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor) of
+ * aContent, if it is actively editable.
+ */
+ static nsINode* IsContentInActivelyEditableTableCell(
+ nsPresContext* aContext, nsIContent* aContent);
+
+ // TODO: annotate this with `MOZ_CAN_RUN_SCRIPT` instead.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult SelectBlockOfCells(nsIContent* aStartCell, nsIContent* aEndCell,
+ mozilla::dom::Selection& aNormalSelection);
+
+ nsresult SelectRowOrColumn(nsIContent* aCellContent,
+ mozilla::dom::Selection& aNormalSelection);
+
+ MOZ_CAN_RUN_SCRIPT nsresult
+ UnselectCells(const nsIContent* aTable, int32_t aStartRowIndex,
+ int32_t aStartColumnIndex, int32_t aEndRowIndex,
+ int32_t aEndColumnIndex, bool aRemoveOutsideOfCellRange,
+ mozilla::dom::Selection& aNormalSelection);
+
+ nsCOMPtr<nsINode>
+ mClosestInclusiveTableCellAncestor; // used to snap to table selection
+ nsCOMPtr<nsIContent> mStartSelectedCell;
+ nsCOMPtr<nsIContent> mEndSelectedCell;
+ nsCOMPtr<nsIContent> mAppendStartSelectedCell;
+ nsCOMPtr<nsIContent> mUnselectCellOnMouseUp;
+ mozilla::TableSelectionMode mMode = mozilla::TableSelectionMode::None;
+ int32_t mSelectedCellIndex = 0;
+ bool mDragSelectingCells = false;
+
+ private:
+ struct MOZ_STACK_CLASS FirstAndLastCell {
+ nsCOMPtr<nsIContent> mFirst;
+ nsCOMPtr<nsIContent> mLast;
+ };
+
+ mozilla::Result<FirstAndLastCell, nsresult>
+ FindFirstAndLastCellOfRowOrColumn(const nsIContent& aCellContent) const;
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult HandleDragSelecting(
+ mozilla::TableSelectionMode aTarget, nsIContent* aChildContent,
+ const mozilla::WidgetMouseEvent* aMouseEvent,
+ mozilla::dom::Selection& aNormalSelection);
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleMouseUpOrDown(
+ mozilla::TableSelectionMode aTarget, bool aDragState,
+ nsIContent* aChildContent, nsINode* aParentContent,
+ int32_t aContentOffset, const mozilla::WidgetMouseEvent* aMouseEvent,
+ mozilla::dom::Selection& aNormalSelection);
+
+ class MOZ_STACK_CLASS RowAndColumnRelation;
+ };
+
+ TableSelection mTableSelection;
+
+ struct MaintainedRange {
+ /**
+ * Ensure anchor and focus of aNormalSelection are ordered appropriately
+ * relative to the maintained range.
+ */
+ MOZ_CAN_RUN_SCRIPT void AdjustNormalSelection(
+ const nsIContent* aContent, int32_t aOffset,
+ mozilla::dom::Selection& aNormalSelection) const;
+
+ /**
+ * @param aStopAtScroller If yes, this will
+ * set `PeekOffsetOption::StopAtScroller`.
+ */
+ enum class StopAtScroller : bool { No, Yes };
+ void AdjustContentOffsets(nsIFrame::ContentOffsets& aOffsets,
+ StopAtScroller aStopAtScroller) const;
+
+ void MaintainAnchorFocusRange(
+ const mozilla::dom::Selection& aNormalSelection,
+ nsSelectionAmount aAmount);
+
+ RefPtr<nsRange> mRange;
+ nsSelectionAmount mAmount = eSelectNoAmount;
+ };
+
+ MaintainedRange mMaintainedRange;
+
+ struct Batching {
+ uint32_t mCounter = 0;
+ bool mChangesDuringBatching = false;
+ };
+
+ Batching mBatching;
+
+ struct Limiters {
+ // Limit selection navigation to a child of this node.
+ nsCOMPtr<nsIContent> mLimiter;
+ // Limit selection navigation to a descendant of this node.
+ nsCOMPtr<nsIContent> mAncestorLimiter;
+ };
+
+ Limiters mLimiters;
+
+ mozilla::PresShell* mPresShell = nullptr;
+ // Reasons for notifications of selection changing.
+ // Can be multiple of the reasons defined in nsISelectionListener.idl.
+ int16_t mSelectionChangeReasons = nsISelectionListener::NO_REASON;
+ // For visual display purposes.
+ int16_t mDisplaySelection = nsISelectionController::SELECTION_OFF;
+ nsSelectionAmount mCaretMoveAmount = eSelectNoAmount;
+
+ struct Caret {
+ // Hint to tell if the selection is at the end of this line or beginning of
+ // next.
+ CaretAssociationHint mHint = CaretAssociationHint::Before;
+ mozilla::intl::BidiEmbeddingLevel mBidiLevel = BIDI_LEVEL_UNDEFINED;
+
+ bool IsVisualMovement(bool aContinueSelection,
+ CaretMovementStyle aMovementStyle) const;
+ };
+
+ Caret mCaret;
+
+ mozilla::intl::BidiEmbeddingLevel mKbdBidiLevel =
+ mozilla::intl::BidiEmbeddingLevel::LTR();
+
+ class DesiredCaretPos {
+ public:
+ // the position requested by the Key Handling for up down
+ nsresult FetchPos(nsPoint& aDesiredCaretPos,
+ const mozilla::PresShell& aPresShell,
+ mozilla::dom::Selection& aNormalSelection) const;
+
+ void Set(const nsPoint& aPos);
+
+ void Invalidate();
+
+ bool mIsSet = false;
+
+ private:
+ nsPoint mValue;
+ };
+
+ DesiredCaretPos mDesiredCaretPos;
+
+ struct DelayedMouseEvent {
+ bool mIsValid = false;
+ // These values are not used since they are only valid when mIsValid is
+ // true, and setting mIsValid always overrides these values.
+ bool mIsShift = false;
+ uint32_t mClickCount = 0;
+ };
+
+ DelayedMouseEvent mDelayedMouseEvent;
+
+ bool mDragState = false; // for drag purposes
+ bool mAccessibleCaretEnabled = false;
+
+ // Records if a selection was created by doubleclicking a word.
+ // This information is needed later on to determine if a leading
+ // or trailing whitespace needs to be removed as well to achieve
+ // native behaviour on macOS.
+ bool mIsDoubleClickSelection{false};
+};
+
+/**
+ * Selection Batcher class that supports multiple FrameSelections.
+ */
+class MOZ_STACK_CLASS AutoFrameSelectionBatcher {
+ public:
+ explicit AutoFrameSelectionBatcher(const char* aFunctionName,
+ size_t aEstimatedSize = 1)
+ : mFunctionName(aFunctionName) {
+ mFrameSelections.SetCapacity(aEstimatedSize);
+ }
+ ~AutoFrameSelectionBatcher() {
+ for (const auto& frameSelection : mFrameSelections) {
+ frameSelection->EndBatchChanges(mFunctionName);
+ }
+ }
+ void AddFrameSelection(nsFrameSelection* aFrameSelection) {
+ if (!aFrameSelection) {
+ return;
+ }
+ aFrameSelection->StartBatchChanges(mFunctionName);
+ mFrameSelections.AppendElement(aFrameSelection);
+ }
+
+ private:
+ const char* mFunctionName;
+ AutoTArray<RefPtr<nsFrameSelection>, 1> mFrameSelections;
+};
+
+#endif /* nsFrameSelection_h___ */
diff --git a/layout/generic/nsFrameSetFrame.cpp b/layout/generic/nsFrameSetFrame.cpp
new file mode 100644
index 0000000000..f292988c55
--- /dev/null
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -0,0 +1,1534 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for HTML <frameset> elements */
+
+#include "nsFrameSetFrame.h"
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsLeafFrame.h"
+#include "nsContainerFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsIContentInlines.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsHTMLParts.h"
+#include "nsNameSpaceManager.h"
+#include "nsCSSAnonBoxes.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoStyleSetInlines.h"
+#include "mozilla/dom/Element.h"
+#include "nsDisplayList.h"
+#include "mozAutoDocUpdate.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/HTMLFrameSetElement.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "nsSubDocumentFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+// masks for mEdgeVisibility
+#define LEFT_VIS 0x0001
+#define RIGHT_VIS 0x0002
+#define TOP_VIS 0x0004
+#define BOTTOM_VIS 0x0008
+#define ALL_VIS 0x000F
+#define NONE_VIS 0x0000
+
+/*******************************************************************************
+ * nsFramesetDrag
+ ******************************************************************************/
+nsFramesetDrag::nsFramesetDrag() { UnSet(); }
+
+void nsFramesetDrag::Reset(bool aVertical, int32_t aIndex, int32_t aChange,
+ nsHTMLFramesetFrame* aSource) {
+ mVertical = aVertical;
+ mIndex = aIndex;
+ mChange = aChange;
+ mSource = aSource;
+}
+
+void nsFramesetDrag::UnSet() {
+ mVertical = true;
+ mIndex = -1;
+ mChange = 0;
+ mSource = nullptr;
+}
+
+/*******************************************************************************
+ * nsHTMLFramesetBorderFrame
+ ******************************************************************************/
+class nsHTMLFramesetBorderFrame final : public nsLeafFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsHTMLFramesetBorderFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ Cursor GetCursor(const nsPoint&) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ bool GetVisibility() { return mVisibility; }
+ void SetVisibility(bool aVisibility);
+ void SetColor(nscolor aColor);
+
+ void PaintBorder(DrawTarget* aDrawTarget, nsPoint aPt);
+
+ protected:
+ nsHTMLFramesetBorderFrame(ComputedStyle*, nsPresContext*, int32_t aWidth,
+ bool aVertical, bool aVisible);
+ virtual ~nsHTMLFramesetBorderFrame();
+ virtual nscoord GetIntrinsicISize() override;
+ virtual nscoord GetIntrinsicBSize() override;
+
+ // the prev and next neighbors are indexes into the row (for a horizontal
+ // border) or col (for a vertical border) of nsHTMLFramesetFrames or
+ // nsHTMLFrames
+ int32_t mPrevNeighbor;
+ int32_t mNextNeighbor;
+ nscolor mColor;
+ int32_t mWidth;
+ bool mVertical;
+ bool mVisibility;
+ bool mCanResize;
+ friend class nsHTMLFramesetFrame;
+};
+/*******************************************************************************
+ * nsHTMLFramesetBlankFrame
+ ******************************************************************************/
+class nsHTMLFramesetBlankFrame final : public nsLeafFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsHTMLFramesetBlankFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"FramesetBlank"_ns, aResult);
+ }
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ protected:
+ explicit nsHTMLFramesetBlankFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsLeafFrame(aStyle, aPresContext, kClassID) {}
+
+ virtual ~nsHTMLFramesetBlankFrame();
+ virtual nscoord GetIntrinsicISize() override;
+ virtual nscoord GetIntrinsicBSize() override;
+
+ friend class nsHTMLFramesetFrame;
+ friend class nsHTMLFrameset;
+};
+
+/*******************************************************************************
+ * nsHTMLFramesetFrame
+ ******************************************************************************/
+bool nsHTMLFramesetFrame::gDragInProgress = false;
+#define DEFAULT_BORDER_WIDTH_PX 6
+
+nsHTMLFramesetFrame::nsHTMLFramesetFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {
+ mNumRows = 0;
+ mNumCols = 0;
+ mEdgeVisibility = 0;
+ mParentFrameborder = eFrameborder_Yes; // default
+ mParentBorderWidth = -1; // default not set
+ mParentBorderColor = NO_COLOR; // default not set
+ mFirstDragPoint.x = mFirstDragPoint.y = 0;
+ mMinDrag = nsPresContext::CSSPixelsToAppUnits(2);
+ mNonBorderChildCount = 0;
+ mNonBlankChildCount = 0;
+ mDragger = nullptr;
+ mChildCount = 0;
+ mTopLevelFrameset = nullptr;
+ mEdgeColors.Set(NO_COLOR);
+}
+
+nsHTMLFramesetFrame::~nsHTMLFramesetFrame() = default;
+
+NS_QUERYFRAME_HEAD(nsHTMLFramesetFrame)
+ NS_QUERYFRAME_ENTRY(nsHTMLFramesetFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+void nsHTMLFramesetFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ // find the highest ancestor that is a frameset
+ nsIFrame* parentFrame = GetParent();
+ mTopLevelFrameset = this;
+ while (parentFrame) {
+ nsHTMLFramesetFrame* frameset = do_QueryFrame(parentFrame);
+ if (frameset) {
+ mTopLevelFrameset = frameset;
+ parentFrame = parentFrame->GetParent();
+ } else {
+ break;
+ }
+ }
+
+ nsPresContext* presContext = PresContext();
+ mozilla::PresShell* presShell = presContext->PresShell();
+
+ nsFrameborder frameborder = GetFrameBorder();
+ int32_t borderWidth = GetBorderWidth(presContext, false);
+ nscolor borderColor = GetBorderColor();
+
+ // Get the rows= cols= data
+ HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
+ NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
+ const nsFramesetSpec* rowSpecs = nullptr;
+ const nsFramesetSpec* colSpecs = nullptr;
+ // GetRowSpec and GetColSpec can fail, but when they do they set
+ // mNumRows and mNumCols respectively to 0, so we deal with it fine.
+ ourContent->GetRowSpec(&mNumRows, &rowSpecs);
+ ourContent->GetColSpec(&mNumCols, &colSpecs);
+
+ static_assert(
+ NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nscoord),
+ "Maximum value of mNumRows and mNumCols is NS_MAX_FRAMESET_SPEC_COUNT");
+ mRowSizes = MakeUnique<nscoord[]>(mNumRows);
+ mColSizes = MakeUnique<nscoord[]>(mNumCols);
+
+ static_assert(
+ NS_MAX_FRAMESET_SPEC_COUNT < INT32_MAX / NS_MAX_FRAMESET_SPEC_COUNT,
+ "Should not overflow numCells");
+ int32_t numCells = mNumRows * mNumCols;
+
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT <
+ UINT_MAX / sizeof(nsHTMLFramesetBorderFrame*),
+ "Should not overflow nsHTMLFramesetBorderFrame");
+ mVerBorders = MakeUnique<nsHTMLFramesetBorderFrame*[]>(
+ mNumCols); // 1 more than number of ver borders
+
+ for (int verX = 0; verX < mNumCols; verX++) mVerBorders[verX] = nullptr;
+
+ mHorBorders = MakeUnique<nsHTMLFramesetBorderFrame*[]>(
+ mNumRows); // 1 more than number of hor borders
+
+ for (int horX = 0; horX < mNumRows; horX++) mHorBorders[horX] = nullptr;
+
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT <
+ UINT_MAX / sizeof(int32_t) / NS_MAX_FRAMESET_SPEC_COUNT,
+ "Should not overflow numCells");
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nsFrameborder) /
+ NS_MAX_FRAMESET_SPEC_COUNT,
+ "Should not overflow numCells");
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nsBorderColor) /
+ NS_MAX_FRAMESET_SPEC_COUNT,
+ "Should not overflow numCells");
+ mChildFrameborder = MakeUnique<nsFrameborder[]>(numCells);
+ mChildBorderColors = MakeUnique<nsBorderColor[]>(numCells);
+
+ // create the children frames; skip content which isn't <frameset> or <frame>
+ mChildCount = 0; // number of <frame> or <frameset> children
+
+ FlattenedChildIterator children(mContent);
+ for (nsIContent* child = children.GetNextChild(); child;
+ child = children.GetNextChild()) {
+ if (mChildCount == numCells) {
+ // we have more <frame> or <frameset> than cells
+ // Clear the lazy bits in the remaining children. Also clear
+ // the restyle flags, like nsCSSFrameConstructor::ProcessChildren does.
+ for (; child; child = child->GetNextSibling()) {
+ child->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+ }
+ break;
+ }
+ child->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+
+ // IMPORTANT: This must match the conditions in
+ // nsCSSFrameConstructor::ContentAppended/Inserted/Removed
+ if (!child->IsAnyOfHTMLElements(nsGkAtoms::frameset, nsGkAtoms::frame)) {
+ continue;
+ }
+
+ // FIXME(emilio): This doesn't even respect display: none, but that matches
+ // other browsers ;_;
+ //
+ // Maybe we should change that though.
+ RefPtr<ComputedStyle> kidStyle =
+ ServoStyleSet::ResolveServoStyle(*child->AsElement());
+ nsIFrame* frame;
+ if (child->IsHTMLElement(nsGkAtoms::frameset)) {
+ frame = NS_NewHTMLFramesetFrame(presShell, kidStyle);
+
+ nsHTMLFramesetFrame* childFrame = (nsHTMLFramesetFrame*)frame;
+ childFrame->SetParentFrameborder(frameborder);
+ childFrame->SetParentBorderWidth(borderWidth);
+ childFrame->SetParentBorderColor(borderColor);
+ frame->Init(child, this, nullptr);
+
+ mChildBorderColors[mChildCount].Set(childFrame->GetBorderColor());
+ } else { // frame
+ frame = NS_NewSubDocumentFrame(presShell, kidStyle);
+
+ frame->Init(child, this, nullptr);
+
+ mChildFrameborder[mChildCount] = GetFrameBorder(child);
+ mChildBorderColors[mChildCount].Set(GetBorderColor(child));
+ }
+ child->SetPrimaryFrame(frame);
+
+ mFrames.AppendFrame(nullptr, frame);
+
+ mChildCount++;
+ }
+
+ mNonBlankChildCount = mChildCount;
+ // add blank frames for frameset cells that had no content provided
+ for (int blankX = mChildCount; blankX < numCells; blankX++) {
+ RefPtr<ComputedStyle> pseudoComputedStyle =
+ presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
+ PseudoStyleType::framesetBlank);
+
+ // XXX the blank frame is using the content of its parent - at some point it
+ // should just have null content, if we support that
+ nsHTMLFramesetBlankFrame* blankFrame = new (presShell)
+ nsHTMLFramesetBlankFrame(pseudoComputedStyle, PresContext());
+
+ blankFrame->Init(mContent, this, nullptr);
+
+ mFrames.AppendFrame(nullptr, blankFrame);
+
+ mChildBorderColors[mChildCount].Set(NO_COLOR);
+ mChildCount++;
+ }
+
+ mNonBorderChildCount = mChildCount;
+}
+
+void nsHTMLFramesetFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ // We do this weirdness where we create our child frames in Init(). On the
+ // other hand, we're going to get a SetInitialChildList() with an empty list
+ // and null list name after the frame constructor is done creating us. So
+ // just ignore that call.
+ if (aListID == FrameChildListID::Principal && aChildList.IsEmpty()) {
+ return;
+ }
+
+ nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
+}
+
+// XXX should this try to allocate twips based on an even pixel boundary?
+void nsHTMLFramesetFrame::Scale(nscoord aDesired, int32_t aNumIndicies,
+ int32_t* aIndicies, int32_t aNumItems,
+ int32_t* aItems) {
+ int32_t actual = 0;
+ int32_t i, j;
+ // get the actual total
+ for (i = 0; i < aNumIndicies; i++) {
+ j = aIndicies[i];
+ actual += aItems[j];
+ }
+
+ if (actual > 0) {
+ float factor = (float)aDesired / (float)actual;
+ actual = 0;
+ // scale the items up or down
+ for (i = 0; i < aNumIndicies; i++) {
+ j = aIndicies[i];
+ aItems[j] = NSToCoordRound((float)aItems[j] * factor);
+ actual += aItems[j];
+ }
+ } else if (aNumIndicies != 0) {
+ // All the specs say zero width, but we have to fill up space
+ // somehow. Distribute it equally.
+ nscoord width = NSToCoordRound((float)aDesired / (float)aNumIndicies);
+ actual = width * aNumIndicies;
+ for (i = 0; i < aNumIndicies; i++) {
+ aItems[aIndicies[i]] = width;
+ }
+ }
+
+ if (aNumIndicies > 0 && aDesired != actual) {
+ int32_t unit = (aDesired > actual) ? 1 : -1;
+ for (i = 0; (i < aNumIndicies) && (aDesired != actual); i++) {
+ j = aIndicies[i];
+ if (j < aNumItems) {
+ aItems[j] += unit;
+ actual += unit;
+ }
+ }
+ }
+}
+
+/**
+ * Translate the rows/cols specs into an array of integer sizes for
+ * each cell in the frameset. Sizes are allocated based on the priorities of the
+ * specifier - fixed sizes have the highest priority, percentage sizes have the
+ * next highest priority and relative sizes have the lowest.
+ */
+void nsHTMLFramesetFrame::CalculateRowCol(nsPresContext* aPresContext,
+ nscoord aSize, int32_t aNumSpecs,
+ const nsFramesetSpec* aSpecs,
+ nscoord* aValues) {
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(int32_t),
+ "aNumSpecs maximum value is NS_MAX_FRAMESET_SPEC_COUNT");
+
+ int32_t fixedTotal = 0;
+ int32_t numFixed = 0;
+ auto fixed = MakeUnique<int32_t[]>(aNumSpecs);
+ int32_t numPercent = 0;
+ auto percent = MakeUnique<int32_t[]>(aNumSpecs);
+ int32_t relativeSums = 0;
+ int32_t numRelative = 0;
+ auto relative = MakeUnique<int32_t[]>(aNumSpecs);
+
+ if (MOZ_UNLIKELY(!fixed || !percent || !relative)) {
+ return; // NS_ERROR_OUT_OF_MEMORY
+ }
+
+ int32_t i, j;
+
+ // initialize the fixed, percent, relative indices, allocate the fixed sizes
+ // and zero the others
+ for (i = 0; i < aNumSpecs; i++) {
+ aValues[i] = 0;
+ switch (aSpecs[i].mUnit) {
+ case eFramesetUnit_Fixed:
+ aValues[i] = nsPresContext::CSSPixelsToAppUnits(aSpecs[i].mValue);
+ fixedTotal += aValues[i];
+ fixed[numFixed] = i;
+ numFixed++;
+ break;
+ case eFramesetUnit_Percent:
+ percent[numPercent] = i;
+ numPercent++;
+ break;
+ case eFramesetUnit_Relative:
+ relative[numRelative] = i;
+ numRelative++;
+ relativeSums += aSpecs[i].mValue;
+ break;
+ }
+ }
+
+ // scale the fixed sizes if they total too much (or too little and there
+ // aren't any percent or relative)
+ if ((fixedTotal > aSize) ||
+ ((fixedTotal < aSize) && (0 == numPercent) && (0 == numRelative))) {
+ Scale(aSize, numFixed, fixed.get(), aNumSpecs, aValues);
+ return;
+ }
+
+ int32_t percentMax = aSize - fixedTotal;
+ int32_t percentTotal = 0;
+ // allocate the percentage sizes from what is left over from the fixed
+ // allocation
+ for (i = 0; i < numPercent; i++) {
+ j = percent[i];
+ aValues[j] =
+ NSToCoordRound((float)aSpecs[j].mValue * (float)aSize / 100.0f);
+ percentTotal += aValues[j];
+ }
+
+ // scale the percent sizes if they total too much (or too little and there
+ // aren't any relative)
+ if ((percentTotal > percentMax) ||
+ ((percentTotal < percentMax) && (0 == numRelative))) {
+ Scale(percentMax, numPercent, percent.get(), aNumSpecs, aValues);
+ return;
+ }
+
+ int32_t relativeMax = percentMax - percentTotal;
+ int32_t relativeTotal = 0;
+ // allocate the relative sizes from what is left over from the percent
+ // allocation
+ for (i = 0; i < numRelative; i++) {
+ j = relative[i];
+ aValues[j] = NSToCoordRound((float)aSpecs[j].mValue * (float)relativeMax /
+ (float)relativeSums);
+ relativeTotal += aValues[j];
+ }
+
+ // scale the relative sizes if they take up too much or too little
+ if (relativeTotal != relativeMax) {
+ Scale(relativeMax, numRelative, relative.get(), aNumSpecs, aValues);
+ }
+}
+
+/**
+ * Translate the rows/cols integer sizes into an array of specs for
+ * each cell in the frameset. Reverse of CalculateRowCol() behaviour.
+ * This allows us to maintain the user size info through reflows.
+ */
+void nsHTMLFramesetFrame::GenerateRowCol(nsPresContext* aPresContext,
+ nscoord aSize, int32_t aNumSpecs,
+ const nsFramesetSpec* aSpecs,
+ nscoord* aValues, nsString& aNewAttr) {
+ int32_t i;
+
+ for (i = 0; i < aNumSpecs; i++) {
+ if (!aNewAttr.IsEmpty()) aNewAttr.Append(char16_t(','));
+
+ switch (aSpecs[i].mUnit) {
+ case eFramesetUnit_Fixed:
+ aNewAttr.AppendInt(nsPresContext::AppUnitsToIntCSSPixels(aValues[i]));
+ break;
+ case eFramesetUnit_Percent: // XXX Only accurate to 1%, need 1 pixel
+ case eFramesetUnit_Relative:
+ // Add 0.5 to the percentage to make rounding work right.
+ aNewAttr.AppendInt(uint32_t((100.0 * aValues[i]) / aSize + 0.5));
+ aNewAttr.Append(char16_t('%'));
+ break;
+ }
+ }
+}
+
+int32_t nsHTMLFramesetFrame::GetBorderWidth(nsPresContext* aPresContext,
+ bool aTakeForcingIntoAccount) {
+ nsFrameborder frameborder = GetFrameBorder();
+ if (frameborder == eFrameborder_No) {
+ return 0;
+ }
+ nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(mContent);
+
+ if (content) {
+ const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::border);
+ if (attr) {
+ int32_t intVal = 0;
+ if (attr->Type() == nsAttrValue::eInteger) {
+ intVal = attr->GetIntegerValue();
+ if (intVal < 0) {
+ intVal = 0;
+ }
+ }
+
+ return nsPresContext::CSSPixelsToAppUnits(intVal);
+ }
+ }
+
+ if (mParentBorderWidth >= 0) {
+ return mParentBorderWidth;
+ }
+
+ return nsPresContext::CSSPixelsToAppUnits(DEFAULT_BORDER_WIDTH_PX);
+}
+
+void nsHTMLFramesetFrame::GetDesiredSize(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ ReflowOutput& aDesiredSize) {
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize desiredSize(wm);
+ nsHTMLFramesetFrame* framesetParent = do_QueryFrame(GetParent());
+ if (nullptr == framesetParent) {
+ if (aPresContext->IsPaginated()) {
+ // XXX This needs to be changed when framesets paginate properly
+ desiredSize.ISize(wm) = aReflowInput.AvailableISize();
+ desiredSize.BSize(wm) = aReflowInput.AvailableBSize();
+ } else {
+ LogicalSize area(wm, aPresContext->GetVisibleArea().Size());
+
+ desiredSize.ISize(wm) = area.ISize(wm);
+ desiredSize.BSize(wm) = area.BSize(wm);
+ }
+ } else {
+ LogicalSize size(wm);
+ framesetParent->GetSizeOfChild(this, wm, size);
+ desiredSize.ISize(wm) = size.ISize(wm);
+ desiredSize.BSize(wm) = size.BSize(wm);
+ }
+ aDesiredSize.SetSize(wm, desiredSize);
+}
+
+// only valid for non border children
+void nsHTMLFramesetFrame::GetSizeOfChildAt(int32_t aIndexInParent,
+ WritingMode aWM, LogicalSize& aSize,
+ nsIntPoint& aCellIndex) {
+ int32_t row = aIndexInParent / mNumCols;
+ int32_t col = aIndexInParent -
+ (row * mNumCols); // remainder from dividing index by mNumCols
+ if ((row < mNumRows) && (col < mNumCols)) {
+ aSize.ISize(aWM) = mColSizes[col];
+ aSize.BSize(aWM) = mRowSizes[row];
+ aCellIndex.x = col;
+ aCellIndex.y = row;
+ } else {
+ aSize.SizeTo(aWM, 0, 0);
+ aCellIndex.x = aCellIndex.y = 0;
+ }
+}
+
+// only valid for non border children
+void nsHTMLFramesetFrame::GetSizeOfChild(nsIFrame* aChild, WritingMode aWM,
+ LogicalSize& aSize) {
+ // Reflow only creates children frames for <frameset> and <frame> content.
+ // this assumption is used here
+ int i = 0;
+ for (nsIFrame* child : mFrames) {
+ if (aChild == child) {
+ nsIntPoint ignore;
+ GetSizeOfChildAt(i, aWM, aSize, ignore);
+ return;
+ }
+ i++;
+ }
+ aSize.SizeTo(aWM, 0, 0);
+}
+
+nsresult nsHTMLFramesetFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (mDragger) {
+ // the nsFramesetBorderFrame has captured NS_MOUSE_DOWN
+ switch (aEvent->mMessage) {
+ case eMouseMove:
+ MouseDrag(aPresContext, aEvent);
+ break;
+ case eMouseUp:
+ if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
+ EndMouseDrag(aPresContext);
+ }
+ break;
+ default:
+ break;
+ }
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ } else {
+ *aEventStatus = nsEventStatus_eIgnore;
+ }
+ return NS_OK;
+}
+
+nsIFrame::Cursor nsHTMLFramesetFrame::GetCursor(const nsPoint&) {
+ auto kind = StyleCursorKind::Default;
+ if (mDragger) {
+ kind = mDragger->mVertical ? StyleCursorKind::EwResize
+ : StyleCursorKind::NsResize;
+ }
+ return Cursor{kind, AllowCustomCursorImage::No};
+}
+
+void nsHTMLFramesetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ BuildDisplayListForInline(aBuilder, aLists);
+
+ if (mDragger && aBuilder->IsForEventDelivery()) {
+ aLists.Content()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
+ }
+}
+
+void nsHTMLFramesetFrame::ReflowPlaceChild(nsIFrame* aChild,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsPoint& aOffset, nsSize& aSize,
+ nsIntPoint* aCellIndex) {
+ // reflow the child
+ ReflowInput reflowInput(aPresContext, aReflowInput, aChild,
+ LogicalSize(aChild->GetWritingMode(), aSize));
+ reflowInput.SetComputedWidth(std::max(
+ 0,
+ aSize.width - reflowInput.ComputedPhysicalBorderPadding().LeftRight()));
+ reflowInput.SetComputedHeight(std::max(
+ 0,
+ aSize.height - reflowInput.ComputedPhysicalBorderPadding().TopBottom()));
+ ReflowOutput reflowOutput(aReflowInput);
+ reflowOutput.Width() = aSize.width;
+ reflowOutput.Height() = aSize.height;
+ nsReflowStatus status;
+
+ ReflowChild(aChild, aPresContext, reflowOutput, reflowInput, aOffset.x,
+ aOffset.y, ReflowChildFlags::Default, status);
+ NS_ASSERTION(status.IsComplete(), "bad status");
+
+ // Place and size the child
+ reflowOutput.Width() = aSize.width;
+ reflowOutput.Height() = aSize.height;
+ FinishReflowChild(aChild, aPresContext, reflowOutput, &reflowInput, aOffset.x,
+ aOffset.y, ReflowChildFlags::Default);
+}
+
+static nsFrameborder GetFrameBorderHelper(nsGenericHTMLElement* aContent) {
+ if (nullptr != aContent) {
+ const nsAttrValue* attr = aContent->GetParsedAttr(nsGkAtoms::frameborder);
+ if (attr && attr->Type() == nsAttrValue::eEnum) {
+ switch (static_cast<FrameBorderProperty>(attr->GetEnumValue())) {
+ case FrameBorderProperty::Yes:
+ case FrameBorderProperty::One:
+ return eFrameborder_Yes;
+
+ case FrameBorderProperty::No:
+ case FrameBorderProperty::Zero:
+ return eFrameborder_No;
+ }
+ }
+ }
+ return eFrameborder_Notset;
+}
+
+nsFrameborder nsHTMLFramesetFrame::GetFrameBorder() {
+ nsFrameborder result = eFrameborder_Notset;
+ nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(mContent);
+
+ if (content) {
+ result = GetFrameBorderHelper(content);
+ }
+ if (eFrameborder_Notset == result) {
+ return mParentFrameborder;
+ }
+ return result;
+}
+
+nsFrameborder nsHTMLFramesetFrame::GetFrameBorder(nsIContent* aContent) {
+ nsFrameborder result = eFrameborder_Notset;
+
+ nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(aContent);
+
+ if (content) {
+ result = GetFrameBorderHelper(content);
+ }
+ if (eFrameborder_Notset == result) {
+ return GetFrameBorder();
+ }
+ return result;
+}
+
+nscolor nsHTMLFramesetFrame::GetBorderColor() {
+ nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(mContent);
+
+ if (content) {
+ const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::bordercolor);
+ if (attr) {
+ nscolor color;
+ if (attr->GetColorValue(color)) {
+ return color;
+ }
+ }
+ }
+
+ return mParentBorderColor;
+}
+
+nscolor nsHTMLFramesetFrame::GetBorderColor(nsIContent* aContent) {
+ nsGenericHTMLElement* content = nsGenericHTMLElement::FromNode(aContent);
+
+ if (content) {
+ const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::bordercolor);
+ if (attr) {
+ nscolor color;
+ if (attr->GetColorValue(color)) {
+ return color;
+ }
+ }
+ }
+ return GetBorderColor();
+}
+
+void nsHTMLFramesetFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsHTMLFramesetFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ mozilla::PresShell* presShell = aPresContext->PresShell();
+ ServoStyleSet* styleSet = presShell->StyleSet();
+
+ GetParent()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+
+ // printf("FramesetFrame2::Reflow %X (%d,%d) \n", this,
+ // aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight());
+ // Always get the size so that the caller knows how big we are
+ GetDesiredSize(aPresContext, aReflowInput, aDesiredSize);
+
+ nscoord width = (aDesiredSize.Width() <= aReflowInput.AvailableWidth())
+ ? aDesiredSize.Width()
+ : aReflowInput.AvailableWidth();
+ nscoord height = (aDesiredSize.Height() <= aReflowInput.AvailableHeight())
+ ? aDesiredSize.Height()
+ : aReflowInput.AvailableHeight();
+
+ // We might be reflowed more than once with NS_FRAME_FIRST_REFLOW;
+ // that's allowed. (Though it will only happen for misuse of frameset
+ // that includes it within other content.) So measure firstTime by
+ // what we care about, which is whether we've processed the data we
+ // process below if firstTime is true.
+ MOZ_ASSERT(!mChildFrameborder == !mChildBorderColors);
+ bool firstTime = !!mChildFrameborder;
+
+ // subtract out the width of all of the potential borders. There are
+ // only borders between <frame>s. There are none on the edges (e.g the
+ // leftmost <frame> has no left border).
+ int32_t borderWidth = GetBorderWidth(aPresContext, true);
+
+ width -= (mNumCols - 1) * borderWidth;
+ if (width < 0) width = 0;
+
+ height -= (mNumRows - 1) * borderWidth;
+ if (height < 0) height = 0;
+
+ HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
+ NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
+ const nsFramesetSpec* rowSpecs = nullptr;
+ const nsFramesetSpec* colSpecs = nullptr;
+ int32_t rows = 0;
+ int32_t cols = 0;
+ ourContent->GetRowSpec(&rows, &rowSpecs);
+ ourContent->GetColSpec(&cols, &colSpecs);
+ // If the number of cols or rows has changed, the frame for the frameset
+ // will be re-created.
+ if (mNumRows != rows || mNumCols != cols) {
+ mDrag.UnSet();
+ return;
+ }
+
+ CalculateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get());
+ CalculateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get());
+
+ UniquePtr<bool[]> verBordersVis; // vertical borders visibility
+ UniquePtr<nscolor[]> verBorderColors;
+ UniquePtr<bool[]> horBordersVis; // horizontal borders visibility
+ UniquePtr<nscolor[]> horBorderColors;
+ nscolor borderColor = GetBorderColor();
+ nsFrameborder frameborder = GetFrameBorder();
+
+ if (firstTime) {
+ // Check for overflow in memory allocations using mNumCols and mNumRows
+ // which have a maxium value of NS_MAX_FRAMESET_SPEC_COUNT.
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(bool),
+ "Check for overflow");
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nscolor),
+ "Check for overflow");
+
+ verBordersVis = MakeUnique<bool[]>(mNumCols);
+ verBorderColors = MakeUnique<nscolor[]>(mNumCols);
+ for (int verX = 0; verX < mNumCols; verX++) {
+ verBordersVis[verX] = false;
+ verBorderColors[verX] = NO_COLOR;
+ }
+
+ horBordersVis = MakeUnique<bool[]>(mNumRows);
+ horBorderColors = MakeUnique<nscolor[]>(mNumRows);
+ for (int horX = 0; horX < mNumRows; horX++) {
+ horBordersVis[horX] = false;
+ horBorderColors[horX] = NO_COLOR;
+ }
+ }
+
+ // reflow the children
+ int32_t lastRow = 0;
+ int32_t lastCol = 0;
+ int32_t borderChildX = mNonBorderChildCount; // index of border children
+ nsHTMLFramesetBorderFrame* borderFrame = nullptr;
+ nsPoint offset(0, 0);
+ nsSize size, lastSize;
+ WritingMode wm = GetWritingMode();
+ LogicalSize logicalSize(wm);
+ nsIFrame* child = mFrames.FirstChild();
+
+ for (int32_t childX = 0; childX < mNonBorderChildCount; childX++) {
+ nsIntPoint cellIndex;
+ GetSizeOfChildAt(childX, wm, logicalSize, cellIndex);
+ size = logicalSize.GetPhysicalSize(wm);
+
+ if (lastRow != cellIndex.y) { // changed to next row
+ offset.x = 0;
+ offset.y += lastSize.height;
+ if (firstTime) { // create horizontal border
+
+ RefPtr<ComputedStyle> pseudoComputedStyle;
+ pseudoComputedStyle = styleSet->ResolveNonInheritingAnonymousBoxStyle(
+ PseudoStyleType::horizontalFramesetBorder);
+
+ borderFrame = new (presShell) nsHTMLFramesetBorderFrame(
+ pseudoComputedStyle, PresContext(), borderWidth, false, false);
+ borderFrame->Init(mContent, this, nullptr);
+ mChildCount++;
+ mFrames.AppendFrame(nullptr, borderFrame);
+ mHorBorders[cellIndex.y - 1] = borderFrame;
+ // set the neighbors for determining drag boundaries
+ borderFrame->mPrevNeighbor = lastRow;
+ borderFrame->mNextNeighbor = cellIndex.y;
+ } else {
+ borderFrame = (nsHTMLFramesetBorderFrame*)mFrames.FrameAt(borderChildX);
+ borderFrame->mWidth = borderWidth;
+ borderChildX++;
+ }
+ nsSize borderSize(aDesiredSize.Width(), borderWidth);
+ ReflowPlaceChild(borderFrame, aPresContext, aReflowInput, offset,
+ borderSize);
+ borderFrame = nullptr;
+ offset.y += borderWidth;
+ } else {
+ if (cellIndex.x > 0) { // moved to next col in same row
+ if (0 == cellIndex.y) { // in 1st row
+ if (firstTime) { // create vertical border
+
+ RefPtr<ComputedStyle> pseudoComputedStyle;
+ pseudoComputedStyle =
+ styleSet->ResolveNonInheritingAnonymousBoxStyle(
+ PseudoStyleType::verticalFramesetBorder);
+
+ borderFrame = new (presShell) nsHTMLFramesetBorderFrame(
+ pseudoComputedStyle, PresContext(), borderWidth, true, false);
+ borderFrame->Init(mContent, this, nullptr);
+ mChildCount++;
+ mFrames.AppendFrame(nullptr, borderFrame);
+ mVerBorders[cellIndex.x - 1] = borderFrame;
+ // set the neighbors for determining drag boundaries
+ borderFrame->mPrevNeighbor = lastCol;
+ borderFrame->mNextNeighbor = cellIndex.x;
+ } else {
+ borderFrame =
+ (nsHTMLFramesetBorderFrame*)mFrames.FrameAt(borderChildX);
+ borderFrame->mWidth = borderWidth;
+ borderChildX++;
+ }
+ nsSize borderSize(borderWidth, aDesiredSize.Height());
+ ReflowPlaceChild(borderFrame, aPresContext, aReflowInput, offset,
+ borderSize);
+ borderFrame = nullptr;
+ }
+ offset.x += borderWidth;
+ }
+ }
+
+ ReflowPlaceChild(child, aPresContext, aReflowInput, offset, size,
+ &cellIndex);
+
+ if (firstTime) {
+ int32_t childVis;
+ nsHTMLFramesetFrame* framesetFrame = do_QueryFrame(child);
+ if (framesetFrame) {
+ childVis = framesetFrame->mEdgeVisibility;
+ mChildBorderColors[childX] = framesetFrame->mEdgeColors;
+ } else if (child->IsSubDocumentFrame()) {
+ if (eFrameborder_Yes == mChildFrameborder[childX]) {
+ childVis = ALL_VIS;
+ } else if (eFrameborder_No == mChildFrameborder[childX]) {
+ childVis = NONE_VIS;
+ } else { // notset
+ childVis = (eFrameborder_No == frameborder) ? NONE_VIS : ALL_VIS;
+ }
+ } else { // blank
+#ifdef DEBUG
+ nsHTMLFramesetBlankFrame* blank = do_QueryFrame(child);
+ MOZ_ASSERT(blank, "unexpected child frame type");
+#endif
+ childVis = NONE_VIS;
+ }
+ nsBorderColor childColors = mChildBorderColors[childX];
+ // set the visibility, color of our edge borders based on children
+ if (0 == cellIndex.x) {
+ if (!(mEdgeVisibility & LEFT_VIS)) {
+ mEdgeVisibility |= (LEFT_VIS & childVis);
+ }
+ if (NO_COLOR == mEdgeColors.mLeft) {
+ mEdgeColors.mLeft = childColors.mLeft;
+ }
+ }
+ if (0 == cellIndex.y) {
+ if (!(mEdgeVisibility & TOP_VIS)) {
+ mEdgeVisibility |= (TOP_VIS & childVis);
+ }
+ if (NO_COLOR == mEdgeColors.mTop) {
+ mEdgeColors.mTop = childColors.mTop;
+ }
+ }
+ if (mNumCols - 1 == cellIndex.x) {
+ if (!(mEdgeVisibility & RIGHT_VIS)) {
+ mEdgeVisibility |= (RIGHT_VIS & childVis);
+ }
+ if (NO_COLOR == mEdgeColors.mRight) {
+ mEdgeColors.mRight = childColors.mRight;
+ }
+ }
+ if (mNumRows - 1 == cellIndex.y) {
+ if (!(mEdgeVisibility & BOTTOM_VIS)) {
+ mEdgeVisibility |= (BOTTOM_VIS & childVis);
+ }
+ if (NO_COLOR == mEdgeColors.mBottom) {
+ mEdgeColors.mBottom = childColors.mBottom;
+ }
+ }
+ // set the visibility of borders that the child may affect
+ if (childVis & RIGHT_VIS) {
+ verBordersVis[cellIndex.x] = true;
+ }
+ if (childVis & BOTTOM_VIS) {
+ horBordersVis[cellIndex.y] = true;
+ }
+ if ((cellIndex.x > 0) && (childVis & LEFT_VIS)) {
+ verBordersVis[cellIndex.x - 1] = true;
+ }
+ if ((cellIndex.y > 0) && (childVis & TOP_VIS)) {
+ horBordersVis[cellIndex.y - 1] = true;
+ }
+ // set the colors of borders that the child may affect
+ if (NO_COLOR == verBorderColors[cellIndex.x]) {
+ verBorderColors[cellIndex.x] = mChildBorderColors[childX].mRight;
+ }
+ if (NO_COLOR == horBorderColors[cellIndex.y]) {
+ horBorderColors[cellIndex.y] = mChildBorderColors[childX].mBottom;
+ }
+ if ((cellIndex.x > 0) && (NO_COLOR == verBorderColors[cellIndex.x - 1])) {
+ verBorderColors[cellIndex.x - 1] = mChildBorderColors[childX].mLeft;
+ }
+ if ((cellIndex.y > 0) && (NO_COLOR == horBorderColors[cellIndex.y - 1])) {
+ horBorderColors[cellIndex.y - 1] = mChildBorderColors[childX].mTop;
+ }
+ }
+ lastRow = cellIndex.y;
+ lastCol = cellIndex.x;
+ lastSize = size;
+ offset.x += size.width;
+ child = child->GetNextSibling();
+ }
+
+ if (firstTime) {
+ nscolor childColor;
+ // set the visibility, color, mouse sensitivity of borders
+ for (int verX = 0; verX < mNumCols - 1; verX++) {
+ if (mVerBorders[verX]) {
+ mVerBorders[verX]->SetVisibility(verBordersVis[verX]);
+ SetBorderResize(mVerBorders[verX]);
+ childColor = (NO_COLOR == verBorderColors[verX])
+ ? borderColor
+ : verBorderColors[verX];
+ mVerBorders[verX]->SetColor(childColor);
+ }
+ }
+ for (int horX = 0; horX < mNumRows - 1; horX++) {
+ if (mHorBorders[horX]) {
+ mHorBorders[horX]->SetVisibility(horBordersVis[horX]);
+ SetBorderResize(mHorBorders[horX]);
+ childColor = (NO_COLOR == horBorderColors[horX])
+ ? borderColor
+ : horBorderColors[horX];
+ mHorBorders[horX]->SetColor(childColor);
+ }
+ }
+
+ mChildFrameborder.reset();
+ mChildBorderColors.reset();
+ }
+
+ mDrag.UnSet();
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ FinishAndStoreOverflow(&aDesiredSize);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsHTMLFramesetFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Frameset"_ns, aResult);
+}
+#endif
+
+bool nsHTMLFramesetFrame::CanResize(bool aVertical, bool aLeft) {
+ int32_t childX;
+ int32_t startX;
+ if (aVertical) {
+ startX = (aLeft) ? 0 : mNumCols - 1;
+ for (childX = startX; childX < mNonBorderChildCount; childX += mNumCols) {
+ if (!CanChildResize(aVertical, aLeft, childX)) {
+ return false;
+ }
+ }
+ } else {
+ startX = (aLeft) ? 0 : (mNumRows - 1) * mNumCols;
+ int32_t endX = startX + mNumCols;
+ for (childX = startX; childX < endX; childX++) {
+ if (!CanChildResize(aVertical, aLeft, childX)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool nsHTMLFramesetFrame::GetNoResize(nsIFrame* aChildFrame) {
+ nsIContent* content = aChildFrame->GetContent();
+
+ return content && content->IsElement() &&
+ content->AsElement()->HasAttr(nsGkAtoms::noresize);
+}
+
+bool nsHTMLFramesetFrame::CanChildResize(bool aVertical, bool aLeft,
+ int32_t aChildX) {
+ nsIFrame* child = mFrames.FrameAt(aChildX);
+ nsHTMLFramesetFrame* frameset = do_QueryFrame(child);
+ return frameset ? frameset->CanResize(aVertical, aLeft) : !GetNoResize(child);
+}
+
+// This calculates and sets the resizability of all border frames
+
+void nsHTMLFramesetFrame::RecalculateBorderResize() {
+ if (!mContent) {
+ return;
+ }
+
+ static_assert(
+ NS_MAX_FRAMESET_SPEC_COUNT < INT32_MAX / NS_MAX_FRAMESET_SPEC_COUNT,
+ "Check for overflow");
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT <
+ UINT_MAX / sizeof(int32_t) / NS_MAX_FRAMESET_SPEC_COUNT,
+ "Check for overflow");
+ // set the visibility and mouse sensitivity of borders
+ int32_t verX;
+ for (verX = 0; verX < mNumCols - 1; verX++) {
+ if (mVerBorders[verX]) {
+ mVerBorders[verX]->mCanResize = true;
+ SetBorderResize(mVerBorders[verX]);
+ }
+ }
+ int32_t horX;
+ for (horX = 0; horX < mNumRows - 1; horX++) {
+ if (mHorBorders[horX]) {
+ mHorBorders[horX]->mCanResize = true;
+ SetBorderResize(mHorBorders[horX]);
+ }
+ }
+}
+
+void nsHTMLFramesetFrame::SetBorderResize(
+ nsHTMLFramesetBorderFrame* aBorderFrame) {
+ if (aBorderFrame->mVertical) {
+ for (int rowX = 0; rowX < mNumRows; rowX++) {
+ int32_t childX = aBorderFrame->mPrevNeighbor + (rowX * mNumCols);
+ if (!CanChildResize(true, false, childX) ||
+ !CanChildResize(true, true, childX + 1)) {
+ aBorderFrame->mCanResize = false;
+ }
+ }
+ } else {
+ int32_t childX = aBorderFrame->mPrevNeighbor * mNumCols;
+ int32_t endX = childX + mNumCols;
+ for (; childX < endX; childX++) {
+ if (!CanChildResize(false, false, childX)) {
+ aBorderFrame->mCanResize = false;
+ }
+ }
+ endX = endX + mNumCols;
+ for (; childX < endX; childX++) {
+ if (!CanChildResize(false, true, childX)) {
+ aBorderFrame->mCanResize = false;
+ }
+ }
+ }
+}
+
+void nsHTMLFramesetFrame::StartMouseDrag(nsPresContext* aPresContext,
+ nsHTMLFramesetBorderFrame* aBorder,
+ WidgetGUIEvent* aEvent) {
+#if 0
+ int32_t index;
+ IndexOf(aBorder, index);
+ NS_ASSERTION((nullptr != aBorder) && (index >= 0), "invalid dragger");
+#endif
+
+ PresShell::SetCapturingContent(GetContent(),
+ CaptureFlags::IgnoreAllowedState);
+
+ mDragger = aBorder;
+
+ mFirstDragPoint = aEvent->mRefPoint;
+
+ // Store the original frame sizes
+ if (mDragger->mVertical) {
+ mPrevNeighborOrigSize = mColSizes[mDragger->mPrevNeighbor];
+ mNextNeighborOrigSize = mColSizes[mDragger->mNextNeighbor];
+ } else {
+ mPrevNeighborOrigSize = mRowSizes[mDragger->mPrevNeighbor];
+ mNextNeighborOrigSize = mRowSizes[mDragger->mNextNeighbor];
+ }
+
+ gDragInProgress = true;
+}
+
+void nsHTMLFramesetFrame::MouseDrag(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent) {
+ // if the capture ended, reset the drag state
+ if (PresShell::GetCapturingContent() != GetContent()) {
+ mDragger = nullptr;
+ gDragInProgress = false;
+ return;
+ }
+
+ int32_t change; // measured positive from left-to-right or top-to-bottom
+ AutoWeakFrame weakFrame(this);
+ if (mDragger->mVertical) {
+ change = aPresContext->DevPixelsToAppUnits(aEvent->mRefPoint.x -
+ mFirstDragPoint.x);
+ if (change > mNextNeighborOrigSize - mMinDrag) {
+ change = mNextNeighborOrigSize - mMinDrag;
+ } else if (change <= mMinDrag - mPrevNeighborOrigSize) {
+ change = mMinDrag - mPrevNeighborOrigSize;
+ }
+ mColSizes[mDragger->mPrevNeighbor] = mPrevNeighborOrigSize + change;
+ mColSizes[mDragger->mNextNeighbor] = mNextNeighborOrigSize - change;
+
+ if (change != 0) {
+ // Recompute the specs from the new sizes.
+ nscoord width =
+ mRect.width - (mNumCols - 1) * GetBorderWidth(aPresContext, true);
+ HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
+ NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
+ const nsFramesetSpec* colSpecs = nullptr;
+ ourContent->GetColSpec(&mNumCols, &colSpecs);
+ nsAutoString newColAttr;
+ GenerateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get(),
+ newColAttr);
+ // Setting the attr will trigger a reflow
+ mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::cols,
+ newColAttr, true);
+ }
+ } else {
+ change = aPresContext->DevPixelsToAppUnits(aEvent->mRefPoint.y -
+ mFirstDragPoint.y);
+ if (change > mNextNeighborOrigSize - mMinDrag) {
+ change = mNextNeighborOrigSize - mMinDrag;
+ } else if (change <= mMinDrag - mPrevNeighborOrigSize) {
+ change = mMinDrag - mPrevNeighborOrigSize;
+ }
+ mRowSizes[mDragger->mPrevNeighbor] = mPrevNeighborOrigSize + change;
+ mRowSizes[mDragger->mNextNeighbor] = mNextNeighborOrigSize - change;
+
+ if (change != 0) {
+ // Recompute the specs from the new sizes.
+ nscoord height =
+ mRect.height - (mNumRows - 1) * GetBorderWidth(aPresContext, true);
+ HTMLFrameSetElement* ourContent = HTMLFrameSetElement::FromNode(mContent);
+ NS_ASSERTION(ourContent, "Someone gave us a broken frameset element!");
+ const nsFramesetSpec* rowSpecs = nullptr;
+ ourContent->GetRowSpec(&mNumRows, &rowSpecs);
+ nsAutoString newRowAttr;
+ GenerateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get(),
+ newRowAttr);
+ // Setting the attr will trigger a reflow
+ mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::rows,
+ newRowAttr, true);
+ }
+ }
+
+ NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
+ if (change != 0) {
+ mDrag.Reset(mDragger->mVertical, mDragger->mPrevNeighbor, change, this);
+ }
+}
+
+void nsHTMLFramesetFrame::EndMouseDrag(nsPresContext* aPresContext) {
+ PresShell::ReleaseCapturingContent();
+ mDragger = nullptr;
+ gDragInProgress = false;
+}
+
+nsIFrame* NS_NewHTMLFramesetFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+#ifdef DEBUG
+ const nsStyleDisplay* disp = aStyle->StyleDisplay();
+ NS_ASSERTION(!disp->IsAbsolutelyPositionedStyle() && !disp->IsFloatingStyle(),
+ "Framesets should not be positioned and should not float");
+#endif
+
+ return new (aPresShell)
+ nsHTMLFramesetFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsHTMLFramesetFrame)
+
+/*******************************************************************************
+ * nsHTMLFramesetBorderFrame
+ ******************************************************************************/
+nsHTMLFramesetBorderFrame::nsHTMLFramesetBorderFrame(
+ ComputedStyle* aStyle, nsPresContext* aPresContext, int32_t aWidth,
+ bool aVertical, bool aVisibility)
+ : nsLeafFrame(aStyle, aPresContext, kClassID),
+ mWidth(aWidth),
+ mVertical(aVertical),
+ mVisibility(aVisibility) {
+ mCanResize = true;
+ mColor = NO_COLOR;
+ mPrevNeighbor = 0;
+ mNextNeighbor = 0;
+}
+
+nsHTMLFramesetBorderFrame::~nsHTMLFramesetBorderFrame() {
+ // printf("nsHTMLFramesetBorderFrame destructor %p \n", this);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsHTMLFramesetBorderFrame)
+
+nscoord nsHTMLFramesetBorderFrame::GetIntrinsicISize() {
+ // No intrinsic width
+ return 0;
+}
+
+nscoord nsHTMLFramesetBorderFrame::GetIntrinsicBSize() {
+ // No intrinsic height
+ return 0;
+}
+
+void nsHTMLFramesetBorderFrame::SetVisibility(bool aVisibility) {
+ mVisibility = aVisibility;
+}
+
+void nsHTMLFramesetBorderFrame::SetColor(nscolor aColor) { mColor = aColor; }
+
+void nsHTMLFramesetBorderFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ DO_GLOBAL_REFLOW_COUNT("nsHTMLFramesetBorderFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ // Override Reflow(), since we don't want to deal with what our
+ // computed values are.
+ SizeToAvailSize(aReflowInput, aDesiredSize);
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+}
+
+class nsDisplayFramesetBorder : public nsPaintedDisplayItem {
+ public:
+ nsDisplayFramesetBorder(nsDisplayListBuilder* aBuilder,
+ nsHTMLFramesetBorderFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayFramesetBorder);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFramesetBorder)
+
+ // REVIEW: see old GetFrameForPoint
+ // Receives events in its bounds
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) override {
+ aOutFrames->AppendElement(mFrame);
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("FramesetBorder", TYPE_FRAMESET_BORDER)
+};
+
+void nsDisplayFramesetBorder::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ static_cast<nsHTMLFramesetBorderFrame*>(mFrame)->PaintBorder(
+ aCtx->GetDrawTarget(), ToReferenceFrame());
+}
+
+void nsHTMLFramesetBorderFrame::BuildDisplayList(
+ nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
+ aLists.Content()->AppendNewToTop<nsDisplayFramesetBorder>(aBuilder, this);
+}
+
+void nsHTMLFramesetBorderFrame::PaintBorder(DrawTarget* aDrawTarget,
+ nsPoint aPt) {
+ nscoord widthInPixels = nsPresContext::AppUnitsToIntCSSPixels(mWidth);
+ nscoord pixelWidth = nsPresContext::CSSPixelsToAppUnits(1);
+
+ if (widthInPixels <= 0) return;
+
+ ColorPattern bgColor(ToDeviceColor(LookAndFeel::Color(
+ LookAndFeel::ColorID::Window, this, NS_RGB(200, 200, 200))));
+
+ ColorPattern fgColor(ToDeviceColor(LookAndFeel::Color(
+ LookAndFeel::ColorID::Windowtext, this, NS_RGB(0, 0, 0))));
+
+ ColorPattern hltColor(ToDeviceColor(LookAndFeel::Color(
+ LookAndFeel::ColorID::Threedhighlight, this, NS_RGB(255, 255, 255))));
+
+ ColorPattern sdwColor(ToDeviceColor(LookAndFeel::Color(
+ LookAndFeel::ColorID::Threedshadow, this, NS_RGB(128, 128, 128))));
+
+ ColorPattern color(ToDeviceColor(NS_RGB(255, 255, 255))); // default to white
+ if (mVisibility) {
+ color =
+ (NO_COLOR == mColor) ? bgColor : ColorPattern(ToDeviceColor(mColor));
+ }
+
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+
+ Point toRefFrame = NSPointToPoint(aPt, appUnitsPerDevPixel);
+
+ AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ aDrawTarget->SetTransform(
+ aDrawTarget->GetTransform().PreTranslate(toRefFrame));
+
+ nsPoint start(0, 0);
+ nsPoint end = mVertical ? nsPoint(0, mRect.height) : nsPoint(mRect.width, 0);
+
+ // draw grey or white first
+ for (int i = 0; i < widthInPixels; i++) {
+ StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
+ color);
+ if (mVertical) {
+ start.x += pixelWidth;
+ end.x = start.x;
+ } else {
+ start.y += pixelWidth;
+ end.y = start.y;
+ }
+ }
+
+ if (!mVisibility) return;
+
+ if (widthInPixels >= 5) {
+ start.x = (mVertical) ? pixelWidth : 0;
+ start.y = (mVertical) ? 0 : pixelWidth;
+ end.x = (mVertical) ? start.x : mRect.width;
+ end.y = (mVertical) ? mRect.height : start.y;
+ StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
+ hltColor);
+ }
+
+ if (widthInPixels >= 2) {
+ start.x = (mVertical) ? mRect.width - (2 * pixelWidth) : 0;
+ start.y = (mVertical) ? 0 : mRect.height - (2 * pixelWidth);
+ end.x = (mVertical) ? start.x : mRect.width;
+ end.y = (mVertical) ? mRect.height : start.y;
+ StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
+ sdwColor);
+ }
+
+ if (widthInPixels >= 1) {
+ start.x = (mVertical) ? mRect.width - pixelWidth : 0;
+ start.y = (mVertical) ? 0 : mRect.height - pixelWidth;
+ end.x = (mVertical) ? start.x : mRect.width;
+ end.y = (mVertical) ? mRect.height : start.y;
+ StrokeLineWithSnapping(start, end, appUnitsPerDevPixel, *aDrawTarget,
+ fgColor);
+ }
+}
+
+nsresult nsHTMLFramesetBorderFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ *aEventStatus = nsEventStatus_eIgnore;
+
+ // XXX Mouse setting logic removed. The remaining logic should also move.
+ if (!mCanResize) {
+ return NS_OK;
+ }
+
+ if (aEvent->mMessage == eMouseDown &&
+ aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
+ nsHTMLFramesetFrame* parentFrame = do_QueryFrame(GetParent());
+ if (parentFrame) {
+ parentFrame->StartMouseDrag(aPresContext, this, aEvent);
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ return NS_OK;
+}
+
+nsIFrame::Cursor nsHTMLFramesetBorderFrame::GetCursor(const nsPoint&) {
+ auto kind = StyleCursorKind::Default;
+ if (mCanResize) {
+ kind = mVertical ? StyleCursorKind::EwResize : StyleCursorKind::NsResize;
+ }
+ return Cursor{kind, AllowCustomCursorImage::No};
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsHTMLFramesetBorderFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"FramesetBorder"_ns, aResult);
+}
+#endif
+
+/*******************************************************************************
+ * nsHTMLFramesetBlankFrame
+ ******************************************************************************/
+
+NS_QUERYFRAME_HEAD(nsHTMLFramesetBlankFrame)
+ NS_QUERYFRAME_ENTRY(nsHTMLFramesetBlankFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsLeafFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsHTMLFramesetBlankFrame)
+
+nsHTMLFramesetBlankFrame::~nsHTMLFramesetBlankFrame() {
+ // printf("nsHTMLFramesetBlankFrame destructor %p \n", this);
+}
+
+nscoord nsHTMLFramesetBlankFrame::GetIntrinsicISize() {
+ // No intrinsic width
+ return 0;
+}
+
+nscoord nsHTMLFramesetBlankFrame::GetIntrinsicBSize() {
+ // No intrinsic height
+ return 0;
+}
+
+void nsHTMLFramesetBlankFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ DO_GLOBAL_REFLOW_COUNT("nsHTMLFramesetBlankFrame");
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ // Override Reflow(), since we don't want to deal with what our
+ // computed values are.
+ SizeToAvailSize(aReflowInput, aDesiredSize);
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+}
+
+class nsDisplayFramesetBlank : public nsPaintedDisplayItem {
+ public:
+ nsDisplayFramesetBlank(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayFramesetBlank);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFramesetBlank)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("FramesetBlank", TYPE_FRAMESET_BLANK)
+};
+
+void nsDisplayFramesetBlank::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ Rect rect = NSRectToSnappedRect(GetPaintRect(aBuilder, aCtx),
+ appUnitsPerDevPixel, *drawTarget);
+ ColorPattern white(ToDeviceColor(sRGBColor::OpaqueWhite()));
+ drawTarget->FillRect(rect, white);
+}
+
+void nsHTMLFramesetBlankFrame::BuildDisplayList(
+ nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
+ aLists.Content()->AppendNewToTop<nsDisplayFramesetBlank>(aBuilder, this);
+}
diff --git a/layout/generic/nsFrameSetFrame.h b/layout/generic/nsFrameSetFrame.h
new file mode 100644
index 0000000000..e836e6ea0a
--- /dev/null
+++ b/layout/generic/nsFrameSetFrame.h
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for HTML <frameset> elements */
+
+#ifndef nsHTMLFrameset_h___
+#define nsHTMLFrameset_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsContainerFrame.h"
+#include "nsColor.h"
+
+class nsIContent;
+class nsPresContext;
+struct nsRect;
+struct nsSize;
+class nsAtom;
+class nsHTMLFramesetBorderFrame;
+class nsHTMLFramesetFrame;
+
+#define NO_COLOR 0xFFFFFFFA
+
+// defined at HTMLFrameSetElement.h
+struct nsFramesetSpec;
+
+struct nsBorderColor {
+ nscolor mLeft;
+ nscolor mRight;
+ nscolor mTop;
+ nscolor mBottom;
+
+ nsBorderColor() { Set(NO_COLOR); }
+ ~nsBorderColor() = default;
+ void Set(nscolor aColor) { mLeft = mRight = mTop = mBottom = aColor; }
+};
+
+enum nsFrameborder {
+ eFrameborder_Yes = 0,
+ eFrameborder_No,
+ eFrameborder_Notset
+};
+
+struct nsFramesetDrag {
+ nsHTMLFramesetFrame* mSource; // frameset whose border was dragged to cause
+ // the resize
+ int32_t mIndex; // index of left col or top row of effected area
+ int32_t mChange; // pos for left to right or top to bottom, neg otherwise
+ bool mVertical; // vertical if true, otherwise horizontal
+
+ nsFramesetDrag();
+ void Reset(bool aVertical, int32_t aIndex, int32_t aChange,
+ nsHTMLFramesetFrame* aSource);
+ void UnSet();
+};
+
+/*******************************************************************************
+ * nsHTMLFramesetFrame
+ ******************************************************************************/
+class nsHTMLFramesetFrame final : public nsContainerFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsHTMLFramesetFrame)
+
+ explicit nsHTMLFramesetFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext);
+
+ virtual ~nsHTMLFramesetFrame();
+
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+
+ static bool gDragInProgress;
+
+ void GetSizeOfChild(nsIFrame* aChild, mozilla::WritingMode aWM,
+ mozilla::LogicalSize& aSize);
+
+ void GetSizeOfChildAt(int32_t aIndexInParent, mozilla::WritingMode aWM,
+ mozilla::LogicalSize& aSize, nsIntPoint& aCellIndex);
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ Cursor GetCursor(const nsPoint&) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ void StartMouseDrag(nsPresContext* aPresContext,
+ nsHTMLFramesetBorderFrame* aBorder,
+ mozilla::WidgetGUIEvent* aEvent);
+
+ void MouseDrag(nsPresContext* aPresContext, mozilla::WidgetGUIEvent* aEvent);
+
+ void EndMouseDrag(nsPresContext* aPresContext);
+
+ nsFrameborder GetParentFrameborder() { return mParentFrameborder; }
+
+ void SetParentFrameborder(nsFrameborder aValue) {
+ mParentFrameborder = aValue;
+ }
+
+ nsFramesetDrag& GetDrag() { return mDrag; }
+
+ void RecalculateBorderResize();
+
+ protected:
+ void Scale(nscoord aDesired, int32_t aNumIndicies, int32_t* aIndicies,
+ int32_t aNumItems, int32_t* aItems);
+
+ void CalculateRowCol(nsPresContext* aPresContext, nscoord aSize,
+ int32_t aNumSpecs, const nsFramesetSpec* aSpecs,
+ nscoord* aValues);
+
+ void GenerateRowCol(nsPresContext* aPresContext, nscoord aSize,
+ int32_t aNumSpecs, const nsFramesetSpec* aSpecs,
+ nscoord* aValues, nsString& aNewAttr);
+
+ virtual void GetDesiredSize(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ ReflowOutput& aDesiredSize);
+
+ int32_t GetBorderWidth(nsPresContext* aPresContext,
+ bool aTakeForcingIntoAccount);
+
+ int32_t GetParentBorderWidth() { return mParentBorderWidth; }
+
+ void SetParentBorderWidth(int32_t aWidth) { mParentBorderWidth = aWidth; }
+
+ nscolor GetParentBorderColor() { return mParentBorderColor; }
+
+ void SetParentBorderColor(nscolor aColor) { mParentBorderColor = aColor; }
+
+ nsFrameborder GetFrameBorder();
+
+ nsFrameborder GetFrameBorder(nsIContent* aContent);
+
+ nscolor GetBorderColor();
+
+ nscolor GetBorderColor(nsIContent* aFrameContent);
+
+ bool GetNoResize(nsIFrame* aChildFrame);
+
+ void ReflowPlaceChild(nsIFrame* aChild, nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput, nsPoint& aOffset,
+ nsSize& aSize, nsIntPoint* aCellIndex = 0);
+
+ bool CanResize(bool aVertical, bool aLeft);
+
+ bool CanChildResize(bool aVertical, bool aLeft, int32_t aChildX);
+
+ void SetBorderResize(nsHTMLFramesetBorderFrame* aBorderFrame);
+
+ template <typename T, class D = mozilla::DefaultDelete<T>>
+ using UniquePtr = mozilla::UniquePtr<T, D>;
+
+ nsFramesetDrag mDrag;
+ nsBorderColor mEdgeColors;
+ nsHTMLFramesetBorderFrame* mDragger;
+ nsHTMLFramesetFrame* mTopLevelFrameset;
+ UniquePtr<nsHTMLFramesetBorderFrame*[]> mVerBorders; // vertical borders
+ UniquePtr<nsHTMLFramesetBorderFrame*[]> mHorBorders; // horizontal borders
+ UniquePtr<nsFrameborder[]>
+ mChildFrameborder; // the frameborder attr of children
+ UniquePtr<nsBorderColor[]> mChildBorderColors;
+ UniquePtr<nscoord[]> mRowSizes; // currently computed row sizes
+ UniquePtr<nscoord[]> mColSizes; // currently computed col sizes
+ mozilla::LayoutDeviceIntPoint mFirstDragPoint;
+ int32_t mNumRows;
+ int32_t mNumCols;
+ int32_t mNonBorderChildCount;
+ int32_t mNonBlankChildCount;
+ int32_t mEdgeVisibility;
+ nsFrameborder mParentFrameborder;
+ nscolor mParentBorderColor;
+ int32_t mParentBorderWidth;
+ int32_t mPrevNeighborOrigSize; // used during resize
+ int32_t mNextNeighborOrigSize;
+ int32_t mMinDrag;
+ int32_t mChildCount;
+};
+
+#endif
diff --git a/layout/generic/nsFrameState.cpp b/layout/generic/nsFrameState.cpp
new file mode 100644
index 0000000000..2e5463e2d3
--- /dev/null
+++ b/layout/generic/nsFrameState.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* constants for frame state bits and a type to store them in a uint64_t */
+
+#include "nsFrameState.h"
+
+#include "nsBlockFrame.h"
+#include "nsFlexContainerFrame.h"
+#include "nsGridContainerFrame.h"
+#include "nsGfxScrollFrame.h"
+#include "nsIFrame.h"
+#include "nsImageFrame.h"
+#include "nsInlineFrame.h"
+#include "nsPageFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsRubyTextFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsTextFrame.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/SVGContainerFrame.h"
+
+namespace mozilla {
+
+#ifdef DEBUG
+nsCString GetFrameState(nsIFrame* aFrame) {
+ nsCString result;
+ AutoTArray<const char*, 3> groups;
+
+ nsFrameState state = aFrame->GetStateBits();
+
+ if (state == nsFrameState(0)) {
+ result.Assign('0');
+ return result;
+ }
+
+# define FRAME_STATE_GROUP_CLASS(name_, class_) \
+ { \
+ class_* frame = do_QueryFrame(aFrame); \
+ if (frame && \
+ (groups.IsEmpty() || strcmp(groups.LastElement(), #name_))) { \
+ groups.AppendElement(#name_); \
+ } \
+ }
+# define FRAME_STATE_BIT(group_, value_, name_) \
+ if ((state & NS_FRAME_STATE_BIT(value_)) && groups.Contains(#group_)) { \
+ if (!result.IsEmpty()) { \
+ result.InsertLiteral(" | ", 0); \
+ } \
+ result.InsertLiteral(#name_, 0); \
+ state = state & ~NS_FRAME_STATE_BIT(value_); \
+ }
+# include "nsFrameStateBits.h"
+# undef FRAME_STATE_GROUP_CLASS
+# undef FRAME_STATE_BIT
+
+ if (state) {
+ result.AppendPrintf(" | 0x%0" PRIx64, static_cast<uint64_t>(state));
+ }
+
+ return result;
+}
+
+void PrintFrameState(nsIFrame* aFrame) {
+ printf("%s\n", GetFrameState(aFrame).get());
+}
+
+enum class FrameStateGroupId {
+# define FRAME_STATE_GROUP_NAME(name_) name_,
+# include "nsFrameStateBits.h"
+# undef FRAME_STATE_GROUP_NAME
+
+ LENGTH
+};
+
+void DebugVerifyFrameStateBits() {
+ // Build an array of all of the bits used by each group. While
+ // building this we assert that a bit isn't used multiple times within
+ // the same group.
+ nsFrameState bitsUsedPerGroup[size_t(FrameStateGroupId::LENGTH)] = {
+ nsFrameState(0)};
+
+# define FRAME_STATE_BIT(group_, value_, name_) \
+ { \
+ auto bit = NS_FRAME_STATE_BIT(value_); \
+ size_t group = size_t(FrameStateGroupId::group_); \
+ MOZ_ASSERT(!(bitsUsedPerGroup[group] & bit), #name_ \
+ " must not use a bit already declared within its group"); \
+ bitsUsedPerGroup[group] |= bit; \
+ }
+
+# include "nsFrameStateBits.h"
+# undef FRAME_STATE_BIT
+
+ // FIXME: Can we somehow check across the groups as well??? In other
+ // words, find the pairs of groups that could be used on the same
+ // frame (Generic paired with everything else, and a few other pairs),
+ // and check that we don't have bits in common between those pairs.
+}
+
+#endif
+
+} // namespace mozilla
diff --git a/layout/generic/nsFrameState.h b/layout/generic/nsFrameState.h
new file mode 100644
index 0000000000..8411c3a115
--- /dev/null
+++ b/layout/generic/nsFrameState.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* constants for frame state bits and a type to store them in a uint64_t */
+
+#ifndef nsFrameState_h_
+#define nsFrameState_h_
+
+#include <stdint.h>
+
+#ifdef DEBUG
+# include "nsString.h"
+
+class nsIFrame;
+#endif
+
+typedef uint64_t nsFrameState_size_t;
+
+#define NS_FRAME_STATE_BIT(n_) (nsFrameState(nsFrameState_size_t(1) << (n_)))
+
+enum nsFrameState : nsFrameState_size_t {
+#define FRAME_STATE_BIT(group_, value_, name_) \
+ name_ = NS_FRAME_STATE_BIT(value_),
+#include "nsFrameStateBits.h"
+#undef FRAME_STATE_BIT
+};
+
+constexpr nsFrameState operator|(nsFrameState aLeft, nsFrameState aRight) {
+ return nsFrameState(nsFrameState_size_t(aLeft) | nsFrameState_size_t(aRight));
+}
+
+constexpr nsFrameState operator&(nsFrameState aLeft, nsFrameState aRight) {
+ return nsFrameState(nsFrameState_size_t(aLeft) & nsFrameState_size_t(aRight));
+}
+
+constexpr nsFrameState& operator|=(nsFrameState& aLeft, nsFrameState aRight) {
+ aLeft = aLeft | aRight;
+ return aLeft;
+}
+
+constexpr nsFrameState& operator&=(nsFrameState& aLeft, nsFrameState aRight) {
+ aLeft = aLeft & aRight;
+ return aLeft;
+}
+
+constexpr nsFrameState operator~(nsFrameState aRight) {
+ return nsFrameState(~nsFrameState_size_t(aRight));
+}
+
+constexpr nsFrameState operator^(nsFrameState aLeft, nsFrameState aRight) {
+ return nsFrameState(nsFrameState_size_t(aLeft) ^ nsFrameState_size_t(aRight));
+}
+
+constexpr nsFrameState& operator^=(nsFrameState& aLeft, nsFrameState aRight) {
+ aLeft = aLeft ^ aRight;
+ return aLeft;
+}
+
+// Bits 20-31 and 60-63 of the frame state are reserved for implementations.
+#define NS_FRAME_IMPL_RESERVED nsFrameState(0xF0000000FFF00000)
+#define NS_FRAME_RESERVED ~NS_FRAME_IMPL_RESERVED
+
+namespace mozilla {
+#ifdef DEBUG
+nsCString GetFrameState(nsIFrame* aFrame);
+void PrintFrameState(nsIFrame* aFrame);
+void DebugVerifyFrameStateBits();
+#endif
+} // namespace mozilla
+
+#endif /* nsFrameState_h_ */
diff --git a/layout/generic/nsFrameStateBits.h b/layout/generic/nsFrameStateBits.h
new file mode 100644
index 0000000000..a9a1700737
--- /dev/null
+++ b/layout/generic/nsFrameStateBits.h
@@ -0,0 +1,737 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a list of all frame state bits, for preprocessing */
+
+/******
+
+ This file contains definitions for frame state bits -- values used
+ in nsIFrame::mState -- and groups of frame state bits and which
+ classes they apply to.
+
+ There are three macros that can be defined before #including this
+ file:
+
+ FRAME_STATE_GROUP_NAME(name_)
+
+ This denotes the existence of a named group of frame state bits.
+
+ The main group of frame state bits is named "Generic" and is
+ defined to apply to nsIFrame, i.e. all frames. All of the global
+ frame state bits -- bits 0..19 and 32..59 -- are in this group.
+
+ FRAME_STATE_GROUP_CLASS(group_, class_)
+ class_ is the name of a frame class that uses the frame state bits
+ that are a part of the group.
+
+ FRAME_STATE_BIT(group_, value_, name_)
+
+ This denotes the existence of a frame state bit. group_ indicates
+ which group the bit belongs to, value_ is the bit number (0..63),
+ and name_ is the name of the frame state bit constant.
+
+ Note that if you add a new frame state group, you'll need to #include
+ the header for its frame classes in nsFrameState.cpp and, if they don't
+ already, add nsQueryFrame implementations (which can be DEBUG-only) to
+ the frame classes.
+
+ ******/
+
+#ifndef FRAME_STATE_GROUP_NAME
+#define FRAME_STATE_GROUP_NAME(name_) /* nothing */
+#define DEFINED_FRAME_STATE_GROUP_NAME
+#endif
+
+#ifndef FRAME_STATE_GROUP_CLASS
+#define FRAME_STATE_GROUP_CLASS(name_, class_) /* nothing */
+#define DEFINED_FRAME_STATE_GROUP_CLASS
+#endif
+
+#ifndef FRAME_STATE_BIT
+#define FRAME_STATE_BIT(group_, value_, name_) /* nothing */
+#define DEFINED_FRAME_STATE_BIT
+#endif
+
+// Helper macro for the common case of a single class
+#define FRAME_STATE_GROUP(name_, class_) \
+FRAME_STATE_GROUP_NAME(name_) \
+FRAME_STATE_GROUP_CLASS(name_, class_)
+
+// == Frame state bits that apply to all frames ===============================
+
+FRAME_STATE_GROUP(Generic, nsIFrame)
+
+// This bit is set when the frame is actively being reflowed. It is set in many
+// frames' Reflow() by calling MarkInReflow() and unset in DidReflow().
+FRAME_STATE_BIT(Generic, 0, NS_FRAME_IN_REFLOW)
+
+// This bit is set when a frame is created. After it has been reflowed
+// once (during the DidReflow with a finished state) the bit is
+// cleared.
+FRAME_STATE_BIT(Generic, 1, NS_FRAME_FIRST_REFLOW)
+
+// For a continuation frame, if this bit is set, then this a "fluid"
+// continuation, i.e., across a line boundary. Otherwise it's a "hard"
+// continuation, e.g. a bidi continuation.
+FRAME_STATE_BIT(Generic, 2, NS_FRAME_IS_FLUID_CONTINUATION)
+
+// Free bit here.
+
+// If this bit is set, then a reference to the frame is being held
+// elsewhere. The frame may want to send a notification when it is
+// destroyed to allow these references to be cleared.
+FRAME_STATE_BIT(Generic, 4, NS_FRAME_EXTERNAL_REFERENCE)
+
+// If this bit is set, this frame or one of its descendants has a
+// percentage block-size that depends on an ancestor of this frame.
+// (Or it did at one point in the past, since we don't necessarily clear
+// the bit when it's no longer needed; it's an optimization.)
+FRAME_STATE_BIT(Generic, 5, NS_FRAME_CONTAINS_RELATIVE_BSIZE)
+
+// If this bit is set, then the frame corresponds to generated content
+FRAME_STATE_BIT(Generic, 6, NS_FRAME_GENERATED_CONTENT)
+
+// If this bit is set the frame is a continuation that is holding overflow,
+// i.e. it is a next-in-flow created to hold overflow after the box's
+// height has ended. This means the frame should be a) at the top of the
+// page and b) invisible: no borders, zero height, ignored in margin
+// collapsing, etc. See nsContainerFrame.h
+FRAME_STATE_BIT(Generic, 7, NS_FRAME_IS_OVERFLOW_CONTAINER)
+
+// If this bit is set, then the frame has been moved out of the flow,
+// e.g., it is absolutely positioned or floated
+FRAME_STATE_BIT(Generic, 8, NS_FRAME_OUT_OF_FLOW)
+
+// Frame can be an abs/fixed pos. container, if its style says so.
+// MarkAs[Not]AbsoluteContainingBlock will assert that this bit is set.
+// NS_FRAME_HAS_ABSPOS_CHILDREN must not be set when this bit is unset.
+FRAME_STATE_BIT(Generic, 9, NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)
+
+// If this bit is set, then the frame and _all_ of its descendant frames need
+// to be reflowed.
+// This bit is set when the frame is first created.
+// This bit is cleared by DidReflow after the required call to Reflow has
+// finished.
+// Do not set this bit yourself if you plan to pass the frame to
+// PresShell::FrameNeedsReflow. Pass the right arguments instead.
+FRAME_STATE_BIT(Generic, 10, NS_FRAME_IS_DIRTY)
+
+// If this bit is set then the frame is too deep in the frame tree, and
+// we'll stop updating it and its children, to prevent stack overflow
+// and the like.
+FRAME_STATE_BIT(Generic, 11, NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
+
+// If this bit is set, then either:
+// 1. the frame has at least one child that has the NS_FRAME_IS_DIRTY bit or
+// NS_FRAME_HAS_DIRTY_CHILDREN bit set, or
+// 2. the frame has had at least one child removed since the last reflow, or
+// 3. the frame has had a style change that requires the frame to be reflowed
+// but does not _necessarily_ require its descendants to be reflowed (e.g.,
+// for a 'height', 'width', 'margin', etc. change, it's up to the
+// applicable Reflow methods to decide whether the frame's children
+// _actually_ need to be reflowed).
+// If this bit is set but the NS_FRAME_IS_DIRTY is not set, then Reflow still
+// needs to be called on the frame, but Reflow will likely not do as much work
+// as it would if NS_FRAME_IS_DIRTY were set. See the comment documenting
+// nsIFrame::Reflow for more.
+// This bit is cleared by DidReflow after the required call to Reflow has
+// finished.
+// Do not set this bit yourself if you plan to pass the frame to
+// PresShell::FrameNeedsReflow. Pass the right arguments instead.
+FRAME_STATE_BIT(Generic, 12, NS_FRAME_HAS_DIRTY_CHILDREN)
+
+// If this bit is set, the frame has an associated view
+FRAME_STATE_BIT(Generic, 13, NS_FRAME_HAS_VIEW)
+
+// If this bit is set, the frame was created from anonymous content.
+FRAME_STATE_BIT(Generic, 14, NS_FRAME_INDEPENDENT_SELECTION)
+
+// If this bit is set, the frame is part of the mangled frame hierarchy
+// that results when an inline has been split because of a nested block.
+// See the comments in nsCSSFrameConstructor::ConstructInline for
+// more details. (this is only set on nsBlockFrame/nsInlineFrame frames)
+FRAME_STATE_BIT(Generic, 15, NS_FRAME_PART_OF_IBSPLIT)
+
+// If this bit is set, then transforms (e.g. CSS or SVG transforms) are allowed
+// to affect the frame, and a transform may currently be in affect. If this bit
+// is not set, then any transforms on the frame will be ignored.
+// This is used primarily in GetTransformMatrix to optimize for the
+// common case.
+FRAME_STATE_BIT(Generic, 16, NS_FRAME_MAY_BE_TRANSFORMED)
+
+// If this bit is set, the frame itself is a bidi continuation,
+// or is incomplete (its next sibling is a bidi continuation)
+FRAME_STATE_BIT(Generic, 17, NS_FRAME_IS_BIDI)
+
+// If this bit is set the frame has descendant with a view
+FRAME_STATE_BIT(Generic, 18, NS_FRAME_HAS_CHILD_WITH_VIEW)
+
+// If this bit is set, then reflow may be dispatched from the current
+// frame instead of the root frame.
+FRAME_STATE_BIT(Generic, 19, NS_FRAME_REFLOW_ROOT)
+
+// NOTE: Bits 20-31 and 60-63 of the frame state are reserved for specific
+// frame classes.
+
+// This bit is set on floats whose parent does not contain their
+// placeholder. This can happen for two reasons: (1) the float was
+// split, and this piece is the continuation, or (2) the entire float
+// didn't fit on the page.
+// Note that this bit is also shared by text frames for
+// TEXT_IS_IN_TOKEN_MATHML. That's OK because we only check the
+// NS_FRAME_IS_PUSHED_FLOAT bit on frames which we already know are
+// out-of-flow.
+FRAME_STATE_BIT(Generic, 32, NS_FRAME_IS_PUSHED_FLOAT)
+
+// This bit acts as a loop flag for recursive paint server drawing.
+FRAME_STATE_BIT(Generic, 33, NS_FRAME_DRAWING_AS_PAINTSERVER)
+
+// Intrinsic ISize depending on the frame's BSize is rare but possible.
+// This flag indicates that the frame has (or once had) a descendant in that
+// situation (possibly the frame itself).
+FRAME_STATE_BIT(Generic, 34,
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)
+
+// A flag that tells us we can take the common path with respect to style
+// properties for this frame when building event regions. This flag is cleared
+// when any styles are changed and then we recompute it on the next build
+// of the event regions.
+FRAME_STATE_BIT(Generic, 35, NS_FRAME_SIMPLE_EVENT_REGIONS)
+
+// Frame is a display root and the retained layer tree needs to be updated
+// at the next paint via display list construction.
+// Only meaningful for display roots, so we don't really need a global state
+// bit; we could free up this bit with a little extra complexity.
+FRAME_STATE_BIT(Generic, 36, NS_FRAME_UPDATE_LAYER_TREE)
+
+// Frame can accept absolutely positioned children.
+FRAME_STATE_BIT(Generic, 37, NS_FRAME_HAS_ABSPOS_CHILDREN)
+
+// A display item for this frame has been painted as part of a PaintedLayer.
+FRAME_STATE_BIT(Generic, 38, NS_FRAME_PAINTED_THEBES)
+
+// Frame is or is a descendant of something with a fixed block-size, unless
+// that ancestor is a body or html element, and has no closer ancestor that is
+// overflow:auto or overflow:scroll.
+FRAME_STATE_BIT(Generic, 39, NS_FRAME_IN_CONSTRAINED_BSIZE)
+
+// This is only set during painting
+FRAME_STATE_BIT(Generic, 40, NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)
+
+// Is this frame a container for font size inflation, i.e., is it a
+// frame whose width is used to determine the inflation factor for
+// everything whose nearest ancestor container for this frame?
+FRAME_STATE_BIT(Generic, 41, NS_FRAME_FONT_INFLATION_CONTAINER)
+
+// Does this frame manage a region in which we do font size inflation,
+// i.e., roughly, is it an element establishing a new block formatting
+// context?
+FRAME_STATE_BIT(Generic, 42, NS_FRAME_FONT_INFLATION_FLOW_ROOT)
+
+// This bit is set on SVG frames that are laid out using SVG's coordinate
+// system based layout (as opposed to any of the CSS layout models). Note that
+// this does not include SVGOuterSVGFrame since it takes part in CSS layout.
+FRAME_STATE_BIT(Generic, 43, NS_FRAME_SVG_LAYOUT)
+
+// This bit is set if a frame has a multi-column ancestor (i.e.
+// ColumnSetWrapperFrame) within the same block formatting context. A top-level
+// ColumnSetWrapperFrame doesn't have this bit set, whereas a
+// ColumnSetWrapperFrame nested inside a column does have this bit set.
+//
+// All the children of the column-spanners or any other type of frames which
+// create their own block formatting context do not have this bit set because
+// they are not in the same block formatting context created by a multi-column
+// ancestor.
+FRAME_STATE_BIT(Generic, 44, NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)
+
+// If this bit is set, then reflow may be dispatched from the current
+// frame instead of the root frame.
+FRAME_STATE_BIT(Generic, 45, NS_FRAME_DYNAMIC_REFLOW_ROOT)
+
+// This bit indicates that we're tracking visibility for this frame, and that
+// the frame has a VisibilityStateProperty property.
+FRAME_STATE_BIT(Generic, 46, NS_FRAME_VISIBILITY_IS_TRACKED)
+
+// The frame is a descendant of SVGTextFrame and is thus used for SVG
+// text layout.
+FRAME_STATE_BIT(Generic, 47, NS_FRAME_IS_SVG_TEXT)
+
+// Frame is marked as needing painting
+FRAME_STATE_BIT(Generic, 48, NS_FRAME_NEEDS_PAINT)
+
+// Frame has a descendant frame that needs painting - This includes
+// cross-doc children.
+FRAME_STATE_BIT(Generic, 49, NS_FRAME_DESCENDANT_NEEDS_PAINT)
+
+// Frame is a descendant of a popup
+FRAME_STATE_BIT(Generic, 50, NS_FRAME_IN_POPUP)
+
+// Frame has only descendant frames that needs painting - This includes
+// cross-doc children. This guarantees that all descendents have
+// NS_FRAME_NEEDS_PAINT and NS_FRAME_ALL_DESCENDANTS_NEED_PAINT, or they
+// have no display items.
+FRAME_STATE_BIT(Generic, 51, NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)
+
+// Frame is marked as NS_FRAME_NEEDS_PAINT and also has an explicit
+// rect stored to invalidate.
+FRAME_STATE_BIT(Generic, 52, NS_FRAME_HAS_INVALID_RECT)
+
+// Frame is not displayed directly due to it being, or being under, an SVG
+// <defs> element or an SVG resource element (<mask>, <pattern>, etc.)
+FRAME_STATE_BIT(Generic, 53, NS_FRAME_IS_NONDISPLAY)
+
+// Frame has a LayerActivityProperty property
+FRAME_STATE_BIT(Generic, 54, NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)
+
+// Frame owns anonymous boxes whose ComputedStyles it will need to update
+// during a stylo tree traversal.
+FRAME_STATE_BIT(Generic, 55, NS_FRAME_OWNS_ANON_BOXES)
+
+// Frame maybe has a counter-reset/increment style
+FRAME_STATE_BIT(Generic, 56, NS_FRAME_HAS_CSS_COUNTER_STYLE)
+
+// The display list of the frame can be handled by the shortcut for
+// COMMON CASE.
+FRAME_STATE_BIT(Generic, 57, NS_FRAME_SIMPLE_DISPLAYLIST)
+
+// Set for all descendants of MathML sub/supscript elements (other than the
+// base frame) to indicate that the SSTY font feature should be used.
+FRAME_STATE_BIT(Generic, 58, NS_FRAME_MATHML_SCRIPT_DESCENDANT)
+
+// This state bit is set on frames within token MathML elements if the
+// token represents an <mi> tag whose inner HTML consists of a single
+// non-whitespace character to allow special rendering behaviour.
+FRAME_STATE_BIT(Generic, 59, NS_FRAME_IS_IN_SINGLE_CHAR_MI)
+
+// NOTE: Bits 20-31 and 60-63 of the frame state are reserved for specific
+// frame classes.
+
+// NOTE: No more unused bits. If needed, investigate removing obsolete bits by
+// adjusting logic, or moving infrequently-used bits elsewhere. If more space
+// for frame state is still needed, look for bit field gaps in nsIFrame.
+
+// == Frame state bits that apply to flex container frames ====================
+
+FRAME_STATE_GROUP(FlexContainer, nsFlexContainerFrame)
+
+// True iff the normal flow children are already in CSS 'order' in the
+// order they occur in the child frame list.
+FRAME_STATE_BIT(FlexContainer, 20,
+ NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
+
+// Set for a flex container that is emulating a legacy
+// 'display:-webkit-{inline-}box'.
+FRAME_STATE_BIT(FlexContainer, 21, NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX)
+
+// True if the container has no flex items; may lie if there is a pending reflow
+FRAME_STATE_BIT(FlexContainer, 22, NS_STATE_FLEX_SYNTHESIZE_BASELINE)
+
+// True iff some first-in-flow in-flow children were pushed.
+// Note that those child frames may have been removed without this bit
+// being updated for performance reasons, so code shouldn't depend on
+// actually finding any pushed items when this bit is set.
+FRAME_STATE_BIT(FlexContainer, 23, NS_STATE_FLEX_DID_PUSH_ITEMS)
+
+// We've merged some OverflowList children since last reflow.
+FRAME_STATE_BIT(FlexContainer, 24, NS_STATE_FLEX_HAS_CHILD_NIFS)
+
+// True if the next reflow of this frame should generate computed info metrics.
+// These are used by devtools to reveal details of the layout process.
+FRAME_STATE_BIT(FlexContainer, 25, NS_STATE_FLEX_COMPUTED_INFO)
+
+// == Frame state bits that apply to grid container frames ====================
+
+FRAME_STATE_GROUP(GridContainer, nsGridContainerFrame)
+
+// True iff the normal flow children are already in CSS 'order' in the
+// order they occur in the child frame list.
+FRAME_STATE_BIT(GridContainer, 20,
+ NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
+
+// True iff some first-in-flow in-flow children were pushed.
+// Note that those child frames may have been removed without this bit
+// being updated for performance reasons, so code shouldn't depend on
+// actually finding any pushed items when this bit is set.
+FRAME_STATE_BIT(GridContainer, 21, NS_STATE_GRID_DID_PUSH_ITEMS)
+
+// True if the container has no grid items; may lie if there is a pending
+// reflow.
+FRAME_STATE_BIT(GridContainer, 22, NS_STATE_GRID_SYNTHESIZE_BASELINE)
+
+// True if the container is a subgrid in its inline axis.
+FRAME_STATE_BIT(GridContainer, 23, NS_STATE_GRID_IS_COL_SUBGRID)
+
+// True if the container is a subgrid in its block axis.
+FRAME_STATE_BIT(GridContainer, 24, NS_STATE_GRID_IS_ROW_SUBGRID)
+
+// The container contains one or more items subgridded in its inline axis.
+FRAME_STATE_BIT(GridContainer, 25, NS_STATE_GRID_HAS_COL_SUBGRID_ITEM)
+
+// The container contains one or more items subgridded in its block axis.
+FRAME_STATE_BIT(GridContainer, 26, NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM)
+
+// We've merged some OverflowList children since last reflow.
+FRAME_STATE_BIT(GridContainer, 27, NS_STATE_GRID_HAS_CHILD_NIFS)
+
+// True if the container has masonry layout in its inline axis.
+// (mutually exclusive with NS_STATE_GRID_IS_ROW_MASONRY)
+FRAME_STATE_BIT(GridContainer, 28, NS_STATE_GRID_IS_COL_MASONRY)
+
+// True if the container has masonry layout in its block axis.
+// (mutually exclusive with NS_STATE_GRID_IS_COL_MASONRY)
+FRAME_STATE_BIT(GridContainer, 29, NS_STATE_GRID_IS_ROW_MASONRY)
+
+// True if the next reflow of this frame should generate computed info metrics.
+// These are used by devtools to reveal details of the layout process.
+FRAME_STATE_BIT(GridContainer, 30, NS_STATE_GRID_COMPUTED_INFO)
+
+// == Frame state bits that apply to SVG frames ===============================
+
+FRAME_STATE_GROUP_NAME(SVG)
+FRAME_STATE_GROUP_CLASS(SVG, ISVGDisplayableFrame)
+FRAME_STATE_GROUP_CLASS(SVG, SVGContainerFrame)
+
+// If this bit is set, we are a <clipPath> element or descendant.
+FRAME_STATE_BIT(SVG, 20, NS_STATE_SVG_CLIPPATH_CHILD)
+
+// For SVG text, the NS_FRAME_IS_DIRTY and NS_FRAME_HAS_DIRTY_CHILDREN bits
+// indicate that our anonymous block child needs to be reflowed, and that
+// mPositions will likely need to be updated as a consequence. These are set,
+// for example, when the font-family changes. Sometimes we only need to
+// update mPositions though. For example if the x/y attributes change.
+// mPositioningDirty is used to indicate this latter "things are dirty" case
+// to allow us to avoid reflowing the anonymous block when it is not
+// necessary.
+FRAME_STATE_BIT(SVG, 21, NS_STATE_SVG_POSITIONING_DIRTY)
+
+// For text, whether the values from x/y/dx/dy attributes have any percentage
+// values that are used in determining the positions of glyphs. The value will
+// be true even if a positioning value is overridden by a descendant element's
+// attribute with a non-percentage length. For example,
+// NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES would be set for:
+//
+// <text x="10%"><tspan x="0">abc</tspan></text>
+//
+// Percentage values beyond the number of addressable characters, however, do
+// not influence NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES. For example,
+// NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES would be false for:
+//
+// <text x="10 20 30 40%">abc</text>
+//
+// NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES is used to determine whether
+// to recompute mPositions when the viewport size changes. So although the
+// first example above shows that NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES
+// can be true even if a viewport size change will not affect mPositions,
+// determining a completley accurate value for
+// NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES would require extra work that is
+// probably not worth it.
+FRAME_STATE_BIT(SVG, 22, NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)
+
+FRAME_STATE_BIT(SVG, 23, NS_STATE_SVG_TEXT_IN_REFLOW)
+
+// Set on SVGTextFrame frames when they need a
+// TextNodeCorrespondenceRecorder::RecordCorrespondence call
+// to update the cached nsTextNode indexes that they correspond to.
+FRAME_STATE_BIT(SVG, 24, NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)
+
+// We can stop ancestor traversal of rendering observers when we hit
+// one a frame with this state bit.
+FRAME_STATE_BIT(SVG, 25, NS_STATE_SVG_RENDERING_OBSERVER_CONTAINER)
+
+// == Frame state bits that apply to text frames ==============================
+
+FRAME_STATE_GROUP(Text, nsTextFrame)
+
+// -- Flags set during reflow -------------------------------------------------
+
+// nsTextFrame.cpp defines TEXT_REFLOW_FLAGS to be all of these bits.
+
+// This bit is set on the first frame in a continuation indicating
+// that it was chopped short because of :first-letter style.
+FRAME_STATE_BIT(Text, 20, TEXT_FIRST_LETTER)
+
+// This bit is set on frames that are logically adjacent to the start of the
+// line (i.e. no prior frame on line with actual displayed in-flow content).
+FRAME_STATE_BIT(Text, 21, TEXT_START_OF_LINE)
+
+// This bit is set on frames that are logically adjacent to the end of the
+// line (i.e. no following on line with actual displayed in-flow content).
+FRAME_STATE_BIT(Text, 22, TEXT_END_OF_LINE)
+
+// This bit is set on frames that end with a hyphenated break.
+FRAME_STATE_BIT(Text, 23, TEXT_HYPHEN_BREAK)
+
+// This bit is set on frames that trimmed trailing whitespace characters when
+// calculating their width during reflow.
+FRAME_STATE_BIT(Text, 24, TEXT_TRIMMED_TRAILING_WHITESPACE)
+
+// This bit is set on frames that have justification enabled. We record
+// this in a state bit because we don't always have the containing block
+// easily available to check text-align on.
+FRAME_STATE_BIT(Text, 25, TEXT_JUSTIFICATION_ENABLED)
+
+// Set this bit if the textframe has overflow area for IME/spellcheck underline.
+FRAME_STATE_BIT(Text, 26, TEXT_SELECTION_UNDERLINE_OVERFLOWED)
+
+// -- Cache bits for IsEmpty() ------------------------------------------------
+
+// nsTextFrame.cpp defines TEXT_WHITESPACE_FLAGS to be both of these bits.
+
+// Set this bit if the textframe is known to be only collapsible whitespace.
+FRAME_STATE_BIT(Text, 27, TEXT_IS_ONLY_WHITESPACE)
+
+// Set this bit if the textframe is known to be not only collapsible whitespace.
+FRAME_STATE_BIT(Text, 28, TEXT_ISNOT_ONLY_WHITESPACE)
+
+// -- Other state bits --------------------------------------------------------
+
+// Set when this text frame is mentioned in the userdata for mTextRun
+FRAME_STATE_BIT(Text, 29, TEXT_IN_TEXTRUN_USER_DATA)
+
+// This state bit is set on frames whose character data offsets need to be
+// fixed up
+FRAME_STATE_BIT(Text, 30, TEXT_OFFSETS_NEED_FIXING)
+
+// This state bit is set on frames that have some non-collapsed characters after
+// reflow
+FRAME_STATE_BIT(Text, 31, TEXT_HAS_NONCOLLAPSED_CHARACTERS)
+
+// This state bit is set on children of token MathML elements.
+// NOTE: TEXT_IS_IN_TOKEN_MATHML has a global state bit value that is shared
+// with NS_FRAME_IS_PUSHED_FLOAT.
+FRAME_STATE_BIT(Text, 32, TEXT_IS_IN_TOKEN_MATHML)
+
+// Set when this text frame is mentioned in the userdata for the
+// uninflated textrun property
+FRAME_STATE_BIT(Text, 60, TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA)
+
+FRAME_STATE_BIT(Text, 61, TEXT_HAS_FONT_INFLATION)
+
+// Set when this text frame contains nothing that will actually render
+FRAME_STATE_BIT(Text, 62, TEXT_NO_RENDERED_GLYPHS)
+
+// Whether this frame is cached in the Offset Frame Cache
+// (OffsetToFrameProperty)
+FRAME_STATE_BIT(Text, 63, TEXT_IN_OFFSET_CACHE)
+
+// == Frame state bits that apply to block frames =============================
+
+FRAME_STATE_GROUP(Block, nsBlockFrame)
+
+// Something in the block has changed that requires Bidi resolution to be
+// performed on the block. This flag must be either set on all blocks in a
+// continuation chain or none of them.
+FRAME_STATE_BIT(Block, 20, NS_BLOCK_NEEDS_BIDI_RESOLUTION)
+
+FRAME_STATE_BIT(Block, 21, NS_BLOCK_HAS_PUSHED_FLOATS)
+
+// This indicates that the frame establishes a block formatting context i.e.
+//
+// 1. This indicates that this is a frame from which child margins can be
+// calculated. The absence of this flag implies that child margin calculations
+// should ignore the frame and look further up the parent chain. Used in
+// nsBlockReflowContext::ComputeCollapsedBStartMargin() via
+// nsBlockFrame::IsMarginRoot().
+// This causes the BlockReflowState's constructor to set the
+// mIsBStartMarginRoot and mIsBEndMarginRoot flags.
+//
+// 2. This indicates that a block frame should create its own float manager.
+// This is required by each block frame that can contain floats. The float
+// manager is used to reserve space for the floated frames.
+FRAME_STATE_BIT(Block, 22, NS_BLOCK_STATIC_BFC)
+
+// This is the same as NS_BLOCK_STATIC_BFC but can be updated dynamically after
+// the frame construction (e.g. paint/layout containment).
+// FIXME(bug 1874823): Try and merge this and NS_BLOCK_STATIC_BFC.
+FRAME_STATE_BIT(Block, 23, NS_BLOCK_DYNAMIC_BFC)
+
+// For testing the relevant bits on a block formatting context:
+#define NS_BLOCK_BFC_STATE_BITS (NS_BLOCK_STATIC_BFC | NS_BLOCK_DYNAMIC_BFC)
+
+FRAME_STATE_BIT(Block, 24, NS_BLOCK_HAS_LINE_CURSOR)
+
+FRAME_STATE_BIT(Block, 25, NS_BLOCK_HAS_OVERFLOW_LINES)
+
+FRAME_STATE_BIT(Block, 26, NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)
+
+// Set on any block that has descendant frames in the normal
+// flow with 'clear' set to something other than 'none'
+// (including <BR CLEAR="..."> frames)
+FRAME_STATE_BIT(Block, 27, NS_BLOCK_HAS_CLEAR_CHILDREN)
+
+// NS_BLOCK_CLIP_PAGINATED_OVERFLOW is only set in paginated prescontexts, on
+// blocks which were forced to not have scrollframes but still need to clip
+// the display of their kids.
+FRAME_STATE_BIT(Block, 28, NS_BLOCK_CLIP_PAGINATED_OVERFLOW)
+
+// NS_BLOCK_HAS_FIRST_LETTER_STYLE means that the block has first-letter style,
+// even if it has no actual first-letter frame among its descendants.
+FRAME_STATE_BIT(Block, 29, NS_BLOCK_HAS_FIRST_LETTER_STYLE)
+
+// NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER and NS_BLOCK_FRAME_HAS_INSIDE_MARKER
+// means the block has an associated ::marker frame, they are mutually
+// exclusive.
+FRAME_STATE_BIT(Block, 30, NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER)
+FRAME_STATE_BIT(Block, 31, NS_BLOCK_FRAME_HAS_INSIDE_MARKER)
+
+// NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS indicates that exactly one line in this
+// block has the LineClampEllipsis flag set, and that such a line must be found
+// and have that flag cleared when reflowing this element's nearest legacy box
+// container.
+FRAME_STATE_BIT(Block, 60, NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)
+
+// This block has had a child marked dirty, so before we reflow we need
+// to look through the lines to find any such children and mark
+// appropriate lines dirty.
+FRAME_STATE_BIT(Block, 61, NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)
+
+// Are our cached intrinsic widths intrinsic widths for font size
+// inflation? i.e., what was the current state of
+// GetPresContext()->mInflationDisabledForShrinkWrap at the time they
+// were computed?
+// nsBlockFrame is the only thing that caches intrinsic widths that
+// needs to track this because it's the only thing that caches intrinsic
+// widths that lives inside of things (form controls) that do intrinsic
+// sizing with font inflation enabled.
+FRAME_STATE_BIT(Block, 62, NS_BLOCK_FRAME_INTRINSICS_INFLATED)
+
+// NS_BLOCK_HAS_FIRST_LETTER_CHILD means that there is an inflow first-letter
+// frame among the block's descendants. If there is a floating first-letter
+// frame, or the block has first-letter style but has no first letter, this
+// bit is not set. This bit is set on the first continuation only.
+FRAME_STATE_BIT(Block, 63, NS_BLOCK_HAS_FIRST_LETTER_CHILD)
+
+// == Frame state bits that apply to image frames =============================
+
+FRAME_STATE_GROUP(Image, nsImageFrame)
+
+FRAME_STATE_BIT(Image, 20, IMAGE_SIZECONSTRAINED)
+
+// == Frame state bits that apply to inline frames ============================
+
+FRAME_STATE_GROUP(Inline, nsInlineFrame)
+
+/** In Bidi inline start (or end) margin/padding/border should be applied to
+ * first (or last) frame (or a continuation frame).
+ * This state value shows if this frame is first (or last) continuation
+ * or not.
+ */
+
+FRAME_STATE_BIT(Inline, 21, NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET)
+FRAME_STATE_BIT(Inline, 22, NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST)
+FRAME_STATE_BIT(Inline, 23, NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST)
+// nsRubyTextFrame inherits from nsInlineFrame
+
+// == Frame state bits that apply to ruby text frames =========================
+
+FRAME_STATE_GROUP(RubyText, nsRubyTextFrame)
+
+// inherits from nsInlineFrame
+FRAME_STATE_BIT(RubyText, 24, NS_RUBY_TEXT_FRAME_COLLAPSED)
+
+// == Frame state bits that apply to ruby text container frames ===============
+
+FRAME_STATE_GROUP(RubyTextContainer, nsRubyTextContainerFrame)
+
+FRAME_STATE_BIT(RubyTextContainer, 20, NS_RUBY_TEXT_CONTAINER_IS_SPAN)
+
+// == Frame state bits that apply to placeholder frames =======================
+
+FRAME_STATE_GROUP(Placeholder, nsPlaceholderFrame)
+
+// Frame state bits that are used to keep track of what this is a
+// placeholder for.
+
+FRAME_STATE_BIT(Placeholder, 20, PLACEHOLDER_FOR_FLOAT)
+FRAME_STATE_BIT(Placeholder, 21, PLACEHOLDER_FOR_ABSPOS)
+FRAME_STATE_BIT(Placeholder, 22, PLACEHOLDER_FOR_FIXEDPOS)
+FRAME_STATE_BIT(Placeholder, 24, PLACEHOLDER_FOR_TOPLAYER)
+
+// This bit indicates that the out-of-flow frame's static position needs to be
+// determined using the CSS Box Alignment properties
+// ([align,justify]-[self,content]). When this is set, the placeholder frame's
+// position doesn't represent the static position, as it usually would --
+// rather, it represents the logical start corner of the alignment containing
+// block. Then, after we've determined the out-of-flow frame's size, we can
+// resolve the actual static position using the alignment properties.
+FRAME_STATE_BIT(Placeholder, 25, PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)
+
+// Are all earlier frames on the same block line empty?
+FRAME_STATE_BIT(Placeholder, 26, PLACEHOLDER_LINE_IS_EMPTY_SO_FAR)
+// Does the above bit have a valid value?
+FRAME_STATE_BIT(Placeholder, 27, PLACEHOLDER_HAVE_LINE_IS_EMPTY_SO_FAR)
+
+// == Frame state bits that apply to table cell frames ========================
+
+FRAME_STATE_GROUP(TableCell, nsTableCellFrame)
+
+FRAME_STATE_BIT(TableCell, 20, NS_TABLE_CELL_HAD_SPECIAL_REFLOW)
+FRAME_STATE_BIT(TableCell, 21, NS_TABLE_CELL_CONTENT_EMPTY)
+
+// == Frame state bits that apply to table column frames ======================
+
+// Bits 28-31 on nsTableColFrames are used to store the column type.
+
+// == Frame state bits that apply to table column group frames ================
+
+// Bits 30-31 on nsTableColGroupFrames are used to store the column type.
+
+// == Frame state bits that apply to table rows and table row group frames ====
+
+FRAME_STATE_GROUP_NAME(TableRowAndRowGroup)
+FRAME_STATE_GROUP_CLASS(TableRowAndRowGroup, nsTableRowFrame)
+FRAME_STATE_GROUP_CLASS(TableRowAndRowGroup, nsTableRowGroupFrame)
+
+// see nsTableRowGroupFrame::InitRepeatedFrame
+FRAME_STATE_BIT(TableRowAndRowGroup, 28, NS_REPEATED_ROW_OR_ROWGROUP)
+
+// == Frame state bits that apply to table row frames =========================
+
+FRAME_STATE_GROUP(TableRow, nsTableRowFrame)
+
+// Indicates whether this row has any cells that have
+// non-auto-bsize and rowspan=1
+FRAME_STATE_BIT(TableRow, 29, NS_ROW_HAS_CELL_WITH_STYLE_BSIZE)
+
+FRAME_STATE_BIT(TableRow, 30, NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE)
+
+// == Frame state bits that apply to table row group frames ===================
+
+FRAME_STATE_GROUP(TableRowGroup, nsTableRowGroupFrame)
+
+FRAME_STATE_BIT(TableRowGroup, 27, NS_ROWGROUP_HAS_ROW_CURSOR)
+FRAME_STATE_BIT(TableRowGroup, 30, NS_ROWGROUP_HAS_STYLE_BSIZE)
+
+// thead or tfoot should be repeated on every printed page
+FRAME_STATE_BIT(TableRowGroup, 31, NS_ROWGROUP_REPEATABLE)
+
+FRAME_STATE_GROUP(Table, nsTableFrame)
+
+FRAME_STATE_BIT(Table, 28, NS_TABLE_PART_HAS_FIXED_BACKGROUND)
+
+// == Frame state bits that apply to page frames ==============================
+FRAME_STATE_GROUP(Page, nsPageFrame)
+
+// If set, this bit indicates that the given nsPageFrame has been skipped
+// via the user's custom-page-range choice, and should not be rendered.
+FRAME_STATE_BIT(Page, 20, NS_PAGE_SKIPPED_BY_CUSTOM_RANGE)
+
+#undef FRAME_STATE_GROUP
+
+#ifdef DEFINED_FRAME_STATE_GROUP_NAME
+#undef DEFINED_FRAME_STATE_GROUP_NAME
+#undef FRAME_STATE_GROUP_NAME
+#endif
+
+#ifdef DEFINED_FRAME_STATE_GROUP_CLASS
+#undef DEFINED_FRAME_STATE_GROUP_CLASS
+#undef FRAME_STATE_GROUP_CLASS
+#endif
+
+#ifdef DEFINED_FRAME_STATE_BIT
+#undef DEFINED_FRAME_STATE_BIT
+#undef FRAME_STATE_BIT
+#endif
diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp
new file mode 100644
index 0000000000..b51f3eccb7
--- /dev/null
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -0,0 +1,8022 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object to wrap rendering objects that should be scrollable */
+
+#include "nsGfxScrollFrame.h"
+
+#include "ScrollPositionUpdate.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsIXULRuntime.h"
+#include "base/compiler_specific.h"
+#include "DisplayItemClip.h"
+#include "nsCOMPtr.h"
+#include "nsIDocumentViewer.h"
+#include "nsPresContext.h"
+#include "nsView.h"
+#include "nsViewportInfo.h"
+#include "nsContainerFrame.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "nsFontMetrics.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsScrollbarFrame.h"
+#include "nsINode.h"
+#include "nsIScrollbarMediator.h"
+#include "nsITextControlFrame.h"
+#include "nsILayoutHistoryState.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsStyleTransformMatrix.h"
+#include "mozilla/PresState.h"
+#include "nsContentUtils.h"
+#include "nsDisplayList.h"
+#include "nsHTMLDocument.h"
+#include "nsLayoutUtils.h"
+#include "nsBidiPresUtils.h"
+#include "nsBidiUtils.h"
+#include "nsDocShell.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ScrollbarPreferences.h"
+#include "mozilla/ScrollingMetrics.h"
+#include "mozilla/StaticPrefs_bidi.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_toolkit.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SVGOuterSVGFrame.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/HTMLMarqueeElement.h"
+#include "mozilla/dom/ScrollTimeline.h"
+#include "mozilla/dom/BrowserChild.h"
+#include <stdint.h>
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Telemetry.h"
+#include "nsSubDocumentFrame.h"
+#include "mozilla/Attributes.h"
+#include "ScrollbarActivity.h"
+#include "nsRefreshDriver.h"
+#include "nsStyleConsts.h"
+#include "nsIScrollPositionListener.h"
+#include "StickyScrollContainer.h"
+#include "nsIFrameInlines.h"
+#include "gfxPlatform.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_general.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_mousewheel.h"
+#include "mozilla/ToString.h"
+#include "ScrollAnimationPhysics.h"
+#include "ScrollAnimationBezierPhysics.h"
+#include "ScrollAnimationMSDPhysics.h"
+#include "ScrollSnap.h"
+#include "UnitTransforms.h"
+#include "nsSliderFrame.h"
+#include "ViewportFrame.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/APZPublicUtils.h"
+#include "mozilla/layers/AxisPhysicsModel.h"
+#include "mozilla/layers/AxisPhysicsMSDModel.h"
+#include "mozilla/layers/ScrollingInteractionContext.h"
+#include "mozilla/layers/ScrollLinkedEffectDetector.h"
+#include "mozilla/Unused.h"
+#include "MobileViewportManager.h"
+#include "VisualViewport.h"
+#include "WindowRenderer.h"
+#include <algorithm>
+#include <cstdlib> // for std::abs(int/long)
+#include <cmath> // for std::abs(float/double)
+#include <tuple> // for std::tie
+
+static mozilla::LazyLogModule sApzPaintSkipLog("apz.paintskip");
+#define PAINT_SKIP_LOG(...) \
+ MOZ_LOG(sApzPaintSkipLog, LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule sScrollRestoreLog("scrollrestore");
+#define SCROLLRESTORE_LOG(...) \
+ MOZ_LOG(sScrollRestoreLog, LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule sRootScrollbarsLog("rootscrollbars");
+#define ROOT_SCROLLBAR_LOG(...) \
+ if (mIsRoot) { \
+ MOZ_LOG(sRootScrollbarsLog, LogLevel::Debug, (__VA_ARGS__)); \
+ }
+static mozilla::LazyLogModule sDisplayportLog("apz.displayport");
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::layout;
+using nsStyleTransformMatrix::TransformReferenceBox;
+
+static ScrollDirections GetOverflowChange(const nsRect& aCurScrolledRect,
+ const nsRect& aPrevScrolledRect) {
+ ScrollDirections result;
+ if (aPrevScrolledRect.x != aCurScrolledRect.x ||
+ aPrevScrolledRect.width != aCurScrolledRect.width) {
+ result += ScrollDirection::eHorizontal;
+ }
+ if (aPrevScrolledRect.y != aCurScrolledRect.y ||
+ aPrevScrolledRect.height != aCurScrolledRect.height) {
+ result += ScrollDirection::eVertical;
+ }
+ return result;
+}
+
+/**
+ * This class handles the dispatching of scroll events to content.
+ *
+ * Scroll events are posted to the refresh driver via
+ * nsRefreshDriver::PostScrollEvent(), and they are fired during a refresh
+ * driver tick, after running requestAnimationFrame callbacks but before
+ * the style flush. This allows rAF callbacks to perform scrolling and have
+ * that scrolling be reflected on the same refresh driver tick, while at
+ * the same time allowing scroll event listeners to make style changes and
+ * have those style changes be reflected on the same refresh driver tick.
+ *
+ * ScrollEvents cannot be refresh observers, because none of the existing
+ * categories of refresh observers (FlushType::Style, FlushType::Layout,
+ * and FlushType::Display) are run at the desired time in a refresh driver
+ * tick. They behave similarly to refresh observers in that their presence
+ * causes the refresh driver to tick.
+ *
+ * ScrollEvents are one-shot runnables; the refresh driver drops them after
+ * running them.
+ */
+class nsHTMLScrollFrame::ScrollEvent : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit ScrollEvent(nsHTMLScrollFrame* aHelper, bool aDelayed);
+ void Revoke() { mHelper = nullptr; }
+
+ private:
+ nsHTMLScrollFrame* mHelper;
+};
+
+class nsHTMLScrollFrame::ScrollEndEvent : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit ScrollEndEvent(nsHTMLScrollFrame* aHelper);
+ void Revoke() { mHelper = nullptr; }
+
+ private:
+ nsHTMLScrollFrame* mHelper;
+};
+
+class nsHTMLScrollFrame::AsyncScrollPortEvent : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit AsyncScrollPortEvent(nsHTMLScrollFrame* helper)
+ : Runnable("nsHTMLScrollFrame::AsyncScrollPortEvent"), mHelper(helper) {}
+ void Revoke() { mHelper = nullptr; }
+
+ private:
+ nsHTMLScrollFrame* mHelper;
+};
+
+class nsHTMLScrollFrame::ScrolledAreaEvent : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit ScrolledAreaEvent(nsHTMLScrollFrame* helper)
+ : Runnable("nsHTMLScrollFrame::ScrolledAreaEvent"), mHelper(helper) {}
+ void Revoke() { mHelper = nullptr; }
+
+ private:
+ nsHTMLScrollFrame* mHelper;
+};
+
+//----------------------------------------------------------------------
+
+//----------nsHTMLScrollFrame-------------------------------------------
+
+class ScrollFrameActivityTracker final
+ : public nsExpirationTracker<nsHTMLScrollFrame, 4> {
+ public:
+ // Wait for 3-4s between scrolls before we remove our layers.
+ // That's 4 generations of 1s each.
+ enum { TIMEOUT_MS = 1000 };
+ explicit ScrollFrameActivityTracker(nsIEventTarget* aEventTarget)
+ : nsExpirationTracker<nsHTMLScrollFrame, 4>(
+ TIMEOUT_MS, "ScrollFrameActivityTracker", aEventTarget) {}
+ ~ScrollFrameActivityTracker() { AgeAllGenerations(); }
+
+ virtual void NotifyExpired(nsHTMLScrollFrame* aObject) override {
+ RemoveObject(aObject);
+ aObject->MarkNotRecentlyScrolled();
+ }
+};
+static StaticAutoPtr<ScrollFrameActivityTracker> gScrollFrameActivityTracker;
+
+nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle, bool aIsRoot) {
+ return new (aPresShell)
+ nsHTMLScrollFrame(aStyle, aPresShell->GetPresContext(), aIsRoot);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
+
+nsHTMLScrollFrame::nsHTMLScrollFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext,
+ nsIFrame::ClassID aID, bool aIsRoot)
+ : nsContainerFrame(aStyle, aPresContext, aID),
+ mHScrollbarBox(nullptr),
+ mVScrollbarBox(nullptr),
+ mScrolledFrame(nullptr),
+ mScrollCornerBox(nullptr),
+ mResizerBox(nullptr),
+ mReferenceFrameDuringPainting(nullptr),
+ mAsyncScroll(nullptr),
+ mAsyncSmoothMSDScroll(nullptr),
+ mLastScrollOrigin(ScrollOrigin::None),
+ mDestination(0, 0),
+ mRestorePos(-1, -1),
+ mLastPos(-1, -1),
+ mApzScrollPos(0, 0),
+ mLastUpdateFramesPos(-1, -1),
+ mScrollParentID(mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID),
+ mAnchor(this),
+ mCurrentAPZScrollAnimationType(APZScrollAnimationType::No),
+ mIsFirstScrollableFrameSequenceNumber(Nothing()),
+ mInScrollingGesture(InScrollingGesture::No),
+ mAllowScrollOriginDowngrade(false),
+ mHadDisplayPortAtLastFrameUpdate(false),
+ mHasVerticalScrollbar(false),
+ mHasHorizontalScrollbar(false),
+ mOnlyNeedVScrollbarToScrollVVInsideLV(false),
+ mOnlyNeedHScrollbarToScrollVVInsideLV(false),
+ mFrameIsUpdatingScrollbar(false),
+ mDidHistoryRestore(false),
+ mIsRoot(aIsRoot),
+ mSuppressScrollbarUpdate(false),
+ mSkippedScrollbarLayout(false),
+ mHadNonInitialReflow(false),
+ mFirstReflow(true),
+ mHorizontalOverflow(false),
+ mVerticalOverflow(false),
+ mPostedReflowCallback(false),
+ mMayHaveDirtyFixedChildren(false),
+ mUpdateScrollbarAttributes(false),
+ mHasBeenScrolledRecently(false),
+ mWillBuildScrollableLayer(false),
+ mIsParentToActiveScrollFrames(false),
+ mHasBeenScrolled(false),
+ mIgnoreMomentumScroll(false),
+ mTransformingByAPZ(false),
+ mScrollableByAPZ(false),
+ mZoomableByAPZ(false),
+ mHasOutOfFlowContentInsideFilter(false),
+ mSuppressScrollbarRepaints(false),
+ mIsUsingMinimumScaleSize(false),
+ mMinimumScaleSizeChanged(false),
+ mProcessingScrollEvent(false),
+ mApzAnimationRequested(false),
+ mApzAnimationTriggeredByScriptRequested(false),
+ mReclampVVOffsetInReflowFinished(false),
+ mMayScheduleScrollAnimations(false),
+#ifdef MOZ_WIDGET_ANDROID
+ mHasVerticalOverflowForDynamicToolbar(false),
+#endif
+ mVelocityQueue(PresContext()) {
+ AppendScrollUpdate(ScrollPositionUpdate::NewScrollframe(nsPoint()));
+
+ if (UsesOverlayScrollbars()) {
+ mScrollbarActivity = new ScrollbarActivity(this);
+ }
+
+ if (mIsRoot) {
+ mZoomableByAPZ = PresShell()->GetZoomableByAPZ();
+ }
+}
+
+nsHTMLScrollFrame::~nsHTMLScrollFrame() = default;
+
+void nsHTMLScrollFrame::ScrollbarActivityStarted() const {
+ if (mScrollbarActivity) {
+ mScrollbarActivity->ActivityStarted();
+ }
+}
+
+void nsHTMLScrollFrame::ScrollbarActivityStopped() const {
+ if (mScrollbarActivity) {
+ mScrollbarActivity->ActivityStopped();
+ }
+}
+
+void nsHTMLScrollFrame::Destroy(DestroyContext& aContext) {
+ DestroyAbsoluteFrames(aContext);
+ if (mIsRoot) {
+ PresShell()->ResetVisualViewportOffset();
+ }
+
+ mAnchor.Destroy();
+
+ if (mScrollbarActivity) {
+ mScrollbarActivity->Destroy();
+ mScrollbarActivity = nullptr;
+ }
+
+ // Unbind the content created in CreateAnonymousContent later...
+ aContext.AddAnonymousContent(mHScrollbarContent.forget());
+ aContext.AddAnonymousContent(mVScrollbarContent.forget());
+ aContext.AddAnonymousContent(mScrollCornerContent.forget());
+ aContext.AddAnonymousContent(mResizerContent.forget());
+
+ if (mPostedReflowCallback) {
+ PresShell()->CancelReflowCallback(this);
+ mPostedReflowCallback = false;
+ }
+
+ if (mDisplayPortExpiryTimer) {
+ mDisplayPortExpiryTimer->Cancel();
+ mDisplayPortExpiryTimer = nullptr;
+ }
+ if (mActivityExpirationState.IsTracked()) {
+ gScrollFrameActivityTracker->RemoveObject(this);
+ }
+ if (gScrollFrameActivityTracker && gScrollFrameActivityTracker->IsEmpty()) {
+ gScrollFrameActivityTracker = nullptr;
+ }
+
+ if (mScrollActivityTimer) {
+ mScrollActivityTimer->Cancel();
+ mScrollActivityTimer = nullptr;
+ }
+ RemoveObservers();
+ if (mScrollEvent) {
+ mScrollEvent->Revoke();
+ }
+ if (mScrollEndEvent) {
+ mScrollEndEvent->Revoke();
+ }
+ nsContainerFrame::Destroy(aContext);
+}
+
+void nsHTMLScrollFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
+ ReloadChildFrames();
+}
+
+void nsHTMLScrollFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal,
+ "Only main list supported");
+ mFrames.AppendFrames(nullptr, std::move(aFrameList));
+ ReloadChildFrames();
+}
+
+void nsHTMLScrollFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal,
+ "Only main list supported");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+ mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
+ ReloadChildFrames();
+}
+
+void nsHTMLScrollFrame::RemoveFrame(DestroyContext& aContext,
+ ChildListID aListID, nsIFrame* aOldFrame) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal,
+ "Only main list supported");
+ mFrames.DestroyFrame(aContext, aOldFrame);
+ ReloadChildFrames();
+}
+
+/**
+ HTML scrolling implementation
+
+ All other things being equal, we prefer layouts with fewer scrollbars showing.
+*/
+
+namespace mozilla {
+
+enum class ShowScrollbar : uint8_t {
+ Auto,
+ Always,
+ // Never is a misnomer. We can still get a scrollbar if we need to scroll the
+ // visual viewport inside the layout viewport. Thus this enum is best thought
+ // of as value used by layout, which does not know about the visual viewport.
+ // The visual viewport does not affect any layout sizes, so this is sound.
+ Never,
+};
+
+static ShowScrollbar ShouldShowScrollbar(StyleOverflow aOverflow) {
+ switch (aOverflow) {
+ case StyleOverflow::Scroll:
+ return ShowScrollbar::Always;
+ case StyleOverflow::Hidden:
+ return ShowScrollbar::Never;
+ default:
+ case StyleOverflow::Auto:
+ return ShowScrollbar::Auto;
+ }
+}
+
+struct MOZ_STACK_CLASS ScrollReflowInput {
+ // === Filled in by the constructor. Members in this section shouldn't change
+ // their values after the constructor. ===
+ const ReflowInput& mReflowInput;
+ ShowScrollbar mHScrollbar;
+ // If the horizontal scrollbar is allowed (even if mHScrollbar ==
+ // ShowScrollbar::Never) provided that it is for scrolling the visual viewport
+ // inside the layout viewport only.
+ bool mHScrollbarAllowedForScrollingVVInsideLV = true;
+ ShowScrollbar mVScrollbar;
+ // If the vertical scrollbar is allowed (even if mVScrollbar ==
+ // ShowScrollbar::Never) provided that it is for scrolling the visual viewport
+ // inside the layout viewport only.
+ bool mVScrollbarAllowedForScrollingVVInsideLV = true;
+ nsMargin mComputedBorder;
+
+ // === Filled in by ReflowScrolledFrame ===
+ OverflowAreas mContentsOverflowAreas;
+ // The scrollbar gutter sizes used in the most recent reflow of
+ // mScrolledFrame. The writing-mode is the same as the scroll
+ // container.
+ LogicalMargin mScrollbarGutterFromLastReflow;
+ // True if the most recent reflow of mScrolledFrame is with the
+ // horizontal scrollbar.
+ bool mReflowedContentsWithHScrollbar = false;
+ // True if the most recent reflow of mScrolledFrame is with the
+ // vertical scrollbar.
+ bool mReflowedContentsWithVScrollbar = false;
+
+ // === Filled in when TryLayout succeeds ===
+ // The size of the inside-border area
+ nsSize mInsideBorderSize;
+ // Whether we decided to show the horizontal scrollbar in the most recent
+ // TryLayout.
+ bool mShowHScrollbar = false;
+ // Whether we decided to show the vertical scrollbar in the most recent
+ // TryLayout.
+ bool mShowVScrollbar = false;
+ // If mShow(H|V)Scrollbar is true then
+ // mOnlyNeed(V|H)ScrollbarToScrollVVInsideLV indicates if the only reason we
+ // need that scrollbar is to scroll the visual viewport inside the layout
+ // viewport. These scrollbars are special in that even if they are layout
+ // scrollbars they do not take up any layout space.
+ bool mOnlyNeedHScrollbarToScrollVVInsideLV = false;
+ bool mOnlyNeedVScrollbarToScrollVVInsideLV = false;
+
+ ScrollReflowInput(nsHTMLScrollFrame* aFrame, const ReflowInput& aReflowInput);
+
+ nscoord VScrollbarMinHeight() const { return mVScrollbarPrefSize.height; }
+ nscoord VScrollbarPrefWidth() const { return mVScrollbarPrefSize.width; }
+ nscoord HScrollbarMinWidth() const { return mHScrollbarPrefSize.width; }
+ nscoord HScrollbarPrefHeight() const { return mHScrollbarPrefSize.height; }
+
+ // Returns the sizes occupied by the scrollbar gutters. If aShowVScroll or
+ // aShowHScroll is true, the sizes occupied by the scrollbars are also
+ // included.
+ nsMargin ScrollbarGutter(bool aShowVScrollbar, bool aShowHScrollbar,
+ bool aScrollbarOnRight) const {
+ if (mOverlayScrollbars) {
+ return mScrollbarGutter;
+ }
+ nsMargin gutter = mScrollbarGutter;
+ if (aShowVScrollbar && gutter.right == 0 && gutter.left == 0) {
+ const nscoord w = VScrollbarPrefWidth();
+ if (aScrollbarOnRight) {
+ gutter.right = w;
+ } else {
+ gutter.left = w;
+ }
+ }
+ if (aShowHScrollbar && gutter.bottom == 0) {
+ // The horizontal scrollbar is always at the bottom side.
+ gutter.bottom = HScrollbarPrefHeight();
+ }
+ return gutter;
+ }
+
+ bool OverlayScrollbars() const { return mOverlayScrollbars; }
+
+ private:
+ // Filled in by the constructor. Put variables here to keep them unchanged
+ // after initializing them in the constructor.
+ nsSize mVScrollbarPrefSize;
+ nsSize mHScrollbarPrefSize;
+ bool mOverlayScrollbars = false;
+ // The scrollbar gutter sizes resolved from the scrollbar-gutter and
+ // scrollbar-width property.
+ nsMargin mScrollbarGutter;
+};
+
+ScrollReflowInput::ScrollReflowInput(nsHTMLScrollFrame* aFrame,
+ const ReflowInput& aReflowInput)
+ : mReflowInput(aReflowInput),
+ mComputedBorder(aReflowInput.ComputedPhysicalBorderPadding() -
+ aReflowInput.ComputedPhysicalPadding()),
+ mScrollbarGutterFromLastReflow(aFrame->GetWritingMode()) {
+ ScrollStyles styles = aFrame->GetScrollStyles();
+ mHScrollbar = ShouldShowScrollbar(styles.mHorizontal);
+ mVScrollbar = ShouldShowScrollbar(styles.mVertical);
+ mOverlayScrollbars = aFrame->UsesOverlayScrollbars();
+
+ if (nsScrollbarFrame* scrollbar = aFrame->GetScrollbarBox(false)) {
+ scrollbar->SetScrollbarMediatorContent(mReflowInput.mFrame->GetContent());
+ mHScrollbarPrefSize = scrollbar->ScrollbarMinSize();
+ // A zero minimum size is a bug with non-overlay scrollbars. That means
+ // we'll always try to place the scrollbar, even if it will ultimately not
+ // fit, see bug 1809630. XUL collapsing is the exception because the
+ // front-end uses it.
+ MOZ_ASSERT(mHScrollbarPrefSize.width && mHScrollbarPrefSize.height,
+ "Shouldn't have a zero horizontal scrollbar-size");
+ } else {
+ mHScrollbar = ShowScrollbar::Never;
+ mHScrollbarAllowedForScrollingVVInsideLV = false;
+ }
+ if (nsScrollbarFrame* scrollbar = aFrame->GetScrollbarBox(true)) {
+ scrollbar->SetScrollbarMediatorContent(mReflowInput.mFrame->GetContent());
+ mVScrollbarPrefSize = scrollbar->ScrollbarMinSize();
+ // See above.
+ MOZ_ASSERT(mVScrollbarPrefSize.width && mVScrollbarPrefSize.height,
+ "Shouldn't have a zero vertical scrollbar-size");
+ } else {
+ mVScrollbar = ShowScrollbar::Never;
+ mVScrollbarAllowedForScrollingVVInsideLV = false;
+ }
+
+ const auto* scrollbarStyle =
+ nsLayoutUtils::StyleForScrollbar(mReflowInput.mFrame);
+ // Hide the scrollbar when the scrollbar-width is set to none.
+ //
+ // Note: In some cases this is unnecessary, because scrollbar-width:none
+ // makes us suppress scrollbars in CreateAnonymousContent. But if this frame
+ // initially had a non-'none' scrollbar-width and dynamically changed to
+ // 'none', then we'll need to handle it here.
+ const auto scrollbarWidth = scrollbarStyle->StyleUIReset()->ScrollbarWidth();
+ if (scrollbarWidth == StyleScrollbarWidth::None) {
+ mHScrollbar = ShowScrollbar::Never;
+ mHScrollbarAllowedForScrollingVVInsideLV = false;
+ mVScrollbar = ShowScrollbar::Never;
+ mVScrollbarAllowedForScrollingVVInsideLV = false;
+ }
+
+ mScrollbarGutter = aFrame->ComputeStableScrollbarGutter(
+ scrollbarWidth, scrollbarStyle->StyleDisplay()->mScrollbarGutter);
+}
+
+} // namespace mozilla
+
+static nsSize ComputeInsideBorderSize(const ScrollReflowInput& aState,
+ const nsSize& aDesiredInsideBorderSize) {
+ // aDesiredInsideBorderSize is the frame size; i.e., it includes
+ // borders and padding (but the scrolled child doesn't have
+ // borders). The scrolled child has the same padding as us.
+ const WritingMode wm = aState.mReflowInput.GetWritingMode();
+ const LogicalSize desiredInsideBorderSize(wm, aDesiredInsideBorderSize);
+ LogicalSize contentSize = aState.mReflowInput.ComputedSize();
+ const LogicalMargin padding = aState.mReflowInput.ComputedLogicalPadding(wm);
+
+ if (contentSize.ISize(wm) == NS_UNCONSTRAINEDSIZE) {
+ contentSize.ISize(wm) =
+ desiredInsideBorderSize.ISize(wm) - padding.IStartEnd(wm);
+ }
+ if (contentSize.BSize(wm) == NS_UNCONSTRAINEDSIZE) {
+ contentSize.BSize(wm) =
+ desiredInsideBorderSize.BSize(wm) - padding.BStartEnd(wm);
+ }
+
+ contentSize.ISize(wm) =
+ aState.mReflowInput.ApplyMinMaxISize(contentSize.ISize(wm));
+ contentSize.BSize(wm) =
+ aState.mReflowInput.ApplyMinMaxBSize(contentSize.BSize(wm));
+
+ return (contentSize + padding.Size(wm)).GetPhysicalSize(wm);
+}
+
+/**
+ * Assuming that we know the metrics for our wrapped frame and
+ * whether the horizontal and/or vertical scrollbars are present,
+ * compute the resulting layout and return true if the layout is
+ * consistent. If the layout is consistent then we fill in the
+ * computed fields of the ScrollReflowInput.
+ *
+ * The layout is consistent when both scrollbars are showing if and only
+ * if they should be showing. A horizontal scrollbar should be showing if all
+ * following conditions are met:
+ * 1) the style is not HIDDEN
+ * 2) our inside-border height is at least the scrollbar height (i.e., the
+ * scrollbar fits vertically)
+ * 3) the style is SCROLL, or the kid's overflow-area XMost is
+ * greater than the scrollport width
+ *
+ * @param aForce if true, then we just assume the layout is consistent.
+ */
+bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput& aState,
+ ReflowOutput* aKidMetrics,
+ bool aAssumeHScroll, bool aAssumeVScroll,
+ bool aForce) {
+ if ((aState.mVScrollbar == ShowScrollbar::Never && aAssumeVScroll) ||
+ (aState.mHScrollbar == ShowScrollbar::Never && aAssumeHScroll)) {
+ NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
+ return false;
+ }
+
+ const auto wm = GetWritingMode();
+ const nsMargin scrollbarGutter = aState.ScrollbarGutter(
+ aAssumeVScroll, aAssumeHScroll, IsScrollbarOnRight());
+ const LogicalMargin logicalScrollbarGutter(wm, scrollbarGutter);
+
+ const bool inlineEndsGutterChanged =
+ aState.mScrollbarGutterFromLastReflow.IStartEnd(wm) !=
+ logicalScrollbarGutter.IStartEnd(wm);
+ const bool blockEndsGutterChanged =
+ aState.mScrollbarGutterFromLastReflow.BStartEnd(wm) !=
+ logicalScrollbarGutter.BStartEnd(wm);
+ const bool shouldReflowScrolledFrame =
+ inlineEndsGutterChanged ||
+ (blockEndsGutterChanged && ScrolledContentDependsOnBSize(aState));
+
+ if (shouldReflowScrolledFrame) {
+ if (blockEndsGutterChanged) {
+ nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(mScrolledFrame);
+ }
+ aKidMetrics->mOverflowAreas.Clear();
+ ROOT_SCROLLBAR_LOG(
+ "TryLayout reflowing scrolled frame with scrollbars h=%d, v=%d\n",
+ aAssumeHScroll, aAssumeVScroll);
+ ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, aKidMetrics);
+ }
+
+ const nsSize scrollbarGutterSize(scrollbarGutter.LeftRight(),
+ scrollbarGutter.TopBottom());
+
+ // First, compute our inside-border size and scrollport size
+ nsSize kidSize = GetContainSizeAxes().ContainSize(
+ aKidMetrics->PhysicalSize(), *aState.mReflowInput.mFrame);
+ const nsSize desiredInsideBorderSize = kidSize + scrollbarGutterSize;
+ aState.mInsideBorderSize =
+ ComputeInsideBorderSize(aState, desiredInsideBorderSize);
+
+ nsSize layoutSize =
+ mIsUsingMinimumScaleSize ? mMinimumScaleSize : aState.mInsideBorderSize;
+
+ const nsSize scrollPortSize =
+ Max(nsSize(0, 0), layoutSize - scrollbarGutterSize);
+ if (mIsUsingMinimumScaleSize) {
+ mICBSize =
+ Max(nsSize(0, 0), aState.mInsideBorderSize - scrollbarGutterSize);
+ }
+
+ nsSize visualViewportSize = scrollPortSize;
+ ROOT_SCROLLBAR_LOG("TryLayout with VV %s\n",
+ ToString(visualViewportSize).c_str());
+ mozilla::PresShell* presShell = PresShell();
+ // Note: we check for a non-null MobileViepwortManager here, but ideally we
+ // should be able to drop that clause as well. It's just that in some cases
+ // with extension popups the composition size comes back as stale, because
+ // the content viewer is only resized after the popup contents are reflowed.
+ // That case also happens to have no APZ and no MVM, so we use that as a
+ // way to detect the scenario. Bug 1648669 tracks removing this clause.
+ if (mIsRoot && presShell->GetMobileViewportManager()) {
+ visualViewportSize = nsLayoutUtils::CalculateCompositionSizeForFrame(
+ this, false, &layoutSize);
+ visualViewportSize =
+ Max(nsSize(0, 0), visualViewportSize - scrollbarGutterSize);
+
+ float resolution = presShell->GetResolution();
+ visualViewportSize.width /= resolution;
+ visualViewportSize.height /= resolution;
+ ROOT_SCROLLBAR_LOG("TryLayout now with VV %s\n",
+ ToString(visualViewportSize).c_str());
+ }
+
+ nsRect overflowRect = aState.mContentsOverflowAreas.ScrollableOverflow();
+ // If the content height expanded by the minimum-scale will be taller than
+ // the scrollable overflow area, we need to expand the area here to tell
+ // properly whether we need to render the overlay vertical scrollbar.
+ // NOTE: This expanded size should NOT be used for non-overley scrollbars
+ // cases since putting the vertical non-overlay scrollbar will make the
+ // content width narrow a little bit, which in turn the minimum scale value
+ // becomes a bit bigger than before, then the vertical scrollbar is no longer
+ // needed, which means the content width becomes the original width, then the
+ // minimum-scale is changed to the original one, and so forth.
+ if (UsesOverlayScrollbars() && mIsUsingMinimumScaleSize &&
+ mMinimumScaleSize.height > overflowRect.YMost()) {
+ overflowRect.height += mMinimumScaleSize.height - overflowRect.YMost();
+ }
+ nsRect scrolledRect =
+ GetUnsnappedScrolledRectInternal(overflowRect, scrollPortSize);
+ ROOT_SCROLLBAR_LOG(
+ "TryLayout scrolledRect:%s overflowRect:%s scrollportSize:%s\n",
+ ToString(scrolledRect).c_str(), ToString(overflowRect).c_str(),
+ ToString(scrollPortSize).c_str());
+ nscoord oneDevPixel = PresContext()->DevPixelsToAppUnits(1);
+
+ bool showHScrollbar = aAssumeHScroll;
+ bool showVScrollbar = aAssumeVScroll;
+ if (!aForce) {
+ nsSize sizeToCompare = visualViewportSize;
+ if (gfxPlatform::UseDesktopZoomingScrollbars()) {
+ sizeToCompare = scrollPortSize;
+ }
+
+ // No need to compute showHScrollbar if we got ShowScrollbar::Never.
+ if (aState.mHScrollbar != ShowScrollbar::Never) {
+ showHScrollbar =
+ aState.mHScrollbar == ShowScrollbar::Always ||
+ scrolledRect.XMost() >= sizeToCompare.width + oneDevPixel ||
+ scrolledRect.x <= -oneDevPixel;
+ // TODO(emilio): This should probably check this scrollbar's minimum size
+ // in both axes, for consistency?
+ if (aState.mHScrollbar == ShowScrollbar::Auto &&
+ scrollPortSize.width < aState.HScrollbarMinWidth()) {
+ showHScrollbar = false;
+ }
+ ROOT_SCROLLBAR_LOG("TryLayout wants H Scrollbar: %d =? %d\n",
+ showHScrollbar, aAssumeHScroll);
+ }
+
+ // No need to compute showVScrollbar if we got ShowScrollbar::Never.
+ if (aState.mVScrollbar != ShowScrollbar::Never) {
+ showVScrollbar =
+ aState.mVScrollbar == ShowScrollbar::Always ||
+ scrolledRect.YMost() >= sizeToCompare.height + oneDevPixel ||
+ scrolledRect.y <= -oneDevPixel;
+ // TODO(emilio): This should probably check this scrollbar's minimum size
+ // in both axes, for consistency?
+ if (aState.mVScrollbar == ShowScrollbar::Auto &&
+ scrollPortSize.height < aState.VScrollbarMinHeight()) {
+ showVScrollbar = false;
+ }
+ ROOT_SCROLLBAR_LOG("TryLayout wants V Scrollbar: %d =? %d\n",
+ showVScrollbar, aAssumeVScroll);
+ }
+
+ if (showHScrollbar != aAssumeHScroll || showVScrollbar != aAssumeVScroll) {
+ const nsMargin wantedScrollbarGutter = aState.ScrollbarGutter(
+ showVScrollbar, showHScrollbar, IsScrollbarOnRight());
+ // We report an inconsistent layout only when the desired visibility of
+ // the scrollbars can change the size of the scrollbar gutters.
+ if (scrollbarGutter != wantedScrollbarGutter) {
+ return false;
+ }
+ }
+ }
+
+ // If we reach here, the layout is consistent. Record the desired visibility
+ // of the scrollbars.
+ aState.mShowHScrollbar = showHScrollbar;
+ aState.mShowVScrollbar = showVScrollbar;
+ const nsPoint scrollPortOrigin(
+ aState.mComputedBorder.left + scrollbarGutter.left,
+ aState.mComputedBorder.top + scrollbarGutter.top);
+ SetScrollPort(nsRect(scrollPortOrigin, scrollPortSize));
+
+ if (mIsRoot && gfxPlatform::UseDesktopZoomingScrollbars()) {
+ bool vvChanged = true;
+ const bool overlay = aState.OverlayScrollbars();
+ // This loop can run at most twice since we can only add a scrollbar once.
+ // At this point we've already decided that this layout is consistent so we
+ // will return true. Scrollbars added here never take up layout space even
+ // if they are layout scrollbars so any changes made here will not make us
+ // return false.
+ while (vvChanged) {
+ vvChanged = false;
+ if (!aState.mShowHScrollbar &&
+ aState.mHScrollbarAllowedForScrollingVVInsideLV) {
+ if (ScrollPort().width >= visualViewportSize.width + oneDevPixel &&
+ (overlay ||
+ visualViewportSize.width >= aState.HScrollbarMinWidth())) {
+ vvChanged = true;
+ if (!overlay) {
+ visualViewportSize.height -= aState.HScrollbarPrefHeight();
+ }
+ aState.mShowHScrollbar = true;
+ aState.mOnlyNeedHScrollbarToScrollVVInsideLV = true;
+ ROOT_SCROLLBAR_LOG("TryLayout added H scrollbar for VV, VV now %s\n",
+ ToString(visualViewportSize).c_str());
+ }
+ }
+
+ if (!aState.mShowVScrollbar &&
+ aState.mVScrollbarAllowedForScrollingVVInsideLV) {
+ if (ScrollPort().height >= visualViewportSize.height + oneDevPixel &&
+ (overlay ||
+ visualViewportSize.height >= aState.VScrollbarMinHeight())) {
+ vvChanged = true;
+ if (!overlay) {
+ visualViewportSize.width -= aState.VScrollbarPrefWidth();
+ }
+ aState.mShowVScrollbar = true;
+ aState.mOnlyNeedVScrollbarToScrollVVInsideLV = true;
+ ROOT_SCROLLBAR_LOG("TryLayout added V scrollbar for VV, VV now %s\n",
+ ToString(visualViewportSize).c_str());
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool nsHTMLScrollFrame::ScrolledContentDependsOnBSize(
+ const ScrollReflowInput& aState) const {
+ return mScrolledFrame->HasAnyStateBits(
+ NS_FRAME_CONTAINS_RELATIVE_BSIZE |
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE) ||
+ aState.mReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
+ aState.mReflowInput.ComputedMinBSize() > 0 ||
+ aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE;
+}
+
+void nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowInput& aState,
+ bool aAssumeHScroll,
+ bool aAssumeVScroll,
+ ReflowOutput* aMetrics) {
+ const WritingMode wm = GetWritingMode();
+
+ // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should
+ // be OK
+ LogicalMargin padding = aState.mReflowInput.ComputedLogicalPadding(wm);
+ nscoord availISize =
+ aState.mReflowInput.ComputedISize() + padding.IStartEnd(wm);
+
+ nscoord computedBSize = aState.mReflowInput.ComputedBSize();
+ nscoord computedMinBSize = aState.mReflowInput.ComputedMinBSize();
+ nscoord computedMaxBSize = aState.mReflowInput.ComputedMaxBSize();
+ if (!ShouldPropagateComputedBSizeToScrolledContent()) {
+ computedBSize = NS_UNCONSTRAINEDSIZE;
+ computedMinBSize = 0;
+ computedMaxBSize = NS_UNCONSTRAINEDSIZE;
+ }
+
+ const LogicalMargin scrollbarGutter(
+ wm, aState.ScrollbarGutter(aAssumeVScroll, aAssumeHScroll,
+ IsScrollbarOnRight()));
+ if (const nscoord inlineEndsGutter = scrollbarGutter.IStartEnd(wm);
+ inlineEndsGutter > 0) {
+ availISize = std::max(0, availISize - inlineEndsGutter);
+ }
+ if (const nscoord blockEndsGutter = scrollbarGutter.BStartEnd(wm);
+ blockEndsGutter > 0) {
+ if (computedBSize != NS_UNCONSTRAINEDSIZE) {
+ computedBSize = std::max(0, computedBSize - blockEndsGutter);
+ }
+ computedMinBSize = std::max(0, computedMinBSize - blockEndsGutter);
+ if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) {
+ computedMaxBSize = std::max(0, computedMaxBSize - blockEndsGutter);
+ }
+ }
+
+ nsPresContext* presContext = PresContext();
+
+ // Pass InitFlags::CallerWillInit so we can pass in the correct padding.
+ ReflowInput kidReflowInput(presContext, aState.mReflowInput, mScrolledFrame,
+ LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE),
+ Nothing(), ReflowInput::InitFlag::CallerWillInit);
+ const WritingMode kidWM = kidReflowInput.GetWritingMode();
+ kidReflowInput.Init(presContext, Nothing(), Nothing(),
+ Some(padding.ConvertTo(kidWM, wm)));
+ kidReflowInput.mFlags.mAssumingHScrollbar = aAssumeHScroll;
+ kidReflowInput.mFlags.mAssumingVScrollbar = aAssumeVScroll;
+ kidReflowInput.mFlags.mTreatBSizeAsIndefinite =
+ aState.mReflowInput.mFlags.mTreatBSizeAsIndefinite;
+ kidReflowInput.SetComputedBSize(computedBSize);
+ kidReflowInput.SetComputedMinBSize(computedMinBSize);
+ kidReflowInput.SetComputedMaxBSize(computedMaxBSize);
+ if (aState.mReflowInput.IsBResizeForWM(kidWM)) {
+ kidReflowInput.SetBResize(true);
+ }
+ if (aState.mReflowInput.IsBResizeForPercentagesForWM(kidWM)) {
+ kidReflowInput.mFlags.mIsBResizeForPercentages = true;
+ }
+
+ // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to
+ // reflect our assumptions while we reflow the child.
+ bool didHaveHorizontalScrollbar = mHasHorizontalScrollbar;
+ bool didHaveVerticalScrollbar = mHasVerticalScrollbar;
+ mHasHorizontalScrollbar = aAssumeHScroll;
+ mHasVerticalScrollbar = aAssumeVScroll;
+
+ nsReflowStatus status;
+ // No need to pass a true container-size to ReflowChild or
+ // FinishReflowChild, because it's only used there when positioning
+ // the frame (i.e. if ReflowChildFlags::NoMoveFrame isn't set)
+ const nsSize dummyContainerSize;
+ ReflowChild(mScrolledFrame, presContext, *aMetrics, kidReflowInput, wm,
+ LogicalPoint(wm), dummyContainerSize,
+ ReflowChildFlags::NoMoveFrame, status);
+
+ mHasHorizontalScrollbar = didHaveHorizontalScrollbar;
+ mHasVerticalScrollbar = didHaveVerticalScrollbar;
+
+ // Don't resize or position the view (if any) because we're going to resize
+ // it to the correct size anyway in PlaceScrollArea. Allowing it to
+ // resize here would size it to the natural height of the frame,
+ // which will usually be different from the scrollport height;
+ // invalidating the difference will cause unnecessary repainting.
+ FinishReflowChild(
+ mScrolledFrame, presContext, *aMetrics, &kidReflowInput, wm,
+ LogicalPoint(wm), dummyContainerSize,
+ ReflowChildFlags::NoMoveFrame | ReflowChildFlags::NoSizeView);
+
+ if (mScrolledFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ // Propagate NS_FRAME_CONTAINS_RELATIVE_BSIZE from our inner scrolled frame
+ // to ourselves so that our containing block is aware of it.
+ //
+ // Note: If the scrolled frame has any child whose block-size depends on the
+ // containing block's block-size, the NS_FRAME_CONTAINS_RELATIVE_BSIZE bit
+ // is set on the scrolled frame when initializing the child's ReflowInput in
+ // ReflowInput::InitResizeFlags(). Therefore, we propagate the bit here
+ // after we reflowed the scrolled frame.
+ AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+
+ // XXX Some frames (e.g. nsFrameFrame, nsTextFrame) don't
+ // bother setting their mOverflowArea. This is wrong because every frame
+ // should always set mOverflowArea. In fact nsFrameFrame doesn't
+ // support the 'outline' property because of this. Rather than fix the
+ // world right now, just fix up the overflow area if necessary. Note that we
+ // don't check HasOverflowRect() because it could be set even though the
+ // overflow area doesn't include the frame bounds.
+ aMetrics->UnionOverflowAreasWithDesiredBounds();
+
+ auto* disp = StyleDisplay();
+ if (MOZ_UNLIKELY(disp->mOverflowClipBoxInline ==
+ StyleOverflowClipBox::ContentBox)) {
+ // The scrolled frame is scrollable in the inline axis with
+ // `overflow-clip-box:content-box`. To prevent its content from being
+ // clipped at the scroll container's padding edges, we inflate its
+ // children's scrollable overflow area with its inline padding, and union
+ // its scrollable overflow area with its children's inflated scrollable
+ // overflow area.
+ OverflowAreas childOverflow;
+ mScrolledFrame->UnionChildOverflow(childOverflow);
+ nsRect childScrollableOverflow = childOverflow.ScrollableOverflow();
+
+ const LogicalMargin inlinePadding =
+ padding.ApplySkipSides(LogicalSides(wm, eLogicalSideBitsBBoth));
+ childScrollableOverflow.Inflate(inlinePadding.GetPhysicalMargin(wm));
+
+ nsRect& so = aMetrics->ScrollableOverflow();
+ so = so.UnionEdges(childScrollableOverflow);
+ }
+
+ aState.mContentsOverflowAreas = aMetrics->mOverflowAreas;
+ aState.mScrollbarGutterFromLastReflow = scrollbarGutter;
+ aState.mReflowedContentsWithHScrollbar = aAssumeHScroll;
+ aState.mReflowedContentsWithVScrollbar = aAssumeVScroll;
+}
+
+bool nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowInput& aState) {
+ if (aState.mHScrollbar != ShowScrollbar::Auto) {
+ // no guessing required
+ return aState.mHScrollbar == ShowScrollbar::Always;
+ }
+ // We only care about scrollbars that might take up space when trying to guess
+ // if we need a scrollbar, so we ignore scrollbars only created to scroll the
+ // visual viewport inside the layout viewport because they take up no layout
+ // space.
+ return mHasHorizontalScrollbar && !mOnlyNeedHScrollbarToScrollVVInsideLV;
+}
+
+bool nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowInput& aState) {
+ if (aState.mVScrollbar != ShowScrollbar::Auto) {
+ // no guessing required
+ return aState.mVScrollbar == ShowScrollbar::Always;
+ }
+
+ // If we've had at least one non-initial reflow, then just assume
+ // the state of the vertical scrollbar will be what we determined
+ // last time.
+ if (mHadNonInitialReflow) {
+ // We only care about scrollbars that might take up space when trying to
+ // guess if we need a scrollbar, so we ignore scrollbars only created to
+ // scroll the visual viewport inside the layout viewport because they take
+ // up no layout space.
+ return mHasVerticalScrollbar && !mOnlyNeedVScrollbarToScrollVVInsideLV;
+ }
+
+ // If this is the initial reflow, guess false because usually
+ // we have very little content by then.
+ if (InInitialReflow()) return false;
+
+ if (mIsRoot) {
+ nsIFrame* f = mScrolledFrame->PrincipalChildList().FirstChild();
+ if (f && f->IsSVGOuterSVGFrame() &&
+ static_cast<SVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) {
+ // Common SVG case - avoid a bad guess.
+ return false;
+ }
+ // Assume that there will be a scrollbar; it seems to me
+ // that 'most pages' do have a scrollbar, and anyway, it's cheaper
+ // to do an extra reflow for the pages that *don't* need a
+ // scrollbar (because on average they will have less content).
+ return true;
+ }
+
+ // For non-viewports, just guess that we don't need a scrollbar.
+ // XXX I wonder if statistically this is the right idea; I'm
+ // basically guessing that there are a lot of overflow:auto DIVs
+ // that get their intrinsic size and don't overflow
+ return false;
+}
+
+bool nsHTMLScrollFrame::InInitialReflow() const {
+ // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a
+ // root scrollframe. In that case we want to skip this clause altogether.
+ // The guess here is that there are lots of overflow:auto divs out there that
+ // end up auto-sizing so they don't overflow, and that the root basically
+ // always needs a scrollbar if it did last time we loaded this page (good
+ // assumption, because our initial reflow is no longer synchronous).
+ return !mIsRoot && HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+}
+
+void nsHTMLScrollFrame::ReflowContents(ScrollReflowInput& aState,
+ const ReflowOutput& aDesiredSize) {
+ const WritingMode desiredWm = aDesiredSize.GetWritingMode();
+ ReflowOutput kidDesiredSize(desiredWm);
+ ReflowScrolledFrame(aState, GuessHScrollbarNeeded(aState),
+ GuessVScrollbarNeeded(aState), &kidDesiredSize);
+
+ // There's an important special case ... if the child appears to fit
+ // in the inside-border rect (but overflows the scrollport), we
+ // should try laying it out without a vertical scrollbar. It will
+ // usually fit because making the available-width wider will not
+ // normally make the child taller. (The only situation I can think
+ // of is when you have a line containing %-width inline replaced
+ // elements whose percentages sum to more than 100%, so increasing
+ // the available width makes the line break where it was fitting
+ // before.) If we don't treat this case specially, then we will
+ // decide that showing scrollbars is OK because the content
+ // overflows when we're showing scrollbars and we won't try to
+ // remove the vertical scrollbar.
+
+ // Detecting when we enter this special case is important for when
+ // people design layouts that exactly fit the container "most of the
+ // time".
+
+ // XXX Is this check really sufficient to catch all the incremental cases
+ // where the ideal case doesn't have a scrollbar?
+ if ((aState.mReflowedContentsWithHScrollbar ||
+ aState.mReflowedContentsWithVScrollbar) &&
+ aState.mVScrollbar != ShowScrollbar::Always &&
+ aState.mHScrollbar != ShowScrollbar::Always) {
+ nsSize kidSize = GetContainSizeAxes().ContainSize(
+ kidDesiredSize.PhysicalSize(), *aState.mReflowInput.mFrame);
+ nsSize insideBorderSize = ComputeInsideBorderSize(aState, kidSize);
+ nsRect scrolledRect = GetUnsnappedScrolledRectInternal(
+ kidDesiredSize.ScrollableOverflow(), insideBorderSize);
+ if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
+ // Let's pretend we had no scrollbars coming in here
+ kidDesiredSize.mOverflowAreas.Clear();
+ ReflowScrolledFrame(aState, false, false, &kidDesiredSize);
+ }
+ }
+
+ if (IsRootScrollFrameOfDocument()) {
+ UpdateMinimumScaleSize(aState.mContentsOverflowAreas.ScrollableOverflow(),
+ kidDesiredSize.PhysicalSize());
+ }
+
+ // Try vertical scrollbar settings that leave the vertical scrollbar
+ // unchanged. Do this first because changing the vertical scrollbar setting is
+ // expensive, forcing a reflow always.
+
+ // Try leaving the horizontal scrollbar unchanged first. This will be more
+ // efficient.
+ ROOT_SCROLLBAR_LOG("Trying layout1 with %d, %d\n",
+ aState.mReflowedContentsWithHScrollbar,
+ aState.mReflowedContentsWithVScrollbar);
+ if (TryLayout(aState, &kidDesiredSize, aState.mReflowedContentsWithHScrollbar,
+ aState.mReflowedContentsWithVScrollbar, false)) {
+ return;
+ }
+ ROOT_SCROLLBAR_LOG("Trying layout2 with %d, %d\n",
+ !aState.mReflowedContentsWithHScrollbar,
+ aState.mReflowedContentsWithVScrollbar);
+ if (TryLayout(aState, &kidDesiredSize,
+ !aState.mReflowedContentsWithHScrollbar,
+ aState.mReflowedContentsWithVScrollbar, false)) {
+ return;
+ }
+
+ // OK, now try toggling the vertical scrollbar. The performance advantage
+ // of trying the status-quo horizontal scrollbar state
+ // does not exist here (we'll have to reflow due to the vertical scrollbar
+ // change), so always try no horizontal scrollbar first.
+ bool newVScrollbarState = !aState.mReflowedContentsWithVScrollbar;
+ ROOT_SCROLLBAR_LOG("Trying layout3 with %d, %d\n", false, newVScrollbarState);
+ if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false)) {
+ return;
+ }
+ ROOT_SCROLLBAR_LOG("Trying layout4 with %d, %d\n", true, newVScrollbarState);
+ if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false)) {
+ return;
+ }
+
+ // OK, we're out of ideas. Try again enabling whatever scrollbars we can
+ // enable and force the layout to stick even if it's inconsistent.
+ // This just happens sometimes.
+ ROOT_SCROLLBAR_LOG("Giving up, adding both scrollbars...\n");
+ TryLayout(aState, &kidDesiredSize, aState.mHScrollbar != ShowScrollbar::Never,
+ aState.mVScrollbar != ShowScrollbar::Never, true);
+}
+
+void nsHTMLScrollFrame::PlaceScrollArea(ScrollReflowInput& aState,
+ const nsPoint& aScrollPosition) {
+ // Set the x,y of the scrolled frame to the correct value
+ mScrolledFrame->SetPosition(ScrollPort().TopLeft() - aScrollPosition);
+
+ // Recompute our scrollable overflow, taking perspective children into
+ // account. Note that this only recomputes the overflow areas stored on the
+ // helper (which are used to compute scrollable length and scrollbar thumb
+ // sizes) but not the overflow areas stored on the frame. This seems to work
+ // for now, but it's possible that we may need to update both in the future.
+ AdjustForPerspective(aState.mContentsOverflowAreas.ScrollableOverflow());
+
+ // Preserve the width or height of empty rects
+ const nsSize portSize = ScrollPort().Size();
+ nsRect scrolledRect = GetUnsnappedScrolledRectInternal(
+ aState.mContentsOverflowAreas.ScrollableOverflow(), portSize);
+ nsRect scrolledArea =
+ scrolledRect.UnionEdges(nsRect(nsPoint(0, 0), portSize));
+
+ // Store the new overflow area. Note that this changes where an outline
+ // of the scrolled frame would be painted, but scrolled frames can't have
+ // outlines (the outline would go on this scrollframe instead).
+ // Using FinishAndStoreOverflow is needed so the overflow rect gets set
+ // correctly. It also messes with the overflow rect in the 'clip' case, but
+ // scrolled frames can't have 'overflow' either.
+ // This needs to happen before SyncFrameViewAfterReflow so
+ // HasOverflowRect() will return the correct value.
+ OverflowAreas overflow(scrolledArea, scrolledArea);
+ mScrolledFrame->FinishAndStoreOverflow(overflow, mScrolledFrame->GetSize());
+
+ // Note that making the view *exactly* the size of the scrolled area
+ // is critical, since the view scrolling code uses the size of the
+ // scrolled view to clamp scroll requests.
+ // Normally the mScrolledFrame won't have a view but in some cases it
+ // might create its own.
+ nsContainerFrame::SyncFrameViewAfterReflow(
+ mScrolledFrame->PresContext(), mScrolledFrame, mScrolledFrame->GetView(),
+ scrolledArea, ReflowChildFlags::Default);
+}
+
+nscoord nsHTMLScrollFrame::IntrinsicScrollbarGutterSizeAtInlineEdges() {
+ const bool isVerticalWM = GetWritingMode().IsVertical();
+ if (PresContext()->UseOverlayScrollbars()) {
+ return 0;
+ }
+
+ const auto* styleForScrollbar = nsLayoutUtils::StyleForScrollbar(this);
+ const auto& styleScrollbarWidth =
+ styleForScrollbar->StyleUIReset()->ScrollbarWidth();
+ if (styleScrollbarWidth == StyleScrollbarWidth::None) {
+ // Scrollbar shouldn't appear at all with "scrollbar-width: none".
+ return 0;
+ }
+
+ const auto& styleScrollbarGutter =
+ styleForScrollbar->StyleDisplay()->mScrollbarGutter;
+ ScrollStyles ss = GetScrollStyles();
+ const StyleOverflow& inlineEndStyleOverflow =
+ isVerticalWM ? ss.mHorizontal : ss.mVertical;
+
+ // Return the scrollbar-gutter size only if we have "overflow:scroll" or
+ // non-auto "scrollbar-gutter", so early-return here if the conditions aren't
+ // satisfied.
+ if (inlineEndStyleOverflow != StyleOverflow::Scroll &&
+ styleScrollbarGutter == StyleScrollbarGutter::AUTO) {
+ return 0;
+ }
+
+ const nscoord scrollbarSize =
+ GetNonOverlayScrollbarSize(PresContext(), styleScrollbarWidth);
+ const auto bothEdges =
+ bool(styleScrollbarGutter & StyleScrollbarGutter::BOTH_EDGES);
+ return bothEdges ? scrollbarSize * 2 : scrollbarSize;
+}
+
+nsMargin nsHTMLScrollFrame::ComputeStableScrollbarGutter(
+ const StyleScrollbarWidth& aStyleScrollbarWidth,
+ const StyleScrollbarGutter& aStyleScrollbarGutter) const {
+ if (PresContext()->UseOverlayScrollbars()) {
+ // Overlay scrollbars do not consume space per spec.
+ return {};
+ }
+
+ if (aStyleScrollbarWidth == StyleScrollbarWidth::None) {
+ // Scrollbar shouldn't appear at all with "scrollbar-width: none".
+ return {};
+ }
+
+ if (aStyleScrollbarGutter == StyleScrollbarGutter::AUTO) {
+ // Scrollbars create space depending on the 'overflow' property and whether
+ // the content overflows. Callers need to check this scenario if they want
+ // to consider the space created by the actual scrollbars.
+ return {};
+ }
+
+ const bool bothEdges =
+ bool(aStyleScrollbarGutter & StyleScrollbarGutter::BOTH_EDGES);
+ const bool isVerticalWM = GetWritingMode().IsVertical();
+ const nscoord scrollbarSize =
+ GetNonOverlayScrollbarSize(PresContext(), aStyleScrollbarWidth);
+
+ nsMargin scrollbarGutter;
+ if (bothEdges) {
+ if (isVerticalWM) {
+ scrollbarGutter.top = scrollbarGutter.bottom = scrollbarSize;
+ } else {
+ scrollbarGutter.left = scrollbarGutter.right = scrollbarSize;
+ }
+ } else {
+ MOZ_ASSERT(bool(aStyleScrollbarGutter & StyleScrollbarGutter::STABLE),
+ "scrollbar-gutter value should be 'stable'!");
+ if (isVerticalWM) {
+ // The horizontal scrollbar-gutter is always at the bottom side.
+ scrollbarGutter.bottom = scrollbarSize;
+ } else if (IsScrollbarOnRight()) {
+ scrollbarGutter.right = scrollbarSize;
+ } else {
+ scrollbarGutter.left = scrollbarSize;
+ }
+ }
+ return scrollbarGutter;
+}
+
+// Legacy, this sucks!
+static bool IsMarqueeScrollbox(const nsIFrame& aScrollFrame) {
+ if (!aScrollFrame.GetContent()) {
+ return false;
+ }
+ if (MOZ_LIKELY(!aScrollFrame.GetContent()->HasBeenInUAWidget())) {
+ return false;
+ }
+ MOZ_ASSERT(aScrollFrame.GetParent() &&
+ aScrollFrame.GetParent()->GetContent());
+ return aScrollFrame.GetParent() &&
+ HTMLMarqueeElement::FromNodeOrNull(
+ aScrollFrame.GetParent()->GetContent());
+}
+
+/* virtual */
+nscoord nsHTMLScrollFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result = [&] {
+ if (const Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ return *containISize;
+ }
+ if (MOZ_UNLIKELY(IsMarqueeScrollbox(*this))) {
+ return 0;
+ }
+ return mScrolledFrame->GetMinISize(aRenderingContext);
+ }();
+
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ return result + IntrinsicScrollbarGutterSizeAtInlineEdges();
+}
+
+/* virtual */
+nscoord nsHTMLScrollFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ const Maybe<nscoord> containISize = ContainIntrinsicISize();
+ nscoord result = containISize
+ ? *containISize
+ : mScrolledFrame->GetPrefISize(aRenderingContext);
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ return NSCoordSaturatingAdd(result,
+ IntrinsicScrollbarGutterSizeAtInlineEdges());
+}
+
+// When we have perspective set on the outer scroll frame, and transformed
+// children (possibly with preserve-3d) then the effective transform on the
+// child depends on the offset to the scroll frame, which changes as we scroll.
+// This perspective transform can cause the element to move relative to the
+// scrolled inner frame, which would cause the scrollable length changes during
+// scrolling if we didn't account for it. Since we don't want scrollHeight/Width
+// and the size of scrollbar thumbs to change during scrolling, we compute the
+// scrollable overflow by determining the scroll position at which the child
+// becomes completely visible within the scrollport rather than using the union
+// of the overflow areas at their current position.
+static void GetScrollableOverflowForPerspective(
+ nsIFrame* aScrolledFrame, nsIFrame* aCurrentFrame, const nsRect aScrollPort,
+ nsPoint aOffset, nsRect& aScrolledFrameOverflowArea) {
+ // Iterate over all children except pop-ups.
+ for (const auto& [list, listID] : aCurrentFrame->ChildLists()) {
+ if (listID == FrameChildListID::Popup) {
+ continue;
+ }
+
+ for (nsIFrame* child : list) {
+ nsPoint offset = aOffset;
+
+ // When we reach a direct child of the scroll, then we record the offset
+ // to convert from that frame's coordinate into the scroll frame's
+ // coordinates. Preserve-3d descendant frames use the same offset as their
+ // ancestors, since TransformRect already converts us into the coordinate
+ // space of the preserve-3d root.
+ if (aScrolledFrame == aCurrentFrame) {
+ offset = child->GetPosition();
+ }
+
+ if (child->Extend3DContext()) {
+ // If we're a preserve-3d frame, then recurse and include our
+ // descendants since overflow of preserve-3d frames is only included
+ // in the post-transform overflow area of the preserve-3d root frame.
+ GetScrollableOverflowForPerspective(aScrolledFrame, child, aScrollPort,
+ offset, aScrolledFrameOverflowArea);
+ }
+
+ // If we're transformed, then we want to consider the possibility that
+ // this frame might move relative to the scrolled frame when scrolling.
+ // For preserve-3d, leaf frames have correct overflow rects relative to
+ // themselves. preserve-3d 'nodes' (intermediate frames and the root) have
+ // only their untransformed children included in their overflow relative
+ // to self, which is what we want to include here.
+ if (child->IsTransformed()) {
+ // Compute the overflow rect for this leaf transform frame in the
+ // coordinate space of the scrolled frame.
+ nsPoint scrollPos = aScrolledFrame->GetPosition();
+ nsRect preScroll, postScroll;
+ {
+ // TODO: Can we reuse the reference box?
+ TransformReferenceBox refBox(child);
+ preScroll = nsDisplayTransform::TransformRect(
+ child->ScrollableOverflowRectRelativeToSelf(), child, refBox);
+ }
+
+ // Temporarily override the scroll position of the scrolled frame by
+ // 10 CSS pixels, and then recompute what the overflow rect would be.
+ // This scroll position may not be valid, but that shouldn't matter
+ // for our calculations.
+ {
+ aScrolledFrame->SetPosition(scrollPos + nsPoint(600, 600));
+ TransformReferenceBox refBox(child);
+ postScroll = nsDisplayTransform::TransformRect(
+ child->ScrollableOverflowRectRelativeToSelf(), child, refBox);
+ aScrolledFrame->SetPosition(scrollPos);
+ }
+
+ // Compute how many app units the overflow rects moves by when we adjust
+ // the scroll position by 1 app unit.
+ double rightDelta =
+ (postScroll.XMost() - preScroll.XMost() + 600.0) / 600.0;
+ double bottomDelta =
+ (postScroll.YMost() - preScroll.YMost() + 600.0) / 600.0;
+
+ // We can't ever have negative scrolling.
+ NS_ASSERTION(rightDelta > 0.0f && bottomDelta > 0.0f,
+ "Scrolling can't be reversed!");
+
+ // Move preScroll into the coordinate space of the scrollport.
+ preScroll += offset + scrollPos;
+
+ // For each of the four edges of preScroll, figure out how far they
+ // extend beyond the scrollport. Ignore negative values since that means
+ // that side is already scrolled in to view and we don't need to add
+ // overflow to account for it.
+ nsMargin overhang(std::max(0, aScrollPort.Y() - preScroll.Y()),
+ std::max(0, preScroll.XMost() - aScrollPort.XMost()),
+ std::max(0, preScroll.YMost() - aScrollPort.YMost()),
+ std::max(0, aScrollPort.X() - preScroll.X()));
+
+ // Scale according to rightDelta/bottomDelta to adjust for the different
+ // scroll rates.
+ overhang.top = NSCoordSaturatingMultiply(
+ overhang.top, static_cast<float>(1 / bottomDelta));
+ overhang.right = NSCoordSaturatingMultiply(
+ overhang.right, static_cast<float>(1 / rightDelta));
+ overhang.bottom = NSCoordSaturatingMultiply(
+ overhang.bottom, static_cast<float>(1 / bottomDelta));
+ overhang.left = NSCoordSaturatingMultiply(
+ overhang.left, static_cast<float>(1 / rightDelta));
+
+ // Take the minimum overflow rect that would allow the current scroll
+ // position, using the size of the scroll port and offset by the
+ // inverse of the scroll position.
+ nsRect overflow = aScrollPort - scrollPos;
+
+ // Expand it by our margins to get an overflow rect that would allow all
+ // edges of our transformed content to be scrolled into view.
+ overflow.Inflate(overhang);
+
+ // Merge it with the combined overflow
+ aScrolledFrameOverflowArea.UnionRect(aScrolledFrameOverflowArea,
+ overflow);
+ } else if (aCurrentFrame == aScrolledFrame) {
+ aScrolledFrameOverflowArea.UnionRect(
+ aScrolledFrameOverflowArea,
+ child->ScrollableOverflowRectRelativeToParent());
+ }
+ }
+ }
+}
+
+BaselineSharingGroup nsHTMLScrollFrame::GetDefaultBaselineSharingGroup() const {
+ return mScrolledFrame->GetDefaultBaselineSharingGroup();
+}
+
+nscoord nsHTMLScrollFrame::SynthesizeFallbackBaseline(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
+ // Marign-end even for central baselines.
+ if (aWM.IsLineInverted()) {
+ return -GetLogicalUsedMargin(aWM).BStart(aWM);
+ }
+ return aBaselineGroup == BaselineSharingGroup::First
+ ? BSize(aWM) + GetLogicalUsedMargin(aWM).BEnd(aWM)
+ : -GetLogicalUsedMargin(aWM).BEnd(aWM);
+}
+
+Maybe<nscoord> nsHTMLScrollFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ // Block containers that are scrollable always have a last baseline
+ // that are synthesized from block-end margin edge.
+ // Note(dshin): This behaviour is really only relevant to `inline-block`
+ // alignment context. In the context of table/flex/grid alignment, first/last
+ // baselines are calculated through `GetFirstLineBaseline`, which does
+ // calculations of its own.
+ // https://drafts.csswg.org/css-align/#baseline-export
+ if (aExportContext == BaselineExportContext::LineLayout &&
+ aBaselineGroup == BaselineSharingGroup::Last &&
+ mScrolledFrame->IsBlockFrameOrSubclass()) {
+ return Some(SynthesizeFallbackBaseline(aWM, aBaselineGroup));
+ }
+
+ if (StyleDisplay()->IsContainLayout()) {
+ return Nothing{};
+ }
+
+ // OK, here's where we defer to our scrolled frame.
+ return mScrolledFrame
+ ->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext)
+ .map([this, aWM](nscoord aBaseline) {
+ // We have to add our border BStart thickness to whatever it returns, to
+ // produce an offset in our frame-rect's coordinate system. (We don't
+ // have to add padding, because the scrolled frame handles our padding.)
+ LogicalMargin border = GetLogicalUsedBorder(aWM);
+ const auto bSize = GetLogicalSize(aWM).BSize(aWM);
+ // Clamp the baseline to the border rect. See bug 1791069.
+ return std::clamp(border.BStart(aWM) + aBaseline, 0, bSize);
+ });
+}
+
+void nsHTMLScrollFrame::AdjustForPerspective(nsRect& aScrollableOverflow) {
+ // If we have perspective that is being applied to our children, then
+ // the effective transform on the child depends on the relative position
+ // of the child to us and changes during scrolling.
+ if (!ChildrenHavePerspective()) {
+ return;
+ }
+ aScrollableOverflow.SetEmpty();
+ GetScrollableOverflowForPerspective(mScrolledFrame, mScrolledFrame,
+ ScrollPort(), nsPoint(),
+ aScrollableOverflow);
+}
+
+void nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ HandleScrollbarStyleSwitching();
+
+ ScrollReflowInput state(this, aReflowInput);
+
+ //------------ Handle Incremental Reflow -----------------
+ bool reflowHScrollbar = true;
+ bool reflowVScrollbar = true;
+ bool reflowScrollCorner = true;
+ if (!aReflowInput.ShouldReflowAllKids()) {
+ auto NeedsReflow = [](const nsIFrame* aFrame) {
+ return aFrame && aFrame->IsSubtreeDirty();
+ };
+
+ reflowHScrollbar = NeedsReflow(mHScrollbarBox);
+ reflowVScrollbar = NeedsReflow(mVScrollbarBox);
+ reflowScrollCorner =
+ NeedsReflow(mScrollCornerBox) || NeedsReflow(mResizerBox);
+ }
+
+ if (mIsRoot) {
+ reflowScrollCorner = false;
+ }
+
+ const nsRect oldScrollPort = ScrollPort();
+ nsRect oldScrolledAreaBounds =
+ mScrolledFrame->ScrollableOverflowRectRelativeToParent();
+ nsPoint oldScrollPosition = GetScrollPosition();
+
+ ReflowContents(state, aDesiredSize);
+
+ nsSize layoutSize =
+ mIsUsingMinimumScaleSize ? mMinimumScaleSize : state.mInsideBorderSize;
+ aDesiredSize.Width() = layoutSize.width + state.mComputedBorder.LeftRight();
+ aDesiredSize.Height() = layoutSize.height + state.mComputedBorder.TopBottom();
+
+ // Set the size of the frame now since computing the perspective-correct
+ // overflow (within PlaceScrollArea) can rely on it.
+ SetSize(aDesiredSize.GetWritingMode(),
+ aDesiredSize.Size(aDesiredSize.GetWritingMode()));
+
+ // Restore the old scroll position, for now, even if that's not valid anymore
+ // because we changed size. We'll fix it up in a post-reflow callback, because
+ // our current size may only be temporary (e.g. we're compute XUL desired
+ // sizes).
+ PlaceScrollArea(state, oldScrollPosition);
+ if (!mPostedReflowCallback) {
+ // Make sure we'll try scrolling to restored position
+ PresShell()->PostReflowCallback(this);
+ mPostedReflowCallback = true;
+ }
+
+ bool didOnlyHScrollbar = mOnlyNeedHScrollbarToScrollVVInsideLV;
+ bool didOnlyVScrollbar = mOnlyNeedVScrollbarToScrollVVInsideLV;
+ mOnlyNeedHScrollbarToScrollVVInsideLV =
+ state.mOnlyNeedHScrollbarToScrollVVInsideLV;
+ mOnlyNeedVScrollbarToScrollVVInsideLV =
+ state.mOnlyNeedVScrollbarToScrollVVInsideLV;
+
+ bool didHaveHScrollbar = mHasHorizontalScrollbar;
+ bool didHaveVScrollbar = mHasVerticalScrollbar;
+ mHasHorizontalScrollbar = state.mShowHScrollbar;
+ mHasVerticalScrollbar = state.mShowVScrollbar;
+ const nsRect& newScrollPort = ScrollPort();
+ nsRect newScrolledAreaBounds =
+ mScrolledFrame->ScrollableOverflowRectRelativeToParent();
+ if (mSkippedScrollbarLayout || reflowHScrollbar || reflowVScrollbar ||
+ reflowScrollCorner || HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
+ didHaveHScrollbar != state.mShowHScrollbar ||
+ didHaveVScrollbar != state.mShowVScrollbar ||
+ didOnlyHScrollbar != mOnlyNeedHScrollbarToScrollVVInsideLV ||
+ didOnlyVScrollbar != mOnlyNeedVScrollbarToScrollVVInsideLV ||
+ !oldScrollPort.IsEqualEdges(newScrollPort) ||
+ !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
+ if (!mSuppressScrollbarUpdate) {
+ mSkippedScrollbarLayout = false;
+ nsHTMLScrollFrame::SetScrollbarVisibility(mHScrollbarBox,
+ state.mShowHScrollbar);
+ nsHTMLScrollFrame::SetScrollbarVisibility(mVScrollbarBox,
+ state.mShowVScrollbar);
+ // place and reflow scrollbars
+ const nsRect insideBorderArea(
+ nsPoint(state.mComputedBorder.left, state.mComputedBorder.top),
+ layoutSize);
+ LayoutScrollbars(state, insideBorderArea, oldScrollPort);
+ } else {
+ mSkippedScrollbarLayout = true;
+ }
+ }
+ if (mIsRoot) {
+ if (RefPtr<MobileViewportManager> manager =
+ PresShell()->GetMobileViewportManager()) {
+ // Note that this runs during layout, and when we get here the root
+ // scrollframe has already been laid out. It may have added or removed
+ // scrollbars as a result of that layout, so we need to ensure the
+ // visual viewport is updated to account for that before we read the
+ // visual viewport size.
+ manager->UpdateVisualViewportSizeForPotentialScrollbarChange();
+ } else if (oldScrollPort.Size() != newScrollPort.Size()) {
+ // We want to make sure to send a visual viewport resize event if the
+ // scrollport changed sizes for root scroll frames. The
+ // MobileViewportManager will do that, but if we don't have one (ie we
+ // aren't a root content document for example) we have to send one
+ // ourselves.
+ if (auto* window = nsGlobalWindowInner::Cast(
+ aPresContext->Document()->GetInnerWindow())) {
+ window->VisualViewport()->PostResizeEvent();
+ }
+ }
+ }
+
+ // Note that we need to do this after the
+ // UpdateVisualViewportSizeForPotentialScrollbarChange call above because that
+ // is what updates the visual viewport size and we need it to be up to date.
+ if (mIsRoot && !state.OverlayScrollbars() &&
+ (didHaveHScrollbar != state.mShowHScrollbar ||
+ didHaveVScrollbar != state.mShowVScrollbar ||
+ didOnlyHScrollbar != mOnlyNeedHScrollbarToScrollVVInsideLV ||
+ didOnlyVScrollbar != mOnlyNeedVScrollbarToScrollVVInsideLV) &&
+ PresShell()->IsVisualViewportOffsetSet()) {
+ // Removing layout/classic scrollbars can make a previously valid vvoffset
+ // invalid. For example, if we are zoomed in on an overflow hidden document
+ // and then zoom back out, when apz reaches the initial resolution (ie 1.0)
+ // it won't know that we can remove the scrollbars, so the vvoffset can
+ // validly be upto the width/height of the scrollbars. After we reflow and
+ // remove the scrollbars the only valid vvoffset is (0,0). We could wait
+ // until we send the new frame metrics to apz and then have it reply with
+ // the new corrected vvoffset but having an inconsistent vvoffset causes
+ // problems so trigger the vvoffset to be re-set and re-clamped in
+ // ReflowFinished.
+ mReclampVVOffsetInReflowFinished = true;
+ }
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ UpdateSticky();
+ FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
+ aStatus);
+
+ if (!InInitialReflow() && !mHadNonInitialReflow) {
+ mHadNonInitialReflow = true;
+ }
+
+ if (mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
+ PostScrolledAreaEvent();
+ }
+
+ UpdatePrevScrolledRect();
+
+ aStatus.Reset(); // This type of frame can't be split.
+ PostOverflowEvent();
+}
+
+void nsHTMLScrollFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) {
+ nsContainerFrame::DidReflow(aPresContext, aReflowInput);
+ if (NeedsResnap()) {
+ PostPendingResnap();
+ } else {
+ PresShell()->PostPendingScrollAnchorAdjustment(Anchor());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsHTMLScrollFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"HTMLScroll"_ns, aResult);
+}
+#endif
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsHTMLScrollFrame::AccessibleType() {
+ if (IsTableCaption()) {
+ return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
+ }
+
+ // Create an accessible regardless of focusable state because the state can be
+ // changed during frame life cycle without any notifications to accessibility.
+ if (mContent->IsRootOfNativeAnonymousSubtree() ||
+ GetScrollStyles().IsHiddenInBothDirections()) {
+ return a11y::eNoType;
+ }
+
+ return a11y::eHyperTextType;
+}
+#endif
+
+NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+ NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
+ NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
+ NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+ NS_QUERYFRAME_ENTRY(nsHTMLScrollFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+nsMargin nsHTMLScrollFrame::GetDesiredScrollbarSizes() const {
+ nsPresContext* pc = PresContext();
+ if (pc->UseOverlayScrollbars()) {
+ return {};
+ }
+
+ const auto& style = *nsLayoutUtils::StyleForScrollbar(this);
+ const auto scrollbarWidth = style.StyleUIReset()->ScrollbarWidth();
+ if (scrollbarWidth == StyleScrollbarWidth::None) {
+ return {};
+ }
+
+ ScrollStyles styles = GetScrollStyles();
+ nsMargin result(0, 0, 0, 0);
+
+ auto size = GetNonOverlayScrollbarSize(pc, scrollbarWidth);
+ if (styles.mVertical != StyleOverflow::Hidden) {
+ if (IsScrollbarOnRight()) {
+ result.right = size;
+ } else {
+ result.left = size;
+ }
+ }
+
+ if (styles.mHorizontal != StyleOverflow::Hidden) {
+ // We don't currently support any scripts that would require a scrollbar
+ // at the top. (Are there any?)
+ result.bottom = size;
+ }
+
+ return result;
+}
+
+nscoord nsHTMLScrollFrame::GetNonOverlayScrollbarSize(
+ const nsPresContext* aPc, StyleScrollbarWidth aScrollbarWidth) {
+ const auto size = aPc->Theme()->GetScrollbarSize(aPc, aScrollbarWidth,
+ nsITheme::Overlay::No);
+ return aPc->DevPixelsToAppUnits(size);
+}
+
+void nsHTMLScrollFrame::HandleScrollbarStyleSwitching() {
+ // Check if we switched between scrollbar styles.
+ if (mScrollbarActivity && !UsesOverlayScrollbars()) {
+ mScrollbarActivity->Destroy();
+ mScrollbarActivity = nullptr;
+ } else if (!mScrollbarActivity && UsesOverlayScrollbars()) {
+ mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(this));
+ }
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+static bool IsFocused(nsIContent* aContent) {
+ // Some content elements, like the GetContent() of a scroll frame
+ // for a text input field, are inside anonymous subtrees, but the focus
+ // manager always reports a non-anonymous element as the focused one, so
+ // walk up the tree until we reach a non-anonymous element.
+ while (aContent && aContent->IsInNativeAnonymousSubtree()) {
+ aContent = aContent->GetParent();
+ }
+
+ return aContent ? nsContentUtils::IsFocusedContent(aContent) : false;
+}
+#endif
+
+void nsHTMLScrollFrame::SetScrollableByAPZ(bool aScrollable) {
+ mScrollableByAPZ = aScrollable;
+}
+
+void nsHTMLScrollFrame::SetZoomableByAPZ(bool aZoomable) {
+ if (!nsLayoutUtils::UsesAsyncScrolling(this)) {
+ // If APZ is disabled on this window, then we're never actually going to
+ // do any zooming. So we don't need to do any of the setup for it. Note
+ // that this function gets called from ZoomConstraintsClient even if APZ
+ // is disabled to indicate the zoomability of content.
+ aZoomable = false;
+ }
+ if (mZoomableByAPZ != aZoomable) {
+ // We might be changing the result of DecideScrollableLayer() so schedule a
+ // paint to make sure we pick up the result of that change.
+ mZoomableByAPZ = aZoomable;
+ SchedulePaint();
+ }
+}
+
+void nsHTMLScrollFrame::SetHasOutOfFlowContentInsideFilter() {
+ mHasOutOfFlowContentInsideFilter = true;
+}
+
+bool nsHTMLScrollFrame::WantAsyncScroll() const {
+ ScrollStyles styles = GetScrollStyles();
+
+ // First, as an optimization because getting the scrollrange is
+ // relatively slow, check overflow hidden and not a zoomed scroll frame.
+ if (styles.mHorizontal == StyleOverflow::Hidden &&
+ styles.mVertical == StyleOverflow::Hidden) {
+ if (!mIsRoot || GetVisualViewportSize() == mScrollPort.Size()) {
+ return false;
+ }
+ }
+
+ nscoord oneDevPixel =
+ GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
+ nsRect scrollRange = GetLayoutScrollRange();
+
+ bool isVScrollable = (scrollRange.height >= oneDevPixel) &&
+ (styles.mVertical != StyleOverflow::Hidden);
+ bool isHScrollable = (scrollRange.width >= oneDevPixel) &&
+ (styles.mHorizontal != StyleOverflow::Hidden);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // Mobile platforms need focus to scroll text inputs.
+ bool canScrollWithoutScrollbars =
+ !IsForTextControlWithNoScrollbars() || IsFocused(GetContent());
+#else
+ bool canScrollWithoutScrollbars = true;
+#endif
+
+ // The check for scroll bars was added in bug 825692 to prevent layerization
+ // of text inputs for performance reasons.
+ bool isVAsyncScrollable =
+ isVScrollable && (mVScrollbarBox || canScrollWithoutScrollbars);
+ bool isHAsyncScrollable =
+ isHScrollable && (mHScrollbarBox || canScrollWithoutScrollbars);
+ if (isVAsyncScrollable || isHAsyncScrollable) {
+ return true;
+ }
+
+ // If the page has a visual viewport size that's different from
+ // the layout viewport size at the current zoom level, we need to be
+ // able to scroll the visual viewport inside the layout viewport
+ // even if the page is not zoomable.
+ return mIsRoot && GetVisualViewportSize() != mScrollPort.Size() &&
+ !GetVisualScrollRange().IsEqualInterior(scrollRange);
+}
+
+static nsRect GetOnePixelRangeAroundPoint(const nsPoint& aPoint,
+ bool aIsHorizontal) {
+ nsRect allowedRange(aPoint, nsSize());
+ nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
+ if (aIsHorizontal) {
+ allowedRange.x = aPoint.x - halfPixel;
+ allowedRange.width = halfPixel * 2 - 1;
+ } else {
+ allowedRange.y = aPoint.y - halfPixel;
+ allowedRange.height = halfPixel * 2 - 1;
+ }
+ return allowedRange;
+}
+
+void nsHTMLScrollFrame::ScrollByPage(nsScrollbarFrame* aScrollbar,
+ int32_t aDirection,
+ ScrollSnapFlags aSnapFlags) {
+ ScrollByUnit(aScrollbar, ScrollMode::Smooth, aDirection, ScrollUnit::PAGES,
+ aSnapFlags);
+}
+
+void nsHTMLScrollFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar,
+ int32_t aDirection,
+ ScrollSnapFlags aSnapFlags) {
+ ScrollByUnit(aScrollbar, ScrollMode::Instant, aDirection, ScrollUnit::WHOLE,
+ aSnapFlags);
+}
+
+void nsHTMLScrollFrame::ScrollByLine(nsScrollbarFrame* aScrollbar,
+ int32_t aDirection,
+ ScrollSnapFlags aSnapFlags) {
+ bool isHorizontal = aScrollbar->IsHorizontal();
+ nsIntPoint delta;
+ if (isHorizontal) {
+ const double kScrollMultiplier =
+ StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
+ delta.x = static_cast<int32_t>(aDirection * kScrollMultiplier);
+ if (GetLineScrollAmount().width * delta.x > GetPageScrollAmount().width) {
+ // The scroll frame is so small that the delta would be more
+ // than an entire page. Scroll by one page instead to maintain
+ // context.
+ ScrollByPage(aScrollbar, aDirection);
+ return;
+ }
+ } else {
+ const double kScrollMultiplier =
+ StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
+ delta.y = static_cast<int32_t>(aDirection * kScrollMultiplier);
+ if (GetLineScrollAmount().height * delta.y > GetPageScrollAmount().height) {
+ // The scroll frame is so small that the delta would be more
+ // than an entire page. Scroll by one page instead to maintain
+ // context.
+ ScrollByPage(aScrollbar, aDirection);
+ return;
+ }
+ }
+
+ nsIntPoint overflow;
+ ScrollBy(delta, ScrollUnit::LINES, ScrollMode::Smooth, &overflow,
+ ScrollOrigin::Other, nsIScrollableFrame::NOT_MOMENTUM, aSnapFlags);
+}
+
+void nsHTMLScrollFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) {
+ aScrollbar->MoveToNewPosition(nsScrollbarFrame::ImplementsScrollByUnit::Yes);
+}
+
+void nsHTMLScrollFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos, nscoord aNewPos) {
+ MOZ_ASSERT(aScrollbar != nullptr);
+ bool isHorizontal = aScrollbar->IsHorizontal();
+ nsPoint current = GetScrollPosition();
+ nsPoint dest = current;
+ if (isHorizontal) {
+ dest.x = IsPhysicalLTR() ? aNewPos : aNewPos - GetLayoutScrollRange().width;
+ } else {
+ dest.y = aNewPos;
+ }
+ nsRect allowedRange = GetOnePixelRangeAroundPoint(dest, isHorizontal);
+
+ // Don't try to scroll if we're already at an acceptable place.
+ // Don't call Contains here since Contains returns false when the point is
+ // on the bottom or right edge of the rectangle.
+ if (allowedRange.ClampPoint(current) == current) {
+ return;
+ }
+
+ ScrollToWithOrigin(
+ dest, &allowedRange,
+ ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Other});
+}
+
+void nsHTMLScrollFrame::ScrollbarReleased(nsScrollbarFrame* aScrollbar) {
+ // Scrollbar scrolling does not result in fling gestures, clear any
+ // accumulated velocity
+ mVelocityQueue.Reset();
+
+ // Perform scroll snapping, if needed. Scrollbar movement uses the same
+ // smooth scrolling animation as keyboard scrolling.
+ ScrollSnap(mDestination, ScrollMode::Smooth);
+}
+
+void nsHTMLScrollFrame::ScrollByUnit(nsScrollbarFrame* aScrollbar,
+ ScrollMode aMode, int32_t aDirection,
+ ScrollUnit aUnit,
+ ScrollSnapFlags aSnapFlags) {
+ MOZ_ASSERT(aScrollbar != nullptr);
+ bool isHorizontal = aScrollbar->IsHorizontal();
+ nsIntPoint delta;
+ if (isHorizontal) {
+ delta.x = aDirection;
+ } else {
+ delta.y = aDirection;
+ }
+ nsIntPoint overflow;
+ ScrollBy(delta, aUnit, aMode, &overflow, ScrollOrigin::Other,
+ nsIScrollableFrame::NOT_MOMENTUM, aSnapFlags);
+}
+
+//-------------------- Helper ----------------------
+
+// AsyncSmoothMSDScroll has ref counting.
+class nsHTMLScrollFrame::AsyncSmoothMSDScroll final
+ : public nsARefreshObserver {
+ public:
+ AsyncSmoothMSDScroll(const nsPoint& aInitialPosition,
+ const nsPoint& aInitialDestination,
+ const nsSize& aInitialVelocity, const nsRect& aRange,
+ const mozilla::TimeStamp& aStartTime,
+ nsPresContext* aPresContext,
+ UniquePtr<ScrollSnapTargetIds> aSnapTargetIds,
+ ScrollTriggeredByScript aTriggeredByScript)
+ : mXAxisModel(aInitialPosition.x, aInitialDestination.x,
+ aInitialVelocity.width,
+ StaticPrefs::layout_css_scroll_behavior_spring_constant(),
+ StaticPrefs::layout_css_scroll_behavior_damping_ratio()),
+ mYAxisModel(aInitialPosition.y, aInitialDestination.y,
+ aInitialVelocity.height,
+ StaticPrefs::layout_css_scroll_behavior_spring_constant(),
+ StaticPrefs::layout_css_scroll_behavior_damping_ratio()),
+ mRange(aRange),
+ mStartPosition(aInitialPosition),
+ mLastRefreshTime(aStartTime),
+ mCallee(nullptr),
+ mOneDevicePixelInAppUnits(aPresContext->DevPixelsToAppUnits(1)),
+ mSnapTargetIds(std::move(aSnapTargetIds)),
+ mTriggeredByScript(aTriggeredByScript) {
+ Telemetry::SetHistogramRecordingEnabled(
+ Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(AsyncSmoothMSDScroll, override)
+
+ nsSize GetVelocity() {
+ // In nscoords per second
+ return nsSize(mXAxisModel.GetVelocity(), mYAxisModel.GetVelocity());
+ }
+
+ nsPoint GetPosition() {
+ // In nscoords
+ return nsPoint(NSToCoordRound(mXAxisModel.GetPosition()),
+ NSToCoordRound(mYAxisModel.GetPosition()));
+ }
+
+ void SetDestination(const nsPoint& aDestination,
+ ScrollTriggeredByScript aTriggeredByScript) {
+ mXAxisModel.SetDestination(static_cast<int32_t>(aDestination.x));
+ mYAxisModel.SetDestination(static_cast<int32_t>(aDestination.y));
+ mTriggeredByScript = aTriggeredByScript;
+ }
+
+ void SetRange(const nsRect& aRange) { mRange = aRange; }
+
+ nsRect GetRange() { return mRange; }
+
+ nsPoint GetStartPosition() { return mStartPosition; }
+
+ void Simulate(const TimeDuration& aDeltaTime) {
+ mXAxisModel.Simulate(aDeltaTime);
+ mYAxisModel.Simulate(aDeltaTime);
+
+ nsPoint desired = GetPosition();
+ nsPoint clamped = mRange.ClampPoint(desired);
+ if (desired.x != clamped.x) {
+ // The scroll has hit the "wall" at the left or right edge of the allowed
+ // scroll range.
+ // Absorb the impact to avoid bounceback effect.
+ mXAxisModel.SetVelocity(0.0);
+ mXAxisModel.SetPosition(clamped.x);
+ }
+
+ if (desired.y != clamped.y) {
+ // The scroll has hit the "wall" at the left or right edge of the allowed
+ // scroll range.
+ // Absorb the impact to avoid bounceback effect.
+ mYAxisModel.SetVelocity(0.0);
+ mYAxisModel.SetPosition(clamped.y);
+ }
+ }
+
+ bool IsFinished() {
+ return mXAxisModel.IsFinished(mOneDevicePixelInAppUnits) &&
+ mYAxisModel.IsFinished(mOneDevicePixelInAppUnits);
+ }
+
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override {
+ mozilla::TimeDuration deltaTime = aTime - mLastRefreshTime;
+ mLastRefreshTime = aTime;
+
+ // The callback may release "this".
+ // We don't access members after returning, so no need for KungFuDeathGrip.
+ nsHTMLScrollFrame::AsyncSmoothMSDScrollCallback(mCallee, deltaTime);
+ }
+
+ /*
+ * Set a refresh observer for smooth scroll iterations (and start observing).
+ * Should be used at most once during the lifetime of this object.
+ */
+ void SetRefreshObserver(nsHTMLScrollFrame* aCallee) {
+ NS_ASSERTION(aCallee && !mCallee,
+ "AsyncSmoothMSDScroll::SetRefreshObserver - Invalid usage.");
+
+ RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style,
+ "Smooth scroll (MSD) animation");
+ mCallee = aCallee;
+ }
+
+ /**
+ * The mCallee holds a strong ref to us since the refresh driver doesn't.
+ * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
+ * whichever comes first removes us from the refresh driver.
+ */
+ void RemoveObserver() {
+ if (mCallee) {
+ RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
+ mCallee = nullptr;
+ }
+ }
+
+ UniquePtr<ScrollSnapTargetIds> TakeSnapTargetIds() {
+ return std::move(mSnapTargetIds);
+ }
+
+ bool WasTriggeredByScript() const {
+ return mTriggeredByScript == ScrollTriggeredByScript::Yes;
+ }
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~AsyncSmoothMSDScroll() {
+ RemoveObserver();
+ Telemetry::SetHistogramRecordingEnabled(
+ Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
+ }
+
+ nsRefreshDriver* RefreshDriver(nsHTMLScrollFrame* aCallee) {
+ return aCallee->PresContext()->RefreshDriver();
+ }
+
+ mozilla::layers::AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
+ nsRect mRange;
+ nsPoint mStartPosition;
+ mozilla::TimeStamp mLastRefreshTime;
+ nsHTMLScrollFrame* mCallee;
+ nscoord mOneDevicePixelInAppUnits;
+ UniquePtr<ScrollSnapTargetIds> mSnapTargetIds;
+ ScrollTriggeredByScript mTriggeredByScript;
+};
+
+// AsyncScroll has ref counting.
+class nsHTMLScrollFrame::AsyncScroll final : public nsARefreshObserver {
+ public:
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::TimeDuration TimeDuration;
+
+ explicit AsyncScroll(UniquePtr<ScrollSnapTargetIds> aSnapTargetIds,
+ ScrollTriggeredByScript aTriggeredByScript)
+ : mOrigin(ScrollOrigin::NotSpecified),
+ mCallee(nullptr),
+ mSnapTargetIds(std::move(aSnapTargetIds)),
+ mTriggeredByScript(aTriggeredByScript) {
+ Telemetry::SetHistogramRecordingEnabled(
+ Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, true);
+ }
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~AsyncScroll() {
+ RemoveObserver();
+ Telemetry::SetHistogramRecordingEnabled(
+ Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, false);
+ }
+
+ public:
+ void InitSmoothScroll(TimeStamp aTime, nsPoint aInitialPosition,
+ nsPoint aDestination, ScrollOrigin aOrigin,
+ const nsRect& aRange, const nsSize& aCurrentVelocity);
+ void Init(nsPoint aInitialPosition, const nsRect& aRange) {
+ mAnimationPhysics = nullptr;
+ mRange = aRange;
+ mStartPosition = aInitialPosition;
+ }
+
+ bool IsSmoothScroll() { return mAnimationPhysics != nullptr; }
+
+ bool IsFinished(const TimeStamp& aTime) const {
+ MOZ_RELEASE_ASSERT(mAnimationPhysics);
+ return mAnimationPhysics->IsFinished(aTime);
+ }
+
+ nsPoint PositionAt(const TimeStamp& aTime) const {
+ MOZ_RELEASE_ASSERT(mAnimationPhysics);
+ return mAnimationPhysics->PositionAt(aTime);
+ }
+
+ nsSize VelocityAt(const TimeStamp& aTime) const {
+ MOZ_RELEASE_ASSERT(mAnimationPhysics);
+ return mAnimationPhysics->VelocityAt(aTime);
+ }
+
+ nsPoint GetStartPosition() const { return mStartPosition; }
+
+ // Most recent scroll origin.
+ ScrollOrigin mOrigin;
+
+ // Allowed destination positions around mDestination
+ nsRect mRange;
+
+ // Initial position where the async scroll was triggered.
+ nsPoint mStartPosition;
+
+ private:
+ void InitPreferences(TimeStamp aTime, nsAtom* aOrigin);
+
+ UniquePtr<ScrollAnimationPhysics> mAnimationPhysics;
+
+ // The next section is observer/callback management
+ // Bodies of WillRefresh and RefreshDriver contain nsHTMLScrollFrame specific
+ // code.
+ public:
+ NS_INLINE_DECL_REFCOUNTING(AsyncScroll, override)
+
+ /*
+ * Set a refresh observer for smooth scroll iterations (and start observing).
+ * Should be used at most once during the lifetime of this object.
+ */
+ void SetRefreshObserver(nsHTMLScrollFrame* aCallee) {
+ NS_ASSERTION(aCallee && !mCallee,
+ "AsyncScroll::SetRefreshObserver - Invalid usage.");
+
+ RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style,
+ "Smooth scroll animation");
+ mCallee = aCallee;
+ auto* presShell = mCallee->PresShell();
+ MOZ_ASSERT(presShell);
+ presShell->SuppressDisplayport(true);
+ }
+
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override {
+ // The callback may release "this".
+ // We don't access members after returning, so no need for KungFuDeathGrip.
+ nsHTMLScrollFrame::AsyncScrollCallback(mCallee, aTime);
+ }
+
+ /**
+ * The mCallee holds a strong ref to us since the refresh driver doesn't.
+ * Our dtor and mCallee's Destroy() method both call RemoveObserver() -
+ * whichever comes first removes us from the refresh driver.
+ */
+ void RemoveObserver() {
+ if (mCallee) {
+ RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style);
+ auto* presShell = mCallee->PresShell();
+ MOZ_ASSERT(presShell);
+ presShell->SuppressDisplayport(false);
+ mCallee = nullptr;
+ }
+ }
+
+ UniquePtr<ScrollSnapTargetIds> TakeSnapTargetIds() {
+ return std::move(mSnapTargetIds);
+ }
+
+ bool WasTriggeredByScript() const {
+ return mTriggeredByScript == ScrollTriggeredByScript::Yes;
+ }
+
+ private:
+ nsHTMLScrollFrame* mCallee;
+ UniquePtr<ScrollSnapTargetIds> mSnapTargetIds;
+ ScrollTriggeredByScript mTriggeredByScript;
+
+ nsRefreshDriver* RefreshDriver(nsHTMLScrollFrame* aCallee) {
+ return aCallee->PresContext()->RefreshDriver();
+ }
+};
+
+void nsHTMLScrollFrame::AsyncScroll::InitSmoothScroll(
+ TimeStamp aTime, nsPoint aInitialPosition, nsPoint aDestination,
+ ScrollOrigin aOrigin, const nsRect& aRange,
+ const nsSize& aCurrentVelocity) {
+ switch (aOrigin) {
+ case ScrollOrigin::NotSpecified:
+ case ScrollOrigin::Restore:
+ case ScrollOrigin::Relative:
+ // We don't have special prefs for "restore", just treat it as "other".
+ // "restore" scrolls are (for now) always instant anyway so unless
+ // something changes we should never have aOrigin ==
+ // ScrollOrigin::Restore here.
+ aOrigin = ScrollOrigin::Other;
+ break;
+ case ScrollOrigin::Apz:
+ // Likewise we should never get APZ-triggered scrolls here, and if that
+ // changes something is likely broken somewhere.
+ MOZ_ASSERT_UNREACHABLE(
+ "APZ scroll position updates should never be smooth");
+ break;
+ case ScrollOrigin::AnchorAdjustment:
+ MOZ_ASSERT_UNREACHABLE(
+ "scroll anchor adjustments should never be smooth");
+ break;
+ default:
+ break;
+ };
+
+ // Read preferences only on first iteration or for a different event origin.
+ if (!mAnimationPhysics || aOrigin != mOrigin) {
+ mOrigin = aOrigin;
+ if (StaticPrefs::general_smoothScroll_msdPhysics_enabled()) {
+ mAnimationPhysics =
+ MakeUnique<ScrollAnimationMSDPhysics>(aInitialPosition);
+ } else {
+ ScrollAnimationBezierPhysicsSettings settings =
+ layers::apz::ComputeBezierAnimationSettingsForOrigin(mOrigin);
+ mAnimationPhysics =
+ MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, settings);
+ }
+ }
+
+ mStartPosition = aInitialPosition;
+ mRange = aRange;
+
+ mAnimationPhysics->Update(aTime, aDestination, aCurrentVelocity);
+}
+
+/*
+ * Callback function from AsyncSmoothMSDScroll, used in
+ * nsHTMLScrollFrame::ScrollTo
+ */
+void nsHTMLScrollFrame::AsyncSmoothMSDScrollCallback(
+ nsHTMLScrollFrame* aInstance, mozilla::TimeDuration aDeltaTime) {
+ NS_ASSERTION(aInstance != nullptr, "aInstance must not be null");
+ NS_ASSERTION(aInstance->mAsyncSmoothMSDScroll,
+ "Did not expect AsyncSmoothMSDScrollCallback without an active "
+ "MSD scroll.");
+
+ nsRect range = aInstance->mAsyncSmoothMSDScroll->GetRange();
+ aInstance->mAsyncSmoothMSDScroll->Simulate(aDeltaTime);
+
+ if (!aInstance->mAsyncSmoothMSDScroll->IsFinished()) {
+ nsPoint destination = aInstance->mAsyncSmoothMSDScroll->GetPosition();
+ // Allow this scroll operation to land on any pixel boundary within the
+ // allowed scroll range for this frame.
+ // If the MSD is under-dampened or the destination is changed rapidly,
+ // it is expected (and desired) that the scrolling may overshoot.
+ nsRect intermediateRange = nsRect(destination, nsSize()).UnionEdges(range);
+ aInstance->ScrollToImpl(destination, intermediateRange);
+ // 'aInstance' might be destroyed here
+ return;
+ }
+
+ aInstance->CompleteAsyncScroll(
+ aInstance->mAsyncSmoothMSDScroll->GetStartPosition(), range,
+ aInstance->mAsyncSmoothMSDScroll->TakeSnapTargetIds());
+}
+
+/*
+ * Callback function from AsyncScroll, used in nsHTMLScrollFrame::ScrollTo
+ */
+void nsHTMLScrollFrame::AsyncScrollCallback(nsHTMLScrollFrame* aInstance,
+ mozilla::TimeStamp aTime) {
+ MOZ_ASSERT(aInstance != nullptr, "aInstance must not be null");
+ MOZ_ASSERT(
+ aInstance->mAsyncScroll,
+ "Did not expect AsyncScrollCallback without an active async scroll.");
+
+ if (!aInstance || !aInstance->mAsyncScroll) {
+ return; // XXX wallpaper bug 1107353 for now.
+ }
+
+ nsRect range = aInstance->mAsyncScroll->mRange;
+ if (aInstance->mAsyncScroll->IsSmoothScroll()) {
+ if (!aInstance->mAsyncScroll->IsFinished(aTime)) {
+ nsPoint destination = aInstance->mAsyncScroll->PositionAt(aTime);
+ // Allow this scroll operation to land on any pixel boundary between the
+ // current position and the final allowed range. (We don't want
+ // intermediate steps to be more constrained than the final step!)
+ nsRect intermediateRange =
+ nsRect(aInstance->GetScrollPosition(), nsSize()).UnionEdges(range);
+ aInstance->ScrollToImpl(destination, intermediateRange);
+ // 'aInstance' might be destroyed here
+ return;
+ }
+ }
+
+ aInstance->CompleteAsyncScroll(aInstance->mAsyncScroll->GetStartPosition(),
+ range,
+ aInstance->mAsyncScroll->TakeSnapTargetIds());
+}
+
+void nsHTMLScrollFrame::SetTransformingByAPZ(bool aTransforming) {
+ if (mTransformingByAPZ && !aTransforming) {
+ PostScrollEndEvent();
+ }
+ mTransformingByAPZ = aTransforming;
+ if (!mozilla::css::TextOverflow::HasClippedTextOverflow(this) ||
+ mozilla::css::TextOverflow::HasBlockEllipsis(mScrolledFrame)) {
+ // If the block has some overflow marker stuff we should kick off a paint
+ // because we have special behaviour for it when APZ scrolling is active.
+ SchedulePaint();
+ }
+}
+
+void nsHTMLScrollFrame::CompleteAsyncScroll(
+ const nsPoint& aStartPosition, const nsRect& aRange,
+ UniquePtr<ScrollSnapTargetIds> aSnapTargetIds, ScrollOrigin aOrigin) {
+ SetLastSnapTargetIds(std::move(aSnapTargetIds));
+
+ bool scrollPositionChanged = mDestination != aStartPosition;
+ bool isNotHandledByApz =
+ nsLayoutUtils::CanScrollOriginClobberApz(aOrigin) ||
+ ScrollAnimationState().contains(AnimationState::MainThread);
+
+ // Apply desired destination range since this is the last step of scrolling.
+ RemoveObservers();
+ AutoWeakFrame weakFrame(this);
+ ScrollToImpl(mDestination, aRange, aOrigin);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ // We are done scrolling, set our destination to wherever we actually ended
+ // up scrolling to.
+ mDestination = GetScrollPosition();
+ // Post a `scrollend` event for scrolling not handled by APZ, including:
+ //
+ // - programmatic instant scrolls
+ // - the end of a smooth scroll animation running on the main thread
+ //
+ // For scrolling handled by APZ, the `scrollend` event is posted in
+ // SetTransformingByAPZ() when the APZC is transitioning from a transforming
+ // to a non-transforming state (e.g. a transition from PANNING to NOTHING).
+ // The scrollend event should not be fired for a scroll that does not
+ // result in a scroll position change.
+ if (isNotHandledByApz && scrollPositionChanged) {
+ PostScrollEndEvent();
+ }
+}
+
+bool nsHTMLScrollFrame::HasBgAttachmentLocal() const {
+ const nsStyleBackground* bg = StyleBackground();
+ return bg->HasLocalBackground();
+}
+
+void nsHTMLScrollFrame::ScrollToInternal(
+ nsPoint aScrollPosition, ScrollMode aMode, ScrollOrigin aOrigin,
+ const nsRect* aRange, ScrollSnapFlags aSnapFlags,
+ ScrollTriggeredByScript aTriggeredByScript) {
+ if (aOrigin == ScrollOrigin::NotSpecified) {
+ aOrigin = ScrollOrigin::Other;
+ }
+ ScrollToWithOrigin(
+ aScrollPosition, aRange,
+ ScrollOperationParams{aMode, aOrigin, aSnapFlags, aTriggeredByScript});
+}
+
+void nsHTMLScrollFrame::ScrollToCSSPixels(const CSSIntPoint& aScrollPosition,
+ ScrollMode aMode) {
+ CSSIntPoint currentCSSPixels = GetRoundedScrollPositionCSSPixels();
+ // Transmogrify this scroll to a relative one if there's any on-going
+ // animation in APZ triggered by __user__.
+ // Bug 1740164: We will apply it for cases there's no animation in APZ.
+
+ auto scrollAnimationState = ScrollAnimationState();
+ bool isScrollAnimating =
+ scrollAnimationState.contains(AnimationState::MainThread) ||
+ scrollAnimationState.contains(AnimationState::APZPending) ||
+ scrollAnimationState.contains(AnimationState::APZRequested);
+ if (mCurrentAPZScrollAnimationType ==
+ APZScrollAnimationType::TriggeredByUserInput &&
+ !isScrollAnimating) {
+ CSSIntPoint delta = aScrollPosition - currentCSSPixels;
+ // This transmogrification need to be an intended end position scroll
+ // operation.
+ ScrollByCSSPixelsInternal(delta, aMode,
+ ScrollSnapFlags::IntendedEndPosition);
+ return;
+ }
+
+ nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
+ nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
+ nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1,
+ 2 * halfPixel - 1);
+ // XXX I don't think the following blocks are needed anymore, now that
+ // ScrollToImpl simply tries to scroll an integer number of layer
+ // pixels from the current position
+ nsPoint current = GetScrollPosition();
+ if (currentCSSPixels.x == aScrollPosition.x) {
+ pt.x = current.x;
+ range.x = pt.x;
+ range.width = 0;
+ }
+ if (currentCSSPixels.y == aScrollPosition.y) {
+ pt.y = current.y;
+ range.y = pt.y;
+ range.height = 0;
+ }
+ ScrollToWithOrigin(
+ pt, &range,
+ ScrollOperationParams{
+ aMode, ScrollOrigin::Other,
+ // This ScrollToCSSPixels is used for Element.scrollTo,
+ // Element.scrollTop, Element.scrollLeft and for Window.scrollTo.
+ ScrollSnapFlags::IntendedEndPosition, ScrollTriggeredByScript::Yes});
+ // 'this' might be destroyed here
+}
+
+void nsHTMLScrollFrame::ScrollToCSSPixelsForApz(
+ const CSSPoint& aScrollPosition, ScrollSnapTargetIds&& aLastSnapTargetIds) {
+ nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
+ nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000);
+ nsRect range(pt.x - halfRange, pt.y - halfRange, 2 * halfRange - 1,
+ 2 * halfRange - 1);
+ ScrollToWithOrigin(
+ pt, &range,
+ ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Apz,
+ std::move(aLastSnapTargetIds)});
+ // 'this' might be destroyed here
+}
+
+CSSIntPoint nsHTMLScrollFrame::GetRoundedScrollPositionCSSPixels() {
+ return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition());
+}
+
+/*
+ * this method wraps calls to ScrollToImpl(), either in one shot or
+ * incrementally, based on the setting of the smoothness scroll pref
+ */
+void nsHTMLScrollFrame::ScrollToWithOrigin(nsPoint aScrollPosition,
+ const nsRect* aRange,
+ ScrollOperationParams&& aParams) {
+ // None is never a valid scroll origin to be passed in.
+ MOZ_ASSERT(aParams.mOrigin != ScrollOrigin::None);
+
+ if (aParams.mOrigin != ScrollOrigin::Restore) {
+ // If we're doing a non-restore scroll, we don't want to later
+ // override it by restoring our saved scroll position.
+ SCROLLRESTORE_LOG("%p: Clearing mRestorePos (cur=%s, dst=%s)\n", this,
+ ToString(GetScrollPosition()).c_str(),
+ ToString(aScrollPosition).c_str());
+ mRestorePos.x = mRestorePos.y = -1;
+ }
+
+ Maybe<SnapDestination> snapDestination;
+ if (!aParams.IsScrollSnapDisabled()) {
+ snapDestination = GetSnapPointForDestination(ScrollUnit::DEVICE_PIXELS,
+ aParams.mSnapFlags,
+ mDestination, aScrollPosition);
+ if (snapDestination) {
+ aScrollPosition = snapDestination->mPosition;
+ }
+ }
+
+ nsRect scrollRange = GetLayoutScrollRange();
+ mDestination = scrollRange.ClampPoint(aScrollPosition);
+ if (mDestination != aScrollPosition &&
+ aParams.mOrigin == ScrollOrigin::Restore &&
+ GetPageLoadingState() != LoadingState::Loading) {
+ // If we're doing a restore but the scroll position is clamped, promote
+ // the origin from one that APZ can clobber to one that it can't clobber.
+ aParams.mOrigin = ScrollOrigin::Other;
+ }
+
+ nsRect range = aRange && snapDestination.isNothing()
+ ? *aRange
+ : nsRect(aScrollPosition, nsSize(0, 0));
+
+ UniquePtr<ScrollSnapTargetIds> snapTargetIds;
+ if (snapDestination) {
+ snapTargetIds =
+ MakeUnique<ScrollSnapTargetIds>(std::move(snapDestination->mTargetIds));
+ } else {
+ snapTargetIds =
+ MakeUnique<ScrollSnapTargetIds>(std::move(aParams.mTargetIds));
+ }
+ if (aParams.IsInstant()) {
+ // Asynchronous scrolling is not allowed, so we'll kill any existing
+ // async-scrolling process and do an instant scroll.
+ CompleteAsyncScroll(GetScrollPosition(), range, std::move(snapTargetIds),
+ aParams.mOrigin);
+ mApzSmoothScrollDestination = Nothing();
+ return;
+ }
+
+ if (!aParams.IsSmoothMsd()) {
+ // If we get a non-smooth-scroll, reset the cached APZ scroll destination,
+ // so that we know to process the next smooth-scroll destined for APZ.
+ mApzSmoothScrollDestination = Nothing();
+ }
+
+ nsPresContext* presContext = PresContext();
+ TimeStamp now =
+ presContext->RefreshDriver()->IsTestControllingRefreshesEnabled()
+ ? presContext->RefreshDriver()->MostRecentRefresh()
+ : TimeStamp::Now();
+
+ nsSize currentVelocity(0, 0);
+
+ const bool canHandoffToApz =
+ nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll() &&
+ CanApzScrollInTheseDirections(
+ DirectionsInDelta(mDestination - GetScrollPosition()));
+
+ if (aParams.IsSmoothMsd()) {
+ mIgnoreMomentumScroll = true;
+ if (!mAsyncSmoothMSDScroll) {
+ nsPoint sv = mVelocityQueue.GetVelocity();
+ currentVelocity.width = sv.x;
+ currentVelocity.height = sv.y;
+ if (mAsyncScroll) {
+ if (mAsyncScroll->IsSmoothScroll()) {
+ currentVelocity = mAsyncScroll->VelocityAt(now);
+ }
+ mAsyncScroll = nullptr;
+ }
+
+ if (canHandoffToApz) {
+ ApzSmoothScrollTo(mDestination, ScrollMode::SmoothMsd, aParams.mOrigin,
+ aParams.mTriggeredByScript, std::move(snapTargetIds));
+ return;
+ }
+
+ mAsyncSmoothMSDScroll = new AsyncSmoothMSDScroll(
+ GetScrollPosition(), mDestination, currentVelocity,
+ GetLayoutScrollRange(), now, presContext, std::move(snapTargetIds),
+ aParams.mTriggeredByScript);
+
+ mAsyncSmoothMSDScroll->SetRefreshObserver(this);
+ } else {
+ // A previous smooth MSD scroll is still in progress, so we just need to
+ // update its range and destination.
+ mAsyncSmoothMSDScroll->SetRange(GetLayoutScrollRange());
+ mAsyncSmoothMSDScroll->SetDestination(mDestination,
+ aParams.mTriggeredByScript);
+ }
+
+ return;
+ }
+
+ if (mAsyncSmoothMSDScroll) {
+ currentVelocity = mAsyncSmoothMSDScroll->GetVelocity();
+ mAsyncSmoothMSDScroll = nullptr;
+ }
+
+ const bool isSmoothScroll =
+ aParams.IsSmooth() && nsLayoutUtils::IsSmoothScrollingEnabled();
+ if (!mAsyncScroll) {
+ if (isSmoothScroll && canHandoffToApz) {
+ ApzSmoothScrollTo(mDestination, ScrollMode::Smooth, aParams.mOrigin,
+ aParams.mTriggeredByScript, std::move(snapTargetIds));
+ return;
+ }
+
+ mAsyncScroll =
+ new AsyncScroll(std::move(snapTargetIds), aParams.mTriggeredByScript);
+ mAsyncScroll->SetRefreshObserver(this);
+ }
+
+ if (isSmoothScroll) {
+ mAsyncScroll->InitSmoothScroll(now, GetScrollPosition(), mDestination,
+ aParams.mOrigin, range, currentVelocity);
+ } else {
+ mAsyncScroll->Init(GetScrollPosition(), range);
+ }
+}
+
+// We can't use nsContainerFrame::PositionChildViews here because
+// we don't want to invalidate views that have moved.
+static void AdjustViews(nsIFrame* aFrame) {
+ nsView* view = aFrame->GetView();
+ if (view) {
+ nsPoint pt;
+ aFrame->GetParent()->GetClosestView(&pt);
+ pt += aFrame->GetPosition();
+ view->SetPosition(pt.x, pt.y);
+
+ return;
+ }
+
+ if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
+ return;
+ }
+
+ // Call AdjustViews recursively for all child frames except the popup list as
+ // the views for popups are not scrolled.
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ if (listID == FrameChildListID::Popup) {
+ continue;
+ }
+ for (nsIFrame* child : list) {
+ AdjustViews(child);
+ }
+ }
+}
+
+void nsHTMLScrollFrame::MarkScrollbarsDirtyForReflow() const {
+ auto* presShell = PresShell();
+ if (mVScrollbarBox) {
+ presShell->FrameNeedsReflow(mVScrollbarBox,
+ IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+ }
+ if (mHScrollbarBox) {
+ presShell->FrameNeedsReflow(mHScrollbarBox,
+ IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+ }
+}
+
+void nsHTMLScrollFrame::InvalidateScrollbars() const {
+ if (mHScrollbarBox) {
+ mHScrollbarBox->InvalidateFrameSubtree();
+ }
+ if (mVScrollbarBox) {
+ mVScrollbarBox->InvalidateFrameSubtree();
+ }
+}
+
+bool nsHTMLScrollFrame::IsAlwaysActive() const {
+ if (nsDisplayItem::ForceActiveLayers()) {
+ return true;
+ }
+
+ // Unless this is the root scrollframe for a non-chrome document
+ // which is the direct child of a chrome document, we default to not
+ // being "active".
+ if (!(mIsRoot && PresContext()->IsRootContentDocumentCrossProcess())) {
+ return false;
+ }
+
+ // If we have scrolled before, then we should stay active.
+ if (mHasBeenScrolled) {
+ return true;
+ }
+
+ // If we're overflow:hidden, then start as inactive until
+ // we get scrolled manually.
+ ScrollStyles styles = GetScrollStyles();
+ return (styles.mHorizontal != StyleOverflow::Hidden &&
+ styles.mVertical != StyleOverflow::Hidden);
+}
+
+static void RemoveDisplayPortCallback(nsITimer* aTimer, void* aClosure) {
+ nsHTMLScrollFrame* sf = static_cast<nsHTMLScrollFrame*>(aClosure);
+
+ // This function only ever gets called from the expiry timer, so it must
+ // be non-null here. Set it to null here so that we don't keep resetting
+ // it unnecessarily in MarkRecentlyScrolled().
+ MOZ_ASSERT(sf->mDisplayPortExpiryTimer);
+ sf->mDisplayPortExpiryTimer = nullptr;
+
+ if (!sf->AllowDisplayPortExpiration() || sf->mIsParentToActiveScrollFrames) {
+ // If this is a scroll parent for some other scrollable frame, don't
+ // expire the displayport because it would break scroll handoff. Once the
+ // descendant scrollframes have their displayports expired, they will
+ // trigger the displayport expiration on this scrollframe as well, and
+ // mIsParentToActiveScrollFrames will presumably be false when that kicks
+ // in.
+ return;
+ }
+
+ // Remove the displayport from this scrollframe if it's been a while
+ // since it's scrolled, except if it needs to be always active. Note that
+ // there is one scrollframe that doesn't fall under this general rule, and
+ // that is the one that nsLayoutUtils::MaybeCreateDisplayPort decides to put
+ // a displayport on (i.e. the first scrollframe that WantAsyncScroll()s).
+ // If that scrollframe is this one, we remove the displayport anyway, and
+ // as part of the next paint MaybeCreateDisplayPort will put another
+ // displayport back on it. Although the displayport will "flicker" off and
+ // back on, the layer itself should never disappear, because this all
+ // happens between actual painting. If the displayport is reset to a
+ // different position that's ok; this scrollframe hasn't been scrolled
+ // recently and so the reset should be correct.
+
+ nsIContent* content = sf->GetContent();
+
+ if (nsHTMLScrollFrame::ShouldActivateAllScrollFrames()) {
+ // If we are activating all scroll frames then we only want to remove the
+ // regular display port and downgrade to a minimal display port.
+ MOZ_ASSERT(!content->GetProperty(nsGkAtoms::MinimalDisplayPort));
+ content->SetProperty(nsGkAtoms::MinimalDisplayPort,
+ reinterpret_cast<void*>(true));
+ } else {
+ content->RemoveProperty(nsGkAtoms::MinimalDisplayPort);
+ DisplayPortUtils::RemoveDisplayPort(content);
+ // Be conservative and unflag this this scrollframe as being scrollable by
+ // APZ. If it is still scrollable this will get flipped back soon enough.
+ sf->mScrollableByAPZ = false;
+ }
+
+ DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(sf);
+ sf->SchedulePaint();
+}
+
+void nsHTMLScrollFrame::MarkEverScrolled() {
+ // Mark this frame as having been scrolled. If this is the root
+ // scroll frame of a content document, then IsAlwaysActive()
+ // will return true from now on and MarkNotRecentlyScrolled() won't
+ // have any effect.
+ mHasBeenScrolled = true;
+}
+
+void nsHTMLScrollFrame::MarkNotRecentlyScrolled() {
+ if (!mHasBeenScrolledRecently) return;
+
+ mHasBeenScrolledRecently = false;
+ SchedulePaint();
+}
+
+void nsHTMLScrollFrame::MarkRecentlyScrolled() {
+ mHasBeenScrolledRecently = true;
+ if (IsAlwaysActive()) {
+ return;
+ }
+
+ if (mActivityExpirationState.IsTracked()) {
+ gScrollFrameActivityTracker->MarkUsed(this);
+ } else {
+ if (!gScrollFrameActivityTracker) {
+ gScrollFrameActivityTracker =
+ new ScrollFrameActivityTracker(GetMainThreadSerialEventTarget());
+ }
+ gScrollFrameActivityTracker->AddObject(this);
+ }
+
+ // If we just scrolled and there's a displayport expiry timer in place,
+ // reset the timer.
+ ResetDisplayPortExpiryTimer();
+}
+
+void nsHTMLScrollFrame::ResetDisplayPortExpiryTimer() {
+ if (mDisplayPortExpiryTimer) {
+ mDisplayPortExpiryTimer->InitWithNamedFuncCallback(
+ RemoveDisplayPortCallback, this,
+ StaticPrefs::apz_displayport_expiry_ms(), nsITimer::TYPE_ONE_SHOT,
+ "nsHTMLScrollFrame::ResetDisplayPortExpiryTimer");
+ }
+}
+
+bool nsHTMLScrollFrame::AllowDisplayPortExpiration() {
+ if (IsAlwaysActive()) {
+ return false;
+ }
+
+ if (mIsRoot && PresContext()->IsRoot()) {
+ return false;
+ }
+
+ // If this was the first scrollable frame found, this displayport should
+ // not expire.
+ if (IsFirstScrollableFrameSequenceNumber().isSome()) {
+ return false;
+ }
+
+ if (ShouldActivateAllScrollFrames() &&
+ GetContent()->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
+ return false;
+ }
+ return true;
+}
+
+void nsHTMLScrollFrame::TriggerDisplayPortExpiration() {
+ if (!AllowDisplayPortExpiration()) {
+ return;
+ }
+
+ if (!StaticPrefs::apz_displayport_expiry_ms()) {
+ // a zero time disables the expiry
+ return;
+ }
+
+ if (!mDisplayPortExpiryTimer) {
+ mDisplayPortExpiryTimer = NS_NewTimer();
+ }
+ ResetDisplayPortExpiryTimer();
+}
+
+void nsHTMLScrollFrame::ScrollVisual() {
+ MarkEverScrolled();
+
+ AdjustViews(mScrolledFrame);
+ // We need to call this after fixing up the view positions
+ // to be consistent with the frame hierarchy.
+ MarkRecentlyScrolled();
+}
+
+/**
+ * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper]
+ * to [aBoundLower, aBoundUpper] and then select the appunit value from among
+ * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) *
+ * aRes/aAppUnitsPerPixel is an integer (or as close as we can get
+ * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and
+ * closest to aDesired. If no such value exists, return the nearest in
+ * [aDestLower, aDestUpper].
+ */
+static nscoord ClampAndAlignWithPixels(nscoord aDesired, nscoord aBoundLower,
+ nscoord aBoundUpper, nscoord aDestLower,
+ nscoord aDestUpper,
+ nscoord aAppUnitsPerPixel, double aRes,
+ nscoord aCurrent) {
+ // Intersect scroll range with allowed range, by clamping the ends
+ // of aRange to be within bounds
+ nscoord destLower = clamped(aDestLower, aBoundLower, aBoundUpper);
+ nscoord destUpper = clamped(aDestUpper, aBoundLower, aBoundUpper);
+
+ nscoord desired = clamped(aDesired, destLower, destUpper);
+ if (StaticPrefs::layout_scroll_disable_pixel_alignment()) {
+ return desired;
+ }
+
+ double currentLayerVal = (aRes * aCurrent) / aAppUnitsPerPixel;
+ double desiredLayerVal = (aRes * desired) / aAppUnitsPerPixel;
+ double delta = desiredLayerVal - currentLayerVal;
+ double nearestLayerVal = NS_round(delta) + currentLayerVal;
+
+ // Convert back from PaintedLayer space to appunits relative to the top-left
+ // of the scrolled frame.
+ nscoord aligned =
+ aRes == 0.0
+ ? 0.0
+ : NSToCoordRoundWithClamp(nearestLayerVal * aAppUnitsPerPixel / aRes);
+
+ // Use a bound if it is within the allowed range and closer to desired than
+ // the nearest pixel-aligned value.
+ if (aBoundUpper == destUpper &&
+ static_cast<decltype(Abs(desired))>(aBoundUpper - desired) <
+ Abs(desired - aligned)) {
+ return aBoundUpper;
+ }
+
+ if (aBoundLower == destLower &&
+ static_cast<decltype(Abs(desired))>(desired - aBoundLower) <
+ Abs(aligned - desired)) {
+ return aBoundLower;
+ }
+
+ // Accept the nearest pixel-aligned value if it is within the allowed range.
+ if (aligned >= destLower && aligned <= destUpper) {
+ return aligned;
+ }
+
+ // Check if opposite pixel boundary fits into allowed range.
+ double oppositeLayerVal =
+ nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0);
+ nscoord opposite = aRes == 0.0
+ ? 0.0
+ : NSToCoordRoundWithClamp(oppositeLayerVal *
+ aAppUnitsPerPixel / aRes);
+ if (opposite >= destLower && opposite <= destUpper) {
+ return opposite;
+ }
+
+ // No alignment available.
+ return desired;
+}
+
+/**
+ * Clamp desired scroll position aPt to aBounds and then snap
+ * it to the same layer pixel edges as aCurrent, keeping it within aRange
+ * during snapping. aCurrent is the current scroll position.
+ */
+static nsPoint ClampAndAlignWithLayerPixels(const nsPoint& aPt,
+ const nsRect& aBounds,
+ const nsRect& aRange,
+ const nsPoint& aCurrent,
+ nscoord aAppUnitsPerPixel,
+ const MatrixScales& aScale) {
+ return nsPoint(
+ ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(), aRange.x,
+ aRange.XMost(), aAppUnitsPerPixel, aScale.xScale,
+ aCurrent.x),
+ ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(), aRange.y,
+ aRange.YMost(), aAppUnitsPerPixel, aScale.yScale,
+ aCurrent.y));
+}
+
+/* static */
+void nsHTMLScrollFrame::ScrollActivityCallback(nsITimer* aTimer,
+ void* anInstance) {
+ nsHTMLScrollFrame* self = static_cast<nsHTMLScrollFrame*>(anInstance);
+
+ // Fire the synth mouse move.
+ self->mScrollActivityTimer->Cancel();
+ self->mScrollActivityTimer = nullptr;
+ self->PresShell()->SynthesizeMouseMove(true);
+}
+
+void nsHTMLScrollFrame::ScheduleSyntheticMouseMove() {
+ if (!mScrollActivityTimer) {
+ mScrollActivityTimer = NS_NewTimer(GetMainThreadSerialEventTarget());
+ if (!mScrollActivityTimer) {
+ return;
+ }
+ }
+
+ mScrollActivityTimer->InitWithNamedFuncCallback(
+ ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT,
+ "nsHTMLScrollFrame::ScheduleSyntheticMouseMove");
+}
+
+void nsHTMLScrollFrame::NotifyApproximateFrameVisibilityUpdate(
+ bool aIgnoreDisplayPort) {
+ mLastUpdateFramesPos = GetScrollPosition();
+ if (aIgnoreDisplayPort) {
+ mHadDisplayPortAtLastFrameUpdate = false;
+ mDisplayPortAtLastFrameUpdate = nsRect();
+ } else {
+ mHadDisplayPortAtLastFrameUpdate = DisplayPortUtils::GetDisplayPort(
+ GetContent(), &mDisplayPortAtLastFrameUpdate);
+ }
+}
+
+bool nsHTMLScrollFrame::GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
+ nsRect* aDisplayPort) {
+ if (mHadDisplayPortAtLastFrameUpdate) {
+ *aDisplayPort = mDisplayPortAtLastFrameUpdate;
+ }
+ return mHadDisplayPortAtLastFrameUpdate;
+}
+
+/* aIncludeCSSTransform controls if we include CSS transforms that are in this
+ * process (the BrowserChild EffectsInfo mTransformToAncestorScale will include
+ * CSS transforms in ancestor processes in all cases). */
+MatrixScales GetPaintedLayerScaleForFrame(nsIFrame* aFrame,
+ bool aIncludeCSSTransform) {
+ MOZ_ASSERT(aFrame, "need a frame");
+
+ nsPresContext* presCtx = aFrame->PresContext()->GetRootPresContext();
+
+ if (!presCtx) {
+ presCtx = aFrame->PresContext();
+ MOZ_ASSERT(presCtx);
+ }
+
+ ParentLayerToScreenScale2D transformToAncestorScale;
+ if (aIncludeCSSTransform) {
+ transformToAncestorScale =
+ nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
+ aFrame);
+ } else {
+ if (BrowserChild* browserChild =
+ BrowserChild::GetFrom(aFrame->PresShell())) {
+ transformToAncestorScale =
+ browserChild->GetEffectsInfo().mTransformToAncestorScale;
+ }
+ }
+ transformToAncestorScale =
+ ParentLayerToParentLayerScale(
+ presCtx->PresShell()->GetCumulativeResolution()) *
+ transformToAncestorScale;
+
+ return transformToAncestorScale.ToUnknownScale();
+}
+
+void nsHTMLScrollFrame::ScrollToImpl(
+ nsPoint aPt, const nsRect& aRange, ScrollOrigin aOrigin,
+ ScrollTriggeredByScript aTriggeredByScript) {
+ // None is never a valid scroll origin to be passed in.
+ MOZ_ASSERT(aOrigin != ScrollOrigin::None);
+
+ // Figure out the effective origin for this scroll request.
+ if (aOrigin == ScrollOrigin::NotSpecified) {
+ // If no origin was specified, we still want to set it to something that's
+ // non-unknown, so that we can use eUnknown to distinguish if the frame was
+ // scrolled at all. Default it to some generic placeholder.
+ aOrigin = ScrollOrigin::Other;
+ }
+
+ // If this scroll is |relative|, but we've already had a user scroll that
+ // was not relative, promote this origin to |other|. This ensures that we
+ // may only transmit a relative update to APZ if all scrolls since the last
+ // transaction or repaint request have been relative.
+ if (aOrigin == ScrollOrigin::Relative &&
+ (mLastScrollOrigin != ScrollOrigin::None &&
+ mLastScrollOrigin != ScrollOrigin::NotSpecified &&
+ mLastScrollOrigin != ScrollOrigin::Relative &&
+ mLastScrollOrigin != ScrollOrigin::AnchorAdjustment &&
+ mLastScrollOrigin != ScrollOrigin::Apz)) {
+ aOrigin = ScrollOrigin::Other;
+ }
+
+ // If the origin is a downgrade, and downgrades are allowed, process the
+ // downgrade even if we're going to early-exit because we're already at
+ // the correct scroll position. This ensures that if there wasn't a main-
+ // thread scroll update pending before a frame reconstruction (as indicated
+ // by mAllowScrollOriginDowngrade=true), then after the frame reconstruction
+ // the origin is downgraded to "restore" even if the layout scroll offset to
+ // be restored is (0,0) (which will take the early-exit below). This is
+ // important so that restoration of a *visual* scroll offset (which might be
+ // to something other than (0,0)) isn't clobbered.
+ bool isScrollOriginDowngrade =
+ nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) &&
+ !nsLayoutUtils::CanScrollOriginClobberApz(aOrigin);
+ bool allowScrollOriginChange =
+ mAllowScrollOriginDowngrade && isScrollOriginDowngrade;
+
+ if (allowScrollOriginChange) {
+ mLastScrollOrigin = aOrigin;
+ mAllowScrollOriginDowngrade = false;
+ }
+
+ nsPresContext* presContext = PresContext();
+ nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ // 'scale' is our estimate of the scale factor that will be applied
+ // when rendering the scrolled content to its own PaintedLayer.
+ MatrixScales scale = GetPaintedLayerScaleForFrame(
+ mScrolledFrame, /* aIncludeCSSTransform = */ true);
+ nsPoint curPos = GetScrollPosition();
+
+ // Try to align aPt with curPos so they have an integer number of layer
+ // pixels between them. This gives us the best chance of scrolling without
+ // having to invalidate due to changes in subpixel rendering.
+ // Note that when we actually draw into a PaintedLayer, the coordinates
+ // that get mapped onto the layer buffer pixels are from the display list,
+ // which are relative to the display root frame's top-left increasing down,
+ // whereas here our coordinates are scroll positions which increase upward
+ // and are relative to the scrollport top-left. This difference doesn't
+ // actually matter since all we are about is that there be an integer number
+ // of layer pixels between pt and curPos.
+ nsPoint pt = ClampAndAlignWithLayerPixels(aPt, GetLayoutScrollRange(), aRange,
+ curPos, appUnitsPerDevPixel, scale);
+ if (pt == curPos) {
+ // Even if we are bailing out due to no-op main-thread scroll position
+ // change, we might need to cancel an APZ smooth scroll that we already
+ // kicked off. It might be reasonable to eventually remove the
+ // mApzSmoothScrollDestination clause from this if statement, as that
+ // may simplify this a bit and should be fine from the APZ side.
+ if (mApzSmoothScrollDestination && aOrigin != ScrollOrigin::Clamp) {
+ if (aOrigin == ScrollOrigin::Relative) {
+ AppendScrollUpdate(ScrollPositionUpdate::NewRelativeScroll(
+ // Clamp |mApzScrollPos| here. See the comment for this clamping
+ // reason below NewRelativeScroll call.
+ GetLayoutScrollRange().ClampPoint(mApzScrollPos), pt));
+ mApzScrollPos = pt;
+ } else if (aOrigin != ScrollOrigin::Apz) {
+ ScrollOrigin origin =
+ (mAllowScrollOriginDowngrade || !isScrollOriginDowngrade)
+ ? aOrigin
+ : mLastScrollOrigin;
+ AppendScrollUpdate(ScrollPositionUpdate::NewScroll(origin, pt));
+ }
+ }
+ return;
+ }
+
+ // If we are scrolling the RCD-RSF, and a visual scroll update is pending,
+ // cancel it; otherwise, it will clobber this scroll.
+ if (IsRootScrollFrameOfDocument() &&
+ presContext->IsRootContentDocumentCrossProcess()) {
+ auto* ps = presContext->GetPresShell();
+ if (const auto& visualScrollUpdate = ps->GetPendingVisualScrollUpdate()) {
+ if (visualScrollUpdate->mVisualScrollOffset != aPt) {
+ // Only clobber if the scroll was originated by the main thread.
+ // Respect the priority of origins (an "eRestore" layout scroll should
+ // not clobber an "eMainThread" visual scroll.)
+ bool shouldClobber =
+ aOrigin == ScrollOrigin::Other ||
+ (aOrigin == ScrollOrigin::Restore &&
+ visualScrollUpdate->mUpdateType == FrameMetrics::eRestore);
+ if (shouldClobber) {
+ ps->AcknowledgePendingVisualScrollUpdate();
+ ps->ClearPendingVisualScrollUpdate();
+ }
+ }
+ }
+ }
+
+ bool needFrameVisibilityUpdate = mLastUpdateFramesPos == nsPoint(-1, -1);
+
+ nsPoint dist(std::abs(pt.x - mLastUpdateFramesPos.x),
+ std::abs(pt.y - mLastUpdateFramesPos.y));
+ nsSize visualViewportSize = GetVisualViewportSize();
+ nscoord horzAllowance = std::max(
+ visualViewportSize.width /
+ std::max(
+ StaticPrefs::
+ layout_framevisibility_amountscrollbeforeupdatehorizontal(),
+ 1),
+ AppUnitsPerCSSPixel());
+ nscoord vertAllowance = std::max(
+ visualViewportSize.height /
+ std::max(
+ StaticPrefs::
+ layout_framevisibility_amountscrollbeforeupdatevertical(),
+ 1),
+ AppUnitsPerCSSPixel());
+ if (dist.x >= horzAllowance || dist.y >= vertAllowance) {
+ needFrameVisibilityUpdate = true;
+ }
+
+ // notify the listeners.
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
+ }
+
+ nsRect oldDisplayPort;
+ nsIContent* content = GetContent();
+ DisplayPortUtils::GetDisplayPort(content, &oldDisplayPort);
+ oldDisplayPort.MoveBy(-mScrolledFrame->GetPosition());
+
+ // Update frame position for scrolling
+ mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
+
+ // If |mLastScrollOrigin| is already set to something that can clobber APZ's
+ // scroll offset, then we don't want to change it to something that can't.
+ // If we allowed this, then we could end up in a state where APZ ignores
+ // legitimate scroll offset updates because the origin has been masked by
+ // a later change within the same refresh driver tick.
+ allowScrollOriginChange =
+ (mAllowScrollOriginDowngrade || !isScrollOriginDowngrade);
+
+ if (allowScrollOriginChange) {
+ mLastScrollOrigin = aOrigin;
+ mAllowScrollOriginDowngrade = false;
+ }
+
+ if (aOrigin == ScrollOrigin::Relative) {
+ MOZ_ASSERT(!isScrollOriginDowngrade);
+ MOZ_ASSERT(mLastScrollOrigin == ScrollOrigin::Relative);
+ AppendScrollUpdate(ScrollPositionUpdate::NewRelativeScroll(
+ // It's possible that |mApzScrollPos| is no longer within the scroll
+ // range, we need to clamp it to the current scroll range, otherwise
+ // calculating a relative scroll distance from the outside point will
+ // result a point far from the desired point.
+ GetLayoutScrollRange().ClampPoint(mApzScrollPos), pt));
+ mApzScrollPos = pt;
+ } else if (aOrigin == ScrollOrigin::AnchorAdjustment) {
+ AppendScrollUpdate(ScrollPositionUpdate::NewMergeableScroll(aOrigin, pt));
+ mApzScrollPos = pt;
+ } else if (aOrigin != ScrollOrigin::Apz) {
+ AppendScrollUpdate(ScrollPositionUpdate::NewScroll(mLastScrollOrigin, pt));
+ }
+
+ if (mLastScrollOrigin == ScrollOrigin::Apz) {
+ mApzScrollPos = GetScrollPosition();
+ }
+
+ ScrollVisual();
+ mAnchor.UserScrolled();
+
+ // Only report user-triggered scrolling interactions
+ bool jsOnStack = nsContentUtils::GetCurrentJSContext() != nullptr;
+ bool scrollingToAnchor = ScrollingInteractionContext::IsScrollingToAnchor();
+ if (!jsOnStack && !scrollingToAnchor) {
+ nsPoint distanceScrolled(std::abs(pt.x - curPos.x),
+ std::abs(pt.y - curPos.y));
+ ScrollingMetrics::OnScrollingInteraction(
+ CSSPoint::FromAppUnits(distanceScrolled).Length());
+ }
+
+ bool schedulePaint = true;
+ if (nsLayoutUtils::AsyncPanZoomEnabled(this) &&
+ !nsLayoutUtils::ShouldDisableApzForElement(content) &&
+ !content->GetProperty(nsGkAtoms::MinimalDisplayPort) &&
+ StaticPrefs::apz_paint_skipping_enabled()) {
+ // If APZ is enabled with paint-skipping, there are certain conditions in
+ // which we can skip paints:
+ // 1) If APZ triggered this scroll, and the tile-aligned displayport is
+ // unchanged.
+ // 2) If non-APZ triggered this scroll, but we can handle it by just asking
+ // APZ to update the scroll position. Again we make this conditional on
+ // the tile-aligned displayport being unchanged.
+ // We do the displayport check first since it's common to all scenarios,
+ // and then if the displayport is unchanged, we check if APZ triggered,
+ // or can handle, this scroll. If so, we set schedulePaint to false and
+ // skip the paint.
+ // Because of bug 1264297, we also don't do paint-skipping for elements with
+ // perspective, because the displayport may not have captured everything
+ // that needs to be painted. So even if the final tile-aligned displayport
+ // is the same, we force a repaint for these elements. Bug 1254260 tracks
+ // fixing this properly.
+ nsRect displayPort;
+ bool usingDisplayPort =
+ DisplayPortUtils::GetDisplayPort(content, &displayPort);
+ displayPort.MoveBy(-mScrolledFrame->GetPosition());
+
+ PAINT_SKIP_LOG(
+ "New scrollpos %s usingDP %d dpEqual %d scrollableByApz "
+ "%d perspective %d bglocal %d filter %d\n",
+ ToString(CSSPoint::FromAppUnits(GetScrollPosition())).c_str(),
+ usingDisplayPort, displayPort.IsEqualEdges(oldDisplayPort),
+ mScrollableByAPZ, HasPerspective(), HasBgAttachmentLocal(),
+ mHasOutOfFlowContentInsideFilter);
+ if (usingDisplayPort && displayPort.IsEqualEdges(oldDisplayPort) &&
+ !HasPerspective() && !HasBgAttachmentLocal() &&
+ !mHasOutOfFlowContentInsideFilter) {
+ bool haveScrollLinkedEffects =
+ content->GetComposedDoc()->HasScrollLinkedEffect();
+ bool apzDisabled = haveScrollLinkedEffects &&
+ StaticPrefs::apz_disable_for_scroll_linked_effects();
+ if (!apzDisabled) {
+ if (LastScrollOrigin() == ScrollOrigin::Apz) {
+ schedulePaint = false;
+ PAINT_SKIP_LOG("Skipping due to APZ scroll\n");
+ } else if (mScrollableByAPZ) {
+ nsIWidget* widget = presContext->GetNearestWidget();
+ WindowRenderer* renderer =
+ widget ? widget->GetWindowRenderer() : nullptr;
+ if (renderer) {
+ mozilla::layers::ScrollableLayerGuid::ViewID id;
+ bool success = nsLayoutUtils::FindIDFor(content, &id);
+ MOZ_ASSERT(success); // we have a displayport, we better have an ID
+
+ // Schedule an empty transaction to carry over the scroll offset
+ // update, instead of a full transaction. This empty transaction
+ // might still get squashed into a full transaction if something
+ // happens to trigger one.
+ MOZ_ASSERT(!mScrollUpdates.IsEmpty());
+ success = renderer->AddPendingScrollUpdateForNextTransaction(
+ id, mScrollUpdates.LastElement());
+ if (success) {
+ schedulePaint = false;
+ SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
+ PAINT_SKIP_LOG(
+ "Skipping due to APZ-forwarded main-thread scroll\n");
+ } else {
+ PAINT_SKIP_LOG(
+ "Failed to set pending scroll update on layer manager\n");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // If the new scroll offset is going to clobber APZ's scroll offset, for
+ // the RCD-RSF this will have the effect of updating the visual viewport
+ // offset in a way that keeps the relative offset between the layout and
+ // visual viewports constant. This will cause APZ to send us a new visual
+ // viewport offset, but instead of waiting for that, just set the value
+ // we expect APZ will set ourselves, to minimize the chances of
+ // inconsistencies from querying a stale value.
+ if (mIsRoot && nsLayoutUtils::CanScrollOriginClobberApz(aOrigin)) {
+ AutoWeakFrame weakFrame(this);
+ AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame,
+ !schedulePaint);
+
+ nsPoint visualViewportOffset = curPos;
+ if (presContext->PresShell()->IsVisualViewportOffsetSet()) {
+ visualViewportOffset =
+ presContext->PresShell()->GetVisualViewportOffset();
+ }
+ nsPoint relativeOffset = visualViewportOffset - curPos;
+
+ presContext->PresShell()->SetVisualViewportOffset(pt + relativeOffset,
+ curPos);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+
+ if (schedulePaint) {
+ SchedulePaint();
+
+ if (needFrameVisibilityUpdate) {
+ presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+ }
+
+ if (ChildrenHavePerspective()) {
+ // The overflow areas of descendants may depend on the scroll position,
+ // so ensure they get updated.
+
+ // First we recompute the overflow areas of the transformed children
+ // that use the perspective. FinishAndStoreOverflow only calls this
+ // if the size changes, so we need to do it manually.
+ RecomputePerspectiveChildrenOverflow(this);
+
+ // Update the overflow for the scrolled frame to take any changes from the
+ // children into account.
+ mScrolledFrame->UpdateOverflow();
+
+ // Update the overflow for the outer so that we recompute scrollbars.
+ UpdateOverflow();
+ }
+
+ ScheduleSyntheticMouseMove();
+
+ nsAutoScriptBlocker scriptBlocker;
+ PresShell::AutoAssertNoFlush noFlush(*PresShell());
+
+ { // scope the AutoScrollbarRepaintSuppression
+ AutoWeakFrame weakFrame(this);
+ AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame,
+ !schedulePaint);
+ UpdateScrollbarPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+
+ presContext->RecordInteractionTime(
+ nsPresContext::InteractionType::ScrollInteraction, TimeStamp::Now());
+
+ PostScrollEvent();
+ // If this is a viewport scroll, this could affect the relative offset
+ // between layout and visual viewport, so we might have to fire a visual
+ // viewport scroll event as well.
+ if (mIsRoot) {
+ if (auto* window = nsGlobalWindowInner::Cast(
+ PresContext()->Document()->GetInnerWindow())) {
+ window->VisualViewport()->PostScrollEvent(
+ presContext->PresShell()->GetVisualViewportOffset(), curPos);
+ }
+ }
+
+ // Schedule the scroll-timelines linked to its scrollable frame.
+ // if `pt == curPos`, we early return, so the position must be changed at
+ // this moment. Therefore, we can schedule scroll animations directly.
+ ScheduleScrollAnimations();
+
+ // notify the listeners.
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
+ }
+
+ if (nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell()) {
+ docShell->NotifyScrollObservers();
+ }
+}
+
+// Finds the max z-index of the items in aList that meet the following
+// conditions
+// 1) have z-index auto or z-index >= 0, and
+// 2) aFrame is a proper ancestor of the item's frame.
+// Returns Nothing() if there is no such item.
+static Maybe<int32_t> MaxZIndexInListOfItemsContainedInFrame(
+ nsDisplayList* aList, nsIFrame* aFrame) {
+ Maybe<int32_t> maxZIndex = Nothing();
+ for (nsDisplayItem* item : *aList) {
+ int32_t zIndex = item->ZIndex();
+ if (zIndex < 0 ||
+ !nsLayoutUtils::IsProperAncestorFrame(aFrame, item->Frame())) {
+ continue;
+ }
+ if (!maxZIndex) {
+ maxZIndex = Some(zIndex);
+ } else {
+ maxZIndex = Some(std::max(maxZIndex.value(), zIndex));
+ }
+ }
+ return maxZIndex;
+}
+
+template <class T>
+static void AppendInternalItemToTop(const nsDisplayListSet& aLists, T* aItem,
+ const Maybe<int32_t>& aZIndex) {
+ if (aZIndex) {
+ aItem->SetOverrideZIndex(aZIndex.value());
+ aLists.PositionedDescendants()->AppendToTop(aItem);
+ } else {
+ aLists.Content()->AppendToTop(aItem);
+ }
+}
+
+static const uint32_t APPEND_OWN_LAYER = 0x1;
+static const uint32_t APPEND_POSITIONED = 0x2;
+static const uint32_t APPEND_SCROLLBAR_CONTAINER = 0x4;
+static const uint32_t APPEND_OVERLAY = 0x8;
+static const uint32_t APPEND_TOP = 0x10;
+
+static void AppendToTop(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists, nsDisplayList* aSource,
+ nsIFrame* aSourceFrame, nsIFrame* aScrollFrame,
+ uint32_t aFlags) {
+ if (aSource->IsEmpty()) {
+ return;
+ }
+
+ nsDisplayWrapList* newItem;
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+ if (aFlags & APPEND_OWN_LAYER) {
+ ScrollbarData scrollbarData;
+ if (aFlags & APPEND_SCROLLBAR_CONTAINER) {
+ scrollbarData = ScrollbarData::CreateForScrollbarContainer(
+ aBuilder->GetCurrentScrollbarDirection(),
+ aBuilder->GetCurrentScrollbarTarget());
+ // Direction should be set
+ MOZ_ASSERT(scrollbarData.mDirection.isSome());
+ }
+
+ newItem = MakeDisplayItemWithIndex<nsDisplayOwnLayer>(
+ aBuilder, aSourceFrame,
+ /* aIndex = */ nsDisplayOwnLayer::OwnLayerForScrollbar, aSource, asr,
+ nsDisplayOwnLayerFlags::None, scrollbarData, true, false);
+ } else {
+ // Build the wrap list with an index of 1, since the scrollbar frame itself
+ // might have already built an nsDisplayWrapList.
+ newItem = MakeDisplayItemWithIndex<nsDisplayWrapper>(
+ aBuilder, aSourceFrame, 1, aSource, asr, false);
+ }
+ if (!newItem) {
+ return;
+ }
+
+ if (aFlags & APPEND_POSITIONED) {
+ // We want overlay scrollbars to always be on top of the scrolled content,
+ // but we don't want them to unnecessarily cover overlapping elements from
+ // outside our scroll frame.
+ Maybe<int32_t> zIndex = Nothing();
+ if (aFlags & APPEND_TOP) {
+ zIndex = Some(INT32_MAX);
+ } else if (aFlags & APPEND_OVERLAY) {
+ zIndex = MaxZIndexInListOfItemsContainedInFrame(
+ aLists.PositionedDescendants(), aScrollFrame);
+ } else if (aSourceFrame->StylePosition()->mZIndex.IsInteger()) {
+ zIndex = Some(aSourceFrame->StylePosition()->mZIndex.integer._0);
+ }
+ AppendInternalItemToTop(aLists, newItem, zIndex);
+ } else {
+ aLists.BorderBackground()->AppendToTop(newItem);
+ }
+}
+
+struct HoveredStateComparator {
+ static bool Hovered(const nsIFrame* aFrame) {
+ return aFrame->GetContent()->IsElement() &&
+ aFrame->GetContent()->AsElement()->HasAttr(nsGkAtoms::hover);
+ }
+
+ bool Equals(nsIFrame* A, nsIFrame* B) const {
+ return Hovered(A) == Hovered(B);
+ }
+
+ bool LessThan(nsIFrame* A, nsIFrame* B) const {
+ return !Hovered(A) && Hovered(B);
+ }
+};
+
+void nsHTMLScrollFrame::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists,
+ bool aCreateLayer,
+ bool aPositioned) {
+ const bool overlayScrollbars = UsesOverlayScrollbars();
+
+ AutoTArray<nsIFrame*, 3> scrollParts;
+ for (nsIFrame* kid : PrincipalChildList()) {
+ if (kid == mScrolledFrame ||
+ (kid->IsAbsPosContainingBlock() || overlayScrollbars) != aPositioned) {
+ continue;
+ }
+
+ scrollParts.AppendElement(kid);
+ }
+ if (scrollParts.IsEmpty()) {
+ return;
+ }
+
+ // We can't check will-change budget during display list building phase.
+ // This means that we will build scroll bar layers for out of budget
+ // will-change: scroll position.
+ const mozilla::layers::ScrollableLayerGuid::ViewID scrollTargetId =
+ IsScrollingActive()
+ ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
+ : mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+
+ scrollParts.Sort(HoveredStateComparator());
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ // Don't let scrollparts extent outside our frame's border-box, if these are
+ // viewport scrollbars. They would create layerization problems. This wouldn't
+ // normally be an issue but themes can add overflow areas to scrollbar parts.
+ if (mIsRoot) {
+ nsRect scrollPartsClip(aBuilder->ToReferenceFrame(this),
+ TrueOuterSize(aBuilder));
+ clipState.ClipContentDescendants(scrollPartsClip);
+ }
+
+ for (uint32_t i = 0; i < scrollParts.Length(); ++i) {
+ MOZ_ASSERT(scrollParts[i]);
+ Maybe<ScrollDirection> scrollDirection;
+ uint32_t appendToTopFlags = 0;
+ if (scrollParts[i] == mVScrollbarBox) {
+ scrollDirection.emplace(ScrollDirection::eVertical);
+ appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
+ }
+ if (scrollParts[i] == mHScrollbarBox) {
+ MOZ_ASSERT(!scrollDirection.isSome());
+ scrollDirection.emplace(ScrollDirection::eHorizontal);
+ appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
+ }
+
+ // The display port doesn't necessarily include the scrollbars, so just
+ // include all of the scrollbars if we are in a RCD-RSF. We only do
+ // this for the root scrollframe of the root content document, which is
+ // zoomable, and where the scrollbar sizes are bounded by the widget.
+ const nsRect visible =
+ mIsRoot && PresContext()->IsRootContentDocumentCrossProcess()
+ ? scrollParts[i]->InkOverflowRectRelativeToParent()
+ : aBuilder->GetVisibleRect();
+ if (visible.IsEmpty()) {
+ continue;
+ }
+ const nsRect dirty =
+ mIsRoot && PresContext()->IsRootContentDocumentCrossProcess()
+ ? scrollParts[i]->InkOverflowRectRelativeToParent()
+ : aBuilder->GetDirtyRect();
+
+ // Always create layers for overlay scrollbars so that we don't create a
+ // giant layer covering the whole scrollport if both scrollbars are visible.
+ const bool isOverlayScrollbar =
+ scrollDirection.isSome() && overlayScrollbars;
+ const bool createLayer =
+ aCreateLayer || isOverlayScrollbar ||
+ StaticPrefs::layout_scrollbars_always_layerize_track();
+
+ nsDisplayListCollection partList(aBuilder);
+ {
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, this, visible, dirty);
+
+ nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter(
+ aBuilder, scrollTargetId, scrollDirection, createLayer);
+ BuildDisplayListForChild(
+ aBuilder, scrollParts[i], partList,
+ nsIFrame::DisplayChildFlag::ForceStackingContext);
+ }
+
+ // DisplayChildFlag::ForceStackingContext put everything into
+ // partList.PositionedDescendants().
+ if (partList.PositionedDescendants()->IsEmpty()) {
+ continue;
+ }
+
+ if (createLayer) {
+ appendToTopFlags |= APPEND_OWN_LAYER;
+ }
+ if (aPositioned) {
+ appendToTopFlags |= APPEND_POSITIONED;
+ }
+
+ if (isOverlayScrollbar || scrollParts[i] == mResizerBox) {
+ if (isOverlayScrollbar && mIsRoot) {
+ appendToTopFlags |= APPEND_TOP;
+ } else {
+ appendToTopFlags |= APPEND_OVERLAY;
+ aBuilder->SetDisablePartialUpdates(true);
+ }
+ }
+
+ {
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, scrollParts[i], visible + GetOffsetTo(scrollParts[i]),
+ dirty + GetOffsetTo(scrollParts[i]));
+ if (scrollParts[i]->IsTransformed()) {
+ nsPoint toOuterReferenceFrame;
+ const nsIFrame* outerReferenceFrame = aBuilder->FindReferenceFrameFor(
+ scrollParts[i]->GetParent(), &toOuterReferenceFrame);
+ toOuterReferenceFrame += scrollParts[i]->GetPosition();
+
+ buildingForChild.SetReferenceFrameAndCurrentOffset(
+ outerReferenceFrame, toOuterReferenceFrame);
+ }
+ nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter(
+ aBuilder, scrollTargetId, scrollDirection, createLayer);
+
+ ::AppendToTop(aBuilder, aLists, partList.PositionedDescendants(),
+ scrollParts[i], this, appendToTopFlags);
+ }
+ }
+}
+
+nsRect nsHTMLScrollFrame::ExpandRectToNearlyVisible(const nsRect& aRect) const {
+ // We don't want to expand a rect in a direction that we can't scroll, so we
+ // check the scroll range.
+ nsRect scrollRange = GetLayoutScrollRange();
+ nsPoint scrollPos = GetScrollPosition();
+ nsMargin expand(0, 0, 0, 0);
+
+ nscoord vertShift =
+ StaticPrefs::layout_framevisibility_numscrollportheights() * aRect.height;
+ if (scrollRange.y < scrollPos.y) {
+ expand.top = vertShift;
+ }
+ if (scrollPos.y < scrollRange.YMost()) {
+ expand.bottom = vertShift;
+ }
+
+ nscoord horzShift =
+ StaticPrefs::layout_framevisibility_numscrollportwidths() * aRect.width;
+ if (scrollRange.x < scrollPos.x) {
+ expand.left = horzShift;
+ }
+ if (scrollPos.x < scrollRange.XMost()) {
+ expand.right = horzShift;
+ }
+
+ nsRect rect = aRect;
+ rect.Inflate(expand);
+ return rect;
+}
+
+static bool ShouldBeClippedByFrame(nsIFrame* aClipFrame,
+ nsIFrame* aClippedFrame) {
+ return nsLayoutUtils::IsProperAncestorFrame(aClipFrame, aClippedFrame);
+}
+
+static void ClipItemsExceptCaret(
+ nsDisplayList* aList, nsDisplayListBuilder* aBuilder, nsIFrame* aClipFrame,
+ const DisplayItemClipChain* aExtraClip,
+ nsTHashMap<nsPtrHashKey<const DisplayItemClipChain>,
+ const DisplayItemClipChain*>& aCache) {
+ for (nsDisplayItem* i : *aList) {
+ if (!ShouldBeClippedByFrame(aClipFrame, i->Frame())) {
+ continue;
+ }
+
+ const DisplayItemType type = i->GetType();
+ if (type != DisplayItemType::TYPE_CARET &&
+ type != DisplayItemType::TYPE_CONTAINER) {
+ const DisplayItemClipChain* clip = i->GetClipChain();
+ const DisplayItemClipChain* intersection = nullptr;
+ if (aCache.Get(clip, &intersection)) {
+ i->SetClipChain(intersection, true);
+ } else {
+ i->IntersectClip(aBuilder, aExtraClip, true);
+ aCache.InsertOrUpdate(clip, i->GetClipChain());
+ }
+ }
+ nsDisplayList* children = i->GetSameCoordinateSystemChildren();
+ if (children) {
+ ClipItemsExceptCaret(children, aBuilder, aClipFrame, aExtraClip, aCache);
+ }
+ }
+}
+
+static void ClipListsExceptCaret(nsDisplayListCollection* aLists,
+ nsDisplayListBuilder* aBuilder,
+ nsIFrame* aClipFrame,
+ const DisplayItemClipChain* aExtraClip) {
+ nsTHashMap<nsPtrHashKey<const DisplayItemClipChain>,
+ const DisplayItemClipChain*>
+ cache;
+ ClipItemsExceptCaret(aLists->BorderBackground(), aBuilder, aClipFrame,
+ aExtraClip, cache);
+ ClipItemsExceptCaret(aLists->BlockBorderBackgrounds(), aBuilder, aClipFrame,
+ aExtraClip, cache);
+ ClipItemsExceptCaret(aLists->Floats(), aBuilder, aClipFrame, aExtraClip,
+ cache);
+ ClipItemsExceptCaret(aLists->PositionedDescendants(), aBuilder, aClipFrame,
+ aExtraClip, cache);
+ ClipItemsExceptCaret(aLists->Outlines(), aBuilder, aClipFrame, aExtraClip,
+ cache);
+ ClipItemsExceptCaret(aLists->Content(), aBuilder, aClipFrame, aExtraClip,
+ cache);
+}
+
+// This is similar to a "save-restore" RAII class for
+// DisplayListBuilder::ContainsBlendMode(), with a slight enhancement.
+// If this class is put on the stack and then unwound, the DL builder's
+// ContainsBlendMode flag will be effectively the same as if this class wasn't
+// put on the stack. However, if the CaptureContainsBlendMode method is called,
+// there will be a difference - the blend mode in the descendant display lists
+// will be "captured" and extracted.
+// The main goal here is to allow conditionally capturing the flag that
+// indicates whether or not a blend mode was encountered in the descendant part
+// of the display list.
+class MOZ_RAII AutoContainsBlendModeCapturer {
+ nsDisplayListBuilder& mBuilder;
+ bool mSavedContainsBlendMode;
+
+ public:
+ explicit AutoContainsBlendModeCapturer(nsDisplayListBuilder& aBuilder)
+ : mBuilder(aBuilder),
+ mSavedContainsBlendMode(aBuilder.ContainsBlendMode()) {
+ mBuilder.SetContainsBlendMode(false);
+ }
+
+ bool CaptureContainsBlendMode() {
+ // "Capture" the flag by extracting and clearing the ContainsBlendMode flag
+ // on the builder.
+ bool capturedBlendMode = mBuilder.ContainsBlendMode();
+ mBuilder.SetContainsBlendMode(false);
+ return capturedBlendMode;
+ }
+
+ ~AutoContainsBlendModeCapturer() {
+ // If CaptureContainsBlendMode() was called, the descendant blend mode was
+ // "captured" and so uncapturedContainsBlendMode will be false. If
+ // CaptureContainsBlendMode() wasn't called, then no capture occurred, and
+ // uncapturedContainsBlendMode may be true if there was a descendant blend
+ // mode. In that case, we set the flag on the DL builder so that we restore
+ // state to what it would have been without this RAII class on the stack.
+ bool uncapturedContainsBlendMode = mBuilder.ContainsBlendMode();
+ mBuilder.SetContainsBlendMode(mSavedContainsBlendMode ||
+ uncapturedContainsBlendMode);
+ }
+};
+
+void nsHTMLScrollFrame::MaybeCreateTopLayerAndWrapRootItems(
+ nsDisplayListBuilder* aBuilder, nsDisplayListCollection& aSet,
+ bool aCreateAsyncZoom,
+ AutoContainsBlendModeCapturer* aAsyncZoomBlendCapture,
+ const nsRect& aAsyncZoomClipRect, nscoord* aRadii) {
+ if (!mIsRoot) {
+ return;
+ }
+
+ // Create any required items for the 'top layer' and check if they'll be
+ // opaque over the entire area of the viewport. If they are, then we can
+ // skip building display items for the rest of the page.
+ if (ViewportFrame* viewport = do_QueryFrame(GetParent())) {
+ bool topLayerIsOpaque = false;
+ if (nsDisplayWrapList* topLayerWrapList =
+ viewport->BuildDisplayListForTopLayer(aBuilder,
+ &topLayerIsOpaque)) {
+ // If the top layer content is opaque, and we're the root content document
+ // in the process, we can drop the display items behind it. We only
+ // support doing this for the root content document in the process, since
+ // the top layer content might have fixed position items that have a
+ // scrolltarget referencing the APZ data for the document. APZ builds this
+ // data implicitly for the root content document in the process, but
+ // subdocuments etc need their display items to generate it, so we can't
+ // cull those.
+ if (topLayerIsOpaque && PresContext()->IsRootContentDocumentInProcess()) {
+ aSet.DeleteAll(aBuilder);
+ }
+ aSet.PositionedDescendants()->AppendToTop(topLayerWrapList);
+ }
+ }
+
+ nsDisplayList rootResultList(aBuilder);
+
+ bool serializedList = false;
+ auto SerializeList = [&] {
+ if (!serializedList) {
+ serializedList = true;
+ aSet.SerializeWithCorrectZOrder(&rootResultList, GetContent());
+ }
+ };
+
+ if (nsIFrame* rootStyleFrame = GetFrameForStyle()) {
+ bool usingBackdropFilter =
+ rootStyleFrame->StyleEffects()->HasBackdropFilters() &&
+ rootStyleFrame->IsVisibleForPainting();
+
+ if (rootStyleFrame->StyleEffects()->HasFilters() &&
+ !aBuilder->IsForGenerateGlyphMask()) {
+ SerializeList();
+ rootResultList.AppendNewToTop<nsDisplayFilters>(
+ aBuilder, this, &rootResultList, rootStyleFrame, usingBackdropFilter);
+ }
+
+ if (usingBackdropFilter) {
+ SerializeList();
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ nsRect backdropRect =
+ GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
+ rootResultList.AppendNewToTop<nsDisplayBackdropFilters>(
+ aBuilder, this, &rootResultList, backdropRect, rootStyleFrame);
+ }
+ }
+
+ if (aCreateAsyncZoom) {
+ MOZ_ASSERT(mIsRoot);
+
+ // Wrap all our scrolled contents in an nsDisplayAsyncZoom. This will be
+ // the layer that gets scaled for APZ zooming. It does not have the
+ // scrolled ASR, but it does have the composition bounds clip applied to
+ // it. The children have the layout viewport clip applied to them (above).
+ // Effectively we are double clipping to the viewport, at potentially
+ // different async scales.
+ SerializeList();
+
+ if (aAsyncZoomBlendCapture->CaptureContainsBlendMode()) {
+ // The async zoom contents contain a mix-blend mode, so let's wrap all
+ // those contents into a blend container, and then wrap the blend
+ // container in the async zoom container. Otherwise the blend container
+ // ends up outside the zoom container which results in blend failure for
+ // WebRender.
+ nsDisplayItem* blendContainer =
+ nsDisplayBlendContainer::CreateForMixBlendMode(
+ aBuilder, this, &rootResultList,
+ aBuilder->CurrentActiveScrolledRoot());
+ rootResultList.AppendToTop(blendContainer);
+
+ // Blend containers can be created or omitted during partial updates
+ // depending on the dirty rect. So we basically can't do partial updates
+ // if there's a blend container involved. There is equivalent code to this
+ // in the BuildDisplayListForStackingContext function as well, with a more
+ // detailed comment explaining things better.
+ if (aBuilder->IsRetainingDisplayList()) {
+ if (aBuilder->IsPartialUpdate()) {
+ aBuilder->SetPartialBuildFailed(true);
+ } else {
+ aBuilder->SetDisablePartialUpdates(true);
+ }
+ }
+ }
+
+ mozilla::layers::FrameMetrics::ViewID viewID =
+ nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent());
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ clipState.ClipContentDescendants(aAsyncZoomClipRect, aRadii);
+
+ rootResultList.AppendNewToTop<nsDisplayAsyncZoom>(
+ aBuilder, this, &rootResultList, aBuilder->CurrentActiveScrolledRoot(),
+ viewID);
+ }
+
+ if (serializedList) {
+ aSet.Content()->AppendToTop(&rootResultList);
+ }
+}
+
+void nsHTMLScrollFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ SetAndNullOnExit<const nsIFrame> tmpBuilder(
+ mReferenceFrameDuringPainting, aBuilder->GetCurrentReferenceFrame());
+ if (aBuilder->IsForFrameVisibility()) {
+ NotifyApproximateFrameVisibilityUpdate(false);
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ const bool isRootContent =
+ mIsRoot && PresContext()->IsRootContentDocumentCrossProcess();
+
+ nsRect effectiveScrollPort = mScrollPort;
+ if (isRootContent && PresContext()->HasDynamicToolbar()) {
+ // Expand the scroll port to the size including the area covered by dynamic
+ // toolbar in the case where the dynamic toolbar is being used since
+ // position:fixed elements attached to this root scroller might be taller
+ // than its scroll port (e.g 100vh). Even if the dynamic toolbar covers the
+ // taller area, it doesn't mean the area is clipped by the toolbar because
+ // the dynamic toolbar is laid out outside of our topmost window and it
+ // transitions without changing our topmost window size.
+ effectiveScrollPort.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ PresContext(), effectiveScrollPort.Size()));
+ }
+
+ // It's safe to get this value before the DecideScrollableLayer call below
+ // because that call cannot create a displayport for root scroll frames,
+ // and hence it cannot create an ignore scroll frame.
+ const bool ignoringThisScrollFrame = aBuilder->GetIgnoreScrollFrame() == this;
+
+ // Overflow clipping can never clip frames outside our subtree, so there
+ // is no need to worry about whether we are a moving frame that might clip
+ // non-moving frames.
+ // Not all our descendants will be clipped by overflow clipping, but all
+ // the ones that aren't clipped will be out of flow frames that have already
+ // had dirty rects saved for them by their parent frames calling
+ // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
+ // dirty rect here.
+ nsRect visibleRect = aBuilder->GetVisibleRect();
+ nsRect dirtyRect = aBuilder->GetDirtyRect();
+ if (!ignoringThisScrollFrame) {
+ visibleRect = visibleRect.Intersect(effectiveScrollPort);
+ dirtyRect = dirtyRect.Intersect(effectiveScrollPort);
+ }
+
+ bool dirtyRectHasBeenOverriden = false;
+ Unused << DecideScrollableLayer(aBuilder, &visibleRect, &dirtyRect,
+ /* aSetBase = */ !mIsRoot,
+ &dirtyRectHasBeenOverriden);
+
+ if (aBuilder->IsForFrameVisibility()) {
+ // We expand the dirty rect to catch frames just outside of the scroll port.
+ // We use the dirty rect instead of the whole scroll port to prevent
+ // too much expansion in the presence of very large (bigger than the
+ // viewport) scroll ports.
+ dirtyRect = ExpandRectToNearlyVisible(dirtyRect);
+ visibleRect = dirtyRect;
+ }
+
+ // We put non-overlay scrollbars in their own layers when this is the root
+ // scroll frame and we are a toplevel content document. In this situation,
+ // the scrollbar(s) would normally be assigned their own layer anyway, since
+ // they're not scrolled with the rest of the document. But when both
+ // scrollbars are visible, the layer's visible rectangle would be the size
+ // of the viewport, so most layer implementations would create a layer buffer
+ // that's much larger than necessary. Creating independent layers for each
+ // scrollbar works around the problem.
+ const bool createLayersForScrollbars = isRootContent;
+
+ nsDisplayListCollection set(aBuilder);
+
+ if (ignoringThisScrollFrame) {
+ // If we are a root scroll frame that has a display port we want to add
+ // scrollbars, they will be children of the scrollable layer, but they get
+ // adjusted by the APZC automatically.
+ bool addScrollBars =
+ mIsRoot && mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow();
+
+ if (addScrollBars) {
+ // Add classic scrollbars.
+ AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, false);
+ }
+
+ {
+ nsDisplayListBuilder::AutoBuildingDisplayList building(
+ aBuilder, this, visibleRect, dirtyRect);
+
+ // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
+ // The scrolled frame shouldn't have its own background/border, so we
+ // can just pass aLists directly.
+ BuildDisplayListForChild(aBuilder, mScrolledFrame, set);
+ }
+
+ MaybeCreateTopLayerAndWrapRootItems(aBuilder, set,
+ /* aCreateAsyncZoom = */ false, nullptr,
+ nsRect(), nullptr);
+
+ if (addScrollBars) {
+ // Add overlay scrollbars.
+ AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, true);
+ }
+
+ set.MoveTo(aLists);
+ return;
+ }
+
+ // Whether we might want to build a scrollable layer for this scroll frame
+ // at some point in the future. This controls whether we add the information
+ // to the layer tree (a scroll info layer if necessary, and add the right
+ // area to the dispatch to content layer event regions) necessary to activate
+ // a scroll frame so it creates a scrollable layer.
+ const bool couldBuildLayer = [&] {
+ if (!aBuilder->IsPaintingToWindow()) {
+ return false;
+ }
+ if (mWillBuildScrollableLayer) {
+ return true;
+ }
+ return StyleVisibility()->IsVisible() &&
+ nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll();
+ }();
+
+ // Now display the scrollbars and scrollcorner. These parts are drawn
+ // in the border-background layer, on top of our own background and
+ // borders and underneath borders and backgrounds of later elements
+ // in the tree.
+ // Note that this does not apply for overlay scrollbars; those are drawn
+ // in the positioned-elements layer on top of everything else by the call
+ // to AppendScrollPartsTo(..., true) further down.
+ AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, false);
+
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (aBuilder->IsForPainting() &&
+ disp->mWillChange.bits & StyleWillChangeBits::SCROLL) {
+ aBuilder->AddToWillChangeBudget(this, GetVisualViewportSize());
+ }
+
+ mScrollParentID = aBuilder->GetCurrentScrollParentId();
+
+ Maybe<nsRect> contentBoxClip;
+ Maybe<const DisplayItemClipChain*> extraContentBoxClipForNonCaretContent;
+ if (MOZ_UNLIKELY(
+ disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
+ disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox)) {
+ WritingMode wm = mScrolledFrame->GetWritingMode();
+ bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
+ : disp->mOverflowClipBoxInline) ==
+ StyleOverflowClipBox::ContentBox;
+ bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
+ : disp->mOverflowClipBoxBlock) ==
+ StyleOverflowClipBox::ContentBox;
+ // We only clip if there is *scrollable* overflow, to avoid clipping
+ // *ink* overflow unnecessarily.
+ nsRect clipRect = effectiveScrollPort + aBuilder->ToReferenceFrame(this);
+ nsMargin padding = GetUsedPadding();
+ if (!cbH) {
+ padding.left = padding.right = nscoord(0);
+ }
+ if (!cbV) {
+ padding.top = padding.bottom = nscoord(0);
+ }
+ clipRect.Deflate(padding);
+
+ nsRect so = mScrolledFrame->ScrollableOverflowRect();
+ if ((cbH && (clipRect.width != so.width || so.x < 0)) ||
+ (cbV && (clipRect.height != so.height || so.y < 0))) {
+ // The non-inflated clip needs to be set on all non-caret items.
+ // We prepare an extra DisplayItemClipChain here that will be intersected
+ // with those items after they've been created.
+ const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
+
+ DisplayItemClip newClip;
+ newClip.SetTo(clipRect);
+
+ const DisplayItemClipChain* extraClip =
+ aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
+
+ extraContentBoxClipForNonCaretContent = Some(extraClip);
+
+ nsIFrame* caretFrame = aBuilder->GetCaretFrame();
+ // Avoid clipping it in a zero-height line box (heuristic only).
+ if (caretFrame && caretFrame->GetRect().height != 0) {
+ nsRect caretRect = aBuilder->GetCaretRect();
+ // Allow the caret to stick out of the content box clip by half the
+ // caret height on the top, and its full width on the right.
+ nsRect inflatedClip = clipRect;
+ inflatedClip.Inflate(
+ nsMargin(caretRect.height / 2, caretRect.width, 0, 0));
+ contentBoxClip = Some(inflatedClip);
+ }
+ }
+ }
+
+ AutoContainsBlendModeCapturer blendCapture(*aBuilder);
+
+ const bool willBuildAsyncZoomContainer =
+ mWillBuildScrollableLayer && aBuilder->ShouldBuildAsyncZoomContainer() &&
+ isRootContent;
+
+ nsRect scrollPortClip =
+ effectiveScrollPort + aBuilder->ToReferenceFrame(this);
+ nsRect clipRect = scrollPortClip;
+ // Our override of GetBorderRadii ensures we never have a radius at
+ // the corners where we have a scrollbar.
+ nscoord radii[8];
+ const bool haveRadii = GetPaddingBoxBorderRadii(radii);
+ if (mIsRoot) {
+ clipRect.SizeTo(nsLayoutUtils::CalculateCompositionSizeForFrame(
+ this, true /* aSubtractScrollbars */,
+ nullptr /* aOverrideScrollPortSize */,
+ // With the dynamic toolbar, this CalculateCompositionSizeForFrame call
+ // basically expands the region being covered up by the dynamic toolbar,
+ // but if the root scroll container is not scrollable, e.g. the root
+ // element has `overflow: hidden` or `position: fixed`, the function
+ // doesn't expand the region since expanding the region in such cases
+ // will prevent the content from restoring zooming to 1.0 zoom level
+ // such as bug 1652190. That said, this `clipRect` which will be used
+ // for the async zoom container needs to be expanded because zoomed-in
+ // contents can be scrollable __visually__ so that the region under the
+ // dynamic toolbar area will be revealed.
+ nsLayoutUtils::IncludeDynamicToolbar::Force));
+
+ // The composition size is essentially in visual coordinates.
+ // If we are hit-testing in layout coordinates, transform the clip rect
+ // to layout coordinates to match.
+ if (aBuilder->IsRelativeToLayoutViewport() && isRootContent) {
+ clipRect = ViewportUtils::VisualToLayout(clipRect, PresShell());
+ }
+ }
+
+ {
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+
+ // If we're building an async zoom container, clip the contents inside
+ // to the layout viewport (scrollPortClip). The composition bounds clip
+ // (clipRect) will be applied to the zoom container itself in
+ // MaybeCreateTopLayerAndWrapRootItems.
+ nsRect clipRectForContents =
+ willBuildAsyncZoomContainer ? scrollPortClip : clipRect;
+ if (mIsRoot) {
+ clipState.ClipContentDescendants(clipRectForContents,
+ haveRadii ? radii : nullptr);
+ } else {
+ clipState.ClipContainingBlockDescendants(clipRectForContents,
+ haveRadii ? radii : nullptr);
+ }
+
+ Maybe<DisplayListClipState::AutoSaveRestore> contentBoxClipState;
+ if (contentBoxClip) {
+ contentBoxClipState.emplace(aBuilder);
+ if (mIsRoot) {
+ contentBoxClipState->ClipContentDescendants(*contentBoxClip);
+ } else {
+ contentBoxClipState->ClipContainingBlockDescendants(*contentBoxClip);
+ }
+ }
+
+ nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
+ aBuilder);
+
+ if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
+ // If this scroll frame has a first scrollable frame sequence number,
+ // ensure that it matches the current paint sequence number. If it does
+ // not, reset it so that we can expire the displayport. The stored
+ // sequence number will not match that of the current paint if the dom
+ // was mutated in some way that alters the order of scroll frames.
+ if (IsFirstScrollableFrameSequenceNumber().isSome() &&
+ *IsFirstScrollableFrameSequenceNumber() !=
+ nsDisplayListBuilder::GetPaintSequenceNumber()) {
+ SetIsFirstScrollableFrameSequenceNumber(Nothing());
+ }
+ asrSetter.EnterScrollFrame(this);
+ }
+
+ if (couldBuildLayer && mScrolledFrame->GetContent()) {
+ asrSetter.SetCurrentScrollParentId(
+ nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()));
+ }
+
+ if (mWillBuildScrollableLayer && aBuilder->BuildCompositorHitTestInfo()) {
+ // Create a hit test info item for the scrolled content that's not
+ // clipped to the displayport. This ensures that within the bounds
+ // of the scroll frame, the scrolled content is always hit, even
+ // if we are checkerboarding.
+ CompositorHitTestInfo info =
+ mScrolledFrame->GetCompositorHitTestInfo(aBuilder);
+
+ if (info != CompositorHitTestInvisibleToHit) {
+ auto* hitInfo =
+ MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>(
+ aBuilder, mScrolledFrame, 1);
+ if (hitInfo) {
+ aBuilder->SetCompositorHitTestInfo(info);
+ set.BorderBackground()->AppendToTop(hitInfo);
+ }
+ }
+ }
+
+ {
+ // Clip our contents to the unsnapped scrolled rect. This makes sure
+ // that we don't have display items over the subpixel seam at the edge
+ // of the scrolled area.
+ DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder);
+ nsRect scrolledRectClip =
+ GetUnsnappedScrolledRectInternal(
+ mScrolledFrame->ScrollableOverflowRect(), mScrollPort.Size()) +
+ mScrolledFrame->GetPosition();
+ bool clippedToDisplayPort = false;
+ if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
+ // Clip the contents to the display port.
+ // The dirty rect already acts kind of like a clip, in that
+ // FrameLayerBuilder intersects item bounds and opaque regions with
+ // it, but it doesn't have the consistent snapping behavior of a
+ // true clip.
+ // For a case where this makes a difference, imagine the following
+ // scenario: The display port has an edge that falls on a fractional
+ // layer pixel, and there's an opaque display item that covers the
+ // whole display port up until that fractional edge, and there is a
+ // transparent display item that overlaps the edge. We want to prevent
+ // this transparent item from enlarging the scrolled layer's visible
+ // region beyond its opaque region. The dirty rect doesn't do that -
+ // it gets rounded out, whereas a true clip gets rounded to nearest
+ // pixels.
+ // If there is no display port, we don't need this because the clip
+ // from the scroll port is still applied.
+ scrolledRectClip = scrolledRectClip.Intersect(visibleRect);
+ clippedToDisplayPort = scrolledRectClip.IsEqualEdges(visibleRect);
+ }
+ scrolledRectClipState.ClipContainingBlockDescendants(
+ scrolledRectClip + aBuilder->ToReferenceFrame(this));
+ if (clippedToDisplayPort) {
+ // We have to do this after the ClipContainingBlockDescendants call
+ // above, otherwise that call will clobber the flag set by this call
+ // to SetClippedToDisplayPort.
+ scrolledRectClipState.SetClippedToDisplayPort();
+ }
+
+ nsRect visibleRectForChildren = visibleRect;
+ nsRect dirtyRectForChildren = dirtyRect;
+
+ // If we are entering the RCD-RSF, we are crossing the async zoom
+ // container boundary, and need to convert from visual to layout
+ // coordinates.
+ if (willBuildAsyncZoomContainer && aBuilder->IsForEventDelivery()) {
+ MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(mScrolledFrame));
+ visibleRectForChildren =
+ ViewportUtils::VisualToLayout(visibleRectForChildren, PresShell());
+ dirtyRectForChildren =
+ ViewportUtils::VisualToLayout(dirtyRectForChildren, PresShell());
+ }
+
+ nsDisplayListBuilder::AutoBuildingDisplayList building(
+ aBuilder, this, visibleRectForChildren, dirtyRectForChildren);
+
+ BuildDisplayListForChild(aBuilder, mScrolledFrame, set);
+
+ if (dirtyRectHasBeenOverriden &&
+ StaticPrefs::layout_display_list_show_rebuild_area()) {
+ nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
+ aBuilder, this,
+ dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
+ NS_RGBA(0, 0, 255, 64), false);
+ if (color) {
+ color->SetOverrideZIndex(INT32_MAX);
+ set.PositionedDescendants()->AppendToTop(color);
+ }
+ }
+ }
+
+ if (extraContentBoxClipForNonCaretContent) {
+ // The items were built while the inflated content box clip was in
+ // effect, so that the caret wasn't clipped unnecessarily. We apply
+ // the non-inflated clip to the non-caret items now, by intersecting
+ // it with their existing clip.
+ ClipListsExceptCaret(&set, aBuilder, mScrolledFrame,
+ *extraContentBoxClipForNonCaretContent);
+ }
+
+ if (aBuilder->IsPaintingToWindow()) {
+ mIsParentToActiveScrollFrames =
+ ShouldActivateAllScrollFrames()
+ ? asrSetter.GetContainsNonMinimalDisplayPort()
+ : asrSetter.ShouldForceLayerForScrollParent();
+ }
+
+ if (asrSetter.ShouldForceLayerForScrollParent()) {
+ // Note that forcing layerization of scroll parents follows the scroll
+ // handoff chain which is subject to the out-of-flow-frames caveat noted
+ // above (where the asrSetter variable is created).
+ MOZ_ASSERT(couldBuildLayer && mScrolledFrame->GetContent() &&
+ aBuilder->IsPaintingToWindow());
+ if (!mWillBuildScrollableLayer) {
+ // Set a displayport so next paint we don't have to force layerization
+ // after the fact. It's ok to pass DoNotRepaint here, since we've
+ // already painted the change and we're just optimizing it to be
+ // detected earlier. We also won't confuse RetainedDisplayLists
+ // with the silent change, since we explicitly request partial updates
+ // to be disabled on the next paint.
+ DisplayPortUtils::SetDisplayPortMargins(
+ GetContent(), PresShell(), DisplayPortMargins::Empty(GetContent()),
+ DisplayPortUtils::ClearMinimalDisplayPortProperty::Yes, 0,
+ DisplayPortUtils::RepaintMode::DoNotRepaint);
+ // Call DecideScrollableLayer to recompute mWillBuildScrollableLayer
+ // and recompute the current animated geometry root if needed. It's
+ // too late to change the dirty rect so pass a copy.
+ nsRect copyOfDirtyRect = dirtyRect;
+ nsRect copyOfVisibleRect = visibleRect;
+ Unused << DecideScrollableLayer(aBuilder, &copyOfVisibleRect,
+ &copyOfDirtyRect,
+ /* aSetBase = */ false, nullptr);
+ if (mWillBuildScrollableLayer) {
+#ifndef MOZ_WIDGET_ANDROID
+ gfxCriticalNoteOnce << "inserted scroll frame";
+#endif
+ asrSetter.InsertScrollFrame(this);
+ aBuilder->SetDisablePartialUpdates(true);
+ }
+ }
+ }
+ }
+
+ if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
+ aBuilder->ForceLayerForScrollParent();
+ }
+
+ MaybeCreateTopLayerAndWrapRootItems(
+ aBuilder, set, willBuildAsyncZoomContainer, &blendCapture, clipRect,
+ haveRadii ? radii : nullptr);
+
+ // We want to call SetContainsNonMinimalDisplayPort if
+ // mWillBuildScrollableLayer is true for any reason other than having a
+ // minimal display port.
+ if (aBuilder->IsPaintingToWindow()) {
+ if (DisplayPortUtils::HasNonMinimalDisplayPort(GetContent()) ||
+ mZoomableByAPZ || nsContentUtils::HasScrollgrab(GetContent())) {
+ aBuilder->SetContainsNonMinimalDisplayPort();
+ }
+ }
+
+ if (couldBuildLayer) {
+ CompositorHitTestInfo info(CompositorHitTestFlags::eVisibleToHitTest,
+ CompositorHitTestFlags::eInactiveScrollframe);
+ // If the scroll frame has non-default overscroll-behavior, instruct
+ // APZ to require a target confirmation before processing events that
+ // hit this scroll frame (that is, to drop the events if a
+ // confirmation does not arrive within the timeout period). Otherwise,
+ // APZ's fallback behaviour of scrolling the enclosing scroll frame
+ // would violate the specified overscroll-behavior.
+ auto overscroll = GetOverscrollBehaviorInfo();
+ if (overscroll.mBehaviorX != OverscrollBehavior::Auto ||
+ overscroll.mBehaviorY != OverscrollBehavior::Auto) {
+ info += CompositorHitTestFlags::eRequiresTargetConfirmation;
+ }
+
+ nsRect area = effectiveScrollPort + aBuilder->ToReferenceFrame(this);
+
+ // Make sure that APZ will dispatch events back to content so we can
+ // create a displayport for this frame. We'll add the item later on.
+ if (!mWillBuildScrollableLayer && aBuilder->BuildCompositorHitTestInfo()) {
+ // Make sure the z-index of the inactive item is at least zero.
+ // Otherwise, it will end up behind non-positioned items in the scrolled
+ // content.
+ int32_t zIndex = MaxZIndexInListOfItemsContainedInFrame(
+ set.PositionedDescendants(), this)
+ .valueOr(0);
+ if (aBuilder->IsPartialUpdate()) {
+ for (nsDisplayItem* item : mScrolledFrame->DisplayItems()) {
+ if (item->GetType() ==
+ DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ auto* hitTestItem =
+ static_cast<nsDisplayCompositorHitTestInfo*>(item);
+ if (hitTestItem->GetHitTestInfo().Info().contains(
+ CompositorHitTestFlags::eInactiveScrollframe)) {
+ zIndex = std::max(zIndex, hitTestItem->ZIndex());
+ item->SetCantBeReused();
+ }
+ }
+ }
+ }
+ nsDisplayCompositorHitTestInfo* hitInfo =
+ MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>(
+ aBuilder, mScrolledFrame, 1, area, info);
+ if (hitInfo) {
+ AppendInternalItemToTop(set, hitInfo, Some(zIndex));
+ aBuilder->SetCompositorHitTestInfo(info);
+ }
+ }
+
+ if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
+ aBuilder->AppendNewScrollInfoItemForHoisting(
+ MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame,
+ this, info, area));
+ }
+ }
+
+ // Now display overlay scrollbars and the resizer, if we have one.
+ AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, true);
+
+ set.MoveTo(aLists);
+}
+
+nsRect nsHTMLScrollFrame::RestrictToRootDisplayPort(
+ const nsRect& aDisplayportBase) {
+ // This function clips aDisplayportBase so that it is no larger than the
+ // root frame's displayport (or the root composition bounds, if we can't
+ // obtain the root frame's displayport). This is useful for ensuring that
+ // the displayport of a tall scrollframe doesn't gobble up all the memory.
+
+ nsPresContext* pc = PresContext();
+ const nsPresContext* rootPresContext =
+ pc->GetInProcessRootContentDocumentPresContext();
+ if (!rootPresContext) {
+ rootPresContext = pc->GetRootPresContext();
+ }
+ if (!rootPresContext) {
+ return aDisplayportBase;
+ }
+ const mozilla::PresShell* const rootPresShell = rootPresContext->PresShell();
+ nsIFrame* rootFrame = rootPresShell->GetRootScrollFrame();
+ if (!rootFrame) {
+ rootFrame = rootPresShell->GetRootFrame();
+ }
+ if (!rootFrame) {
+ return aDisplayportBase;
+ }
+
+ // Make sure we aren't trying to restrict to our own displayport, which is a
+ // circular dependency.
+ MOZ_ASSERT(!mIsRoot || rootPresContext != pc);
+
+ nsRect rootDisplayPort;
+ bool hasDisplayPort =
+ rootFrame->GetContent() && DisplayPortUtils::GetDisplayPort(
+ rootFrame->GetContent(), &rootDisplayPort);
+ if (hasDisplayPort) {
+ // The display port of the root frame already factors in it's callback
+ // transform, so subtract it out here, the GetCumulativeApzCallbackTransform
+ // call below will add it back.
+ MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
+ ("RestrictToRootDisplayPort: Existing root displayport is %s\n",
+ ToString(rootDisplayPort).c_str()));
+ if (nsIContent* content = rootFrame->GetContent()) {
+ if (void* property =
+ content->GetProperty(nsGkAtoms::apzCallbackTransform)) {
+ rootDisplayPort -=
+ CSSPoint::ToAppUnits(*static_cast<CSSPoint*>(property));
+ }
+ }
+ } else {
+ // If we don't have a display port on the root frame let's fall back to
+ // the root composition bounds instead.
+ nsRect rootCompBounds =
+ nsRect(nsPoint(0, 0),
+ nsLayoutUtils::CalculateCompositionSizeForFrame(rootFrame));
+
+ // If rootFrame is the RCD-RSF then
+ // CalculateCompositionSizeForFrame did not take the document's
+ // resolution into account, so we must.
+ if (rootPresContext->IsRootContentDocumentCrossProcess() &&
+ rootFrame == rootPresShell->GetRootScrollFrame()) {
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Verbose,
+ ("RestrictToRootDisplayPort: Removing resolution %f from root "
+ "composition bounds %s\n",
+ rootPresShell->GetResolution(), ToString(rootCompBounds).c_str()));
+ rootCompBounds =
+ rootCompBounds.RemoveResolution(rootPresShell->GetResolution());
+ }
+
+ rootDisplayPort = rootCompBounds;
+ }
+ MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
+ ("RestrictToRootDisplayPort: Intermediate root displayport %s\n",
+ ToString(rootDisplayPort).c_str()));
+
+ // We want to convert the root display port from the
+ // coordinate space of |rootFrame| to the coordinate space of
+ // |this|. We do that with the TransformRect call below.
+ // However, since we care about the root display port
+ // relative to what the user is actually seeing, we also need to
+ // incorporate the APZ callback transforms into this. Most of the
+ // time those transforms are negligible, but in some cases (e.g.
+ // when a zoom is applied on an overflow:hidden document) it is
+ // not (see bug 1280013).
+ // XXX: Eventually we may want to create a modified version of
+ // TransformRect that includes the APZ callback transforms
+ // directly.
+ nsLayoutUtils::TransformRect(rootFrame, this, rootDisplayPort);
+ MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
+ ("RestrictToRootDisplayPort: Transformed root displayport %s\n",
+ ToString(rootDisplayPort).c_str()));
+ rootDisplayPort += CSSPoint::ToAppUnits(
+ nsLayoutUtils::GetCumulativeApzCallbackTransform(this));
+ MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
+ ("RestrictToRootDisplayPort: Final root displayport %s\n",
+ ToString(rootDisplayPort).c_str()));
+
+ // We want to limit aDisplayportBase to be no larger than
+ // rootDisplayPort on either axis, but we don't want to just
+ // blindly intersect the two, because rootDisplayPort might be
+ // offset from where aDisplayportBase is (see bug 1327095 comment
+ // 8). Instead, we translate rootDisplayPort so as to maximize the
+ // overlap with aDisplayportBase, and *then* do the intersection.
+ if (rootDisplayPort.x > aDisplayportBase.x &&
+ rootDisplayPort.XMost() > aDisplayportBase.XMost()) {
+ // rootDisplayPort is at a greater x-position for both left and
+ // right, so translate it such that the XMost() values are the
+ // same. This will line up the right edge of the two rects, and
+ // might mean that rootDisplayPort.x is smaller than
+ // aDisplayportBase.x. We can avoid that by taking the min of the
+ // x delta and XMost() delta, but it doesn't really matter
+ // because the intersection between the two rects below will end
+ // up the same.
+ rootDisplayPort.x -= (rootDisplayPort.XMost() - aDisplayportBase.XMost());
+ } else if (rootDisplayPort.x < aDisplayportBase.x &&
+ rootDisplayPort.XMost() < aDisplayportBase.XMost()) {
+ // Analaogous code for when the rootDisplayPort is at a smaller
+ // x-position.
+ rootDisplayPort.x = aDisplayportBase.x;
+ }
+ // Do the same for y-axis
+ if (rootDisplayPort.y > aDisplayportBase.y &&
+ rootDisplayPort.YMost() > aDisplayportBase.YMost()) {
+ rootDisplayPort.y -= (rootDisplayPort.YMost() - aDisplayportBase.YMost());
+ } else if (rootDisplayPort.y < aDisplayportBase.y &&
+ rootDisplayPort.YMost() < aDisplayportBase.YMost()) {
+ rootDisplayPort.y = aDisplayportBase.y;
+ }
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Verbose,
+ ("RestrictToRootDisplayPort: Root displayport translated to %s to "
+ "better enclose %s\n",
+ ToString(rootDisplayPort).c_str(), ToString(aDisplayportBase).c_str()));
+
+ // Now we can do the intersection
+ return aDisplayportBase.Intersect(rootDisplayPort);
+}
+
+/* static */ bool nsHTMLScrollFrame::ShouldActivateAllScrollFrames() {
+ return (StaticPrefs::apz_wr_activate_all_scroll_frames() ||
+ (StaticPrefs::apz_wr_activate_all_scroll_frames_when_fission() &&
+ FissionAutostart()));
+}
+
+bool nsHTMLScrollFrame::DecideScrollableLayer(
+ nsDisplayListBuilder* aBuilder, nsRect* aVisibleRect, nsRect* aDirtyRect,
+ bool aSetBase, bool* aDirtyRectHasBeenOverriden) {
+ nsIContent* content = GetContent();
+ bool hasDisplayPort = DisplayPortUtils::HasDisplayPort(content);
+ // For hit testing purposes with fission we want to create a
+ // minimal display port for every scroll frame that could be active. (We only
+ // do this when aSetBase is true because we only want to do this the first
+ // time this function is called for the same scroll frame.)
+ if (aSetBase && !hasDisplayPort && aBuilder->IsPaintingToWindow() &&
+ ShouldActivateAllScrollFrames() &&
+ nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll()) {
+ // SetDisplayPortMargins calls TriggerDisplayPortExpiration which starts a
+ // display port expiry timer for display ports that do expire. However
+ // minimal display ports do not expire, so the display port has to be
+ // marked before the SetDisplayPortMargins call so the expiry timer
+ // doesn't get started.
+ content->SetProperty(nsGkAtoms::MinimalDisplayPort,
+ reinterpret_cast<void*>(true));
+
+ DisplayPortUtils::SetDisplayPortMargins(
+ content, PresShell(), DisplayPortMargins::Empty(content),
+ DisplayPortUtils::ClearMinimalDisplayPortProperty::No, 0,
+ DisplayPortUtils::RepaintMode::DoNotRepaint);
+ hasDisplayPort = true;
+ }
+
+ if (aBuilder->IsPaintingToWindow()) {
+ if (aSetBase) {
+ nsRect displayportBase = *aVisibleRect;
+ nsPresContext* pc = PresContext();
+
+ bool isChromeRootDoc =
+ !pc->Document()->IsContentDocument() && !pc->GetParentPresContext();
+
+ if (mIsRoot &&
+ (pc->IsRootContentDocumentCrossProcess() || isChromeRootDoc)) {
+ displayportBase =
+ nsRect(nsPoint(0, 0),
+ nsLayoutUtils::CalculateCompositionSizeForFrame(this));
+ } else {
+ // Make the displayport base equal to the visible rect restricted to
+ // the scrollport and the root composition bounds, relative to the
+ // scrollport.
+ displayportBase = aVisibleRect->Intersect(mScrollPort);
+
+ mozilla::layers::ScrollableLayerGuid::ViewID viewID =
+ mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) {
+ nsLayoutUtils::FindIDFor(GetContent(), &viewID);
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Verbose,
+ ("Scroll id %" PRIu64 " has visible rect %s, scroll port %s\n",
+ viewID, ToString(*aVisibleRect).c_str(),
+ ToString(mScrollPort).c_str()));
+ }
+
+ // Only restrict to the root displayport bounds if necessary,
+ // as the required coordinate transformation is expensive.
+ // And don't call RestrictToRootDisplayPort if we would be trying to
+ // restrict to our own display port, which doesn't make sense (ie if we
+ // are a root scroll frame in a process root prescontext).
+ if (hasDisplayPort && (!mIsRoot || pc->GetParentPresContext()) &&
+ !DisplayPortUtils::WillUseEmptyDisplayPortMargins(content)) {
+ displayportBase = RestrictToRootDisplayPort(displayportBase);
+ MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
+ ("Scroll id %" PRIu64 " has restricted base %s\n", viewID,
+ ToString(displayportBase).c_str()));
+ }
+ displayportBase -= mScrollPort.TopLeft();
+ }
+
+ DisplayPortUtils::SetDisplayPortBase(GetContent(), displayportBase);
+ }
+
+ // If we don't have aSetBase == true then should have already
+ // been called with aSetBase == true which should have set a
+ // displayport base.
+ MOZ_ASSERT(content->GetProperty(nsGkAtoms::DisplayPortBase));
+ nsRect displayPort;
+ hasDisplayPort = DisplayPortUtils::GetDisplayPort(
+ content, &displayPort,
+ DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
+
+ auto OverrideDirtyRect = [&](const nsRect& aRect) {
+ *aDirtyRect = aRect;
+ if (aDirtyRectHasBeenOverriden) {
+ *aDirtyRectHasBeenOverriden = true;
+ }
+ };
+
+ if (hasDisplayPort) {
+ // Override the dirty rectangle if the displayport has been set.
+ *aVisibleRect = displayPort;
+ if (aBuilder->IsReusingStackingContextItems() ||
+ !aBuilder->IsPartialUpdate() || aBuilder->InInvalidSubtree() ||
+ IsFrameModified()) {
+ OverrideDirtyRect(displayPort);
+ } else if (HasOverrideDirtyRegion()) {
+ nsRect* rect = GetProperty(
+ nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
+ if (rect) {
+ OverrideDirtyRect(*rect);
+ }
+ }
+ } else if (mIsRoot) {
+ // The displayPort getter takes care of adjusting for resolution. So if
+ // we have resolution but no displayPort then we need to adjust for
+ // resolution here.
+ auto* presShell = PresShell();
+ *aVisibleRect =
+ aVisibleRect->RemoveResolution(presShell->GetResolution());
+ *aDirtyRect = aDirtyRect->RemoveResolution(presShell->GetResolution());
+ }
+ }
+
+ // Since making new layers is expensive, only create a scrollable layer
+ // for some scroll frames.
+ // When a displayport is being used, force building of a layer so that
+ // the compositor can find the scrollable layer for async scrolling.
+ // If the element is marked 'scrollgrab', also force building of a layer
+ // so that APZ can implement scroll grabbing.
+ mWillBuildScrollableLayer = hasDisplayPort ||
+ nsContentUtils::HasScrollgrab(content) ||
+ mZoomableByAPZ;
+ return mWillBuildScrollableLayer;
+}
+
+void nsHTMLScrollFrame::NotifyApzTransaction() {
+ mAllowScrollOriginDowngrade = true;
+ mApzScrollPos = GetScrollPosition();
+ mApzAnimationRequested = IsLastScrollUpdateAnimating();
+ mApzAnimationTriggeredByScriptRequested =
+ IsLastScrollUpdateTriggeredByScriptAnimating();
+ mScrollUpdates.Clear();
+ if (mIsRoot) {
+ PresShell()->SetResolutionUpdated(false);
+ }
+}
+
+Maybe<ScrollMetadata> nsHTMLScrollFrame::ComputeScrollMetadata(
+ WebRenderLayerManager* aLayerManager, const nsIFrame* aItemFrame,
+ const nsPoint& aOffsetToReferenceFrame) const {
+ if (!mWillBuildScrollableLayer) {
+ return Nothing();
+ }
+
+ bool isRootContent =
+ mIsRoot && PresContext()->IsRootContentDocumentCrossProcess();
+
+ MOZ_ASSERT(mScrolledFrame->GetContent());
+
+ return Some(nsLayoutUtils::ComputeScrollMetadata(
+ mScrolledFrame, this, GetContent(), aItemFrame, aOffsetToReferenceFrame,
+ aLayerManager, mScrollParentID, mScrollPort.Size(), isRootContent));
+}
+
+bool nsHTMLScrollFrame::IsRectNearlyVisible(const nsRect& aRect) const {
+ // Use the right rect depending on if a display port is set.
+ nsRect displayPort;
+ bool usingDisplayport = DisplayPortUtils::GetDisplayPort(
+ GetContent(), &displayPort,
+ DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
+
+ if (mIsRoot && !usingDisplayport &&
+ PresContext()->IsRootContentDocumentInProcess() &&
+ !PresContext()->IsRootContentDocumentCrossProcess()) {
+ // In the case of the root scroller of OOP iframes, there are cases where
+ // any display port value isn't set, e.g. the iframe element is out of view
+ // in the parent document. In such cases we'd consider the iframe is not
+ // visible.
+ return false;
+ }
+
+ return aRect.Intersects(
+ ExpandRectToNearlyVisible(usingDisplayport ? displayPort : mScrollPort));
+}
+
+OverscrollBehaviorInfo nsHTMLScrollFrame::GetOverscrollBehaviorInfo() const {
+ nsIFrame* frame = GetFrameForStyle();
+ if (!frame) {
+ return {};
+ }
+
+ auto& disp = *frame->StyleDisplay();
+ return OverscrollBehaviorInfo::FromStyleConstants(disp.mOverscrollBehaviorX,
+ disp.mOverscrollBehaviorY);
+}
+
+ScrollStyles nsHTMLScrollFrame::GetScrollStyles() const {
+ nsPresContext* presContext = PresContext();
+ if (!presContext->IsDynamic() &&
+ !(mIsRoot && presContext->HasPaginatedScrolling())) {
+ return ScrollStyles(StyleOverflow::Hidden, StyleOverflow::Hidden);
+ }
+
+ if (!mIsRoot) {
+ return ScrollStyles(*StyleDisplay(),
+ ScrollStyles::MapOverflowToValidScrollStyle);
+ }
+
+ ScrollStyles result = presContext->GetViewportScrollStylesOverride();
+ if (nsDocShell* ds = presContext->GetDocShell()) {
+ switch (ds->ScrollbarPreference()) {
+ case ScrollbarPreference::Auto:
+ break;
+ case ScrollbarPreference::Never:
+ result.mHorizontal = result.mVertical = StyleOverflow::Hidden;
+ break;
+ }
+ }
+ return result;
+}
+
+nsRect nsHTMLScrollFrame::GetLayoutScrollRange() const {
+ return GetScrollRange(mScrollPort.width, mScrollPort.height);
+}
+
+nsRect nsHTMLScrollFrame::GetScrollRange(nscoord aWidth,
+ nscoord aHeight) const {
+ nsRect range = GetScrolledRect();
+ range.width = std::max(range.width - aWidth, 0);
+ range.height = std::max(range.height - aHeight, 0);
+ return range;
+}
+
+nsRect nsHTMLScrollFrame::GetVisualScrollRange() const {
+ nsSize visualViewportSize = GetVisualViewportSize();
+ return GetScrollRange(visualViewportSize.width, visualViewportSize.height);
+}
+
+nsSize nsHTMLScrollFrame::GetVisualViewportSize() const {
+ auto* presShell = PresShell();
+ if (mIsRoot && presShell->IsVisualViewportSizeSet()) {
+ return presShell->GetVisualViewportSize();
+ }
+ return mScrollPort.Size();
+}
+
+nsPoint nsHTMLScrollFrame::GetVisualViewportOffset() const {
+ if (mIsRoot) {
+ auto* presShell = PresShell();
+ if (auto pendingUpdate = presShell->GetPendingVisualScrollUpdate()) {
+ // The pending visual scroll update on the PresShell contains a raw,
+ // unclamped offset (basically, whatever was passed to ScrollToVisual()).
+ // It will be clamped on the APZ side, but if we use it as the
+ // main-thread's visual viewport offset we need to clamp it ourselves.
+ // Use GetScrollRangeForUserInputEvents() to do the clamping because this
+ // the scroll range that APZ will use.
+ return GetScrollRangeForUserInputEvents().ClampPoint(
+ pendingUpdate->mVisualScrollOffset);
+ }
+ return presShell->GetVisualViewportOffset();
+ }
+ return GetScrollPosition();
+}
+
+bool nsHTMLScrollFrame::SetVisualViewportOffset(const nsPoint& aOffset,
+ bool aRepaint) {
+ MOZ_ASSERT(mIsRoot);
+ AutoWeakFrame weakFrame(this);
+ AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame,
+ !aRepaint);
+
+ bool retVal =
+ PresShell()->SetVisualViewportOffset(aOffset, GetScrollPosition());
+ if (!weakFrame.IsAlive()) {
+ return false;
+ }
+ return retVal;
+}
+
+nsRect nsHTMLScrollFrame::GetVisualOptimalViewingRect() const {
+ auto* presShell = PresShell();
+ nsRect rect = mScrollPort;
+ if (mIsRoot && presShell->IsVisualViewportSizeSet() &&
+ presShell->IsVisualViewportOffsetSet()) {
+ rect = nsRect(mScrollPort.TopLeft() - GetScrollPosition() +
+ presShell->GetVisualViewportOffset(),
+ presShell->GetVisualViewportSize());
+ }
+ // NOTE: We intentionally resolve scroll-padding percentages against the
+ // scrollport even when the visual viewport is set, see
+ // https://github.com/w3c/csswg-drafts/issues/4393.
+ rect.Deflate(GetScrollPadding());
+ return rect;
+}
+
+static void AdjustDestinationForWholeDelta(const nsIntPoint& aDelta,
+ const nsRect& aScrollRange,
+ nsPoint& aPoint) {
+ if (aDelta.x < 0) {
+ aPoint.x = aScrollRange.X();
+ } else if (aDelta.x > 0) {
+ aPoint.x = aScrollRange.XMost();
+ }
+ if (aDelta.y < 0) {
+ aPoint.y = aScrollRange.Y();
+ } else if (aDelta.y > 0) {
+ aPoint.y = aScrollRange.YMost();
+ }
+}
+
+/**
+ * Calculate lower/upper scrollBy range in given direction.
+ * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size
+ * @param aPos desired destination in AppUnits
+ * @param aNeg/PosTolerance defines relative range distance
+ * below and above of aPos point
+ * @param aMultiplier used for conversion of tolerance into appUnis
+ */
+static void CalcRangeForScrollBy(int32_t aDelta, nscoord aPos,
+ float aNegTolerance, float aPosTolerance,
+ nscoord aMultiplier, nscoord* aLower,
+ nscoord* aUpper) {
+ if (!aDelta) {
+ *aLower = *aUpper = aPos;
+ return;
+ }
+ *aLower = aPos - NSToCoordRound(aMultiplier *
+ (aDelta > 0 ? aNegTolerance : aPosTolerance));
+ *aUpper = aPos + NSToCoordRound(aMultiplier *
+ (aDelta > 0 ? aPosTolerance : aNegTolerance));
+}
+
+void nsHTMLScrollFrame::ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit,
+ ScrollMode aMode, nsIntPoint* aOverflow,
+ ScrollOrigin aOrigin,
+ nsIScrollableFrame::ScrollMomentum aMomentum,
+ ScrollSnapFlags aSnapFlags) {
+ // When a smooth scroll is being processed on a frame, mouse wheel and
+ // trackpad momentum scroll event updates must notcancel the SMOOTH or
+ // SMOOTH_MSD scroll animations, enabling Javascript that depends on them to
+ // be responsive without forcing the user to wait for the fling animations to
+ // completely stop.
+ switch (aMomentum) {
+ case nsIScrollableFrame::NOT_MOMENTUM:
+ mIgnoreMomentumScroll = false;
+ break;
+ case nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT:
+ if (mIgnoreMomentumScroll) {
+ return;
+ }
+ break;
+ }
+
+ if (mAsyncSmoothMSDScroll != nullptr) {
+ // When CSSOM-View scroll-behavior smooth scrolling is interrupted,
+ // the scroll is not completed to avoid non-smooth snapping to the
+ // prior smooth scroll's destination.
+ mDestination = GetScrollPosition();
+ }
+
+ nsSize deltaMultiplier;
+ float negativeTolerance;
+ float positiveTolerance;
+ if (aOrigin == ScrollOrigin::NotSpecified) {
+ aOrigin = ScrollOrigin::Other;
+ }
+ bool isGenericOrigin = (aOrigin == ScrollOrigin::Other);
+
+ bool askApzToDoTheScroll = false;
+ if ((aSnapFlags == ScrollSnapFlags::Disabled || !NeedsScrollSnap()) &&
+ gfxPlatform::UseDesktopZoomingScrollbars() &&
+ nsLayoutUtils::AsyncPanZoomEnabled(this) &&
+ !nsLayoutUtils::ShouldDisableApzForElement(GetContent()) &&
+ (WantAsyncScroll() || mZoomableByAPZ) &&
+ CanApzScrollInTheseDirections(DirectionsInDelta(aDelta))) {
+ askApzToDoTheScroll = true;
+ }
+
+ switch (aUnit) {
+ case ScrollUnit::DEVICE_PIXELS: {
+ nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
+ if (isGenericOrigin) {
+ aOrigin = ScrollOrigin::Pixels;
+ }
+ negativeTolerance = positiveTolerance = 0.5f;
+ break;
+ }
+ case ScrollUnit::LINES: {
+ deltaMultiplier = GetLineScrollAmount();
+ if (isGenericOrigin) {
+ aOrigin = ScrollOrigin::Lines;
+ }
+ negativeTolerance = positiveTolerance = 0.1f;
+ break;
+ }
+ case ScrollUnit::PAGES: {
+ deltaMultiplier = GetPageScrollAmount();
+ if (isGenericOrigin) {
+ aOrigin = ScrollOrigin::Pages;
+ }
+ negativeTolerance = 0.05f;
+ positiveTolerance = 0;
+ break;
+ }
+ case ScrollUnit::WHOLE: {
+ if (askApzToDoTheScroll) {
+ MOZ_ASSERT(aDelta.x >= -1 && aDelta.x <= 1 && aDelta.y >= -1 &&
+ aDelta.y <= 1);
+ deltaMultiplier = GetScrollRangeForUserInputEvents().Size();
+ break;
+ } else {
+ nsPoint pos = GetScrollPosition();
+ AdjustDestinationForWholeDelta(aDelta, GetLayoutScrollRange(), pos);
+ ScrollToWithOrigin(
+ pos, nullptr /* range */,
+ ScrollOperationParams{aMode, ScrollOrigin::Other, aSnapFlags,
+ ScrollTriggeredByScript::No});
+ // 'this' might be destroyed here
+ if (aOverflow) {
+ *aOverflow = nsIntPoint(0, 0);
+ }
+ return;
+ }
+ }
+ default:
+ NS_ERROR("Invalid scroll mode");
+ return;
+ }
+
+ if (askApzToDoTheScroll) {
+ nsPoint delta(
+ NSCoordSaturatingNonnegativeMultiply(aDelta.x, deltaMultiplier.width),
+ NSCoordSaturatingNonnegativeMultiply(aDelta.y, deltaMultiplier.height));
+
+ AppendScrollUpdate(
+ ScrollPositionUpdate::NewPureRelativeScroll(aOrigin, aMode, delta));
+
+ nsIContent* content = GetContent();
+ if (!DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(content)) {
+ if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
+ mozilla::layers::ScrollableLayerGuid::ViewID viewID =
+ mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ nsLayoutUtils::FindIDFor(content, &viewID);
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Debug,
+ ("ScrollBy setting displayport on scrollId=%" PRIu64 "\n", viewID));
+ }
+
+ DisplayPortUtils::CalculateAndSetDisplayPortMargins(
+ GetScrollTargetFrame(), DisplayPortUtils::RepaintMode::Repaint);
+ nsIFrame* frame = do_QueryFrame(GetScrollTargetFrame());
+ DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
+ frame);
+ }
+
+ SchedulePaint();
+ return;
+ }
+
+ nsPoint newPos(NSCoordSaturatingAdd(mDestination.x,
+ NSCoordSaturatingNonnegativeMultiply(
+ aDelta.x, deltaMultiplier.width)),
+ NSCoordSaturatingAdd(mDestination.y,
+ NSCoordSaturatingNonnegativeMultiply(
+ aDelta.y, deltaMultiplier.height)));
+
+ Maybe<SnapDestination> snapDestination;
+ if (aSnapFlags != ScrollSnapFlags::Disabled) {
+ if (NeedsScrollSnap()) {
+ nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
+ negativeTolerance = 0.1f;
+ positiveTolerance = 0;
+ ScrollUnit snapUnit = aUnit;
+ if (aOrigin == ScrollOrigin::MouseWheel) {
+ // When using a clicky scroll wheel, snap point selection works the same
+ // as keyboard up/down/left/right navigation, but with varying amounts
+ // of scroll delta.
+ snapUnit = ScrollUnit::LINES;
+ }
+ snapDestination = GetSnapPointForDestination(snapUnit, aSnapFlags,
+ mDestination, newPos);
+ if (snapDestination) {
+ newPos = snapDestination->mPosition;
+ }
+ }
+ }
+
+ // Calculate desired range values.
+ nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY;
+ CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance,
+ deltaMultiplier.width, &rangeLowerX, &rangeUpperX);
+ CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance,
+ deltaMultiplier.height, &rangeLowerY, &rangeUpperY);
+ nsRect range(rangeLowerX, rangeLowerY, rangeUpperX - rangeLowerX,
+ rangeUpperY - rangeLowerY);
+ AutoWeakFrame weakFrame(this);
+ ScrollToWithOrigin(
+ newPos, &range,
+ snapDestination
+ ? ScrollOperationParams{aMode, aOrigin,
+ std::move(snapDestination->mTargetIds)}
+ : ScrollOperationParams{aMode, aOrigin});
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+
+ if (aOverflow) {
+ nsPoint clampAmount = newPos - mDestination;
+ float appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ *aOverflow =
+ nsIntPoint(NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel),
+ NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel));
+ }
+
+ if (aUnit == ScrollUnit::DEVICE_PIXELS &&
+ !nsLayoutUtils::AsyncPanZoomEnabled(this)) {
+ // When APZ is disabled, we must track the velocity
+ // on the main thread; otherwise, the APZC will manage this.
+ mVelocityQueue.Sample(GetScrollPosition());
+ }
+}
+
+void nsHTMLScrollFrame::ScrollByCSSPixelsInternal(const CSSIntPoint& aDelta,
+ ScrollMode aMode,
+ ScrollSnapFlags aSnapFlags) {
+ nsPoint current = GetScrollPosition();
+ // `current` value above might be a value which was aligned to in
+ // layer-pixels, so starting from such points will make the difference between
+ // the given position in script (e.g. scrollTo) and the aligned position
+ // larger, in the worst case the difference can be observed in CSS pixels.
+ // To avoid it, we use the current position in CSS pixels as the start
+ // position. Hopefully it exactly matches the position where it was given by
+ // the previous scrolling operation, but there may be some edge cases where
+ // the current position in CSS pixels differs from the given position, the
+ // cases should be fixed in bug 1556685.
+ CSSPoint currentCSSPixels;
+ if (StaticPrefs::layout_scroll_disable_pixel_alignment()) {
+ currentCSSPixels = GetScrollPositionCSSPixels();
+ } else {
+ currentCSSPixels = GetRoundedScrollPositionCSSPixels();
+ }
+ nsPoint pt = CSSPoint::ToAppUnits(currentCSSPixels + aDelta);
+
+ nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
+ nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1,
+ 2 * halfPixel - 1);
+ // XXX I don't think the following blocks are needed anymore, now that
+ // ScrollToImpl simply tries to scroll an integer number of layer
+ // pixels from the current position
+ if (aDelta.x == 0.0f) {
+ pt.x = current.x;
+ range.x = pt.x;
+ range.width = 0;
+ }
+ if (aDelta.y == 0.0f) {
+ pt.y = current.y;
+ range.y = pt.y;
+ range.height = 0;
+ }
+ ScrollToWithOrigin(
+ pt, &range,
+ ScrollOperationParams{aMode, ScrollOrigin::Relative, aSnapFlags,
+ ScrollTriggeredByScript::Yes});
+ // 'this' might be destroyed here
+}
+
+void nsHTMLScrollFrame::ScrollSnap(ScrollMode aMode) {
+ float flingSensitivity =
+ StaticPrefs::layout_css_scroll_snap_prediction_sensitivity();
+ int maxVelocity =
+ StaticPrefs::layout_css_scroll_snap_prediction_max_velocity();
+ maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
+ int maxOffset = maxVelocity * flingSensitivity;
+ nsPoint velocity = mVelocityQueue.GetVelocity();
+ // Multiply each component individually to avoid integer multiply
+ nsPoint predictedOffset =
+ nsPoint(velocity.x * flingSensitivity, velocity.y * flingSensitivity);
+ predictedOffset.Clamp(maxOffset);
+ nsPoint pos = GetScrollPosition();
+ nsPoint destinationPos = pos + predictedOffset;
+ ScrollSnap(destinationPos, aMode);
+}
+
+void nsHTMLScrollFrame::ScrollSnap(const nsPoint& aDestination,
+ ScrollMode aMode) {
+ nsRect scrollRange = GetLayoutScrollRange();
+ nsPoint pos = GetScrollPosition();
+ nsPoint destination = scrollRange.ClampPoint(aDestination);
+ ScrollSnapFlags snapFlags = ScrollSnapFlags::IntendedEndPosition;
+ if (mVelocityQueue.GetVelocity() != nsPoint()) {
+ snapFlags |= ScrollSnapFlags::IntendedDirection;
+ }
+
+ // Bug 1776624 : Consider using mDestination as |aStartPos| argument for this
+ // GetSnapPointForDestination call, this function call is the only one call
+ // site using `GetScrollPosition()` as |aStartPos|.
+ if (auto snapDestination = GetSnapPointForDestination(
+ ScrollUnit::DEVICE_PIXELS, snapFlags, pos, destination)) {
+ destination = snapDestination->mPosition;
+ ScrollToWithOrigin(
+ destination, nullptr /* range */,
+ ScrollOperationParams{aMode, ScrollOrigin::Other,
+ std::move(snapDestination->mTargetIds)});
+ }
+}
+
+nsSize nsHTMLScrollFrame::GetLineScrollAmount() const {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+ NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ nscoord minScrollAmountInAppUnits =
+ std::max(1, StaticPrefs::mousewheel_min_line_scroll_amount()) *
+ appUnitsPerDevPixel;
+ nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0;
+ nscoord verticalAmount = fm ? fm->MaxHeight() : 0;
+ return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits),
+ std::max(verticalAmount, minScrollAmountInAppUnits));
+}
+
+/**
+ * Compute the scrollport size excluding any fixed-pos and sticky-pos (that are
+ * stuck) headers and footers. A header or footer is an box that spans that
+ * entire width of the viewport and touches the top (or bottom, respectively) of
+ * the viewport. We also want to consider fixed/sticky elements that stack or
+ * overlap to effectively create a larger header or footer. Headers and footers
+ * that cover more than a third of the the viewport are ignored since they
+ * probably aren't true headers and footers and we don't want to restrict
+ * scrolling too much in such cases. This is a bit conservative --- some
+ * pages use elements as headers or footers that don't span the entire width
+ * of the viewport --- but it should be a good start.
+ *
+ * If aViewportFrame is non-null then the scroll frame is the root scroll
+ * frame and we should consider fixed-pos items.
+ */
+struct TopAndBottom {
+ TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {}
+
+ nscoord top, bottom;
+};
+struct TopComparator {
+ bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
+ return A.top == B.top;
+ }
+ bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
+ return A.top < B.top;
+ }
+};
+struct ReverseBottomComparator {
+ bool Equals(const TopAndBottom& A, const TopAndBottom& B) const {
+ return A.bottom == B.bottom;
+ }
+ bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const {
+ return A.bottom > B.bottom;
+ }
+};
+
+static void AddToListIfHeaderFooter(nsIFrame* aFrame,
+ nsIFrame* aScrollPortFrame,
+ const nsRect& aScrollPort,
+ nsTArray<TopAndBottom>& aList) {
+ nsRect r = aFrame->GetRectRelativeToSelf();
+ r = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, r, aScrollPortFrame);
+ r = r.Intersect(aScrollPort);
+ if ((r.width >= aScrollPort.width / 2 ||
+ r.width >= NSIntPixelsToAppUnits(800, AppUnitsPerCSSPixel())) &&
+ r.height <= aScrollPort.height / 3) {
+ aList.AppendElement(TopAndBottom(r.y, r.YMost()));
+ }
+}
+
+static nsSize GetScrollPortSizeExcludingHeadersAndFooters(
+ nsIFrame* aScrollFrame, nsIFrame* aViewportFrame,
+ const nsRect& aScrollPort) {
+ AutoTArray<TopAndBottom, 10> list;
+ if (aViewportFrame) {
+ for (nsIFrame* f : aViewportFrame->GetChildList(FrameChildListID::Fixed)) {
+ AddToListIfHeaderFooter(f, aViewportFrame, aScrollPort, list);
+ }
+ }
+
+ // Add sticky frames that are currently in "fixed" positions
+ StickyScrollContainer* ssc =
+ StickyScrollContainer::GetStickyScrollContainerForScrollFrame(
+ aScrollFrame);
+ if (ssc) {
+ const nsTArray<nsIFrame*>& stickyFrames = ssc->GetFrames();
+ for (nsIFrame* f : stickyFrames) {
+ // If it's acting like fixed position.
+ if (ssc->IsStuckInYDirection(f)) {
+ AddToListIfHeaderFooter(f, aScrollFrame, aScrollPort, list);
+ }
+ }
+ }
+
+ list.Sort(TopComparator());
+ nscoord headerBottom = 0;
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ if (list[i].top <= headerBottom) {
+ headerBottom = std::max(headerBottom, list[i].bottom);
+ }
+ }
+
+ list.Sort(ReverseBottomComparator());
+ nscoord footerTop = aScrollPort.height;
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ if (list[i].bottom >= footerTop) {
+ footerTop = std::min(footerTop, list[i].top);
+ }
+ }
+
+ headerBottom = std::min(aScrollPort.height / 3, headerBottom);
+ footerTop = std::max(aScrollPort.height - aScrollPort.height / 3, footerTop);
+
+ return nsSize(aScrollPort.width, footerTop - headerBottom);
+}
+
+nsSize nsHTMLScrollFrame::GetPageScrollAmount() const {
+ nsSize effectiveScrollPortSize;
+
+ if (GetVisualViewportSize() != mScrollPort.Size()) {
+ // We want to use the visual viewport size if one is set.
+ // The headers/footers adjustment is too complicated to do if there is a
+ // visual viewport that differs from the layout viewport, this is probably
+ // okay.
+ effectiveScrollPortSize = GetVisualViewportSize();
+ } else {
+ // Reduce effective scrollport height by the height of any
+ // fixed-pos/sticky-pos headers or footers
+ effectiveScrollPortSize = GetScrollPortSizeExcludingHeadersAndFooters(
+ const_cast<nsHTMLScrollFrame*>(this),
+ mIsRoot ? PresShell()->GetRootFrame() : nullptr, mScrollPort);
+ }
+
+ nsSize lineScrollAmount = GetLineScrollAmount();
+
+ // The page increment is the size of the page, minus the smaller of
+ // 10% of the size or 2 lines.
+ return nsSize(effectiveScrollPortSize.width -
+ std::min(effectiveScrollPortSize.width / 10,
+ 2 * lineScrollAmount.width),
+ effectiveScrollPortSize.height -
+ std::min(effectiveScrollPortSize.height / 10,
+ 2 * lineScrollAmount.height));
+}
+
+/**
+ * this code is resposible for restoring the scroll position back to some
+ * saved position. if the user has not moved the scroll position manually
+ * we keep scrolling down until we get to our original position. keep in
+ * mind that content could incrementally be coming in. we only want to stop
+ * when we reach our new position.
+ */
+void nsHTMLScrollFrame::ScrollToRestoredPosition() {
+ if (mRestorePos.y == -1 || mLastPos.x == -1 || mLastPos.y == -1) {
+ return;
+ }
+ // make sure our scroll position did not change for where we last put
+ // it. if it does then the user must have moved it, and we no longer
+ // need to restore.
+ //
+ // In the RTL case, we check whether the scroll position changed using the
+ // logical scroll position, but we scroll to the physical scroll position in
+ // all cases
+
+ // The layout offset we want to restore is the same as the visual offset
+ // (for now, may change in bug 1499210), but clamped to the layout scroll
+ // range (which can be a subset of the visual scroll range).
+ // Note that we can't do the clamping when initializing mRestorePos in
+ // RestoreState(), since the scrollable rect (which the clamping depends
+ // on) can change over the course of the restoration process.
+ nsPoint layoutRestorePos = GetLayoutScrollRange().ClampPoint(mRestorePos);
+ nsPoint visualRestorePos = GetVisualScrollRange().ClampPoint(mRestorePos);
+
+ // Continue restoring until both the layout and visual scroll positions
+ // reach the destination. (Note that the two can only be different for
+ // the root content document's root scroll frame, and when zoomed in).
+ // This is necessary to avoid situations where the two offsets get stuck
+ // at different values and nothing reconciles them (see bug 1519621 comment
+ // 8).
+ nsPoint logicalLayoutScrollPos = GetLogicalScrollPosition();
+
+ SCROLLRESTORE_LOG(
+ "%p: ScrollToRestoredPosition (mRestorePos=%s, mLastPos=%s, "
+ "layoutRestorePos=%s, visualRestorePos=%s, "
+ "logicalLayoutScrollPos=%s, "
+ "GetLogicalVisualViewportOffset()=%s)\n",
+ this, ToString(mRestorePos).c_str(), ToString(mLastPos).c_str(),
+ ToString(layoutRestorePos).c_str(), ToString(visualRestorePos).c_str(),
+ ToString(logicalLayoutScrollPos).c_str(),
+ ToString(GetLogicalVisualViewportOffset()).c_str());
+
+ // if we didn't move, we still need to restore
+ if (GetLogicalVisualViewportOffset() == mLastPos ||
+ logicalLayoutScrollPos == mLastPos) {
+ // if our desired position is different to the scroll position, scroll.
+ // remember that we could be incrementally loading so we may enter
+ // and scroll many times.
+ if (mRestorePos != mLastPos /* GetLogicalVisualViewportOffset() */ ||
+ layoutRestorePos != logicalLayoutScrollPos) {
+ LoadingState state = GetPageLoadingState();
+ if (state == LoadingState::Stopped && !IsSubtreeDirty()) {
+ return;
+ }
+ nsPoint visualScrollToPos = visualRestorePos;
+ nsPoint layoutScrollToPos = layoutRestorePos;
+ if (!IsPhysicalLTR()) {
+ // convert from logical to physical scroll position
+ visualScrollToPos.x -=
+ (GetVisualViewportSize().width - mScrolledFrame->GetRect().width);
+ layoutScrollToPos.x -=
+ (GetVisualViewportSize().width - mScrolledFrame->GetRect().width);
+ }
+ AutoWeakFrame weakFrame(this);
+ // It's very important to pass ScrollOrigin::Restore here, so
+ // ScrollToWithOrigin won't clear out mRestorePos.
+ ScrollToWithOrigin(
+ layoutScrollToPos, nullptr,
+ ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Restore});
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ if (mIsRoot) {
+ PresShell()->ScrollToVisual(visualScrollToPos, FrameMetrics::eRestore,
+ ScrollMode::Instant);
+ }
+ if (state == LoadingState::Loading || IsSubtreeDirty()) {
+ // If we're trying to do a history scroll restore, then we want to
+ // keep trying this until we succeed, because the page can be loading
+ // incrementally. So re-get the scroll position for the next iteration,
+ // it might not be exactly equal to mRestorePos due to rounding and
+ // clamping.
+ mLastPos = GetLogicalVisualViewportOffset();
+ return;
+ }
+ }
+ // If we get here, either we reached the desired position (mLastPos ==
+ // mRestorePos) or we're not trying to do a history scroll restore, so
+ // we can stop after the scroll attempt above.
+ mRestorePos.y = -1;
+ mLastPos.x = -1;
+ mLastPos.y = -1;
+ } else {
+ // user moved the position, so we won't need to restore
+ mLastPos.x = -1;
+ mLastPos.y = -1;
+ }
+}
+
+auto nsHTMLScrollFrame::GetPageLoadingState() -> LoadingState {
+ bool loadCompleted = false, stopped = false;
+ nsCOMPtr<nsIDocShell> ds = GetContent()->GetComposedDoc()->GetDocShell();
+ if (ds) {
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ ds->GetDocViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ loadCompleted = viewer->GetLoadCompleted();
+ stopped = viewer->GetIsStopped();
+ }
+ }
+ return loadCompleted
+ ? (stopped ? LoadingState::Stopped : LoadingState::Loaded)
+ : LoadingState::Loading;
+}
+
+nsHTMLScrollFrame::OverflowState nsHTMLScrollFrame::GetOverflowState() const {
+ nsSize scrollportSize = mScrollPort.Size();
+ nsSize childSize = GetScrolledRect().Size();
+
+ OverflowState result = OverflowState::None;
+
+ if (childSize.height > scrollportSize.height) {
+ result |= OverflowState::Vertical;
+ }
+
+ if (childSize.width > scrollportSize.width) {
+ result |= OverflowState::Horizontal;
+ }
+
+ return result;
+}
+
+nsresult nsHTMLScrollFrame::FireScrollPortEvent() {
+ mAsyncScrollPortEvent.Forget();
+
+ // TODO(emilio): why do we need the whole WillPaintObserver infrastructure and
+ // can't use AddScriptRunner & co? I guess it made sense when we used
+ // WillPaintObserver for scroll events too, or when this used to flush.
+ //
+ // Should we remove this?
+
+ OverflowState overflowState = GetOverflowState();
+
+ bool newVerticalOverflow = !!(overflowState & OverflowState::Vertical);
+ bool vertChanged = mVerticalOverflow != newVerticalOverflow;
+
+ bool newHorizontalOverflow = !!(overflowState & OverflowState::Horizontal);
+ bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
+
+ if (!vertChanged && !horizChanged) {
+ return NS_OK;
+ }
+
+ // If both either overflowed or underflowed then we dispatch only one
+ // DOM event.
+ bool both = vertChanged && horizChanged &&
+ newVerticalOverflow == newHorizontalOverflow;
+ InternalScrollPortEvent::OrientType orient;
+ if (both) {
+ orient = InternalScrollPortEvent::eBoth;
+ mHorizontalOverflow = newHorizontalOverflow;
+ mVerticalOverflow = newVerticalOverflow;
+ } else if (vertChanged) {
+ orient = InternalScrollPortEvent::eVertical;
+ mVerticalOverflow = newVerticalOverflow;
+ if (horizChanged) {
+ // We need to dispatch a separate horizontal DOM event. Do that the next
+ // time around since dispatching the vertical DOM event might destroy
+ // the frame.
+ PostOverflowEvent();
+ }
+ } else {
+ orient = InternalScrollPortEvent::eHorizontal;
+ mHorizontalOverflow = newHorizontalOverflow;
+ }
+
+ InternalScrollPortEvent event(
+ true,
+ (orient == InternalScrollPortEvent::eHorizontal ? mHorizontalOverflow
+ : mVerticalOverflow)
+ ? eScrollPortOverflow
+ : eScrollPortUnderflow,
+ nullptr);
+ event.mOrient = orient;
+
+ RefPtr<nsIContent> content = GetContent();
+ RefPtr<nsPresContext> presContext = PresContext();
+ return EventDispatcher::Dispatch(content, presContext, &event);
+}
+
+void nsHTMLScrollFrame::PostScrollEndEvent() {
+ if (mScrollEndEvent) {
+ return;
+ }
+
+ // The ScrollEndEvent constructor registers itself with the refresh driver.
+ mScrollEndEvent = new ScrollEndEvent(this);
+}
+
+void nsHTMLScrollFrame::FireScrollEndEvent() {
+ MOZ_ASSERT(GetContent());
+ MOZ_ASSERT(mScrollEndEvent);
+
+ RefPtr<nsPresContext> presContext = PresContext();
+ mScrollEndEvent->Revoke();
+ mScrollEndEvent = nullptr;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetGUIEvent event(true, eScrollend, nullptr);
+ event.mFlags.mBubbles = mIsRoot;
+ event.mFlags.mCancelable = false;
+ RefPtr<nsINode> target =
+ mIsRoot ? static_cast<nsINode*>(presContext->Document()) : GetContent();
+ EventDispatcher::Dispatch(target, presContext, &event, nullptr, &status);
+}
+
+void nsHTMLScrollFrame::ReloadChildFrames() {
+ mScrolledFrame = nullptr;
+ mHScrollbarBox = nullptr;
+ mVScrollbarBox = nullptr;
+ mScrollCornerBox = nullptr;
+ mResizerBox = nullptr;
+
+ for (nsIFrame* frame : PrincipalChildList()) {
+ nsIContent* content = frame->GetContent();
+ if (content == GetContent()) {
+ NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame");
+ mScrolledFrame = frame;
+ } else {
+ nsAutoString value;
+ if (content->IsElement()) {
+ content->AsElement()->GetAttr(nsGkAtoms::orient, value);
+ }
+ if (!value.IsEmpty()) {
+ // probably a scrollbar then
+ if (value.LowerCaseEqualsLiteral("horizontal")) {
+ NS_ASSERTION(!mHScrollbarBox,
+ "Found multiple horizontal scrollbars?");
+ mHScrollbarBox = do_QueryFrame(frame);
+ MOZ_ASSERT(mHScrollbarBox, "Not a scrollbar?");
+ } else {
+ NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?");
+ mVScrollbarBox = do_QueryFrame(frame);
+ MOZ_ASSERT(mVScrollbarBox, "Not a scrollbar?");
+ }
+ } else if (content->IsXULElement(nsGkAtoms::resizer)) {
+ NS_ASSERTION(!mResizerBox, "Found multiple resizers");
+ mResizerBox = frame;
+ } else if (content->IsXULElement(nsGkAtoms::scrollcorner)) {
+ // probably a scrollcorner
+ NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners");
+ mScrollCornerBox = frame;
+ }
+ }
+ }
+}
+
+already_AddRefed<Element> nsHTMLScrollFrame::MakeScrollbar(
+ NodeInfo* aNodeInfo, bool aVertical, AnonymousContentKey& aKey) {
+ MOZ_ASSERT(aNodeInfo);
+ MOZ_ASSERT(
+ aNodeInfo->Equals(nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL));
+
+ static constexpr nsLiteralString kOrientValues[2] = {
+ u"horizontal"_ns,
+ u"vertical"_ns,
+ };
+
+ aKey = AnonymousContentKey::Type_Scrollbar;
+ if (aVertical) {
+ aKey |= AnonymousContentKey::Flag_Vertical;
+ }
+
+ RefPtr<Element> e;
+ NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo));
+
+#ifdef DEBUG
+ // Scrollbars can get restyled by theme changes. Whether such a restyle
+ // will actually reconstruct them correctly if it involves a frame
+ // reconstruct... I don't know. :(
+ e->SetProperty(nsGkAtoms::restylableAnonymousNode,
+ reinterpret_cast<void*>(true));
+#endif // DEBUG
+
+ e->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, kOrientValues[aVertical],
+ false);
+
+ if (mIsRoot) {
+ e->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
+ reinterpret_cast<void*>(true));
+ e->SetAttr(kNameSpaceID_None, nsGkAtoms::root_, u"true"_ns, false);
+
+ // Don't bother making style caching take [root="true"] styles into account.
+ aKey = AnonymousContentKey::None;
+ }
+
+ return e.forget();
+}
+
+bool nsHTMLScrollFrame::IsForTextControlWithNoScrollbars() const {
+ // FIXME(emilio): we should probably make the scroller inside <input> an
+ // internal pseudo-element, and then this would be simpler.
+ //
+ // Also, this could just use scrollbar-width these days.
+ auto* content = GetContent();
+ if (!content) {
+ return false;
+ }
+ auto* input = content->GetClosestNativeAnonymousSubtreeRootParentOrHost();
+ return input && input->IsHTMLElement(nsGkAtoms::input);
+}
+
+auto nsHTMLScrollFrame::GetCurrentAnonymousContent() const
+ -> EnumSet<AnonymousContentType> {
+ EnumSet<AnonymousContentType> result;
+ if (mHScrollbarContent) {
+ result += AnonymousContentType::HorizontalScrollbar;
+ }
+ if (mVScrollbarContent) {
+ result += AnonymousContentType::VerticalScrollbar;
+ }
+ if (mResizerContent) {
+ result += AnonymousContentType::Resizer;
+ }
+ return result;
+}
+
+auto nsHTMLScrollFrame::GetNeededAnonymousContent() const
+ -> EnumSet<AnonymousContentType> {
+ nsPresContext* pc = PresContext();
+
+ // Don't create scrollbars if we're an SVG document being used as an image,
+ // or if we're printing/print previewing.
+ // (In the printing case, we allow scrollbars if this is the child of the
+ // viewport & paginated scrolling is enabled, because then we must be the
+ // scroll frame for the print preview window, & that does need scrollbars.)
+ if (pc->Document()->IsBeingUsedAsImage() ||
+ (!pc->IsDynamic() && !(mIsRoot && pc->HasPaginatedScrolling()))) {
+ return {};
+ }
+
+ if (IsForTextControlWithNoScrollbars()) {
+ return {};
+ }
+
+ EnumSet<AnonymousContentType> result;
+ // If we're the scrollframe for the root, then we want to construct our
+ // scrollbar frames no matter what. That way later dynamic changes to
+ // propagated overflow styles will show or hide scrollbars on the viewport
+ // without requiring frame reconstruction of the viewport (good!).
+ //
+ // TODO(emilio): Figure out if we can remove this special-case now that we
+ // have more targeted optimizations.
+ if (mIsRoot) {
+ result += AnonymousContentType::HorizontalScrollbar;
+ result += AnonymousContentType::VerticalScrollbar;
+ // If scrollbar-width is none, don't generate scrollbars.
+ } else if (StyleUIReset()->ScrollbarWidth() != StyleScrollbarWidth::None) {
+ ScrollStyles styles = GetScrollStyles();
+ if (styles.mHorizontal != StyleOverflow::Hidden) {
+ result += AnonymousContentType::HorizontalScrollbar;
+ }
+ if (styles.mVertical != StyleOverflow::Hidden) {
+ result += AnonymousContentType::VerticalScrollbar;
+ }
+ }
+
+ // Check if the frame is resizable. Note:
+ // "The effect of the resize property on generated content is undefined.
+ // Implementations should not apply the resize property to generated
+ // content." [1]
+ // For info on what is generated content, see [2].
+ // [1]: https://drafts.csswg.org/css-ui/#resize
+ // [2]: https://www.w3.org/TR/CSS2/generate.html#content
+ auto resizeStyle = StyleDisplay()->mResize;
+ if (resizeStyle != StyleResize::None &&
+ !HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) {
+ result += AnonymousContentType::Resizer;
+ }
+
+ return result;
+}
+
+nsresult nsHTMLScrollFrame::CreateAnonymousContent(
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements) {
+ typedef nsIAnonymousContentCreator::ContentInfo ContentInfo;
+
+ nsPresContext* presContext = PresContext();
+ nsNodeInfoManager* nodeInfoManager =
+ presContext->Document()->NodeInfoManager();
+
+ auto neededAnonContent = GetNeededAnonymousContent();
+ if (neededAnonContent.isEmpty()) {
+ return NS_OK;
+ }
+
+ {
+ RefPtr<NodeInfo> nodeInfo = nodeInfoManager->GetNodeInfo(
+ nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+ if (neededAnonContent.contains(AnonymousContentType::HorizontalScrollbar)) {
+ AnonymousContentKey key;
+ mHScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ false, key);
+ aElements.AppendElement(ContentInfo(mHScrollbarContent, key));
+ }
+
+ if (neededAnonContent.contains(AnonymousContentType::VerticalScrollbar)) {
+ AnonymousContentKey key;
+ mVScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ true, key);
+ aElements.AppendElement(ContentInfo(mVScrollbarContent, key));
+ }
+ }
+
+ if (neededAnonContent.contains(AnonymousContentType::Resizer)) {
+ MOZ_ASSERT(!mIsRoot, "Root scroll frame shouldn't be resizable");
+
+ RefPtr<NodeInfo> nodeInfo;
+ nodeInfo = nodeInfoManager->GetNodeInfo(
+ nsGkAtoms::resizer, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget());
+
+ nsAutoString dir;
+ switch (StyleDisplay()->mResize) {
+ case StyleResize::Horizontal:
+ if (IsScrollbarOnRight()) {
+ dir.AssignLiteral("right");
+ } else {
+ dir.AssignLiteral("left");
+ }
+ break;
+ case StyleResize::Vertical:
+ dir.AssignLiteral("bottom");
+ if (!IsScrollbarOnRight()) {
+ mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::flip, u""_ns,
+ false);
+ }
+ break;
+ case StyleResize::Both:
+ if (IsScrollbarOnRight()) {
+ dir.AssignLiteral("bottomright");
+ } else {
+ dir.AssignLiteral("bottomleft");
+ }
+ break;
+ default:
+ NS_WARNING("only resizable types should have resizers");
+ }
+ mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false);
+ aElements.AppendElement(mResizerContent);
+ }
+
+ if (neededAnonContent.contains(AnonymousContentType::HorizontalScrollbar) &&
+ neededAnonContent.contains(AnonymousContentType::VerticalScrollbar)) {
+ AnonymousContentKey key = AnonymousContentKey::Type_ScrollCorner;
+
+ RefPtr<NodeInfo> nodeInfo =
+ nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr,
+ kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
+ NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent),
+ nodeInfo.forget());
+ if (mIsRoot) {
+ mScrollCornerContent->SetProperty(
+ nsGkAtoms::docLevelNativeAnonymousContent,
+ reinterpret_cast<void*>(true));
+ mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root_,
+ u"true"_ns, false);
+
+ // Don't bother making style caching take [root="true"] styles into
+ // account.
+ key = AnonymousContentKey::None;
+ }
+ aElements.AppendElement(ContentInfo(mScrollCornerContent, key));
+ }
+
+ // Don't cache styles if we are a child of a <select> element, since we have
+ // some UA style sheet rules that depend on the <select>'s attributes.
+ if (GetContent()->IsHTMLElement(nsGkAtoms::select)) {
+ for (auto& info : aElements) {
+ info.mKey = AnonymousContentKey::None;
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsHTMLScrollFrame::AppendAnonymousContentTo(
+ nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
+ if (mHScrollbarContent) {
+ aElements.AppendElement(mHScrollbarContent);
+ }
+
+ if (mVScrollbarContent) {
+ aElements.AppendElement(mVScrollbarContent);
+ }
+
+ if (mScrollCornerContent) {
+ aElements.AppendElement(mScrollCornerContent);
+ }
+
+ if (mResizerContent) {
+ aElements.AppendElement(mResizerContent);
+ }
+}
+
+void nsHTMLScrollFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
+ if (aOldComputedStyle && !mIsRoot &&
+ StyleDisplay()->mScrollSnapType !=
+ aOldComputedStyle->StyleDisplay()->mScrollSnapType) {
+ PostPendingResnap();
+ }
+}
+
+void nsHTMLScrollFrame::RemoveObservers() {
+ if (mAsyncScroll) {
+ mAsyncScroll->RemoveObserver();
+ mAsyncScroll = nullptr;
+ }
+ if (mAsyncSmoothMSDScroll) {
+ mAsyncSmoothMSDScroll->RemoveObserver();
+ mAsyncSmoothMSDScroll = nullptr;
+ }
+}
+
+/**
+ * Called when we want to update the scrollbar position, either because
+ * scrolling happened or the user moved the scrollbar position and we need to
+ * undo that (e.g., when the user clicks to scroll and we're using smooth
+ * scrolling, so we need to put the thumb back to its initial position for the
+ * start of the smooth sequence).
+ */
+void nsHTMLScrollFrame::UpdateScrollbarPosition() {
+ AutoWeakFrame weakFrame(this);
+ mFrameIsUpdatingScrollbar = true;
+
+ nsPoint pt = GetScrollPosition();
+ nsRect scrollRange = GetVisualScrollRange();
+
+ if (gfxPlatform::UseDesktopZoomingScrollbars()) {
+ pt = GetVisualViewportOffset();
+ scrollRange = GetScrollRangeForUserInputEvents();
+ }
+
+ if (mVScrollbarBox) {
+ SetCoordAttribute(mVScrollbarBox->GetContent()->AsElement(),
+ nsGkAtoms::curpos, pt.y - scrollRange.y);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+ if (mHScrollbarBox) {
+ SetCoordAttribute(mHScrollbarBox->GetContent()->AsElement(),
+ nsGkAtoms::curpos, pt.x - scrollRange.x);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+
+ mFrameIsUpdatingScrollbar = false;
+}
+
+void nsHTMLScrollFrame::CurPosAttributeChangedInternal(nsIContent* aContent,
+ bool aDoScroll) {
+ NS_ASSERTION(aContent, "aContent must not be null");
+ NS_ASSERTION((mHScrollbarBox && mHScrollbarBox->GetContent() == aContent) ||
+ (mVScrollbarBox && mVScrollbarBox->GetContent() == aContent),
+ "unexpected child");
+ MOZ_ASSERT(aContent->IsElement());
+
+ // Attribute changes on the scrollbars happen in one of three ways:
+ // 1) The scrollbar changed the attribute in response to some user event
+ // 2) We changed the attribute in response to a ScrollPositionDidChange
+ // callback from the scrolling view
+ // 3) We changed the attribute to adjust the scrollbars for the start
+ // of a smooth scroll operation
+ //
+ // In cases 2 and 3 we do not need to scroll because we're just
+ // updating our scrollbar.
+ if (mFrameIsUpdatingScrollbar) {
+ return;
+ }
+
+ nsRect scrollRange = GetVisualScrollRange();
+
+ nsPoint current = GetScrollPosition() - scrollRange.TopLeft();
+
+ if (gfxPlatform::UseDesktopZoomingScrollbars()) {
+ scrollRange = GetScrollRangeForUserInputEvents();
+ current = GetVisualViewportOffset() - scrollRange.TopLeft();
+ }
+
+ nsPoint dest;
+ nsRect allowedRange;
+ dest.x = GetCoordAttribute(mHScrollbarBox, nsGkAtoms::curpos, current.x,
+ &allowedRange.x, &allowedRange.width);
+ dest.y = GetCoordAttribute(mVScrollbarBox, nsGkAtoms::curpos, current.y,
+ &allowedRange.y, &allowedRange.height);
+ current += scrollRange.TopLeft();
+ dest += scrollRange.TopLeft();
+ allowedRange += scrollRange.TopLeft();
+
+ // Don't try to scroll if we're already at an acceptable place.
+ // Don't call Contains here since Contains returns false when the point is
+ // on the bottom or right edge of the rectangle.
+ if (allowedRange.ClampPoint(current) == current) {
+ return;
+ }
+
+ if (mScrollbarActivity &&
+ (mHasHorizontalScrollbar || mHasVerticalScrollbar)) {
+ RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
+ scrollbarActivity->ActivityOccurred();
+ }
+
+ const bool isSmooth = aContent->AsElement()->HasAttr(nsGkAtoms::smooth);
+ if (isSmooth) {
+ // Make sure an attribute-setting callback occurs even if the view
+ // didn't actually move yet. We need to make sure other listeners
+ // see that the scroll position is not (yet) what they thought it
+ // was.
+ AutoWeakFrame weakFrame(this);
+ UpdateScrollbarPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+
+ if (aDoScroll) {
+ ScrollToWithOrigin(dest, &allowedRange,
+ ScrollOperationParams{
+ isSmooth ? ScrollMode::Smooth : ScrollMode::Instant,
+ ScrollOrigin::Scrollbars});
+ }
+ // 'this' might be destroyed here
+}
+
+/* ============= Scroll events ========== */
+
+nsHTMLScrollFrame::ScrollEvent::ScrollEvent(nsHTMLScrollFrame* aHelper,
+ bool aDelayed)
+ : Runnable("nsHTMLScrollFrame::ScrollEvent"), mHelper(aHelper) {
+ mHelper->PresContext()->RefreshDriver()->PostScrollEvent(this, aDelayed);
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsHTMLScrollFrame::ScrollEvent::Run() {
+ if (mHelper) {
+ mHelper->FireScrollEvent();
+ }
+ return NS_OK;
+}
+
+nsHTMLScrollFrame::ScrollEndEvent::ScrollEndEvent(nsHTMLScrollFrame* aHelper)
+ : Runnable("nsHTMLScrollFrame::ScrollEndEvent"), mHelper(aHelper) {
+ mHelper->PresContext()->RefreshDriver()->PostScrollEvent(this);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsHTMLScrollFrame::ScrollEndEvent::Run() {
+ if (mHelper) {
+ mHelper->FireScrollEndEvent();
+ }
+ return NS_OK;
+}
+
+void nsHTMLScrollFrame::FireScrollEvent() {
+ RefPtr<nsIContent> content = GetContent();
+ RefPtr<nsPresContext> presContext = PresContext();
+ AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "FireScrollEvent", GRAPHICS,
+ presContext->GetDocShell());
+
+ MOZ_ASSERT(mScrollEvent);
+ mScrollEvent->Revoke();
+ mScrollEvent = nullptr;
+
+ // If event handling is suppressed, keep posting the scroll event to the
+ // refresh driver until it is unsuppressed. The event is marked as delayed so
+ // that the refresh driver does not continue ticking.
+ if (content->GetComposedDoc() &&
+ content->GetComposedDoc()->EventHandlingSuppressed()) {
+ content->GetComposedDoc()->SetHasDelayedRefreshEvent();
+ PostScrollEvent(/* aDelayed = */ true);
+ return;
+ }
+
+ bool oldProcessing = mProcessingScrollEvent;
+ AutoWeakFrame weakFrame(this);
+ auto RestoreProcessingScrollEvent = mozilla::MakeScopeExit([&] {
+ if (weakFrame.IsAlive()) { // Otherwise `this` will be dead too.
+ mProcessingScrollEvent = oldProcessing;
+ }
+ });
+
+ mProcessingScrollEvent = true;
+
+ WidgetGUIEvent event(true, eScroll, nullptr);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ // Fire viewport scroll events at the document (where they
+ // will bubble to the window)
+ mozilla::layers::ScrollLinkedEffectDetector detector(
+ content->GetComposedDoc(),
+ presContext->RefreshDriver()->MostRecentRefresh());
+ if (mIsRoot) {
+ if (RefPtr<Document> doc = content->GetUncomposedDoc()) {
+ EventDispatcher::Dispatch(doc, presContext, &event, nullptr, &status);
+ }
+ } else {
+ // scroll events fired at elements don't bubble (although scroll events
+ // fired at documents do, to the window)
+ event.mFlags.mBubbles = false;
+ EventDispatcher::Dispatch(content, presContext, &event, nullptr, &status);
+ }
+}
+
+void nsHTMLScrollFrame::PostScrollEvent(bool aDelayed) {
+ if (mScrollEvent) {
+ return;
+ }
+
+ // The ScrollEvent constructor registers itself with the refresh driver.
+ mScrollEvent = new ScrollEvent(this, aDelayed);
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsHTMLScrollFrame::AsyncScrollPortEvent::Run() {
+ return mHelper ? mHelper->FireScrollPortEvent() : NS_OK;
+}
+
+void nsHTMLScrollFrame::PostOverflowEvent() {
+ if (mAsyncScrollPortEvent.IsPending()) {
+ return;
+ }
+
+ OverflowState overflowState = GetOverflowState();
+
+ bool newVerticalOverflow = !!(overflowState & OverflowState::Vertical);
+ bool vertChanged = mVerticalOverflow != newVerticalOverflow;
+
+ bool newHorizontalOverflow = !!(overflowState & OverflowState::Horizontal);
+ bool horizChanged = mHorizontalOverflow != newHorizontalOverflow;
+
+ if (!vertChanged && !horizChanged) {
+ return;
+ }
+
+ nsRootPresContext* rpc = PresContext()->GetRootPresContext();
+ if (!rpc) {
+ return;
+ }
+
+ mAsyncScrollPortEvent = new AsyncScrollPortEvent(this);
+ rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get());
+}
+
+nsIFrame* nsHTMLScrollFrame::GetFrameForStyle() const {
+ nsIFrame* styleFrame = nullptr;
+ if (mIsRoot) {
+ if (const Element* rootElement =
+ PresContext()->Document()->GetRootElement()) {
+ styleFrame = rootElement->GetPrimaryFrame();
+ }
+ } else {
+ styleFrame = const_cast<nsHTMLScrollFrame*>(this);
+ }
+
+ return styleFrame;
+}
+
+bool nsHTMLScrollFrame::NeedsScrollSnap() const {
+ nsIFrame* scrollSnapFrame = GetFrameForStyle();
+ if (!scrollSnapFrame) {
+ return false;
+ }
+ return scrollSnapFrame->StyleDisplay()->mScrollSnapType.strictness !=
+ StyleScrollSnapStrictness::None;
+}
+
+nsSize nsHTMLScrollFrame::GetSnapportSize() const {
+ nsRect snapport = GetScrollPortRect();
+ nsMargin scrollPadding = GetScrollPadding();
+ snapport.Deflate(scrollPadding);
+ return snapport.Size();
+}
+
+bool nsHTMLScrollFrame::IsScrollbarOnRight() const {
+ // The position of the scrollbar in top-level windows depends on the pref
+ // layout.scrollbar.side. For non-top-level elements, it depends only on the
+ // directionaliy of the element (equivalent to a value of "1" for the pref).
+ if (!mIsRoot) {
+ return IsPhysicalLTR();
+ }
+ switch (StaticPrefs::layout_scrollbar_side()) {
+ default:
+ case 0: // UI directionality
+ return StaticPrefs::bidi_direction() == IBMBIDI_TEXTDIRECTION_LTR;
+ case 1: // Document / content directionality
+ return IsPhysicalLTR();
+ case 2: // Always right
+ return true;
+ case 3: // Always left
+ return false;
+ }
+}
+
+bool nsHTMLScrollFrame::IsScrollingActive() const {
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (disp->mWillChange.bits & StyleWillChangeBits::SCROLL) {
+ return true;
+ }
+
+ nsIContent* content = GetContent();
+ return mHasBeenScrolledRecently || IsAlwaysActive() ||
+ DisplayPortUtils::HasDisplayPort(content) ||
+ nsContentUtils::HasScrollgrab(content);
+}
+
+void nsHTMLScrollFrame::FinishReflowForScrollbar(Element* aElement,
+ nscoord aMinXY, nscoord aMaxXY,
+ nscoord aCurPosXY,
+ nscoord aPageIncrement,
+ nscoord aIncrement) {
+ // Scrollbars assume zero is the minimum position, so translate for them.
+ SetCoordAttribute(aElement, nsGkAtoms::curpos, aCurPosXY - aMinXY);
+ SetScrollbarEnabled(aElement, aMaxXY - aMinXY);
+ SetCoordAttribute(aElement, nsGkAtoms::maxpos, aMaxXY - aMinXY);
+ SetCoordAttribute(aElement, nsGkAtoms::pageincrement, aPageIncrement);
+ SetCoordAttribute(aElement, nsGkAtoms::increment, aIncrement);
+}
+
+class MOZ_RAII AutoMinimumScaleSizeChangeDetector final {
+ public:
+ explicit AutoMinimumScaleSizeChangeDetector(
+ nsHTMLScrollFrame* ansHTMLScrollFrame)
+ : mHelper(ansHTMLScrollFrame) {
+ MOZ_ASSERT(mHelper);
+ MOZ_ASSERT(mHelper->mIsRoot);
+
+ mPreviousMinimumScaleSize = ansHTMLScrollFrame->mMinimumScaleSize;
+ mPreviousIsUsingMinimumScaleSize =
+ ansHTMLScrollFrame->mIsUsingMinimumScaleSize;
+ }
+ ~AutoMinimumScaleSizeChangeDetector() {
+ if (mPreviousMinimumScaleSize != mHelper->mMinimumScaleSize ||
+ mPreviousIsUsingMinimumScaleSize != mHelper->mIsUsingMinimumScaleSize) {
+ mHelper->mMinimumScaleSizeChanged = true;
+ }
+ }
+
+ private:
+ nsHTMLScrollFrame* mHelper;
+
+ nsSize mPreviousMinimumScaleSize;
+ bool mPreviousIsUsingMinimumScaleSize;
+};
+
+nsSize nsHTMLScrollFrame::TrueOuterSize(nsDisplayListBuilder* aBuilder) const {
+ if (!PresShell()->UsesMobileViewportSizing()) {
+ return GetSize();
+ }
+
+ RefPtr<MobileViewportManager> manager =
+ PresShell()->GetMobileViewportManager();
+ MOZ_ASSERT(manager);
+
+ LayoutDeviceIntSize displaySize = manager->DisplaySize();
+
+ MOZ_ASSERT(aBuilder);
+ // In case of WebRender, we expand the outer size to include the dynamic
+ // toolbar area here.
+ // In case of non WebRender, we expand the size dynamically in
+ // MoveScrollbarForLayerMargin in AsyncCompositionManager.cpp.
+ WebRenderLayerManager* layerManager = aBuilder->GetWidgetLayerManager();
+ if (layerManager) {
+ displaySize.height += ViewAs<LayoutDevicePixel>(
+ PresContext()->GetDynamicToolbarMaxHeight(),
+ PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ }
+
+ return LayoutDeviceSize::ToAppUnits(displaySize,
+ PresContext()->AppUnitsPerDevPixel());
+}
+
+void nsHTMLScrollFrame::UpdateMinimumScaleSize(
+ const nsRect& aScrollableOverflow, const nsSize& aICBSize) {
+ MOZ_ASSERT(mIsRoot);
+
+ AutoMinimumScaleSizeChangeDetector minimumScaleSizeChangeDetector(this);
+
+ mIsUsingMinimumScaleSize = false;
+
+ if (!PresShell()->UsesMobileViewportSizing()) {
+ return;
+ }
+
+ nsPresContext* pc = PresContext();
+ MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess(),
+ "The pres context should be for the root content document");
+
+ RefPtr<MobileViewportManager> manager =
+ PresShell()->GetMobileViewportManager();
+ MOZ_ASSERT(manager);
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ manager->DisplaySize(),
+ PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ if (displaySize.width == 0 || displaySize.height == 0) {
+ return;
+ }
+ if (aScrollableOverflow.IsEmpty()) {
+ // Bail if the scrollable overflow rect is empty, as we're going to be
+ // dividing by it.
+ return;
+ }
+
+ Document* doc = pc->Document();
+ MOZ_ASSERT(doc, "The document should be valid");
+ if (doc->GetFullscreenElement()) {
+ // Don't use the minimum scale size in the case of fullscreen state.
+ // FIXME: 1508177: We will no longer need this.
+ return;
+ }
+
+ nsViewportInfo viewportInfo = doc->GetViewportInfo(displaySize);
+ if (!viewportInfo.IsZoomAllowed()) {
+ // Don't apply the minimum scale size if user-scalable=no is specified.
+ return;
+ }
+
+ // The intrinsic minimum scale is the scale that fits the entire content
+ // width into the visual viewport.
+ CSSToScreenScale intrinsicMinScale(
+ displaySize.width / CSSRect::FromAppUnits(aScrollableOverflow).XMost());
+
+ // The scale used to compute the minimum-scale size is the larger of the
+ // intrinsic minimum and the min-scale from the meta viewport tag.
+ CSSToScreenScale minScale =
+ std::max(intrinsicMinScale, viewportInfo.GetMinZoom());
+
+ // The minimum-scale size is the size of the visual viewport when zoomed
+ // to be the minimum scale.
+ mMinimumScaleSize = CSSSize::ToAppUnits(ScreenSize(displaySize) / minScale);
+
+ // Ensure the minimum-scale size is never smaller than the ICB size.
+ // That could happen if a page has a meta viewport tag with large explicitly
+ // specified viewport dimensions (making the ICB large) and also a large
+ // minimum scale (making the min-scale size small).
+ mMinimumScaleSize = Max(aICBSize, mMinimumScaleSize);
+
+ mIsUsingMinimumScaleSize = true;
+}
+
+bool nsHTMLScrollFrame::ReflowFinished() {
+ mPostedReflowCallback = false;
+
+ TryScheduleScrollAnimations();
+
+ if (mIsRoot) {
+ if (mMinimumScaleSizeChanged && PresShell()->UsesMobileViewportSizing() &&
+ !PresShell()->IsResolutionUpdatedByApz()) {
+ RefPtr<MobileViewportManager> manager =
+ PresShell()->GetMobileViewportManager();
+ MOZ_ASSERT(manager);
+
+ manager->ShrinkToDisplaySizeIfNeeded();
+ mMinimumScaleSizeChanged = false;
+ }
+
+ if (!UsesOverlayScrollbars()) {
+ // Layout scrollbars may have added or removed during reflow, so let's
+ // update the visual viewport accordingly. Note that this may be a no-op
+ // because we might have recomputed the visual viewport size during the
+ // reflow itself, just before laying out the fixed-pos items. But there
+ // might be cases where that code doesn't run, so this is a sort of
+ // backstop to ensure we do that recomputation.
+ if (RefPtr<MobileViewportManager> manager =
+ PresShell()->GetMobileViewportManager()) {
+ manager->UpdateVisualViewportSizeForPotentialScrollbarChange();
+ }
+ }
+
+#if defined(MOZ_WIDGET_ANDROID)
+ const bool hasVerticalOverflow =
+ GetOverflowState() & OverflowState::Vertical &&
+ GetScrollStyles().mVertical != StyleOverflow::Hidden;
+ if (!mFirstReflow && mHasVerticalOverflowForDynamicToolbar &&
+ !hasVerticalOverflow) {
+ PresShell()->MaybeNotifyShowDynamicToolbar();
+ }
+ mHasVerticalOverflowForDynamicToolbar = hasVerticalOverflow;
+#endif // defined(MOZ_WIDGET_ANDROID)
+ }
+
+ bool doScroll = true;
+ if (IsSubtreeDirty()) {
+ // We will get another call after the next reflow and scrolling
+ // later is less janky.
+ doScroll = false;
+ }
+
+ if (mFirstReflow) {
+ nsPoint currentScrollPos = GetScrollPosition();
+ if (!mScrollUpdates.IsEmpty() &&
+ mScrollUpdates.LastElement().GetOrigin() == ScrollOrigin::None &&
+ currentScrollPos != nsPoint()) {
+ // With frame reconstructions, the reconstructed frame may have a nonzero
+ // scroll position by the end of the reflow, but without going through
+ // RestoreState. In particular this can happen with RTL XUL scrollframes,
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1664638#c14.
+ // Upon construction, the nsHTMLScrollFrame constructor will have inserted
+ // a ScrollPositionUpdate into mScrollUpdates with origin None and a zero
+ // scroll position, but here we update that to hold the correct scroll
+ // position. Otherwise APZ may end up resetting the scroll position to
+ // zero incorrectly. If we ever hit this codepath, it must be on a reflow
+ // immediately following the scrollframe construction, so there should be
+ // exactly one ScrollPositionUpdate in mScrollUpdates.
+ MOZ_ASSERT(mScrollUpdates.Length() == 1);
+ MOZ_ASSERT(mScrollUpdates.LastElement().GetGeneration() ==
+ mScrollGeneration);
+ MOZ_ASSERT(mScrollUpdates.LastElement().GetDestination() == CSSPoint());
+ SCROLLRESTORE_LOG("%p: updating initial SPU to pos %s\n", this,
+ ToString(currentScrollPos).c_str());
+ mScrollUpdates.Clear();
+ AppendScrollUpdate(
+ ScrollPositionUpdate::NewScrollframe(currentScrollPos));
+ }
+
+ mFirstReflow = false;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mReclampVVOffsetInReflowFinished) {
+ MOZ_ASSERT(mIsRoot && PresShell()->IsVisualViewportOffsetSet());
+ mReclampVVOffsetInReflowFinished = false;
+ AutoWeakFrame weakFrame(this);
+ PresShell()->SetVisualViewportOffset(PresShell()->GetVisualViewportOffset(),
+ GetScrollPosition());
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ }
+
+ if (doScroll) {
+ ScrollToRestoredPosition();
+
+ // Clamp current scroll position to new bounds. Normally this won't
+ // do anything.
+ nsPoint currentScrollPos = GetScrollPosition();
+ ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)),
+ ScrollOrigin::Clamp);
+ if (ScrollAnimationState().isEmpty()) {
+ // We need to have mDestination track the current scroll position,
+ // in case it falls outside the new reflow area. mDestination is used
+ // by ScrollBy as its starting position.
+ mDestination = GetScrollPosition();
+ }
+ }
+
+ if (!mUpdateScrollbarAttributes) {
+ return false;
+ }
+ mUpdateScrollbarAttributes = false;
+
+ // Update scrollbar attributes.
+ if (mMayHaveDirtyFixedChildren) {
+ mMayHaveDirtyFixedChildren = false;
+ nsIFrame* parentFrame = GetParent();
+ for (nsIFrame* fixedChild =
+ parentFrame->GetChildList(FrameChildListID::Fixed).FirstChild();
+ fixedChild; fixedChild = fixedChild->GetNextSibling()) {
+ // force a reflow of the fixed child
+ PresShell()->FrameNeedsReflow(fixedChild, IntrinsicDirty::None,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ }
+
+ // Suppress handling of the curpos attribute changes we make here.
+ NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here");
+ mFrameIsUpdatingScrollbar = true;
+
+ // FIXME(emilio): Why this instead of mHScrollbarContent / mVScrollbarContent?
+ RefPtr<Element> vScroll =
+ mVScrollbarBox ? mVScrollbarBox->GetContent()->AsElement() : nullptr;
+ RefPtr<Element> hScroll =
+ mHScrollbarBox ? mHScrollbarBox->GetContent()->AsElement() : nullptr;
+
+ // Note, in some cases this may get deleted while finishing reflow
+ // for scrollbars. XXXmats is this still true now that we have a script
+ // blocker in this scope? (if not, remove the weak frame checks below).
+ if (vScroll || hScroll) {
+ nsSize visualViewportSize = GetVisualViewportSize();
+ nsRect scrollRange = GetVisualScrollRange();
+ nsPoint scrollPos = GetScrollPosition();
+ nsSize lineScrollAmount = GetLineScrollAmount();
+
+ if (gfxPlatform::UseDesktopZoomingScrollbars()) {
+ scrollRange = GetScrollRangeForUserInputEvents();
+ scrollPos = GetVisualViewportOffset();
+ }
+
+ // If modifying the logic here, be sure to modify the corresponding
+ // compositor-side calculation in ScrollThumbUtils::ApplyTransformForAxis().
+ AutoWeakFrame weakFrame(this);
+ if (vScroll) {
+ const double kScrollMultiplier =
+ StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
+ nscoord increment = lineScrollAmount.height * kScrollMultiplier;
+ // We normally use (visualViewportSize.height - increment) for height of
+ // page scrolling. However, it is too small when increment is very large.
+ // (If increment is larger than visualViewportSize.height, direction of
+ // scrolling will be opposite). To avoid it, we use
+ // (float(visualViewportSize.height) * 0.8) as lower bound value of height
+ // of page scrolling. (bug 383267)
+ // XXX shouldn't we use GetPageScrollAmount here?
+ nscoord pageincrement = nscoord(visualViewportSize.height - increment);
+ nscoord pageincrementMin =
+ nscoord(float(visualViewportSize.height) * 0.8);
+ FinishReflowForScrollbar(
+ vScroll, scrollRange.y, scrollRange.YMost(), scrollPos.y,
+ std::max(pageincrement, pageincrementMin), increment);
+ }
+ if (hScroll) {
+ const double kScrollMultiplier =
+ StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
+ nscoord increment = lineScrollAmount.width * kScrollMultiplier;
+ FinishReflowForScrollbar(
+ hScroll, scrollRange.x, scrollRange.XMost(), scrollPos.x,
+ nscoord(float(visualViewportSize.width) * 0.8), increment);
+ }
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
+ }
+
+ mFrameIsUpdatingScrollbar = false;
+ // We used to rely on the curpos attribute changes above to scroll the
+ // view. However, for scrolling to the left of the viewport, we
+ // rescale the curpos attribute, which means that operations like
+ // resizing the window while it is scrolled all the way to the left
+ // hold the curpos attribute constant at 0 while still requiring
+ // scrolling. So we suppress the effect of the changes above with
+ // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here.
+ // (It actually even works some of the time without this, thanks to
+ // nsSliderFrame::AttributeChanged's handling of maxpos, but not when
+ // we hide the scrollbar on a large size change, such as
+ // maximization.)
+ if (!mHScrollbarBox && !mVScrollbarBox) {
+ return false;
+ }
+ CurPosAttributeChangedInternal(
+ mVScrollbarBox ? mVScrollbarBox->GetContent()->AsElement()
+ : mHScrollbarBox->GetContent()->AsElement(),
+ doScroll);
+ return doScroll;
+}
+
+void nsHTMLScrollFrame::ReflowCallbackCanceled() {
+ mPostedReflowCallback = false;
+}
+
+bool nsHTMLScrollFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
+ ScrollStyles ss = GetScrollStyles();
+
+ // Reflow when the change in overflow leads to one of our scrollbars
+ // changing or might require repositioning the scrolled content due to
+ // reduced extents.
+ nsRect scrolledRect = GetScrolledRect();
+ ScrollDirections overflowChange =
+ GetOverflowChange(scrolledRect, mPrevScrolledRect);
+ mPrevScrolledRect = scrolledRect;
+
+ bool needReflow = false;
+ nsPoint scrollPosition = GetScrollPosition();
+ if (overflowChange.contains(ScrollDirection::eHorizontal)) {
+ if (ss.mHorizontal != StyleOverflow::Hidden || scrollPosition.x) {
+ needReflow = true;
+ }
+ }
+ if (overflowChange.contains(ScrollDirection::eVertical)) {
+ if (ss.mVertical != StyleOverflow::Hidden || scrollPosition.y) {
+ needReflow = true;
+ }
+ }
+
+ if (needReflow) {
+ // If there are scrollbars, or we're not at the beginning of the pane,
+ // the scroll position may change. In this case, mark the frame as
+ // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means
+ // we have to reflow the frame and all its descendants, and we don't
+ // have to do that here. Only this frame needs to be reflowed.
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ // Ensure that next time nsHTMLScrollFrame::Reflow runs, we don't skip
+ // updating the scrollbars. (Because the overflow area of the scrolled
+ // frame has probably just been updated, Reflow won't see it change.)
+ mSkippedScrollbarLayout = true;
+ return false; // reflowing will update overflow
+ }
+ PostOverflowEvent();
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+void nsHTMLScrollFrame::UpdateSticky() {
+ StickyScrollContainer* ssc =
+ StickyScrollContainer::GetStickyScrollContainerForScrollFrame(this);
+ if (ssc) {
+ ssc->UpdatePositions(GetScrollPosition(), this);
+ }
+}
+
+void nsHTMLScrollFrame::UpdatePrevScrolledRect() {
+ // The layout scroll range is determinated by the scrolled rect and the scroll
+ // port, so if the scrolled rect is updated, we may have to schedule the
+ // associated scroll-driven animations' restyles.
+ nsRect currScrolledRect = GetScrolledRect();
+ if (!currScrolledRect.IsEqualEdges(mPrevScrolledRect)) {
+ mMayScheduleScrollAnimations = true;
+ }
+ mPrevScrolledRect = currScrolledRect;
+}
+
+void nsHTMLScrollFrame::AdjustScrollbarRectForResizer(
+ nsIFrame* aFrame, nsPresContext* aPresContext, nsRect& aRect,
+ bool aHasResizer, ScrollDirection aDirection) {
+ if ((aDirection == ScrollDirection::eVertical ? aRect.width : aRect.height) ==
+ 0) {
+ return;
+ }
+
+ // if a content resizer is present, use its size. Otherwise, check if the
+ // widget has a resizer.
+ nsRect resizerRect;
+ if (aHasResizer) {
+ resizerRect = mResizerBox->GetRect();
+ } else {
+ nsPoint offset;
+ nsIWidget* widget = aFrame->GetNearestWidget(offset);
+ LayoutDeviceIntRect widgetRect;
+ if (!widget || !widget->ShowsResizeIndicator(&widgetRect)) {
+ return;
+ }
+
+ resizerRect =
+ nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
+ aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
+ aPresContext->DevPixelsToAppUnits(widgetRect.width),
+ aPresContext->DevPixelsToAppUnits(widgetRect.height));
+ }
+
+ if (resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1))) {
+ switch (aDirection) {
+ case ScrollDirection::eVertical:
+ aRect.height = std::max(0, resizerRect.y - aRect.y);
+ break;
+ case ScrollDirection::eHorizontal:
+ aRect.width = std::max(0, resizerRect.x - aRect.x);
+ break;
+ }
+ } else if (resizerRect.Contains(aRect.BottomLeft() + nsPoint(1, -1))) {
+ switch (aDirection) {
+ case ScrollDirection::eVertical:
+ aRect.height = std::max(0, resizerRect.y - aRect.y);
+ break;
+ case ScrollDirection::eHorizontal: {
+ nscoord xmost = aRect.XMost();
+ aRect.x = std::max(aRect.x, resizerRect.XMost());
+ aRect.width = xmost - aRect.x;
+ break;
+ }
+ }
+ }
+}
+
+static void AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect) {
+ if (aVRect.IsEmpty() || aHRect.IsEmpty()) return;
+
+ const nsRect oldVRect = aVRect;
+ const nsRect oldHRect = aHRect;
+ if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) {
+ aHRect.width = std::max(0, oldVRect.x - oldHRect.x);
+ } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) {
+ nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x);
+ aHRect.x += overlap;
+ aHRect.width -= overlap;
+ }
+ if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) {
+ aVRect.height = std::max(0, oldHRect.y - oldVRect.y);
+ }
+}
+
+void nsHTMLScrollFrame::LayoutScrollbarPartAtRect(
+ const ScrollReflowInput& aState, ReflowInput& aKidReflowInput,
+ const nsRect& aRect) {
+ nsPresContext* pc = PresContext();
+ nsIFrame* kid = aKidReflowInput.mFrame;
+ const auto wm = kid->GetWritingMode();
+ ReflowOutput desiredSize(wm);
+ MOZ_ASSERT(!wm.IsVertical(),
+ "Scrollbar parts should have writing-mode: initial");
+ MOZ_ASSERT(!wm.IsInlineReversed(),
+ "Scrollbar parts should have writing-mode: initial");
+ // XXX Maybe get a meaningful container size or something. Shouldn't matter
+ // given our asserts above.
+ const nsSize containerSize;
+ aKidReflowInput.SetComputedISize(aRect.Width());
+ aKidReflowInput.SetComputedBSize(aRect.Height());
+
+ const LogicalPoint pos(wm, aRect.TopLeft(), containerSize);
+ const auto flags = ReflowChildFlags::Default;
+ nsReflowStatus status;
+ ReflowOutput kidDesiredSize(wm);
+ ReflowChild(kid, pc, kidDesiredSize, aKidReflowInput, wm, pos, containerSize,
+ flags, status);
+ FinishReflowChild(kid, pc, kidDesiredSize, &aKidReflowInput, wm, pos,
+ containerSize, flags);
+}
+
+void nsHTMLScrollFrame::LayoutScrollbars(ScrollReflowInput& aState,
+ const nsRect& aInsideBorderArea,
+ const nsRect& aOldScrollPort) {
+ NS_ASSERTION(!mSuppressScrollbarUpdate, "This should have been suppressed");
+
+ const bool scrollbarOnLeft = !IsScrollbarOnRight();
+ const bool overlayScrollbars = UsesOverlayScrollbars();
+ const bool overlayScrollBarsOnRoot = overlayScrollbars && mIsRoot;
+ const bool showVScrollbar = mVScrollbarBox && mHasVerticalScrollbar;
+ const bool showHScrollbar = mHScrollbarBox && mHasHorizontalScrollbar;
+
+ nsSize compositionSize = mScrollPort.Size();
+ if (overlayScrollBarsOnRoot) {
+ compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(
+ this, false, &compositionSize);
+ }
+
+ nsPresContext* presContext = mScrolledFrame->PresContext();
+ nsRect vRect;
+ if (showVScrollbar) {
+ vRect.height =
+ overlayScrollBarsOnRoot ? compositionSize.height : mScrollPort.height;
+ vRect.y = mScrollPort.y;
+ if (scrollbarOnLeft) {
+ vRect.width = mScrollPort.x - aInsideBorderArea.x;
+ vRect.x = aInsideBorderArea.x;
+ } else {
+ vRect.width = aInsideBorderArea.XMost() - mScrollPort.XMost();
+ vRect.x = mScrollPort.x + compositionSize.width;
+ }
+ if (overlayScrollbars || mOnlyNeedVScrollbarToScrollVVInsideLV) {
+ const nscoord width = aState.VScrollbarPrefWidth();
+ // There is no space reserved for the layout scrollbar, it is currently
+ // not visible because it is positioned just outside the scrollport. But
+ // we know that it needs to be made visible so we shift it back in.
+ vRect.width += width;
+ if (!scrollbarOnLeft) {
+ vRect.x -= width;
+ }
+ }
+ }
+
+ nsRect hRect;
+ if (showHScrollbar) {
+ hRect.width =
+ overlayScrollBarsOnRoot ? compositionSize.width : mScrollPort.width;
+ hRect.x = mScrollPort.x;
+ hRect.height = aInsideBorderArea.YMost() - mScrollPort.YMost();
+ hRect.y = mScrollPort.y + compositionSize.height;
+
+ if (overlayScrollbars || mOnlyNeedHScrollbarToScrollVVInsideLV) {
+ const nscoord height = aState.HScrollbarPrefHeight();
+ hRect.height += height;
+ // There is no space reserved for the layout scrollbar, it is currently
+ // not visible because it is positioned just outside the scrollport. But
+ // we know that it needs to be made visible so we shift it back in.
+ hRect.y -= height;
+ }
+ }
+
+ const bool hasVisualOnlyScrollbarsOnBothDirections =
+ !overlayScrollbars && showHScrollbar &&
+ mOnlyNeedHScrollbarToScrollVVInsideLV && showVScrollbar &&
+ mOnlyNeedVScrollbarToScrollVVInsideLV;
+ nsPresContext* pc = PresContext();
+
+ // place the scrollcorner
+ if (mScrollCornerBox) {
+ nsRect r(0, 0, 0, 0);
+ if (scrollbarOnLeft) {
+ // scrollbar (if any) on left
+ r.width = showVScrollbar ? mScrollPort.x - aInsideBorderArea.x : 0;
+ r.x = aInsideBorderArea.x;
+ } else {
+ // scrollbar (if any) on right
+ r.width =
+ showVScrollbar ? aInsideBorderArea.XMost() - mScrollPort.XMost() : 0;
+ r.x = aInsideBorderArea.XMost() - r.width;
+ }
+ NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
+
+ if (showHScrollbar) {
+ // scrollbar (if any) on bottom
+ // Note we don't support the horizontal scrollbar at the top side.
+ r.height = aInsideBorderArea.YMost() - mScrollPort.YMost();
+ NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
+ }
+ r.y = aInsideBorderArea.YMost() - r.height;
+
+ // If we have layout scrollbars and both scrollbars are present and both are
+ // only needed to scroll the VV inside the LV then we need a scrollcorner
+ // but the above calculation will result in an empty rect, so adjust it.
+ if (r.IsEmpty() && hasVisualOnlyScrollbarsOnBothDirections) {
+ r.width = vRect.width;
+ r.height = hRect.height;
+ r.x = scrollbarOnLeft ? mScrollPort.x : mScrollPort.XMost() - r.width;
+ r.y = mScrollPort.YMost() - r.height;
+ }
+
+ ReflowInput scrollCornerRI(
+ pc, aState.mReflowInput, mScrollCornerBox,
+ LogicalSize(mScrollCornerBox->GetWritingMode(), r.Size()));
+ LayoutScrollbarPartAtRect(aState, scrollCornerRI, r);
+ }
+
+ if (mResizerBox) {
+ // If a resizer is present, get its size.
+ //
+ // TODO(emilio): Should this really account for scrollbar-width?
+ auto scrollbarWidth = nsLayoutUtils::StyleForScrollbar(this)
+ ->StyleUIReset()
+ ->ScrollbarWidth();
+ const nscoord scrollbarSize =
+ GetNonOverlayScrollbarSize(pc, scrollbarWidth);
+ ReflowInput resizerRI(pc, aState.mReflowInput, mResizerBox,
+ LogicalSize(mResizerBox->GetWritingMode()));
+ nsSize resizerMinSize = {resizerRI.ComputedMinWidth(),
+ resizerRI.ComputedMinHeight()};
+
+ nsRect r;
+ r.width = std::max(std::max(r.width, scrollbarSize), resizerMinSize.width);
+ r.x = scrollbarOnLeft ? aInsideBorderArea.x
+ : aInsideBorderArea.XMost() - r.width;
+ r.height =
+ std::max(std::max(r.height, scrollbarSize), resizerMinSize.height);
+ r.y = aInsideBorderArea.YMost() - r.height;
+
+ LayoutScrollbarPartAtRect(aState, resizerRI, r);
+ }
+
+ // Note that AdjustScrollbarRectForResizer has to be called after the
+ // resizer has been laid out immediately above this because it gets the rect
+ // of the resizer frame.
+ if (mVScrollbarBox) {
+ AdjustScrollbarRectForResizer(this, presContext, vRect, mResizerBox,
+ ScrollDirection::eVertical);
+ }
+ if (mHScrollbarBox) {
+ AdjustScrollbarRectForResizer(this, presContext, hRect, mResizerBox,
+ ScrollDirection::eHorizontal);
+ }
+
+ // Layout scrollbars can overlap at this point if they are both present and
+ // both only needed to scroll the VV inside the LV.
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::AllowOverlayScrollbarsOverlap) ||
+ hasVisualOnlyScrollbarsOnBothDirections) {
+ AdjustOverlappingScrollbars(vRect, hRect);
+ }
+ if (mVScrollbarBox) {
+ ReflowInput vScrollbarRI(
+ pc, aState.mReflowInput, mVScrollbarBox,
+ LogicalSize(mVScrollbarBox->GetWritingMode(), vRect.Size()));
+ LayoutScrollbarPartAtRect(aState, vScrollbarRI, vRect);
+ }
+ if (mHScrollbarBox) {
+ ReflowInput hScrollbarRI(
+ pc, aState.mReflowInput, mHScrollbarBox,
+ LogicalSize(mHScrollbarBox->GetWritingMode(), hRect.Size()));
+ LayoutScrollbarPartAtRect(aState, hScrollbarRI, hRect);
+ }
+
+ // may need to update fixed position children of the viewport,
+ // if the client area changed size because of an incremental
+ // reflow of a descendant. (If the outer frame is dirty, the fixed
+ // children will be re-laid out anyway)
+ if (aOldScrollPort.Size() != mScrollPort.Size() &&
+ !HasAnyStateBits(NS_FRAME_IS_DIRTY) && mIsRoot) {
+ mMayHaveDirtyFixedChildren = true;
+ }
+
+ // post reflow callback to modify scrollbar attributes
+ mUpdateScrollbarAttributes = true;
+ if (!mPostedReflowCallback) {
+ PresShell()->PostReflowCallback(this);
+ mPostedReflowCallback = true;
+ }
+}
+
+#if DEBUG
+static bool ShellIsAlive(nsWeakPtr& aWeakPtr) {
+ RefPtr<PresShell> presShell = do_QueryReferent(aWeakPtr);
+ return !!presShell;
+}
+#endif
+
+void nsHTMLScrollFrame::SetScrollbarEnabled(Element* aElement,
+ nscoord aMaxPos) {
+ DebugOnly<nsWeakPtr> weakShell(do_GetWeakReference(PresShell()));
+ if (aMaxPos) {
+ aElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ } else {
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, u"true"_ns, true);
+ }
+ MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
+}
+
+void nsHTMLScrollFrame::SetCoordAttribute(Element* aElement, nsAtom* aAtom,
+ nscoord aSize) {
+ DebugOnly<nsWeakPtr> weakShell(do_GetWeakReference(PresShell()));
+ // convert to pixels
+ int32_t pixelSize = nsPresContext::AppUnitsToIntCSSPixels(aSize);
+
+ // only set the attribute if it changed.
+
+ nsAutoString newValue;
+ newValue.AppendInt(pixelSize);
+
+ if (aElement->AttrValueIs(kNameSpaceID_None, aAtom, newValue, eCaseMatters)) {
+ return;
+ }
+
+ AutoWeakFrame weakFrame(this);
+ RefPtr<Element> kungFuDeathGrip = aElement;
+ aElement->SetAttr(kNameSpaceID_None, aAtom, newValue, true);
+ MOZ_ASSERT(ShellIsAlive(weakShell), "pres shell was destroyed by scrolling");
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+
+ if (mScrollbarActivity &&
+ (mHasHorizontalScrollbar || mHasVerticalScrollbar)) {
+ RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity);
+ scrollbarActivity->ActivityOccurred();
+ }
+}
+
+static void ReduceRadii(nscoord aXBorder, nscoord aYBorder, nscoord& aXRadius,
+ nscoord& aYRadius) {
+ // In order to ensure that the inside edge of the border has no
+ // curvature, we need at least one of its radii to be zero.
+ if (aXRadius <= aXBorder || aYRadius <= aYBorder) return;
+
+ // For any corner where we reduce the radii, preserve the corner's shape.
+ double ratio =
+ std::max(double(aXBorder) / aXRadius, double(aYBorder) / aYRadius);
+ aXRadius *= ratio;
+ aYRadius *= ratio;
+}
+
+/**
+ * Implement an override for nsIFrame::GetBorderRadii to ensure that
+ * the clipping region for the border radius does not clip the scrollbars.
+ *
+ * In other words, we require that the border radius be reduced until the
+ * inner border radius at the inner edge of the border is 0 wherever we
+ * have scrollbars.
+ */
+bool nsHTMLScrollFrame::GetBorderRadii(const nsSize& aFrameSize,
+ const nsSize& aBorderArea,
+ Sides aSkipSides,
+ nscoord aRadii[8]) const {
+ if (!nsContainerFrame::GetBorderRadii(aFrameSize, aBorderArea, aSkipSides,
+ aRadii)) {
+ return false;
+ }
+
+ // Since we can use GetActualScrollbarSizes (rather than
+ // GetDesiredScrollbarSizes) since this doesn't affect reflow, we
+ // probably should.
+ nsMargin sb = GetActualScrollbarSizes();
+ nsMargin border = GetUsedBorder();
+
+ if (sb.left > 0 || sb.top > 0) {
+ ReduceRadii(border.left, border.top, aRadii[eCornerTopLeftX],
+ aRadii[eCornerTopLeftY]);
+ }
+
+ if (sb.top > 0 || sb.right > 0) {
+ ReduceRadii(border.right, border.top, aRadii[eCornerTopRightX],
+ aRadii[eCornerTopRightY]);
+ }
+
+ if (sb.right > 0 || sb.bottom > 0) {
+ ReduceRadii(border.right, border.bottom, aRadii[eCornerBottomRightX],
+ aRadii[eCornerBottomRightY]);
+ }
+
+ if (sb.bottom > 0 || sb.left > 0) {
+ ReduceRadii(border.left, border.bottom, aRadii[eCornerBottomLeftX],
+ aRadii[eCornerBottomLeftY]);
+ }
+
+ return true;
+}
+
+static nscoord SnapCoord(nscoord aCoord, double aRes,
+ nscoord aAppUnitsPerPixel) {
+ if (StaticPrefs::layout_scroll_disable_pixel_alignment()) {
+ return aCoord;
+ }
+ double snappedToLayerPixels = NS_round((aRes * aCoord) / aAppUnitsPerPixel);
+ return NSToCoordRoundWithClamp(snappedToLayerPixels * aAppUnitsPerPixel /
+ aRes);
+}
+
+nsRect nsHTMLScrollFrame::GetScrolledRect() const {
+ nsRect result = GetUnsnappedScrolledRectInternal(
+ mScrolledFrame->ScrollableOverflowRect(), mScrollPort.Size());
+
+#if 0
+ // This happens often enough.
+ if (result.width < mScrollPort.width || result.height < mScrollPort.height) {
+ NS_WARNING("Scrolled rect smaller than scrollport?");
+ }
+#endif
+
+ // Expand / contract the result by up to half a layer pixel so that scrolling
+ // to the right / bottom edge does not change the layer pixel alignment of
+ // the scrolled contents.
+
+ if (result.x == 0 && result.y == 0 && result.width == mScrollPort.width &&
+ result.height == mScrollPort.height) {
+ // The edges that we would snap are already aligned with the scroll port,
+ // so we can skip all the work below.
+ return result;
+ }
+
+ // For that, we first convert the scroll port and the scrolled rect to rects
+ // relative to the reference frame, since that's the space where painting does
+ // snapping.
+ nsSize visualViewportSize = GetVisualViewportSize();
+ const nsIFrame* referenceFrame =
+ mReferenceFrameDuringPainting ? mReferenceFrameDuringPainting
+ : nsLayoutUtils::GetReferenceFrame(
+ const_cast<nsHTMLScrollFrame*>(this));
+ nsPoint toReferenceFrame = GetOffsetToCrossDoc(referenceFrame);
+ nsRect scrollPort(mScrollPort.TopLeft() + toReferenceFrame,
+ visualViewportSize);
+ nsRect scrolledRect = result + scrollPort.TopLeft();
+
+ if (scrollPort.Overflows() || scrolledRect.Overflows()) {
+ return result;
+ }
+
+ // Now, snap the bottom right corner of both of these rects.
+ // We snap to layer pixels, so we need to respect the layer's scale.
+ nscoord appUnitsPerDevPixel =
+ mScrolledFrame->PresContext()->AppUnitsPerDevPixel();
+ MatrixScales scale = GetPaintedLayerScaleForFrame(
+ mScrolledFrame, /* aIncludeCSSTransform = */ false);
+ if (scale.xScale == 0 || scale.yScale == 0) {
+ scale = MatrixScales();
+ }
+
+ // Compute bounds for the scroll position, and computed the snapped scrolled
+ // rect from the scroll position bounds.
+ nscoord snappedScrolledAreaBottom =
+ SnapCoord(scrolledRect.YMost(), scale.yScale, appUnitsPerDevPixel);
+ nscoord snappedScrollPortBottom =
+ SnapCoord(scrollPort.YMost(), scale.yScale, appUnitsPerDevPixel);
+ nscoord maximumScrollOffsetY =
+ snappedScrolledAreaBottom - snappedScrollPortBottom;
+ result.SetBottomEdge(scrollPort.height + maximumScrollOffsetY);
+
+ if (GetScrolledFrameDir() == StyleDirection::Ltr) {
+ nscoord snappedScrolledAreaRight =
+ SnapCoord(scrolledRect.XMost(), scale.xScale, appUnitsPerDevPixel);
+ nscoord snappedScrollPortRight =
+ SnapCoord(scrollPort.XMost(), scale.xScale, appUnitsPerDevPixel);
+ nscoord maximumScrollOffsetX =
+ snappedScrolledAreaRight - snappedScrollPortRight;
+ result.SetRightEdge(scrollPort.width + maximumScrollOffsetX);
+ } else {
+ // In RTL, the scrolled area's right edge is at scrollPort.XMost(),
+ // and the scrolled area's x position is zero or negative. We want
+ // the right edge to stay flush with the scroll port, so we snap the
+ // left edge.
+ nscoord snappedScrolledAreaLeft =
+ SnapCoord(scrolledRect.x, scale.xScale, appUnitsPerDevPixel);
+ nscoord snappedScrollPortLeft =
+ SnapCoord(scrollPort.x, scale.xScale, appUnitsPerDevPixel);
+ nscoord minimumScrollOffsetX =
+ snappedScrolledAreaLeft - snappedScrollPortLeft;
+ result.SetLeftEdge(minimumScrollOffsetX);
+ }
+
+ return result;
+}
+
+StyleDirection nsHTMLScrollFrame::GetScrolledFrameDir() const {
+ // If the scrolled frame has unicode-bidi: plaintext, the paragraph
+ // direction set by the text content overrides the direction of the frame
+ if (mScrolledFrame->StyleTextReset()->mUnicodeBidi ==
+ StyleUnicodeBidi::Plaintext) {
+ if (nsIFrame* child = mScrolledFrame->PrincipalChildList().FirstChild()) {
+ return nsBidiPresUtils::ParagraphDirection(child) ==
+ mozilla::intl::BidiDirection::LTR
+ ? StyleDirection::Ltr
+ : StyleDirection::Rtl;
+ }
+ }
+ return IsBidiLTR() ? StyleDirection::Ltr : StyleDirection::Rtl;
+}
+
+nsRect nsHTMLScrollFrame::GetUnsnappedScrolledRectInternal(
+ const nsRect& aScrolledOverflowArea, const nsSize& aScrollPortSize) const {
+ return nsLayoutUtils::GetScrolledRect(mScrolledFrame, aScrolledOverflowArea,
+ aScrollPortSize, GetScrolledFrameDir());
+}
+
+nsMargin nsHTMLScrollFrame::GetActualScrollbarSizes(
+ nsIScrollableFrame::ScrollbarSizesOptions
+ aOptions /* = nsIScrollableFrame::ScrollbarSizesOptions::NONE */)
+ const {
+ nsRect r = GetPaddingRectRelativeToSelf();
+
+ nsMargin m(mScrollPort.y - r.y, r.XMost() - mScrollPort.XMost(),
+ r.YMost() - mScrollPort.YMost(), mScrollPort.x - r.x);
+
+ if (aOptions == nsIScrollableFrame::ScrollbarSizesOptions::
+ INCLUDE_VISUAL_VIEWPORT_SCROLLBARS &&
+ !UsesOverlayScrollbars()) {
+ // If we are using layout scrollbars and they only exist to scroll the
+ // visual viewport then they do not take up any layout space (so the
+ // scrollport is the same as the padding rect) but they do cover everything
+ // below them so some callers may want to include this special type of
+ // scrollbars in the returned value.
+ if (mHScrollbarBox && mHasHorizontalScrollbar &&
+ mOnlyNeedHScrollbarToScrollVVInsideLV) {
+ m.bottom += mHScrollbarBox->GetRect().height;
+ }
+ if (mVScrollbarBox && mHasVerticalScrollbar &&
+ mOnlyNeedVScrollbarToScrollVVInsideLV) {
+ if (IsScrollbarOnRight()) {
+ m.right += mVScrollbarBox->GetRect().width;
+ } else {
+ m.left += mVScrollbarBox->GetRect().width;
+ }
+ }
+ }
+
+ return m;
+}
+
+void nsHTMLScrollFrame::SetScrollbarVisibility(nsIFrame* aScrollbar,
+ bool aVisible) {
+ nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar);
+ if (scrollbar) {
+ // See if we have a mediator.
+ nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator();
+ if (mediator) {
+ // Inform the mediator of the visibility change.
+ mediator->VisibilityChanged(aVisible);
+ }
+ }
+}
+
+nscoord nsHTMLScrollFrame::GetCoordAttribute(nsIFrame* aBox, nsAtom* aAtom,
+ nscoord aDefaultValue,
+ nscoord* aRangeStart,
+ nscoord* aRangeLength) {
+ if (aBox) {
+ nsIContent* content = aBox->GetContent();
+
+ nsAutoString value;
+ if (content->IsElement()) {
+ content->AsElement()->GetAttr(aAtom, value);
+ }
+ if (!value.IsEmpty()) {
+ nsresult error;
+ // convert it to appunits
+ nscoord result =
+ nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
+ nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f);
+ // Any nscoord value that would round to the attribute value when
+ // converted to CSS pixels is allowed.
+ *aRangeStart = result - halfPixel;
+ *aRangeLength = halfPixel * 2 - 1;
+ return result;
+ }
+ }
+
+ // Only this exact default value is allowed.
+ *aRangeStart = aDefaultValue;
+ *aRangeLength = 0;
+ return aDefaultValue;
+}
+
+bool nsHTMLScrollFrame::IsLastScrollUpdateAnimating() const {
+ if (!mScrollUpdates.IsEmpty()) {
+ switch (mScrollUpdates.LastElement().GetMode()) {
+ case ScrollMode::Smooth:
+ case ScrollMode::SmoothMsd:
+ return true;
+ case ScrollMode::Instant:
+ case ScrollMode::Normal:
+ break;
+ }
+ }
+ return false;
+}
+
+bool nsHTMLScrollFrame::IsLastScrollUpdateTriggeredByScriptAnimating() const {
+ if (!mScrollUpdates.IsEmpty()) {
+ const ScrollPositionUpdate& lastUpdate = mScrollUpdates.LastElement();
+ if (lastUpdate.WasTriggeredByScript() &&
+ (mScrollUpdates.LastElement().GetMode() == ScrollMode::Smooth ||
+ mScrollUpdates.LastElement().GetMode() == ScrollMode::SmoothMsd)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+using AnimationState = nsIScrollableFrame::AnimationState;
+EnumSet<AnimationState> nsHTMLScrollFrame::ScrollAnimationState() const {
+ EnumSet<AnimationState> retval;
+ if (IsApzAnimationInProgress()) {
+ retval += AnimationState::APZInProgress;
+ if (mCurrentAPZScrollAnimationType ==
+ APZScrollAnimationType::TriggeredByScript) {
+ retval += AnimationState::TriggeredByScript;
+ }
+ }
+
+ if (mApzAnimationRequested) {
+ retval += AnimationState::APZRequested;
+ if (mApzAnimationTriggeredByScriptRequested) {
+ retval += AnimationState::TriggeredByScript;
+ }
+ }
+
+ if (IsLastScrollUpdateAnimating()) {
+ retval += AnimationState::APZPending;
+ if (IsLastScrollUpdateTriggeredByScriptAnimating()) {
+ retval += AnimationState::TriggeredByScript;
+ }
+ }
+ if (mAsyncScroll) {
+ retval += AnimationState::MainThread;
+ if (mAsyncScroll->WasTriggeredByScript()) {
+ retval += AnimationState::TriggeredByScript;
+ }
+ }
+
+ if (mAsyncSmoothMSDScroll) {
+ retval += AnimationState::MainThread;
+ if (mAsyncSmoothMSDScroll->WasTriggeredByScript()) {
+ retval += AnimationState::TriggeredByScript;
+ }
+ }
+ return retval;
+}
+
+void nsHTMLScrollFrame::ResetScrollInfoIfNeeded(
+ const MainThreadScrollGeneration& aGeneration,
+ const APZScrollGeneration& aGenerationOnApz,
+ APZScrollAnimationType aAPZScrollAnimationType,
+ InScrollingGesture aInScrollingGesture) {
+ if (aGeneration == mScrollGeneration) {
+ mLastScrollOrigin = ScrollOrigin::None;
+ mApzAnimationRequested = false;
+ mApzAnimationTriggeredByScriptRequested = false;
+ }
+
+ mScrollGenerationOnApz = aGenerationOnApz;
+ // We can reset this regardless of scroll generation, as this is only set
+ // here, as a response to APZ requesting a repaint.
+ mCurrentAPZScrollAnimationType = aAPZScrollAnimationType;
+
+ mInScrollingGesture = aInScrollingGesture;
+}
+
+UniquePtr<PresState> nsHTMLScrollFrame::SaveState() {
+ nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
+ if (mediator) {
+ // child handles its own scroll state, so don't bother saving state here
+ return nullptr;
+ }
+
+ // Don't store a scroll state if we never have been scrolled or restored
+ // a previous scroll state, and we're not in the middle of a smooth scroll.
+ auto scrollAnimationState = ScrollAnimationState();
+ bool isScrollAnimating =
+ scrollAnimationState.contains(AnimationState::MainThread) ||
+ scrollAnimationState.contains(AnimationState::APZPending) ||
+ scrollAnimationState.contains(AnimationState::APZRequested);
+ if (!mHasBeenScrolled && !mDidHistoryRestore && !isScrollAnimating) {
+ return nullptr;
+ }
+
+ UniquePtr<PresState> state = NewPresState();
+ bool allowScrollOriginDowngrade =
+ !nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) ||
+ mAllowScrollOriginDowngrade;
+ // Save mRestorePos instead of our actual current scroll position, if it's
+ // valid and we haven't moved since the last update of mLastPos (same check
+ // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
+ // while we're in the process of loading content to scroll to a restored
+ // position, we'll keep trying after the reframe. Similarly, if we're in the
+ // middle of a smooth scroll, store the destination so that when we restore
+ // we'll jump straight to the end of the scroll animation, rather than
+ // effectively dropping it. Note that the mRestorePos will override the
+ // smooth scroll destination if both are present.
+ nsPoint pt = GetLogicalVisualViewportOffset();
+ if (isScrollAnimating) {
+ pt = mDestination;
+ allowScrollOriginDowngrade = false;
+ }
+ SCROLLRESTORE_LOG("%p: SaveState, pt=%s, mLastPos=%s, mRestorePos=%s\n", this,
+ ToString(pt).c_str(), ToString(mLastPos).c_str(),
+ ToString(mRestorePos).c_str());
+ if (mRestorePos.y != -1 && pt == mLastPos) {
+ pt = mRestorePos;
+ }
+ state->scrollState() = pt;
+ state->allowScrollOriginDowngrade() = allowScrollOriginDowngrade;
+ if (mIsRoot) {
+ // Only save resolution properties for root scroll frames
+ state->resolution() = PresShell()->GetResolution();
+ }
+ return state;
+}
+
+NS_IMETHODIMP nsHTMLScrollFrame::RestoreState(PresState* aState) {
+ mRestorePos = aState->scrollState();
+ MOZ_ASSERT(mLastScrollOrigin == ScrollOrigin::None);
+ mAllowScrollOriginDowngrade = aState->allowScrollOriginDowngrade();
+ // When restoring state, we promote mLastScrollOrigin to a stronger value
+ // from the default of eNone, to restore the behaviour that existed when
+ // the state was saved. If mLastScrollOrigin was a weaker value previously,
+ // then mAllowScrollOriginDowngrade will be true, and so the combination of
+ // mAllowScrollOriginDowngrade and the stronger mLastScrollOrigin will allow
+ // the same types of scrolls as before. It might be possible to also just
+ // save and restore the mAllowScrollOriginDowngrade and mLastScrollOrigin
+ // values directly without this sort of fiddling. Something to try in the
+ // future or if we tinker with this code more.
+ mLastScrollOrigin = ScrollOrigin::Other;
+ mDidHistoryRestore = true;
+ mLastPos = mScrolledFrame ? GetLogicalVisualViewportOffset() : nsPoint(0, 0);
+ SCROLLRESTORE_LOG("%p: RestoreState, set mRestorePos=%s mLastPos=%s\n", this,
+ ToString(mRestorePos).c_str(), ToString(mLastPos).c_str());
+
+ // Resolution properties should only exist on root scroll frames.
+ MOZ_ASSERT(mIsRoot || aState->resolution() == 1.0);
+
+ if (mIsRoot) {
+ PresShell()->SetResolutionAndScaleTo(
+ aState->resolution(), ResolutionChangeOrigin::MainThreadRestore);
+ }
+ return NS_OK;
+}
+
+void nsHTMLScrollFrame::PostScrolledAreaEvent() {
+ if (mScrolledAreaEvent.IsPending()) {
+ return;
+ }
+ mScrolledAreaEvent = new ScrolledAreaEvent(this);
+ nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ScrolledArea change event dispatch
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsHTMLScrollFrame::ScrolledAreaEvent::Run() {
+ if (mHelper) {
+ mHelper->FireScrolledAreaEvent();
+ }
+ return NS_OK;
+}
+
+void nsHTMLScrollFrame::FireScrolledAreaEvent() {
+ mScrolledAreaEvent.Forget();
+
+ InternalScrollAreaEvent event(true, eScrolledAreaChanged, nullptr);
+ RefPtr<nsPresContext> presContext = PresContext();
+ nsIContent* content = GetContent();
+
+ event.mArea = mScrolledFrame->ScrollableOverflowRectRelativeToParent();
+ if (RefPtr<Document> doc = content->GetUncomposedDoc()) {
+ EventDispatcher::Dispatch(doc, presContext, &event, nullptr);
+ }
+}
+
+ScrollDirections nsIScrollableFrame::GetAvailableScrollingDirections() const {
+ nscoord oneDevPixel =
+ GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel();
+ ScrollDirections directions;
+ nsRect scrollRange = GetScrollRange();
+ if (scrollRange.width >= oneDevPixel) {
+ directions += ScrollDirection::eHorizontal;
+ }
+ if (scrollRange.height >= oneDevPixel) {
+ directions += ScrollDirection::eVertical;
+ }
+ return directions;
+}
+
+nsRect nsHTMLScrollFrame::GetScrollRangeForUserInputEvents() const {
+ // This function computes a scroll range based on a scrolled rect and scroll
+ // port defined as follows:
+ // scrollable rect = overflow:hidden ? layout viewport : scrollable rect
+ // scroll port = have visual viewport ? visual viewport : layout viewport
+ // The results in the same notion of scroll range that APZ uses (the combined
+ // effect of FrameMetrics::CalculateScrollRange() and
+ // nsLayoutUtils::CalculateScrollableRectForFrame).
+
+ ScrollStyles ss = GetScrollStyles();
+
+ nsPoint scrollPos = GetScrollPosition();
+
+ nsRect scrolledRect = GetScrolledRect();
+ if (StyleOverflow::Hidden == ss.mHorizontal) {
+ scrolledRect.width = mScrollPort.width;
+ scrolledRect.x = scrollPos.x;
+ }
+ if (StyleOverflow::Hidden == ss.mVertical) {
+ scrolledRect.height = mScrollPort.height;
+ scrolledRect.y = scrollPos.y;
+ }
+
+ nsSize scrollPort = GetVisualViewportSize();
+
+ nsRect scrollRange = scrolledRect;
+ scrollRange.width = std::max(scrolledRect.width - scrollPort.width, 0);
+ scrollRange.height = std::max(scrolledRect.height - scrollPort.height, 0);
+
+ return scrollRange;
+}
+
+ScrollDirections
+nsHTMLScrollFrame::GetAvailableScrollingDirectionsForUserInputEvents() const {
+ nsRect scrollRange = GetScrollRangeForUserInputEvents();
+
+ // We check if there is at least one half of a screen pixel of scroll range to
+ // roughly match what apz does when it checks if the change in scroll position
+ // in screen pixels round to zero or not.
+ // (https://searchfox.org/mozilla-central/rev/2f09184ec781a2667feec87499d4b81b32b6c48e/gfx/layers/apz/src/AsyncPanZoomController.cpp#3210)
+ // This isn't quite half a screen pixel, it doesn't take into account CSS
+ // transforms, but should be good enough.
+ float halfScreenPixel =
+ GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel() /
+ (PresShell()->GetCumulativeResolution() * 2.f);
+ ScrollDirections directions;
+ if (scrollRange.width >= halfScreenPixel) {
+ directions += ScrollDirection::eHorizontal;
+ }
+ if (scrollRange.height >= halfScreenPixel) {
+ directions += ScrollDirection::eVertical;
+ }
+ return directions;
+}
+
+/**
+ * Append scroll positions for valid snap positions into |aSnapInfo| if
+ * applicable.
+ */
+static void AppendScrollPositionsForSnap(
+ const nsIFrame* aFrame, const nsIFrame* aScrolledFrame,
+ const nsRect& aScrolledRect, const nsMargin& aScrollPadding,
+ const nsRect& aScrollRange, WritingMode aWritingModeOnScroller,
+ ScrollSnapInfo& aSnapInfo, nsHTMLScrollFrame::SnapTargetSet* aSnapTargets) {
+ ScrollSnapTargetId targetId = ScrollSnapUtils::GetTargetIdFor(aFrame);
+
+ nsRect snapArea =
+ ScrollSnapUtils::GetSnapAreaFor(aFrame, aScrolledFrame, aScrolledRect);
+ // Use the writing-mode on the target element if the snap area is larger than
+ // the snapport.
+ // https://drafts.csswg.org/css-scroll-snap/#snap-scope
+ WritingMode writingMode = ScrollSnapUtils::NeedsToRespectTargetWritingMode(
+ snapArea.Size(), aSnapInfo.mSnapportSize)
+ ? aFrame->GetWritingMode()
+ : aWritingModeOnScroller;
+
+ // These snap range shouldn't be involved with scroll-margin since we just
+ // need the visible range of the target element.
+ if (snapArea.width > aSnapInfo.mSnapportSize.width) {
+ aSnapInfo.mXRangeWiderThanSnapport.AppendElement(
+ ScrollSnapInfo::ScrollSnapRange(snapArea, ScrollDirection::eHorizontal,
+ targetId));
+ }
+ if (snapArea.height > aSnapInfo.mSnapportSize.height) {
+ aSnapInfo.mYRangeWiderThanSnapport.AppendElement(
+ ScrollSnapInfo::ScrollSnapRange(snapArea, ScrollDirection::eVertical,
+ targetId));
+ }
+
+ // Shift target rect position by the scroll padding to get the padded
+ // position thus we don't need to take account scroll-padding values in
+ // ScrollSnapUtils::GetSnapPointForDestination() when it gets called from
+ // the compositor thread.
+ snapArea.y -= aScrollPadding.top;
+ snapArea.x -= aScrollPadding.left;
+
+ LogicalRect logicalTargetRect(writingMode, snapArea, aSnapInfo.mSnapportSize);
+ LogicalSize logicalSnapportRect(writingMode, aSnapInfo.mSnapportSize);
+ LogicalRect logicalScrollRange(aWritingModeOnScroller, aScrollRange,
+ // The origin of this logical coordinate system
+ // what we need here is (0, 0), so we use an
+ // empty size.
+ nsSize());
+
+ Maybe<nscoord> blockDirectionPosition;
+ const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
+ nscoord containerBSize = logicalSnapportRect.BSize(writingMode);
+ switch (styleDisplay->mScrollSnapAlign.block) {
+ case StyleScrollSnapAlignKeyword::None:
+ break;
+ case StyleScrollSnapAlignKeyword::Start:
+ blockDirectionPosition.emplace(
+ writingMode.IsVerticalRL() ? -logicalTargetRect.BStart(writingMode)
+ : logicalTargetRect.BStart(writingMode));
+ break;
+ case StyleScrollSnapAlignKeyword::End: {
+ nscoord candidate = std::clamp(
+ // What we need here is the scroll position instead of the snap
+ // position itself, so we need, for example, the top edge of the
+ // scroll port on horizontal-tb when the frame is positioned at
+ // the bottom edge of the scroll port. For this reason we subtract
+ // containerBSize from BEnd of the target and clamp it inside the
+ // scrollable range.
+ logicalTargetRect.BEnd(writingMode) - containerBSize,
+ logicalScrollRange.BStart(writingMode),
+ logicalScrollRange.BEnd(writingMode));
+ blockDirectionPosition.emplace(writingMode.IsVerticalRL() ? -candidate
+ : candidate);
+ break;
+ }
+ case StyleScrollSnapAlignKeyword::Center: {
+ nscoord targetCenter = (logicalTargetRect.BStart(writingMode) +
+ logicalTargetRect.BEnd(writingMode)) /
+ 2;
+ nscoord halfSnapportSize = containerBSize / 2;
+ // Get the center of the target to align with the center of the snapport
+ // depending on direction.
+ nscoord candidate = std::clamp(targetCenter - halfSnapportSize,
+ logicalScrollRange.BStart(writingMode),
+ logicalScrollRange.BEnd(writingMode));
+ blockDirectionPosition.emplace(writingMode.IsVerticalRL() ? -candidate
+ : candidate);
+ break;
+ }
+ }
+
+ Maybe<nscoord> inlineDirectionPosition;
+ nscoord containerISize = logicalSnapportRect.ISize(writingMode);
+ switch (styleDisplay->mScrollSnapAlign.inline_) {
+ case StyleScrollSnapAlignKeyword::None:
+ break;
+ case StyleScrollSnapAlignKeyword::Start:
+ inlineDirectionPosition.emplace(
+ writingMode.IsInlineReversed()
+ ? -logicalTargetRect.IStart(writingMode)
+ : logicalTargetRect.IStart(writingMode));
+ break;
+ case StyleScrollSnapAlignKeyword::End: {
+ nscoord candidate = std::clamp(
+ // Same as above BEnd case, we subtract containerISize.
+ //
+ // Note that the logical scroll range is mapped to [0, x] range even
+ // if it's in RTL contents. So for example, if the physical range is
+ // [-200, 0], it's mapped to [0, 200], i.e. IStart() is 0, IEnd() is
+ // 200. So we can just use std::clamp with the same arguments in both
+ // RTL/LTR cases.
+ logicalTargetRect.IEnd(writingMode) - containerISize,
+ logicalScrollRange.IStart(writingMode),
+ logicalScrollRange.IEnd(writingMode));
+ inlineDirectionPosition.emplace(
+ writingMode.IsInlineReversed() ? -candidate : candidate);
+ break;
+ }
+ case StyleScrollSnapAlignKeyword::Center: {
+ nscoord targetCenter = (logicalTargetRect.IStart(writingMode) +
+ logicalTargetRect.IEnd(writingMode)) /
+ 2;
+ nscoord halfSnapportSize = containerISize / 2;
+ // Get the center of the target to align with the center of the snapport
+ // depending on direction.
+ nscoord candidate = std::clamp(targetCenter - halfSnapportSize,
+ logicalScrollRange.IStart(writingMode),
+ logicalScrollRange.IEnd(writingMode));
+ inlineDirectionPosition.emplace(
+ writingMode.IsInlineReversed() ? -candidate : candidate);
+ break;
+ }
+ }
+
+ if (blockDirectionPosition || inlineDirectionPosition) {
+ aSnapInfo.mSnapTargets.AppendElement(
+ writingMode.IsVertical()
+ ? ScrollSnapInfo::SnapTarget(
+ std::move(blockDirectionPosition),
+ std::move(inlineDirectionPosition), std::move(snapArea),
+ styleDisplay->mScrollSnapStop, targetId)
+ : ScrollSnapInfo::SnapTarget(
+ std::move(inlineDirectionPosition),
+ std::move(blockDirectionPosition), std::move(snapArea),
+ styleDisplay->mScrollSnapStop, targetId));
+ if (aSnapTargets) {
+ aSnapTargets->EnsureInserted(aFrame->GetContent());
+ }
+ }
+}
+
+/**
+ * Collect the scroll positions corresponding to snap positions of frames in the
+ * subtree rooted at |aFrame|, relative to |aScrolledFrame|, into |aSnapInfo|.
+ */
+static void CollectScrollPositionsForSnap(
+ nsIFrame* aFrame, nsIFrame* aScrolledFrame, const nsRect& aScrolledRect,
+ const nsMargin& aScrollPadding, const nsRect& aScrollRange,
+ WritingMode aWritingModeOnScroller, ScrollSnapInfo& aSnapInfo,
+ nsHTMLScrollFrame::SnapTargetSet* aSnapTargets) {
+ // Snap positions only affect the nearest ancestor scroll container on the
+ // element's containing block chain.
+ nsIScrollableFrame* sf = do_QueryFrame(aFrame);
+ if (sf) {
+ return;
+ }
+
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* f : childList.mList) {
+ const nsStyleDisplay* styleDisplay = f->StyleDisplay();
+ if (styleDisplay->mScrollSnapAlign.inline_ !=
+ StyleScrollSnapAlignKeyword::None ||
+ styleDisplay->mScrollSnapAlign.block !=
+ StyleScrollSnapAlignKeyword::None) {
+ AppendScrollPositionsForSnap(
+ f, aScrolledFrame, aScrolledRect, aScrollPadding, aScrollRange,
+ aWritingModeOnScroller, aSnapInfo, aSnapTargets);
+ }
+ CollectScrollPositionsForSnap(
+ f, aScrolledFrame, aScrolledRect, aScrollPadding, aScrollRange,
+ aWritingModeOnScroller, aSnapInfo, aSnapTargets);
+ }
+ }
+}
+
+static nscoord ResolveScrollPaddingStyleValue(
+ const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>&
+ aScrollPaddingStyle,
+ Side aSide, const nsSize& aScrollPortSize) {
+ if (aScrollPaddingStyle.Get(aSide).IsAuto()) {
+ // https://drafts.csswg.org/css-scroll-snap-1/#valdef-scroll-padding-auto
+ return 0;
+ }
+
+ nscoord percentageBasis;
+ switch (aSide) {
+ case eSideTop:
+ case eSideBottom:
+ percentageBasis = aScrollPortSize.height;
+ break;
+ case eSideLeft:
+ case eSideRight:
+ percentageBasis = aScrollPortSize.width;
+ break;
+ }
+
+ return aScrollPaddingStyle.Get(aSide).AsLengthPercentage().Resolve(
+ percentageBasis);
+}
+
+static nsMargin ResolveScrollPaddingStyle(
+ const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>&
+ aScrollPaddingStyle,
+ const nsSize& aScrollPortSize) {
+ return nsMargin(ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideTop,
+ aScrollPortSize),
+ ResolveScrollPaddingStyleValue(aScrollPaddingStyle,
+ eSideRight, aScrollPortSize),
+ ResolveScrollPaddingStyleValue(aScrollPaddingStyle,
+ eSideBottom, aScrollPortSize),
+ ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideLeft,
+ aScrollPortSize));
+}
+
+nsMargin nsHTMLScrollFrame::GetScrollPadding() const {
+ nsIFrame* styleFrame = GetFrameForStyle();
+ if (!styleFrame) {
+ return nsMargin();
+ }
+
+ // The spec says percentage values are relative to the scroll port size.
+ // https://drafts.csswg.org/css-scroll-snap-1/#scroll-padding
+ return ResolveScrollPaddingStyle(styleFrame->StylePadding()->mScrollPadding,
+ GetScrollPortRect().Size());
+}
+
+ScrollSnapInfo nsHTMLScrollFrame::ComputeScrollSnapInfo() {
+ ScrollSnapInfo result;
+
+ nsIFrame* scrollSnapFrame = GetFrameForStyle();
+ if (!scrollSnapFrame) {
+ return result;
+ }
+
+ const nsStyleDisplay* disp = scrollSnapFrame->StyleDisplay();
+ if (disp->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) {
+ // We won't be snapping, short-circuit the computation.
+ return result;
+ }
+
+ WritingMode writingMode = GetWritingMode();
+ result.InitializeScrollSnapStrictness(writingMode, disp);
+
+ result.mSnapportSize = GetSnapportSize();
+ CollectScrollPositionsForSnap(
+ mScrolledFrame, mScrolledFrame, GetScrolledRect(), GetScrollPadding(),
+ GetLayoutScrollRange(), writingMode, result, &mSnapTargets);
+ return result;
+}
+
+ScrollSnapInfo nsHTMLScrollFrame::GetScrollSnapInfo() {
+ // TODO(botond): Should we cache it?
+ return ComputeScrollSnapInfo();
+}
+
+Maybe<SnapDestination> nsHTMLScrollFrame::GetSnapPointForDestination(
+ ScrollUnit aUnit, ScrollSnapFlags aFlags, const nsPoint& aStartPos,
+ const nsPoint& aDestination) {
+ // We can release the strong references for the previous snap target
+ // elements here since calling this ComputeScrollSnapInfo means we are going
+ // to evaluate new snap points, thus there's no chance to generating
+ // nsIContent instances in between this function call and the function call
+ // for the (re-)evaluation.
+ mSnapTargets.Clear();
+ return ScrollSnapUtils::GetSnapPointForDestination(
+ ComputeScrollSnapInfo(), aUnit, aFlags, GetLayoutScrollRange(), aStartPos,
+ aDestination);
+}
+
+Maybe<SnapDestination> nsHTMLScrollFrame::GetSnapPointForResnap() {
+ // Same as in GetSnapPointForDestination, We can release the strong references
+ // for the previous snap targets here.
+ mSnapTargets.Clear();
+ nsIContent* focusedContent =
+ GetContent()->GetComposedDoc()->GetUnretargetedFocusedContent();
+ return ScrollSnapUtils::GetSnapPointForResnap(
+ ComputeScrollSnapInfo(), GetLayoutScrollRange(), GetScrollPosition(),
+ mLastSnapTargetIds, focusedContent);
+}
+
+bool nsHTMLScrollFrame::NeedsResnap() {
+ nsIContent* focusedContent =
+ GetContent()->GetComposedDoc()->GetUnretargetedFocusedContent();
+ return ScrollSnapUtils::GetSnapPointForResnap(
+ ComputeScrollSnapInfo(), GetLayoutScrollRange(),
+ GetScrollPosition(), mLastSnapTargetIds, focusedContent)
+ .isSome();
+}
+
+void nsHTMLScrollFrame::SetLastSnapTargetIds(
+ UniquePtr<ScrollSnapTargetIds> aIds) {
+ if (!aIds) {
+ mLastSnapTargetIds = nullptr;
+ return;
+ }
+
+ // This SetLastSnapTargetIds gets called asynchronously so that there's a race
+ // condition something like;
+ // 1) an async scroll operation triggered snapping to a point on an element
+ // 2) during the async scroll operation, the element got removed from this
+ // scroll container
+ // 3) re-snapping triggered
+ // 4) this SetLastSnapTargetIds got called
+ // In such case |aIds| are stale, we shouldn't use it.
+ for (const auto* idList : {&aIds->mIdsOnX, &aIds->mIdsOnY}) {
+ for (const auto id : *idList) {
+ if (!mSnapTargets.Contains(reinterpret_cast<nsIContent*>(id))) {
+ mLastSnapTargetIds = nullptr;
+ return;
+ }
+ }
+ }
+
+ mLastSnapTargetIds = std::move(aIds);
+}
+
+bool nsHTMLScrollFrame::IsLastSnappedTarget(const nsIFrame* aFrame) const {
+ ScrollSnapTargetId id = ScrollSnapUtils::GetTargetIdFor(aFrame);
+ MOZ_ASSERT(id != ScrollSnapTargetId::None,
+ "This function is supposed to be called for contents");
+
+ if (!mLastSnapTargetIds) {
+ return false;
+ }
+
+ return mLastSnapTargetIds->mIdsOnX.Contains(id) ||
+ mLastSnapTargetIds->mIdsOnY.Contains(id);
+}
+
+void nsHTMLScrollFrame::TryResnap() {
+ // If there's any async scroll is running or we are processing pan/touch
+ // gestures or scroll thumb dragging, don't clobber the scroll.
+ if (!ScrollAnimationState().isEmpty() ||
+ mInScrollingGesture == InScrollingGesture::Yes) {
+ return;
+ }
+
+ if (auto snapDestination = GetSnapPointForResnap()) {
+ // We are going to re-snap so that we need to clobber scroll anchoring.
+ mAnchor.UserScrolled();
+
+ // Snap to the nearest snap position if exists.
+ ScrollToWithOrigin(
+ snapDestination->mPosition, nullptr /* range */,
+ ScrollOperationParams{
+ IsSmoothScroll(ScrollBehavior::Auto) ? ScrollMode::SmoothMsd
+ : ScrollMode::Instant,
+ ScrollOrigin::Other, std::move(snapDestination->mTargetIds)});
+ }
+}
+
+void nsHTMLScrollFrame::PostPendingResnapIfNeeded(const nsIFrame* aFrame) {
+ if (!IsLastSnappedTarget(aFrame)) {
+ return;
+ }
+
+ PostPendingResnap();
+}
+
+void nsHTMLScrollFrame::PostPendingResnap() {
+ PresShell()->PostPendingScrollResnap(this);
+}
+
+nsIScrollableFrame::PhysicalScrollSnapAlign
+nsHTMLScrollFrame::GetScrollSnapAlignFor(const nsIFrame* aFrame) const {
+ StyleScrollSnapAlignKeyword alignForY = StyleScrollSnapAlignKeyword::None;
+ StyleScrollSnapAlignKeyword alignForX = StyleScrollSnapAlignKeyword::None;
+
+ nsIFrame* styleFrame = GetFrameForStyle();
+ if (!styleFrame) {
+ return {alignForX, alignForY};
+ }
+
+ if (styleFrame->StyleDisplay()->mScrollSnapType.strictness ==
+ StyleScrollSnapStrictness::None) {
+ return {alignForX, alignForY};
+ }
+
+ const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
+ if (styleDisplay->mScrollSnapAlign.inline_ ==
+ StyleScrollSnapAlignKeyword::None &&
+ styleDisplay->mScrollSnapAlign.block ==
+ StyleScrollSnapAlignKeyword::None) {
+ return {alignForX, alignForY};
+ }
+
+ nsSize snapAreaSize =
+ ScrollSnapUtils::GetSnapAreaFor(aFrame, mScrolledFrame, GetScrolledRect())
+ .Size();
+ const WritingMode writingMode =
+ ScrollSnapUtils::NeedsToRespectTargetWritingMode(snapAreaSize,
+ GetSnapportSize())
+ ? aFrame->GetWritingMode()
+ : styleFrame->GetWritingMode();
+
+ switch (styleFrame->StyleDisplay()->mScrollSnapType.axis) {
+ case StyleScrollSnapAxis::X:
+ alignForX = writingMode.IsVertical()
+ ? styleDisplay->mScrollSnapAlign.block
+ : styleDisplay->mScrollSnapAlign.inline_;
+ break;
+ case StyleScrollSnapAxis::Y:
+ alignForY = writingMode.IsVertical()
+ ? styleDisplay->mScrollSnapAlign.inline_
+ : styleDisplay->mScrollSnapAlign.block;
+ break;
+ case StyleScrollSnapAxis::Block:
+ if (writingMode.IsVertical()) {
+ alignForX = styleDisplay->mScrollSnapAlign.block;
+ } else {
+ alignForY = styleDisplay->mScrollSnapAlign.block;
+ }
+ break;
+ case StyleScrollSnapAxis::Inline:
+ if (writingMode.IsVertical()) {
+ alignForY = styleDisplay->mScrollSnapAlign.inline_;
+ } else {
+ alignForX = styleDisplay->mScrollSnapAlign.inline_;
+ }
+ break;
+ case StyleScrollSnapAxis::Both:
+ if (writingMode.IsVertical()) {
+ alignForX = styleDisplay->mScrollSnapAlign.block;
+ alignForY = styleDisplay->mScrollSnapAlign.inline_;
+ } else {
+ alignForX = styleDisplay->mScrollSnapAlign.inline_;
+ alignForY = styleDisplay->mScrollSnapAlign.block;
+ }
+ break;
+ }
+
+ return {alignForX, alignForY};
+}
+
+bool nsHTMLScrollFrame::UsesOverlayScrollbars() const {
+ return PresContext()->UseOverlayScrollbars();
+}
+
+bool nsHTMLScrollFrame::DragScroll(WidgetEvent* aEvent) {
+ // Dragging is allowed while within a 20 pixel border. Note that device pixels
+ // are used so that the same margin is used even when zoomed in or out.
+ nscoord margin = 20 * PresContext()->AppUnitsPerDevPixel();
+
+ // Don't drag scroll for small scrollareas.
+ if (mScrollPort.width < margin * 2 || mScrollPort.height < margin * 2) {
+ return false;
+ }
+
+ // If willScroll is computed as false, then the frame is already scrolled as
+ // far as it can go in both directions. Return false so that an ancestor
+ // scrollframe can scroll instead.
+ bool willScroll = false;
+ nsPoint pnt =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
+ nsPoint scrollPoint = GetScrollPosition();
+ nsRect rangeRect = GetLayoutScrollRange();
+
+ // Only drag scroll when a scrollbar is present.
+ nsPoint offset;
+ if (mHasHorizontalScrollbar) {
+ if (pnt.x >= mScrollPort.x && pnt.x <= mScrollPort.x + margin) {
+ offset.x = -margin;
+ if (scrollPoint.x > 0) {
+ willScroll = true;
+ }
+ } else if (pnt.x >= mScrollPort.XMost() - margin &&
+ pnt.x <= mScrollPort.XMost()) {
+ offset.x = margin;
+ if (scrollPoint.x < rangeRect.width) {
+ willScroll = true;
+ }
+ }
+ }
+
+ if (mHasVerticalScrollbar) {
+ if (pnt.y >= mScrollPort.y && pnt.y <= mScrollPort.y + margin) {
+ offset.y = -margin;
+ if (scrollPoint.y > 0) {
+ willScroll = true;
+ }
+ } else if (pnt.y >= mScrollPort.YMost() - margin &&
+ pnt.y <= mScrollPort.YMost()) {
+ offset.y = margin;
+ if (scrollPoint.y < rangeRect.height) {
+ willScroll = true;
+ }
+ }
+ }
+
+ if (offset.x || offset.y) {
+ ScrollToWithOrigin(
+ GetScrollPosition() + offset, nullptr /* range */,
+ ScrollOperationParams{ScrollMode::Normal, ScrollOrigin::Other});
+ }
+
+ return willScroll;
+}
+
+static nsSliderFrame* GetSliderFrame(nsIFrame* aScrollbarFrame) {
+ if (!aScrollbarFrame) {
+ return nullptr;
+ }
+
+ for (const auto& childList : aScrollbarFrame->ChildLists()) {
+ for (nsIFrame* frame : childList.mList) {
+ if (nsSliderFrame* sliderFrame = do_QueryFrame(frame)) {
+ return sliderFrame;
+ }
+ }
+ }
+ return nullptr;
+}
+
+static void AsyncScrollbarDragInitiated(uint64_t aDragBlockId,
+ nsIFrame* aScrollbar) {
+ if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) {
+ sliderFrame->AsyncScrollbarDragInitiated(aDragBlockId);
+ }
+}
+
+void nsHTMLScrollFrame::AsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, ScrollDirection aDirection) {
+ switch (aDirection) {
+ case ScrollDirection::eVertical:
+ ::AsyncScrollbarDragInitiated(aDragBlockId, mVScrollbarBox);
+ break;
+ case ScrollDirection::eHorizontal:
+ ::AsyncScrollbarDragInitiated(aDragBlockId, mHScrollbarBox);
+ break;
+ }
+}
+
+static void AsyncScrollbarDragRejected(nsIFrame* aScrollbar) {
+ if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) {
+ sliderFrame->AsyncScrollbarDragRejected();
+ }
+}
+
+void nsHTMLScrollFrame::AsyncScrollbarDragRejected() {
+ // We don't get told which scrollbar requested the async drag,
+ // so we notify both.
+ ::AsyncScrollbarDragRejected(mHScrollbarBox);
+ ::AsyncScrollbarDragRejected(mVScrollbarBox);
+}
+
+void nsHTMLScrollFrame::ApzSmoothScrollTo(
+ const nsPoint& aDestination, ScrollMode aMode, ScrollOrigin aOrigin,
+ ScrollTriggeredByScript aTriggeredByScript,
+ UniquePtr<ScrollSnapTargetIds> aSnapTargetIds) {
+ if (mApzSmoothScrollDestination == Some(aDestination)) {
+ // If we already sent APZ a smooth-scroll request to this
+ // destination (i.e. it was the last request
+ // we sent), then don't send another one because it is redundant.
+ // This is to avoid a scenario where pages do repeated scrollBy
+ // calls, incrementing the generation counter, and blocking APZ from
+ // syncing the scroll offset back to the main thread.
+ // Note that if we get two smooth-scroll requests to the same
+ // destination with some other scroll in between,
+ // mApzSmoothScrollDestination will get reset to Nothing() and so
+ // we shouldn't have the problem where this check discards a
+ // legitimate smooth-scroll.
+ return;
+ }
+
+ // The animation will be handled in the compositor, pass the
+ // information needed to start the animation and skip the main-thread
+ // animation for this scroll.
+ MOZ_ASSERT(aOrigin != ScrollOrigin::None);
+ mApzSmoothScrollDestination = Some(aDestination);
+ AppendScrollUpdate(ScrollPositionUpdate::NewSmoothScroll(
+ aMode, aOrigin, aDestination, aTriggeredByScript,
+ std::move(aSnapTargetIds)));
+
+ nsIContent* content = GetContent();
+ if (!DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(content)) {
+ // If this frame doesn't have a displayport then there won't be an
+ // APZC instance for it and so there won't be anything to process
+ // this smooth scroll request. We should set a displayport on this
+ // frame to force an APZC which can handle the request.
+ if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
+ mozilla::layers::ScrollableLayerGuid::ViewID viewID =
+ mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ nsLayoutUtils::FindIDFor(content, &viewID);
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Debug,
+ ("ApzSmoothScrollTo setting displayport on scrollId=%" PRIu64 "\n",
+ viewID));
+ }
+
+ DisplayPortUtils::CalculateAndSetDisplayPortMargins(
+ GetScrollTargetFrame(), DisplayPortUtils::RepaintMode::Repaint);
+ nsIFrame* frame = do_QueryFrame(GetScrollTargetFrame());
+ DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame);
+ }
+
+ // Schedule a paint to ensure that the frame metrics get updated on
+ // the compositor thread.
+ SchedulePaint();
+}
+
+bool nsHTMLScrollFrame::CanApzScrollInTheseDirections(
+ ScrollDirections aDirections) {
+ ScrollStyles styles = GetScrollStyles();
+ if (aDirections.contains(ScrollDirection::eHorizontal) &&
+ styles.mHorizontal == StyleOverflow::Hidden)
+ return false;
+ if (aDirections.contains(ScrollDirection::eVertical) &&
+ styles.mVertical == StyleOverflow::Hidden)
+ return false;
+ return true;
+}
+
+bool nsHTMLScrollFrame::SmoothScrollVisual(
+ const nsPoint& aVisualViewportOffset,
+ FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
+ bool canDoApzSmoothScroll =
+ nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll();
+ if (!canDoApzSmoothScroll) {
+ return false;
+ }
+
+ // Clamp the destination to the visual scroll range.
+ // There is a potential issue here, where |mDestination| is usually
+ // clamped to the layout scroll range, and so e.g. a subsequent
+ // window.scrollBy() may have an undesired effect. However, as this function
+ // is only called internally, this should not be a problem in practice.
+ // If it turns out to be, the fix would be:
+ // - add a new "destination" field that doesn't have to be clamped to
+ // the layout scroll range
+ // - clamp mDestination to the layout scroll range here
+ // - make sure ComputeScrollMetadata() picks up the former as the
+ // smooth scroll destination to send to APZ.
+ mDestination = GetVisualScrollRange().ClampPoint(aVisualViewportOffset);
+
+ UniquePtr<ScrollSnapTargetIds> snapTargetIds;
+ // Perform the scroll.
+ ApzSmoothScrollTo(mDestination, ScrollMode::SmoothMsd,
+ aUpdateType == FrameMetrics::eRestore
+ ? ScrollOrigin::Restore
+ : ScrollOrigin::Other,
+ ScrollTriggeredByScript::No, std::move(snapTargetIds));
+ return true;
+}
+
+bool nsHTMLScrollFrame::IsSmoothScroll(dom::ScrollBehavior aBehavior) const {
+ if (aBehavior == dom::ScrollBehavior::Instant) {
+ return false;
+ }
+
+ // The user smooth scrolling preference should be honored for any requested
+ // smooth scrolls. A requested smooth scroll when smooth scrolling is
+ // disabled should be equivalent to an instant scroll. This is not enforced
+ // for the <scrollbox> XUL element to allow for the browser chrome to
+ // override this behavior when toolkit.scrollbox.smoothScroll is enabled.
+ if (!GetContent()->IsXULElement(nsGkAtoms::scrollbox)) {
+ if (!nsLayoutUtils::IsSmoothScrollingEnabled()) {
+ return false;
+ }
+ } else {
+ if (!StaticPrefs::toolkit_scrollbox_smoothScroll()) {
+ return false;
+ }
+ }
+
+ if (aBehavior == dom::ScrollBehavior::Smooth) {
+ return true;
+ }
+
+ nsIFrame* styleFrame = GetFrameForStyle();
+ if (!styleFrame) {
+ return false;
+ }
+ return (aBehavior == dom::ScrollBehavior::Auto &&
+ styleFrame->StyleDisplay()->mScrollBehavior ==
+ StyleScrollBehavior::Smooth);
+}
+
+nsTArray<ScrollPositionUpdate> nsHTMLScrollFrame::GetScrollUpdates() const {
+ return mScrollUpdates.Clone();
+}
+
+void nsHTMLScrollFrame::AppendScrollUpdate(
+ const ScrollPositionUpdate& aUpdate) {
+ mScrollGeneration = aUpdate.GetGeneration();
+ mScrollUpdates.AppendElement(aUpdate);
+}
+
+void nsHTMLScrollFrame::ScheduleScrollAnimations() {
+ nsIContent* content = GetContent();
+ MOZ_ASSERT(content && content->IsElement(),
+ "The nsIScrollableFrame should have the element.");
+
+ const Element* elementOrPseudo = content->AsElement();
+ PseudoStyleType pseudo = elementOrPseudo->GetPseudoElementType();
+ if (pseudo != PseudoStyleType::NotPseudo &&
+ !AnimationUtils::IsSupportedPseudoForAnimations(pseudo)) {
+ // This is not an animatable pseudo element, and so we don't generate
+ // scroll-timeline for it.
+ return;
+ }
+
+ const auto [element, type] =
+ AnimationUtils::GetElementPseudoPair(elementOrPseudo);
+ const auto* scheduler = ProgressTimelineScheduler::Get(element, type);
+ if (!scheduler) {
+ // We don't have scroll timelines associated with this frame.
+ return;
+ }
+
+ scheduler->ScheduleAnimations();
+}
diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h
new file mode 100644
index 0000000000..e05a2a077b
--- /dev/null
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -0,0 +1,1090 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object to wrap rendering objects that should be scrollable */
+
+#ifndef nsGfxScrollFrame_h___
+#define nsGfxScrollFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollbarMediator.h"
+#include "nsIStatefulFrame.h"
+#include "nsThreadUtils.h"
+#include "nsIReflowCallback.h"
+#include "nsQueryFrame.h"
+#include "nsExpirationTracker.h"
+#include "TextOverflow.h"
+#include "ScrollVelocityQueue.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/PresState.h"
+#include "mozilla/layout/ScrollAnchorContainer.h"
+#include "mozilla/TypedEnumBits.h"
+
+class nsPresContext;
+class nsIContent;
+class nsAtom;
+class nsIScrollPositionListener;
+class AutoContainsBlendModeCapturer;
+
+namespace mozilla {
+class PresShell;
+enum class StyleScrollbarWidth : uint8_t;
+struct ScrollReflowInput;
+struct StyleScrollSnapAlign;
+namespace layers {
+class Layer;
+class WebRenderLayerManager;
+} // namespace layers
+namespace layout {
+class ScrollbarActivity;
+} // namespace layout
+
+} // namespace mozilla
+
+/**
+ * The scroll frame creates and manages the scrolling view
+ *
+ * It only supports having a single child frame that typically is an area
+ * frame, but doesn't have to be. The child frame must have a view, though
+ *
+ * Scroll frames don't support incremental changes, i.e. you can't replace
+ * or remove the scrolled frame
+ */
+class nsHTMLScrollFrame : public nsContainerFrame,
+ public nsIScrollableFrame,
+ public nsIAnonymousContentCreator,
+ public nsIReflowCallback,
+ public nsIStatefulFrame {
+ public:
+ using Sides = nsIFrame::Sides;
+ using ScrollbarActivity = mozilla::layout::ScrollbarActivity;
+ using ScrollSnapFlags = mozilla::ScrollSnapFlags;
+ using ScrollTriggeredByScript = mozilla::ScrollTriggeredByScript;
+ using ScrollSnapTargetIds = mozilla::ScrollSnapTargetIds;
+ using FrameMetrics = mozilla::layers::FrameMetrics;
+ using ScrollableLayerGuid = mozilla::layers::ScrollableLayerGuid;
+ using ScrollSnapInfo = mozilla::ScrollSnapInfo;
+ using WebRenderLayerManager = mozilla::layers::WebRenderLayerManager;
+ using APZScrollAnimationType = mozilla::APZScrollAnimationType;
+ using ScrollDirections = mozilla::layers::ScrollDirections;
+ using ScrollDirection = mozilla::layers::ScrollDirection;
+ using Element = mozilla::dom::Element;
+ using SnapTargetSet = nsTHashSet<RefPtr<nsIContent>>;
+ using InScrollingGesture = nsIScrollableFrame::InScrollingGesture;
+
+ using CSSIntPoint = mozilla::CSSIntPoint;
+ using CSSPoint = mozilla::CSSPoint;
+ using ScrollReflowInput = mozilla::ScrollReflowInput;
+ using ScrollAnchorContainer = mozilla::layout::ScrollAnchorContainer;
+ friend nsHTMLScrollFrame* NS_NewHTMLScrollFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle, bool aIsRoot);
+ friend class mozilla::layout::ScrollAnchorContainer;
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsHTMLScrollFrame)
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ bool TryLayout(ScrollReflowInput& aState, ReflowOutput* aKidMetrics,
+ bool aAssumeHScroll, bool aAssumeVScroll, bool aForce);
+
+ // Return true if ReflowScrolledFrame is going to do something different based
+ // on the presence of a horizontal scrollbar in a horizontal writing mode or a
+ // vertical scrollbar in a vertical writing mode.
+ bool ScrolledContentDependsOnBSize(const ScrollReflowInput& aState) const;
+
+ void ReflowScrolledFrame(ScrollReflowInput& aState, bool aAssumeHScroll,
+ bool aAssumeVScroll, ReflowOutput* aMetrics);
+ void ReflowContents(ScrollReflowInput& aState,
+ const ReflowOutput& aDesiredSize);
+ void PlaceScrollArea(ScrollReflowInput& aState,
+ const nsPoint& aScrollPosition);
+
+ // Return the sum of inline-size of the scrollbar gutters (if any) at the
+ // inline-start and inline-end edges of the scroll frame (for a potential
+ // scrollbar that scrolls in the block axis).
+ nscoord IntrinsicScrollbarGutterSizeAtInlineEdges();
+
+ // Compute stable scrollbar-gutter from scrollbar-width and scrollbar-gutter
+ // properties.
+ //
+ // Note: this method doesn't consider overflow property and the space created
+ // by the actual scrollbars. That is, if scrollbar-gutter is 'auto', this
+ // method just returns empty result.
+ nsMargin ComputeStableScrollbarGutter(
+ const mozilla::StyleScrollbarWidth& aStyleScrollbarWidth,
+ const mozilla::StyleScrollbarGutter& aStyleScrollbarGutter) const;
+
+ bool GetBorderRadii(const nsSize& aFrameSize, const nsSize& aBorderArea,
+ Sides aSkipSides, nscoord aRadii[8]) const final;
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+ void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) override;
+
+ bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) final;
+
+ BaselineSharingGroup GetDefaultBaselineSharingGroup() const override;
+ nscoord SynthesizeFallbackBaseline(
+ mozilla::WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup) const override;
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const override;
+
+ // Recomputes the scrollable overflow area we store in the helper to take
+ // children that are affected by perpsective set on the outer frame and scroll
+ // at different rates.
+ void AdjustForPerspective(nsRect& aScrollableOverflow);
+
+ // Called to set the child frames. We typically have three: the scroll area,
+ // the vertical scrollbar, and the horizontal scrollbar.
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) final;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) final;
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) final;
+
+ void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) final;
+
+ void Destroy(DestroyContext&) override;
+
+ nsIScrollableFrame* GetScrollTargetFrame() const final {
+ return const_cast<nsHTMLScrollFrame*>(this);
+ }
+
+ nsContainerFrame* GetContentInsertionFrame() override {
+ return GetScrolledFrame()->GetContentInsertionFrame();
+ }
+
+ nsPoint GetPositionOfChildIgnoringScrolling(const nsIFrame* aChild) final {
+ nsPoint pt = aChild->GetPosition();
+ if (aChild == GetScrolledFrame()) {
+ pt += GetScrollPosition();
+ }
+ return pt;
+ }
+
+ // nsIAnonymousContentCreator
+ nsresult CreateAnonymousContent(nsTArray<ContentInfo>&) final;
+ void AppendAnonymousContentTo(nsTArray<nsIContent*>&, uint32_t aFilter) final;
+
+ // nsIScrollableFrame
+ nsIFrame* GetScrolledFrame() const final { return mScrolledFrame; }
+ mozilla::ScrollStyles GetScrollStyles() const final;
+ bool IsForTextControlWithNoScrollbars() const final;
+ bool HasAllNeededScrollbars() const final {
+ return GetCurrentAnonymousContent().contains(GetNeededAnonymousContent());
+ }
+
+ mozilla::layers::OverscrollBehaviorInfo GetOverscrollBehaviorInfo()
+ const final;
+ ScrollDirections GetAvailableScrollingDirectionsForUserInputEvents()
+ const final;
+ ScrollDirections GetScrollbarVisibility() const final {
+ ScrollDirections result;
+ if (mHasHorizontalScrollbar) {
+ result += ScrollDirection::eHorizontal;
+ }
+ if (mHasVerticalScrollbar) {
+ result += ScrollDirection::eVertical;
+ }
+ return result;
+ }
+ nsMargin GetActualScrollbarSizes(
+ nsIScrollableFrame::ScrollbarSizesOptions aOptions =
+ nsIScrollableFrame::ScrollbarSizesOptions::NONE) const final;
+ nsMargin GetDesiredScrollbarSizes() const final;
+ static nscoord GetNonOverlayScrollbarSize(const nsPresContext*,
+ mozilla::StyleScrollbarWidth);
+ nsSize GetLayoutSize() const final {
+ if (mIsUsingMinimumScaleSize) {
+ return mICBSize;
+ }
+ return mScrollPort.Size();
+ }
+ nsRect GetScrolledRect() const final;
+ nsRect GetScrollPortRect() const final { return mScrollPort; }
+ nsPoint GetScrollPosition() const final {
+ return mScrollPort.TopLeft() - mScrolledFrame->GetPosition();
+ }
+ nsPoint GetLogicalScrollPosition() const final {
+ nsPoint pt;
+ pt.x = IsPhysicalLTR()
+ ? mScrollPort.x - mScrolledFrame->GetPosition().x
+ : mScrollPort.XMost() - mScrolledFrame->GetRect().XMost();
+ pt.y = mScrollPort.y - mScrolledFrame->GetPosition().y;
+ return pt;
+ }
+
+ nsRect GetScrollRange() const final { return GetLayoutScrollRange(); }
+ nsSize GetVisualViewportSize() const final;
+ nsPoint GetVisualViewportOffset() const final;
+ bool SetVisualViewportOffset(const nsPoint& aOffset, bool aRepaint) final;
+ nsRect GetVisualScrollRange() const final;
+ nsRect GetScrollRangeForUserInputEvents() const final;
+ nsSize GetLineScrollAmount() const final;
+ nsSize GetPageScrollAmount() const final;
+ nsMargin GetScrollPadding() const final;
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void ScrollTo(nsPoint aScrollPosition, ScrollMode aMode,
+ const nsRect* aRange = nullptr,
+ ScrollSnapFlags aSnapFlags = ScrollSnapFlags::Disabled,
+ ScrollTriggeredByScript aTriggeredByScript =
+ ScrollTriggeredByScript::No) final {
+ return ScrollToInternal(aScrollPosition, aMode, ScrollOrigin::Other, aRange,
+ aSnapFlags, aTriggeredByScript);
+ }
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void ScrollToCSSPixels(const CSSIntPoint& aScrollPosition,
+ ScrollMode aMode = ScrollMode::Instant) final;
+ void ScrollToCSSPixelsForApz(const mozilla::CSSPoint& aScrollPosition,
+ ScrollSnapTargetIds&& aLastSnapTargetIds) final;
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ CSSIntPoint GetRoundedScrollPositionCSSPixels() final;
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void ScrollBy(nsIntPoint aDelta, mozilla::ScrollUnit aUnit, ScrollMode aMode,
+ nsIntPoint* aOverflow,
+ ScrollOrigin aOrigin = ScrollOrigin::NotSpecified,
+ nsIScrollableFrame::ScrollMomentum aMomentum =
+ nsIScrollableFrame::NOT_MOMENTUM,
+ ScrollSnapFlags aSnapFlags = ScrollSnapFlags::Disabled) final;
+ void ScrollByCSSPixels(const CSSIntPoint& aDelta,
+ ScrollMode aMode = ScrollMode::Instant) final {
+ return ScrollByCSSPixelsInternal(aDelta, aMode);
+ }
+ void ScrollByCSSPixelsInternal(
+ const CSSIntPoint& aDelta, ScrollMode aMode = ScrollMode::Instant,
+ // This ScrollByCSSPixels is mainly used for Element.scrollBy and
+ // Window.scrollBy. An exception is the transmogrification of
+ // ScrollToCSSPixels.
+ ScrollSnapFlags aSnapFlags = ScrollSnapFlags::IntendedDirection |
+ ScrollSnapFlags::IntendedEndPosition);
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void ScrollToRestoredPosition() final;
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void CurPosAttributeChanged(nsIContent* aChild) final {
+ return CurPosAttributeChangedInternal(aChild);
+ }
+ NS_IMETHOD PostScrolledAreaEventForCurrentArea() final {
+ PostScrolledAreaEvent();
+ return NS_OK;
+ }
+ bool IsScrollingActive() const final;
+ bool IsMaybeAsynchronouslyScrolled() const final {
+ // If this is true, then we'll build an ASR, and that's what we want
+ // to know I think.
+ return mWillBuildScrollableLayer;
+ }
+
+ bool DidHistoryRestore() const final { return mDidHistoryRestore; }
+ void ClearDidHistoryRestore() final { mDidHistoryRestore = false; }
+ void MarkEverScrolled() final;
+ bool IsRectNearlyVisible(const nsRect& aRect) const final;
+ nsRect ExpandRectToNearlyVisible(const nsRect& aRect) const final;
+ ScrollOrigin LastScrollOrigin() const final { return mLastScrollOrigin; }
+ using AnimationState = nsIScrollableFrame::AnimationState;
+ mozilla::EnumSet<AnimationState> ScrollAnimationState() const final;
+ mozilla::MainThreadScrollGeneration CurrentScrollGeneration() const final {
+ return mScrollGeneration;
+ }
+ mozilla::APZScrollGeneration ScrollGenerationOnApz() const final {
+ return mScrollGenerationOnApz;
+ }
+ nsPoint LastScrollDestination() final { return mDestination; }
+ nsTArray<mozilla::ScrollPositionUpdate> GetScrollUpdates() const final;
+ bool HasScrollUpdates() const final { return !mScrollUpdates.IsEmpty(); }
+ void ResetScrollInfoIfNeeded(
+ const mozilla::MainThreadScrollGeneration& aGeneration,
+ const mozilla::APZScrollGeneration& aGenerationOnApz,
+ mozilla::APZScrollAnimationType aAPZScrollAnimationType,
+ InScrollingGesture aInScrollingGesture) final;
+ bool WantAsyncScroll() const final;
+ mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
+ mozilla::layers::WebRenderLayerManager* aLayerManager,
+ const nsIFrame* aItemFrame,
+ const nsPoint& aOffsetToReferenceFrame) const final;
+ void MarkScrollbarsDirtyForReflow() const final;
+ void InvalidateScrollbars() const final;
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * Update scrollbar curpos attributes to reflect current scroll position
+ */
+ void UpdateScrollbarPosition() final;
+ bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
+ nsRect* aVisibleRect, nsRect* aDirtyRect,
+ bool aSetBase) final {
+ return DecideScrollableLayer(aBuilder, aVisibleRect, aDirtyRect, aSetBase,
+ nullptr);
+ }
+ void NotifyApzTransaction() final;
+ void NotifyApproximateFrameVisibilityUpdate(bool aIgnoreDisplayPort) final;
+ bool GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
+ nsRect* aDisplayPort) final;
+ void TriggerDisplayPortExpiration() final;
+
+ // nsIStatefulFrame
+ mozilla::UniquePtr<mozilla::PresState> SaveState() final;
+ NS_IMETHOD RestoreState(mozilla::PresState* aState) final;
+
+ // nsIScrollbarMediator
+ void ScrollByPage(
+ nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ ScrollSnapFlags aSnapFlags = ScrollSnapFlags::Disabled) final;
+ void ScrollByWhole(
+ nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ ScrollSnapFlags aSnapFlags = ScrollSnapFlags::Disabled) final;
+ void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ ScrollSnapFlags = ScrollSnapFlags::Disabled) final;
+ void ScrollByUnit(nsScrollbarFrame* aScrollbar, ScrollMode aMode,
+ int32_t aDirection, mozilla::ScrollUnit aUnit,
+ ScrollSnapFlags = ScrollSnapFlags::Disabled) final;
+ void RepeatButtonScroll(nsScrollbarFrame* aScrollbar) final;
+ void ThumbMoved(nsScrollbarFrame* aScrollbar, nscoord aOldPos,
+ nscoord aNewPos) final;
+ void ScrollbarReleased(nsScrollbarFrame* aScrollbar) final;
+ void VisibilityChanged(bool aVisible) final {}
+ nsScrollbarFrame* GetScrollbarBox(bool aVertical) final {
+ return aVertical ? mVScrollbarBox : mHScrollbarBox;
+ }
+
+ void ScrollbarActivityStarted() const final;
+ void ScrollbarActivityStopped() const final;
+
+ bool IsScrollbarOnRight() const final;
+
+ bool ShouldSuppressScrollbarRepaints() const final {
+ return mSuppressScrollbarRepaints;
+ }
+ void SetTransformingByAPZ(bool aTransforming) final;
+ bool IsTransformingByAPZ() const final { return mTransformingByAPZ; }
+ void SetScrollableByAPZ(bool aScrollable) final;
+ void SetZoomableByAPZ(bool aZoomable) final;
+ void SetHasOutOfFlowContentInsideFilter() final;
+ ScrollSnapInfo GetScrollSnapInfo() final;
+
+ void TryResnap() final;
+ void PostPendingResnapIfNeeded(const nsIFrame* aFrame) final;
+ void PostPendingResnap() final;
+ using PhysicalScrollSnapAlign = nsIScrollableFrame::PhysicalScrollSnapAlign;
+ PhysicalScrollSnapAlign GetScrollSnapAlignFor(
+ const nsIFrame* aFrame) const final;
+
+ bool DragScroll(mozilla::WidgetEvent* aEvent) final;
+
+ void AsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, mozilla::layers::ScrollDirection aDirection) final;
+
+ void AsyncScrollbarDragRejected() final;
+
+ bool IsRootScrollFrameOfDocument() const final { return mIsRoot; }
+
+ Maybe<uint32_t> IsFirstScrollableFrameSequenceNumber() const final {
+ return mIsFirstScrollableFrameSequenceNumber;
+ }
+
+ void SetIsFirstScrollableFrameSequenceNumber(Maybe<uint32_t> aValue) final {
+ mIsFirstScrollableFrameSequenceNumber = aValue;
+ }
+
+ const ScrollAnchorContainer* Anchor() const final { return &mAnchor; }
+ ScrollAnchorContainer* Anchor() final { return &mAnchor; }
+
+ // Return the scrolled frame.
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) final {
+ aResult.AppendElement(OwnedAnonBox(GetScrolledFrame()));
+ }
+
+ bool SmoothScrollVisual(
+ const nsPoint& aVisualViewportOffset,
+ mozilla::layers::FrameMetrics::ScrollOffsetUpdateType aUpdateType) final;
+
+ bool IsSmoothScroll(mozilla::dom::ScrollBehavior aBehavior) const final;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ protected:
+ nsHTMLScrollFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ bool aIsRoot)
+ : nsHTMLScrollFrame(aStyle, aPresContext, kClassID, aIsRoot) {}
+ nsHTMLScrollFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ nsIFrame::ClassID aID, bool aIsRoot);
+ ~nsHTMLScrollFrame();
+ void SetSuppressScrollbarUpdate(bool aSuppress) {
+ mSuppressScrollbarUpdate = aSuppress;
+ }
+ bool GuessHScrollbarNeeded(const ScrollReflowInput& aState);
+ bool GuessVScrollbarNeeded(const ScrollReflowInput& aState);
+
+ bool IsScrollbarUpdateSuppressed() const { return mSuppressScrollbarUpdate; }
+
+ // Return whether we're in an "initial" reflow. Some reflows with
+ // NS_FRAME_FIRST_REFLOW set are NOT "initial" as far as we're concerned.
+ bool InInitialReflow() const;
+
+ /**
+ * Override this to return false if computed bsize/min-bsize/max-bsize
+ * should NOT be propagated to child content.
+ * nsListControlFrame uses this.
+ */
+ virtual bool ShouldPropagateComputedBSizeToScrolledContent() const {
+ return true;
+ }
+
+ private:
+ class AsyncScroll;
+ class AsyncSmoothMSDScroll;
+
+ enum class AnonymousContentType {
+ VerticalScrollbar,
+ HorizontalScrollbar,
+ Resizer,
+ };
+ mozilla::EnumSet<AnonymousContentType> GetNeededAnonymousContent() const;
+ mozilla::EnumSet<AnonymousContentType> GetCurrentAnonymousContent() const;
+
+ // If a child frame was added or removed on the scrollframe,
+ // reload our child frame list.
+ // We need this if a scrollbar frame is recreated.
+ void ReloadChildFrames();
+
+ public:
+ enum class OverflowState : uint32_t {
+ None = 0,
+ Vertical = (1 << 0),
+ Horizontal = (1 << 1),
+ };
+
+ protected:
+ OverflowState GetOverflowState() const;
+
+ MOZ_CAN_RUN_SCRIPT nsresult FireScrollPortEvent();
+ void PostScrollEndEvent();
+ MOZ_CAN_RUN_SCRIPT void FireScrollEndEvent();
+ void PostOverflowEvent();
+
+ // Add display items for the top-layer (which includes things like
+ // the fullscreen element, its backdrop, and text selection carets)
+ // to |aLists|.
+ // This is a no-op for scroll frames other than the viewport's
+ // root scroll frame.
+ // This should be called with an nsDisplayListSet that will be
+ // wrapped in the async zoom container, if we're building one.
+ // It should not be called with an ASR setter on the stack, as the
+ // top-layer items handle setting up their own ASRs.
+ void MaybeCreateTopLayerAndWrapRootItems(
+ nsDisplayListBuilder*, mozilla::nsDisplayListCollection&,
+ bool aCreateAsyncZoom,
+ AutoContainsBlendModeCapturer* aAsyncZoomBlendCapture,
+ const nsRect& aAsyncZoomClipRect, nscoord* aRadii);
+
+ void AppendScrollPartsTo(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists, bool aCreateLayer,
+ bool aPositioned);
+
+ // nsIReflowCallback
+ bool ReflowFinished() final;
+ void ReflowCallbackCanceled() final;
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * Called when the 'curpos' attribute on one of the scrollbars changes.
+ */
+ void CurPosAttributeChangedInternal(nsIContent*, bool aDoScroll = true);
+
+ void PostScrollEvent(bool aDelayed = false);
+ MOZ_CAN_RUN_SCRIPT void FireScrollEvent();
+ void PostScrolledAreaEvent();
+ MOZ_CAN_RUN_SCRIPT void FireScrolledAreaEvent();
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void FinishReflowForScrollbar(Element* aElement, nscoord aMinXY,
+ nscoord aMaxXY, nscoord aCurPosXY,
+ nscoord aPageIncrement, nscoord aIncrement);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void SetScrollbarEnabled(Element* aElement, nscoord aMaxPos);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void SetCoordAttribute(Element* aElement, nsAtom* aAtom, nscoord aSize);
+
+ nscoord GetCoordAttribute(nsIFrame* aFrame, nsAtom* aAtom,
+ nscoord aDefaultValue, nscoord* aRangeStart,
+ nscoord* aRangeLength);
+
+ nsRect GetLayoutScrollRange() const;
+ // Get the scroll range assuming the viewport has size (aWidth, aHeight).
+ nsRect GetScrollRange(nscoord aWidth, nscoord aHeight) const;
+
+ const nsRect& ScrollPort() const { return mScrollPort; }
+ void SetScrollPort(const nsRect& aNewScrollPort) {
+ if (!mScrollPort.IsEqualEdges(aNewScrollPort)) {
+ mMayScheduleScrollAnimations = true;
+ }
+ mScrollPort = aNewScrollPort;
+ }
+
+ /**
+ * Return the 'optimal viewing region' as a rect suitable for use by
+ * scroll anchoring. This rect is in the same coordinate space as
+ * 'GetScrollPortRect', and accounts for 'scroll-padding' as defined by:
+ *
+ * https://drafts.csswg.org/css-scroll-snap-1/#optimal-viewing-region
+ */
+ nsRect GetVisualOptimalViewingRect() const;
+
+ /**
+ * For LTR frames, this is the same as GetVisualViewportOffset().
+ * For RTL frames, we take the offset from the top right corner of the frame
+ * to the top right corner of the visual viewport.
+ */
+ nsPoint GetLogicalVisualViewportOffset() const {
+ nsPoint pt = GetVisualViewportOffset();
+ if (!IsPhysicalLTR()) {
+ pt.x += GetVisualViewportSize().width - mScrolledFrame->GetRect().width;
+ }
+ return pt;
+ }
+ void ScrollSnap() final { return ScrollSnap(ScrollMode::SmoothMsd); }
+ void ScrollSnap(ScrollMode aMode);
+ void ScrollSnap(const nsPoint& aDestination,
+ ScrollMode aMode = ScrollMode::SmoothMsd);
+
+ bool HasPendingScrollRestoration() const {
+ return mRestorePos != nsPoint(-1, -1);
+ }
+
+ bool IsProcessingScrollEvent() const { return mProcessingScrollEvent; }
+
+ public:
+ static void AsyncScrollCallback(nsHTMLScrollFrame* aInstance,
+ mozilla::TimeStamp aTime);
+ static void AsyncSmoothMSDScrollCallback(nsHTMLScrollFrame* aInstance,
+ mozilla::TimeDuration aDeltaTime);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * aRange is the range of allowable scroll positions around the desired
+ * aScrollPosition. Null means only aScrollPosition is allowed.
+ * This is a closed-ended range --- aRange.XMost()/aRange.YMost() are allowed.
+ */
+ void ScrollToInternal(
+ nsPoint aScrollPosition, ScrollMode aMode,
+ ScrollOrigin aOrigin = ScrollOrigin::NotSpecified,
+ const nsRect* aRange = nullptr,
+ ScrollSnapFlags aSnapFlags = ScrollSnapFlags::Disabled,
+ ScrollTriggeredByScript aTriggeredByScript = ScrollTriggeredByScript::No);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void ScrollToImpl(
+ nsPoint aPt, const nsRect& aRange,
+ ScrollOrigin aOrigin = ScrollOrigin::NotSpecified,
+ ScrollTriggeredByScript aTriggeredByScript = ScrollTriggeredByScript::No);
+ void ScrollVisual();
+
+ enum class LoadingState { Loading, Stopped, Loaded };
+
+ LoadingState GetPageLoadingState();
+
+ /**
+ * GetSnapPointForDestination determines which point to snap to after
+ * scrolling. aStartPos gives the position before scrolling and aDestination
+ * gives the position after scrolling, with no snapping. Behaviour is
+ * dependent on the value of aUnit.
+ * Returns true if a suitable snap point could be found and aDestination has
+ * been updated to a valid snapping position.
+ */
+ Maybe<mozilla::SnapDestination> GetSnapPointForDestination(
+ mozilla::ScrollUnit aUnit, ScrollSnapFlags aFlags,
+ const nsPoint& aStartPos, const nsPoint& aDestination);
+
+ Maybe<mozilla::SnapDestination> GetSnapPointForResnap();
+ bool NeedsResnap();
+
+ void SetLastSnapTargetIds(mozilla::UniquePtr<ScrollSnapTargetIds> aId);
+ void AddScrollPositionListener(nsIScrollPositionListener* aListener) final {
+ mListeners.AppendElement(aListener);
+ }
+ void RemoveScrollPositionListener(
+ nsIScrollPositionListener* aListener) final {
+ mListeners.RemoveElement(aListener);
+ }
+
+ static void SetScrollbarVisibility(nsIFrame* aScrollbar, bool aVisible);
+
+ /**
+ * GetUnsnappedScrolledRectInternal is designed to encapsulate deciding which
+ * directions of overflow should be reachable by scrolling and which
+ * should not. Callers should NOT depend on it having any particular
+ * behavior.
+ *
+ * Currently it allows scrolling down and to the right for
+ * nsHTMLScrollFrames with LTR directionality, and allows scrolling down and
+ * to the left for nsHTMLScrollFrames with RTL directionality.
+ */
+ nsRect GetUnsnappedScrolledRectInternal(const nsRect& aScrolledOverflowArea,
+ const nsSize& aScrollPortSize) const;
+
+ bool IsPhysicalLTR() const { return GetWritingMode().IsPhysicalLTR(); }
+ bool IsBidiLTR() const { return GetWritingMode().IsBidiLTR(); }
+
+ private:
+ // NOTE: Use GetScrollStylesFromFrame() if you want to know `overflow`
+ // and `overflow-behavior` properties.
+ nsIFrame* GetFrameForStyle() const;
+
+ // Compute all scroll snap related information and store eash snap target
+ // element in |mSnapTargets|.
+ ScrollSnapInfo ComputeScrollSnapInfo();
+
+ bool NeedsScrollSnap() const;
+
+ // Returns the snapport size of this scroll container.
+ // https://drafts.csswg.org/css-scroll-snap/#scroll-snapport
+ nsSize GetSnapportSize() const;
+
+ // Schedule the scroll-driven animations.
+ void ScheduleScrollAnimations();
+ void TryScheduleScrollAnimations() {
+ if (!mMayScheduleScrollAnimations) {
+ return;
+ }
+ ScheduleScrollAnimations();
+ mMayScheduleScrollAnimations = false;
+ }
+
+ CSSPoint GetScrollPositionCSSPixels() const {
+ return CSSPoint::FromAppUnits(GetScrollPosition());
+ }
+
+ public:
+ void UpdateSticky();
+
+ void UpdatePrevScrolledRect();
+
+ // adjust the scrollbar rectangle aRect to account for any visible resizer.
+ // aHasResizer specifies if there is a content resizer, however this method
+ // will also check if a widget resizer is present as well.
+ void AdjustScrollbarRectForResizer(
+ nsIFrame* aFrame, nsPresContext* aPresContext, nsRect& aRect,
+ bool aHasResizer, mozilla::layers::ScrollDirection aDirection);
+ void LayoutScrollbars(ScrollReflowInput& aState,
+ const nsRect& aInsideBorderArea,
+ const nsRect& aOldScrollPort);
+
+ void LayoutScrollbarPartAtRect(const ScrollReflowInput&,
+ ReflowInput& aKidReflowInput, const nsRect&);
+
+ bool IsAlwaysActive() const;
+ void MarkRecentlyScrolled();
+ void MarkNotRecentlyScrolled();
+ nsExpirationState* GetExpirationState() { return &mActivityExpirationState; }
+
+ bool UsesOverlayScrollbars() const;
+ bool IsLastSnappedTarget(const nsIFrame* aFrame) const;
+
+ static bool ShouldActivateAllScrollFrames();
+ nsRect RestrictToRootDisplayPort(const nsRect& aDisplayportBase);
+ bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
+ nsRect* aVisibleRect, nsRect* aDirtyRect,
+ bool aSetBase,
+ bool* aDirtyRectHasBeenOverriden = nullptr);
+ bool AllowDisplayPortExpiration();
+ void ResetDisplayPortExpiryTimer();
+
+ void ScheduleSyntheticMouseMove();
+ static void ScrollActivityCallback(nsITimer* aTimer, void* anInstance);
+
+ void HandleScrollbarStyleSwitching();
+
+ bool IsApzAnimationInProgress() const {
+ return mCurrentAPZScrollAnimationType != APZScrollAnimationType::No;
+ }
+ nsPoint LastScrollDestination() const { return mDestination; }
+
+ bool IsLastScrollUpdateAnimating() const;
+ bool IsLastScrollUpdateTriggeredByScriptAnimating() const;
+
+ // Update minimum-scale size. The minimum-scale size will be set/used only
+ // if there is overflow-x:hidden region.
+ void UpdateMinimumScaleSize(const nsRect& aScrollableOverflow,
+ const nsSize& aICBSize);
+
+ // Return the scroll frame's "true outer size".
+ // This is GetSize(), except when we've been sized to reflect a virtual
+ // (layout) viewport in which case this returns the outer size used to size
+ // the physical (visual) viewport.
+ nsSize TrueOuterSize(nsDisplayListBuilder* aBuilder) const;
+
+ already_AddRefed<Element> MakeScrollbar(mozilla::dom::NodeInfo* aNodeInfo,
+ bool aVertical,
+ mozilla::AnonymousContentKey& aKey);
+
+ void AppendScrollUpdate(const mozilla::ScrollPositionUpdate& aUpdate);
+
+ // owning references to the nsIAnonymousContentCreator-built content
+ nsCOMPtr<Element> mHScrollbarContent;
+ nsCOMPtr<Element> mVScrollbarContent;
+ nsCOMPtr<Element> mScrollCornerContent;
+ nsCOMPtr<Element> mResizerContent;
+
+ class ScrollEvent;
+ class ScrollEndEvent;
+ class AsyncScrollPortEvent;
+ class ScrolledAreaEvent;
+
+ RefPtr<ScrollEvent> mScrollEvent;
+ RefPtr<ScrollEndEvent> mScrollEndEvent;
+ nsRevocableEventPtr<AsyncScrollPortEvent> mAsyncScrollPortEvent;
+ nsRevocableEventPtr<ScrolledAreaEvent> mScrolledAreaEvent;
+ nsScrollbarFrame* mHScrollbarBox;
+ nsScrollbarFrame* mVScrollbarBox;
+ nsIFrame* mScrolledFrame;
+ nsIFrame* mScrollCornerBox;
+ nsIFrame* mResizerBox;
+ const nsIFrame* mReferenceFrameDuringPainting;
+ RefPtr<AsyncScroll> mAsyncScroll;
+ RefPtr<AsyncSmoothMSDScroll> mAsyncSmoothMSDScroll;
+ RefPtr<ScrollbarActivity> mScrollbarActivity;
+ nsTArray<nsIScrollPositionListener*> mListeners;
+ ScrollOrigin mLastScrollOrigin;
+ Maybe<nsPoint> mApzSmoothScrollDestination;
+ mozilla::MainThreadScrollGeneration mScrollGeneration;
+ mozilla::APZScrollGeneration mScrollGenerationOnApz;
+
+ nsTArray<mozilla::ScrollPositionUpdate> mScrollUpdates;
+
+ nsSize mMinimumScaleSize;
+
+ // Stores the ICB size for the root document if this frame is using the
+ // minimum scale size for |mScrollPort|.
+ nsSize mICBSize;
+
+ // Where we're currently scrolling to, if we're scrolling asynchronously.
+ // If we're not in the middle of an asynchronous scroll then this is
+ // just the current scroll position. ScrollBy will choose its
+ // destination based on this value.
+ nsPoint mDestination;
+
+ // A goal position to try to scroll to as content loads. As long as mLastPos
+ // matches the current logical scroll position, we try to scroll to
+ // mRestorePos after every reflow --- because after each time content is
+ // loaded/added to the scrollable element, there will be a reflow.
+ // Note that for frames where layout and visual viewport aren't one and the
+ // same thing, this scroll position will be the logical scroll position of
+ // the *visual* viewport, as its position will be more relevant to the user.
+ nsPoint mRestorePos;
+ // The last logical position we scrolled to while trying to restore
+ // mRestorePos, or 0,0 when this is a new frame. Set to -1,-1 once we've
+ // scrolled for any reason other than trying to restore mRestorePos.
+ // Just as with mRestorePos, this position will be the logical position of
+ // the *visual* viewport where available.
+ nsPoint mLastPos;
+
+ // The latest scroll position we've sent or received from APZ. This
+ // represents the main thread's best knowledge of the APZ scroll position,
+ // and is used to calculate relative scroll offset updates.
+ nsPoint mApzScrollPos;
+
+ nsExpirationState mActivityExpirationState;
+
+ nsCOMPtr<nsITimer> mScrollActivityTimer;
+
+ // The scroll position where we last updated frame visibility.
+ nsPoint mLastUpdateFramesPos;
+ nsRect mDisplayPortAtLastFrameUpdate;
+
+ nsRect mPrevScrolledRect;
+
+ ScrollableLayerGuid::ViewID mScrollParentID;
+
+ // Timer to remove the displayport some time after scrolling has stopped
+ nsCOMPtr<nsITimer> mDisplayPortExpiryTimer;
+
+ ScrollAnchorContainer mAnchor;
+
+ // We keep holding a strong reference for each snap target element until the
+ // next snapping happens so that it avoids using the same nsIContent* pointer
+ // for newly created contents in this scroll container. Otherwise we will try
+ // to match different nsIContent(s) generated at the same address during
+ // re-snapping.
+ SnapTargetSet mSnapTargets;
+
+ // Representing there's an APZ animation is in progress and what caused the
+ // animation. Note that this is only set when repainted via APZ, which means
+ // that there may be a request for an APZ animation in flight for example,
+ // while this is still `No`. In order to answer "is an APZ animation in the
+ // process of starting or in progress" you need to check mScrollUpdates,
+ // mApzAnimationRequested, and this type.
+ APZScrollAnimationType mCurrentAPZScrollAnimationType;
+
+ // The paint sequence number if the scroll frame is the first scrollable frame
+ // encountered.
+ Maybe<uint32_t> mIsFirstScrollableFrameSequenceNumber;
+
+ // Representing whether the APZC corresponding to this frame is now in the
+ // middle of handling a gesture (e.g. a pan gesture).
+ InScrollingGesture mInScrollingGesture : 1;
+
+ bool mAllowScrollOriginDowngrade : 1;
+ bool mHadDisplayPortAtLastFrameUpdate : 1;
+
+ // True if the most recent reflow of the scroll container frame has
+ // the vertical scrollbar shown.
+ bool mHasVerticalScrollbar : 1;
+ // True if the most recent reflow of the scroll container frame has the
+ // horizontal scrollbar shown.
+ bool mHasHorizontalScrollbar : 1;
+
+ // If mHas(Vertical|Horizontal)Scrollbar is true then
+ // mOnlyNeed(V|H)ScrollbarToScrollVVInsideLV indicates if the only reason we
+ // need that scrollbar is to scroll the visual viewport inside the layout
+ // viewport. These scrollbars are special in that even if they are layout
+ // scrollbars they do not take up any layout space.
+ bool mOnlyNeedVScrollbarToScrollVVInsideLV : 1;
+ bool mOnlyNeedHScrollbarToScrollVVInsideLV : 1;
+ bool mFrameIsUpdatingScrollbar : 1;
+ bool mDidHistoryRestore : 1;
+ // Is this the scrollframe for the document's viewport?
+ bool mIsRoot : 1;
+ // If true, don't try to layout the scrollbars in Reflow(). This can be
+ // useful if multiple passes are involved, because we don't want to place the
+ // scrollbars at the wrong size.
+ bool mSuppressScrollbarUpdate : 1;
+ // If true, we skipped a scrollbar layout due to mSuppressScrollbarUpdate
+ // being set at some point. That means we should lay out scrollbars even if
+ // it might not strictly be needed next time mSuppressScrollbarUpdate is
+ // false.
+ bool mSkippedScrollbarLayout : 1;
+
+ bool mHadNonInitialReflow : 1;
+ // Initially true; first call to ReflowFinished() sets it to false.
+ bool mFirstReflow : 1;
+ // State used only by PostScrollEvents so we know
+ // which overflow states have changed.
+ bool mHorizontalOverflow : 1;
+ bool mVerticalOverflow : 1;
+ bool mPostedReflowCallback : 1;
+ bool mMayHaveDirtyFixedChildren : 1;
+ // If true, need to actually update our scrollbar attributes in the
+ // reflow callback.
+ bool mUpdateScrollbarAttributes : 1;
+ // If true, we should be prepared to scroll using this scrollframe
+ // by placing descendant content into its own layer(s)
+ bool mHasBeenScrolledRecently : 1;
+
+ // If true, the scroll frame should always be active because we always build
+ // a scrollable layer. Used for asynchronous scrolling.
+ bool mWillBuildScrollableLayer : 1;
+
+ // If true, the scroll frame is an ancestor of other "active" scrolling
+ // frames, where "active" means has a non-minimal display port if
+ // ShouldActivateAllScrollFrames is true, or has a display port if
+ // ShouldActivateAllScrollFrames is false. And this means that we shouldn't
+ // expire the display port (if ShouldActivateAllScrollFrames is true then
+ // expiring a display port means making it minimal, otherwise it means
+ // removing the display port). If those descendant scrollframes have their
+ // display ports removed or made minimal, then we expire our display port.
+ bool mIsParentToActiveScrollFrames : 1;
+
+ // True if this frame has been scrolled at least once
+ bool mHasBeenScrolled : 1;
+
+ // True if the events synthesized by OSX to produce momentum scrolling should
+ // be ignored. Reset when the next real, non-synthesized scroll event occurs.
+ bool mIgnoreMomentumScroll : 1;
+
+ // True if the APZ is in the process of async-transforming this scrollframe,
+ // (as best as we can tell on the main thread, anyway).
+ bool mTransformingByAPZ : 1;
+
+ // True if APZ can scroll this frame asynchronously (i.e. it has an APZC
+ // set up for this frame and it's not a scrollinfo layer).
+ bool mScrollableByAPZ : 1;
+
+ // True if the APZ is allowed to zoom this scrollframe.
+ bool mZoomableByAPZ : 1;
+
+ // True if the scroll frame contains out-of-flow content and is inside
+ // a CSS filter.
+ bool mHasOutOfFlowContentInsideFilter : 1;
+
+ // True if we don't want the scrollbar to repaint itself right now.
+ bool mSuppressScrollbarRepaints : 1;
+
+ // True if we are using the minimum scale size instead of ICB for scroll port.
+ bool mIsUsingMinimumScaleSize : 1;
+
+ // True if the minimum scale size has been changed since the last reflow.
+ bool mMinimumScaleSizeChanged : 1;
+
+ // True if we're processing an scroll event.
+ bool mProcessingScrollEvent : 1;
+
+ // This is true from the time a scroll animation is requested of APZ to the
+ // time that APZ responds with an up-to-date repaint request. More precisely,
+ // this is flipped to true if a repaint request is dispatched to APZ where
+ // the most recent scroll request is a smooth scroll, and it is cleared when
+ // mApzAnimationInProgress is updated.
+ bool mApzAnimationRequested : 1;
+
+ // Similar to above mApzAnimationRequested but the request came from script,
+ // e.g., scrollBy().
+ bool mApzAnimationTriggeredByScriptRequested : 1;
+
+ // Whether we need to reclamp the visual viewport offset in ReflowFinished.
+ bool mReclampVVOffsetInReflowFinished : 1;
+
+ // Whether we need to schedule the scroll-driven animations.
+ bool mMayScheduleScrollAnimations : 1;
+
+#ifdef MOZ_WIDGET_ANDROID
+ // True if this scrollable frame was vertically overflowed on the last reflow.
+ bool mHasVerticalOverflowForDynamicToolbar : 1;
+#endif
+
+ mozilla::layout::ScrollVelocityQueue mVelocityQueue;
+
+ protected:
+ class AutoScrollbarRepaintSuppression;
+ friend class AutoScrollbarRepaintSuppression;
+ class AutoScrollbarRepaintSuppression {
+ public:
+ AutoScrollbarRepaintSuppression(nsHTMLScrollFrame* aFrame,
+ AutoWeakFrame& aWeakOuter, bool aSuppress)
+ : mFrame(aFrame),
+ mWeakOuter(aWeakOuter),
+ mOldSuppressValue(aFrame->mSuppressScrollbarRepaints) {
+ mFrame->mSuppressScrollbarRepaints = aSuppress;
+ }
+
+ ~AutoScrollbarRepaintSuppression() {
+ if (mWeakOuter.IsAlive()) {
+ mFrame->mSuppressScrollbarRepaints = mOldSuppressValue;
+ }
+ }
+
+ private:
+ nsHTMLScrollFrame* mFrame;
+ AutoWeakFrame& mWeakOuter;
+ bool mOldSuppressValue;
+ };
+
+ struct ScrollOperationParams {
+ ScrollOperationParams(const ScrollOperationParams&) = delete;
+ ScrollOperationParams(ScrollMode aMode, ScrollOrigin aOrigin)
+ : mMode(aMode), mOrigin(aOrigin) {}
+ ScrollOperationParams(ScrollMode aMode, ScrollOrigin aOrigin,
+ ScrollSnapTargetIds&& aSnapTargetIds)
+ : ScrollOperationParams(aMode, aOrigin) {
+ mTargetIds = std::move(aSnapTargetIds);
+ }
+ ScrollOperationParams(ScrollMode aMode, ScrollOrigin aOrigin,
+ ScrollSnapFlags aSnapFlags,
+ ScrollTriggeredByScript aTriggeredByScript)
+ : ScrollOperationParams(aMode, aOrigin) {
+ mSnapFlags = aSnapFlags;
+ mTriggeredByScript = aTriggeredByScript;
+ }
+
+ ScrollMode mMode;
+ ScrollOrigin mOrigin;
+ ScrollSnapFlags mSnapFlags = ScrollSnapFlags::Disabled;
+ ScrollTriggeredByScript mTriggeredByScript = ScrollTriggeredByScript::No;
+ ScrollSnapTargetIds mTargetIds;
+
+ bool IsInstant() const { return mMode == ScrollMode::Instant; }
+ bool IsSmoothMsd() const { return mMode == ScrollMode::SmoothMsd; }
+ bool IsSmooth() const { return mMode == ScrollMode::Smooth; }
+ bool IsScrollSnapDisabled() const {
+ return mSnapFlags == ScrollSnapFlags::Disabled;
+ }
+ };
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ *
+ * A caller can ask this ScrollToWithOrigin() function to perform snapping by
+ * passing in aParams.mSnapFlags != ScrollSnapFlags::Disabled. Alternatively,
+ * a caller may want to do its own snapping, in which case it should pass
+ * ScrollSnapFlags::Disabled and populate aParams.mTargetIds based on the
+ * result of the snapping.
+ */
+ void ScrollToWithOrigin(nsPoint aScrollPosition, const nsRect* aRange,
+ ScrollOperationParams&& aParams);
+
+ void CompleteAsyncScroll(
+ const nsPoint& aScrollPosition, const nsRect& aRange,
+ mozilla::UniquePtr<ScrollSnapTargetIds> aSnapTargetIds,
+ ScrollOrigin aOrigin = ScrollOrigin::NotSpecified);
+
+ bool HasPerspective() const { return ChildrenHavePerspective(); }
+ bool HasBgAttachmentLocal() const;
+ mozilla::StyleDirection GetScrolledFrameDir() const;
+
+ // Ask APZ to smooth scroll to |aDestination|.
+ // This method does not clamp the destination; callers should clamp it to
+ // either the layout or the visual scroll range (APZ will happily smooth
+ // scroll to either).
+ void ApzSmoothScrollTo(
+ const nsPoint& aDestination, ScrollMode, ScrollOrigin,
+ mozilla::ScrollTriggeredByScript,
+ mozilla::UniquePtr<ScrollSnapTargetIds> aSnapTargetIds);
+
+ // Check whether APZ can scroll in the provided directions, keeping in mind
+ // that APZ currently cannot scroll along axes which are overflow:hidden.
+ bool CanApzScrollInTheseDirections(
+ mozilla::layers::ScrollDirections aDirections);
+
+ // Removes any RefreshDriver observers we might have registered.
+ void RemoveObservers();
+
+ private:
+ // NOTE: On mobile this value might be factoring into overflow:hidden region
+ // in the case of the top level document.
+ nsRect mScrollPort;
+ mozilla::UniquePtr<ScrollSnapTargetIds> mLastSnapTargetIds;
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsHTMLScrollFrame::OverflowState)
+
+#endif /* nsGfxScrollFrame_h___ */
diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp
new file mode 100644
index 0000000000..97d8549cb0
--- /dev/null
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -0,0 +1,10261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: grid | inline-grid" */
+
+#include "nsGridContainerFrame.h"
+
+#include <functional>
+#include <stdlib.h> // for div()
+#include <type_traits>
+#include "gfxContext.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Baseline.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/CSSAlignUtils.h"
+#include "mozilla/dom/Grid.h"
+#include "mozilla/dom/GridBinding.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PodOperations.h" // for PodZero
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "nsAlgorithm.h" // for clamped()
+#include "nsCSSFrameConstructor.h"
+#include "nsDisplayList.h"
+#include "nsFieldSetFrame.h"
+#include "nsHashKeys.h"
+#include "nsIFrameInlines.h" // for nsIFrame::GetLogicalNormalPosition (don't remove)
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+#include "nsReadableUtils.h"
+#include "nsTableWrapperFrame.h"
+
+using namespace mozilla;
+
+typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
+typedef nsGridContainerFrame::TrackSize TrackSize;
+typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
+
+using GridTemplate = StyleGridTemplateComponent;
+using TrackListValue =
+ StyleGenericTrackListValue<LengthPercentage, StyleInteger>;
+using TrackRepeat = StyleGenericTrackRepeat<LengthPercentage, StyleInteger>;
+using NameList = StyleOwnedSlice<StyleCustomIdent>;
+using SizingConstraint = nsGridContainerFrame::SizingConstraint;
+using GridItemCachedBAxisMeasurement =
+ nsGridContainerFrame::CachedBAxisMeasurement;
+
+static mozilla::LazyLogModule gGridContainerLog("GridContainer");
+#define GRID_LOG(...) \
+ MOZ_LOG(gGridContainerLog, LogLevel::Debug, (__VA_ARGS__));
+
+static const int32_t kMaxLine = StyleMAX_GRID_LINE;
+static const int32_t kMinLine = StyleMIN_GRID_LINE;
+// The maximum line number, in the zero-based translated grid.
+static const uint32_t kTranslatedMaxLine = uint32_t(kMaxLine - kMinLine);
+static const uint32_t kAutoLine = kTranslatedMaxLine + 3457U;
+
+static const nsFrameState kIsSubgridBits =
+ (NS_STATE_GRID_IS_COL_SUBGRID | NS_STATE_GRID_IS_ROW_SUBGRID);
+
+namespace mozilla {
+
+template <>
+inline Span<const StyleOwnedSlice<StyleCustomIdent>>
+GridTemplate::LineNameLists(bool aIsSubgrid) const {
+ if (IsTrackList()) {
+ return AsTrackList()->line_names.AsSpan();
+ }
+ if (IsSubgrid() && aIsSubgrid) {
+ // For subgrid, we need to resolve <line-name-list> from each
+ // StyleGenericLineNameListValue, so return empty.
+ return {};
+ }
+ MOZ_ASSERT(IsNone() || IsMasonry() || (IsSubgrid() && !aIsSubgrid));
+ return {};
+}
+
+template <>
+inline const StyleTrackBreadth& StyleTrackSize::GetMax() const {
+ if (IsBreadth()) {
+ return AsBreadth();
+ }
+ if (IsMinmax()) {
+ return AsMinmax()._1;
+ }
+ MOZ_ASSERT(IsFitContent());
+ return AsFitContent();
+}
+
+template <>
+inline const StyleTrackBreadth& StyleTrackSize::GetMin() const {
+ static const StyleTrackBreadth kAuto = StyleTrackBreadth::Auto();
+ if (IsBreadth()) {
+ // <flex> behaves like minmax(auto, <flex>)
+ return AsBreadth().IsFr() ? kAuto : AsBreadth();
+ }
+ if (IsMinmax()) {
+ return AsMinmax()._0;
+ }
+ MOZ_ASSERT(IsFitContent());
+ return kAuto;
+}
+
+} // namespace mozilla
+
+static nscoord ClampToCSSMaxBSize(nscoord aSize,
+ const ReflowInput* aReflowInput) {
+ auto maxSize = aReflowInput->ComputedMaxBSize();
+ if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
+ MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize);
+ aSize = std::min(aSize, maxSize);
+ }
+ return aSize;
+}
+
+// Same as above and set aStatus INCOMPLETE if aSize wasn't clamped.
+// (If we clamp aSize it means our size is less than the break point,
+// i.e. we're effectively breaking in our overflow, so we should leave
+// aStatus as is (it will likely be set to OVERFLOW_INCOMPLETE later)).
+static nscoord ClampToCSSMaxBSize(nscoord aSize,
+ const ReflowInput* aReflowInput,
+ nsReflowStatus* aStatus) {
+ auto maxSize = aReflowInput->ComputedMaxBSize();
+ if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
+ MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize);
+ if (aSize < maxSize) {
+ aStatus->SetIncomplete();
+ } else {
+ aSize = maxSize;
+ }
+ } else {
+ aStatus->SetIncomplete();
+ }
+ return aSize;
+}
+
+template <typename Size>
+static bool IsPercentOfIndefiniteSize(const Size& aCoord,
+ nscoord aPercentBasis) {
+ return aPercentBasis == NS_UNCONSTRAINEDSIZE && aCoord.HasPercent();
+}
+
+static nscoord ResolveToDefiniteSize(const StyleTrackBreadth& aBreadth,
+ nscoord aPercentBasis) {
+ MOZ_ASSERT(aBreadth.IsBreadth());
+ if (::IsPercentOfIndefiniteSize(aBreadth.AsBreadth(), aPercentBasis)) {
+ return nscoord(0);
+ }
+ return std::max(nscoord(0), aBreadth.AsBreadth().Resolve(aPercentBasis));
+}
+
+// Synthesize a baseline from a border box. For an alphabetical baseline
+// this is the end edge of the border box. For a central baseline it's
+// the center of the border box.
+// https://drafts.csswg.org/css-align-3/#synthesize-baseline
+// For a 'first baseline' the measure is from the border-box start edge and
+// for a 'last baseline' the measure is from the border-box end edge.
+//
+// The 'LogicalAxis aAxis' represents the axis (in terms of aWM) that the
+// baseline corresponds to. (Typically, baselines are a measurement in the
+// block axis; e.g. for English horizontal-tb text, a traditional baseline
+// would be a y-axis measurement. But in some cases (e.g. orthogonal WMs), we
+// may need to synthesize a baseline in a child's inline axis, which is when
+// this function might receive an aAxis of eLogicalAxisInline. In that case, we
+// assume that the writing mode's preference for central vs. alphabetic
+// baselines is irrelevant, since that's a choice about its block-axis
+// baselines, and we just unconditionally use the alphabetic baseline
+// (e.g. border-box bottom edge).
+static nscoord SynthesizeBaselineFromBorderBox(BaselineSharingGroup aGroup,
+ WritingMode aWM,
+ LogicalAxis aAxis,
+ nscoord aBorderBoxSize) {
+ const bool useAlphabeticBaseline =
+ (aAxis == eLogicalAxisInline) ? true : aWM.IsAlphabeticalBaseline();
+
+ if (aGroup == BaselineSharingGroup::First) {
+ return useAlphabeticBaseline ? aBorderBoxSize : aBorderBoxSize / 2;
+ }
+ MOZ_ASSERT(aGroup == BaselineSharingGroup::Last);
+ // Round up for central baseline offset, to be consistent with eFirst.
+ return useAlphabeticBaseline ? 0
+ : (aBorderBoxSize / 2) + (aBorderBoxSize % 2);
+}
+
+// The input sizes for calculating the number of repeat(auto-fill/fit) tracks.
+// https://drafts.csswg.org/css-grid/#auto-repeat
+struct RepeatTrackSizingInput {
+ explicit RepeatTrackSizingInput(WritingMode aWM)
+ : mMin(aWM, 0, 0),
+ mSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
+ mMax(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) {}
+ RepeatTrackSizingInput(const LogicalSize& aMin, const LogicalSize& aSize,
+ const LogicalSize& aMax)
+ : mMin(aMin), mSize(aSize), mMax(aMax) {}
+
+ // This should be used in intrinsic sizing (i.e. when we can't initialize
+ // the sizes directly from ReflowInput values).
+ void InitFromStyle(LogicalAxis aAxis, WritingMode aWM,
+ const ComputedStyle* aStyle) {
+ const auto& pos = aStyle->StylePosition();
+ const bool borderBoxSizing = pos->mBoxSizing == StyleBoxSizing::Border;
+ nscoord bp = NS_UNCONSTRAINEDSIZE; // a sentinel to calculate it only once
+ auto adjustForBoxSizing = [borderBoxSizing, aWM, aAxis, aStyle,
+ &bp](nscoord aSize) {
+ if (!borderBoxSizing) {
+ return aSize;
+ }
+ if (bp == NS_UNCONSTRAINEDSIZE) {
+ const auto& padding = aStyle->StylePadding()->mPadding;
+ LogicalMargin border(aWM, aStyle->StyleBorder()->GetComputedBorder());
+ // We can use zero percentage basis since this is only called from
+ // intrinsic sizing code.
+ const nscoord percentageBasis = 0;
+ if (aAxis == eLogicalAxisInline) {
+ bp = std::max(padding.GetIStart(aWM).Resolve(percentageBasis), 0) +
+ std::max(padding.GetIEnd(aWM).Resolve(percentageBasis), 0) +
+ border.IStartEnd(aWM);
+ } else {
+ bp = std::max(padding.GetBStart(aWM).Resolve(percentageBasis), 0) +
+ std::max(padding.GetBEnd(aWM).Resolve(percentageBasis), 0) +
+ border.BStartEnd(aWM);
+ }
+ }
+ return std::max(aSize - bp, 0);
+ };
+ nscoord& min = mMin.Size(aAxis, aWM);
+ nscoord& size = mSize.Size(aAxis, aWM);
+ nscoord& max = mMax.Size(aAxis, aWM);
+ const auto& minCoord =
+ aAxis == eLogicalAxisInline ? pos->MinISize(aWM) : pos->MinBSize(aWM);
+ if (minCoord.ConvertsToLength()) {
+ min = adjustForBoxSizing(minCoord.ToLength());
+ }
+ const auto& maxCoord =
+ aAxis == eLogicalAxisInline ? pos->MaxISize(aWM) : pos->MaxBSize(aWM);
+ if (maxCoord.ConvertsToLength()) {
+ max = std::max(min, adjustForBoxSizing(maxCoord.ToLength()));
+ }
+ const auto& sizeCoord =
+ aAxis == eLogicalAxisInline ? pos->ISize(aWM) : pos->BSize(aWM);
+ if (sizeCoord.ConvertsToLength()) {
+ size = Clamp(adjustForBoxSizing(sizeCoord.ToLength()), min, max);
+ }
+ }
+
+ LogicalSize mMin;
+ LogicalSize mSize;
+ LogicalSize mMax;
+};
+
+enum class GridLineSide {
+ BeforeGridGap,
+ AfterGridGap,
+};
+
+struct nsGridContainerFrame::TrackSize {
+ enum StateBits : uint16_t {
+ // clang-format off
+ eAutoMinSizing = 0x1,
+ eMinContentMinSizing = 0x2,
+ eMaxContentMinSizing = 0x4,
+ eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing,
+ eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing,
+ eModified = 0x8,
+ eAutoMaxSizing = 0x10,
+ eMinContentMaxSizing = 0x20,
+ eMaxContentMaxSizing = 0x40,
+ eAutoOrMaxContentMaxSizing = eAutoMaxSizing | eMaxContentMaxSizing,
+ eIntrinsicMaxSizing = eAutoOrMaxContentMaxSizing | eMinContentMaxSizing,
+ eFlexMaxSizing = 0x80,
+ eFrozen = 0x100,
+ eSkipGrowUnlimited1 = 0x200,
+ eSkipGrowUnlimited2 = 0x400,
+ eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2,
+ eBreakBefore = 0x800,
+ eFitContent = 0x1000,
+ eInfinitelyGrowable = 0x2000,
+
+ // These are only used in the masonry axis. They share the same value
+ // as *MinSizing above, but that's OK because we don't use those in
+ // the masonry axis.
+ //
+ // This track corresponds to an item margin-box size that is stretching.
+ eItemStretchSize = 0x1,
+ // This bit says that we should clamp that size to mLimit.
+ eClampToLimit = 0x2,
+ // This bit says that the corresponding item has `auto` margin(s).
+ eItemHasAutoMargin = 0x4,
+ // clang-format on
+ };
+
+ StateBits Initialize(nscoord aPercentageBasis, const StyleTrackSize&);
+ bool IsFrozen() const { return mState & eFrozen; }
+#ifdef DEBUG
+ static void DumpStateBits(StateBits aState);
+ void Dump() const;
+#endif
+
+ static bool IsDefiniteMaxSizing(StateBits aStateBits) {
+ return (aStateBits & (eIntrinsicMaxSizing | eFlexMaxSizing)) == 0;
+ }
+
+ nscoord mBase;
+ nscoord mLimit;
+ nscoord mPosition; // zero until we apply 'align/justify-content'
+ // mBaselineSubtreeSize is the size of a baseline-aligned subtree within
+ // this track. One subtree per baseline-sharing group (per track).
+ PerBaseline<nscoord> mBaselineSubtreeSize;
+ StateBits mState;
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits)
+
+static_assert(
+ std::is_trivially_copyable<nsGridContainerFrame::TrackSize>::value,
+ "Must be trivially copyable");
+static_assert(
+ std::is_trivially_destructible<nsGridContainerFrame::TrackSize>::value,
+ "Must be trivially destructible");
+
+TrackSize::StateBits nsGridContainerFrame::TrackSize::Initialize(
+ nscoord aPercentageBasis, const StyleTrackSize& aSize) {
+ using Tag = StyleTrackBreadth::Tag;
+
+ MOZ_ASSERT(mBase == 0 && mLimit == 0 && mState == 0,
+ "track size data is expected to be initialized to zero");
+ mBaselineSubtreeSize[BaselineSharingGroup::First] = nscoord(0);
+ mBaselineSubtreeSize[BaselineSharingGroup::Last] = nscoord(0);
+
+ auto& min = aSize.GetMin();
+ auto& max = aSize.GetMax();
+
+ Tag minSizeTag = min.tag;
+ Tag maxSizeTag = max.tag;
+ if (aSize.IsFitContent()) {
+ // In layout, fit-content(size) behaves as minmax(auto, max-content), with
+ // 'size' as an additional upper-bound.
+ mState = eFitContent;
+ minSizeTag = Tag::Auto;
+ maxSizeTag = Tag::MaxContent;
+ }
+ if (::IsPercentOfIndefiniteSize(min, aPercentageBasis)) {
+ // https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-percentage
+ // "If the inline or block size of the grid container is indefinite,
+ // <percentage> values relative to that size are treated as 'auto'."
+ minSizeTag = Tag::Auto;
+ }
+ if (::IsPercentOfIndefiniteSize(max, aPercentageBasis)) {
+ maxSizeTag = Tag::Auto;
+ }
+
+ // http://dev.w3.org/csswg/css-grid/#algo-init
+ switch (minSizeTag) {
+ case Tag::Auto:
+ mState |= eAutoMinSizing;
+ break;
+ case Tag::MinContent:
+ mState |= eMinContentMinSizing;
+ break;
+ case Tag::MaxContent:
+ mState |= eMaxContentMinSizing;
+ break;
+ default:
+ MOZ_ASSERT(!min.IsFr(), "<flex> min-sizing is invalid as a track size");
+ mBase = ::ResolveToDefiniteSize(min, aPercentageBasis);
+ }
+ switch (maxSizeTag) {
+ case Tag::Auto:
+ mState |= eAutoMaxSizing;
+ mLimit = NS_UNCONSTRAINEDSIZE;
+ break;
+ case Tag::MinContent:
+ case Tag::MaxContent:
+ mState |= maxSizeTag == Tag::MinContent ? eMinContentMaxSizing
+ : eMaxContentMaxSizing;
+ mLimit = NS_UNCONSTRAINEDSIZE;
+ break;
+ case Tag::Fr:
+ mState |= eFlexMaxSizing;
+ mLimit = mBase;
+ break;
+ default:
+ mLimit = ::ResolveToDefiniteSize(max, aPercentageBasis);
+ if (mLimit < mBase) {
+ mLimit = mBase;
+ }
+ }
+ return mState;
+}
+
+/**
+ * A LineRange can be definite or auto - when it's definite it represents
+ * a consecutive set of tracks between a starting line and an ending line.
+ * Before it's definite it can also represent an auto position with a span,
+ * where mStart == kAutoLine and mEnd is the (non-zero positive) span.
+ * For normal-flow items, the invariant mStart < mEnd holds when both
+ * lines are definite.
+ *
+ * For abs.pos. grid items, mStart and mEnd may both be kAutoLine, meaning
+ * "attach this side to the grid container containing block edge".
+ * Additionally, mStart <= mEnd holds when both are definite (non-kAutoLine),
+ * i.e. the invariant is slightly relaxed compared to normal flow items.
+ */
+struct nsGridContainerFrame::LineRange {
+ LineRange(int32_t aStart, int32_t aEnd)
+ : mUntranslatedStart(aStart), mUntranslatedEnd(aEnd) {
+#ifdef DEBUG
+ if (!IsAutoAuto()) {
+ if (IsAuto()) {
+ MOZ_ASSERT(aEnd >= kMinLine && aEnd <= kMaxLine, "invalid span");
+ } else {
+ MOZ_ASSERT(aStart >= kMinLine && aStart <= kMaxLine,
+ "invalid start line");
+ MOZ_ASSERT(aEnd == int32_t(kAutoLine) ||
+ (aEnd >= kMinLine && aEnd <= kMaxLine),
+ "invalid end line");
+ }
+ }
+#endif
+ }
+ bool IsAutoAuto() const { return mStart == kAutoLine && mEnd == kAutoLine; }
+ bool IsAuto() const { return mStart == kAutoLine; }
+ bool IsDefinite() const { return mStart != kAutoLine; }
+ uint32_t Extent() const {
+ MOZ_ASSERT(mEnd != kAutoLine, "Extent is undefined for abs.pos. 'auto'");
+ if (IsAuto()) {
+ MOZ_ASSERT(mEnd >= 1 && mEnd < uint32_t(kMaxLine), "invalid span");
+ return mEnd;
+ }
+ return mEnd - mStart;
+ }
+
+ /**
+ * Return an object suitable for iterating this range.
+ */
+ auto Range() const { return IntegerRange<uint32_t>(mStart, mEnd); }
+
+ /**
+ * Resolve this auto range to start at aStart, making it definite.
+ * @param aClampMaxLine the maximum allowed line number (zero-based)
+ * Precondition: this range IsAuto()
+ */
+ void ResolveAutoPosition(uint32_t aStart, uint32_t aClampMaxLine) {
+ MOZ_ASSERT(IsAuto(), "Why call me?");
+ mStart = aStart;
+ mEnd += aStart;
+ // Clamp to aClampMaxLine, which is where kMaxLine is in the explicit
+ // grid in a non-subgrid axis; this implements clamping per
+ // http://dev.w3.org/csswg/css-grid/#overlarge-grids
+ // In a subgrid axis it's the end of the grid in that axis.
+ if (MOZ_UNLIKELY(mStart >= aClampMaxLine)) {
+ mEnd = aClampMaxLine;
+ mStart = mEnd - 1;
+ } else if (MOZ_UNLIKELY(mEnd > aClampMaxLine)) {
+ mEnd = aClampMaxLine;
+ }
+ }
+ /**
+ * Translate the lines to account for (empty) removed tracks. This method
+ * is only for grid items and should only be called after placement.
+ * aNumRemovedTracks contains a count for each line in the grid how many
+ * tracks were removed between the start of the grid and that line.
+ */
+ void AdjustForRemovedTracks(const nsTArray<uint32_t>& aNumRemovedTracks) {
+ MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item");
+ MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item");
+ uint32_t numRemovedTracks = aNumRemovedTracks[mStart];
+ MOZ_ASSERT(numRemovedTracks == aNumRemovedTracks[mEnd],
+ "tracks that a grid item spans can't be removed");
+ mStart -= numRemovedTracks;
+ mEnd -= numRemovedTracks;
+ }
+ /**
+ * Translate the lines to account for (empty) removed tracks. This method
+ * is only for abs.pos. children and should only be called after placement.
+ * Same as for in-flow items, but we don't touch 'auto' lines here and we
+ * also need to adjust areas that span into the removed tracks.
+ */
+ void AdjustAbsPosForRemovedTracks(
+ const nsTArray<uint32_t>& aNumRemovedTracks) {
+ if (mStart != kAutoLine) {
+ mStart -= aNumRemovedTracks[mStart];
+ }
+ if (mEnd != kAutoLine) {
+ MOZ_ASSERT(mStart == kAutoLine || mEnd > mStart, "invalid line range");
+ mEnd -= aNumRemovedTracks[mEnd];
+ }
+ }
+ /**
+ * Return the contribution of this line range for step 2 in
+ * http://dev.w3.org/csswg/css-grid/#auto-placement-algo
+ */
+ uint32_t HypotheticalEnd() const { return mEnd; }
+ /**
+ * Given an array of track sizes, return the starting position and length
+ * of the tracks in this line range.
+ */
+ void ToPositionAndLength(const nsTArray<TrackSize>& aTrackSizes,
+ nscoord* aPos, nscoord* aLength) const;
+ /**
+ * Given an array of track sizes, return the length of the tracks in this
+ * line range.
+ */
+ nscoord ToLength(const nsTArray<TrackSize>& aTrackSizes) const;
+ /**
+ * Given an array of track sizes and a grid origin coordinate, adjust the
+ * abs.pos. containing block along an axis given by aPos and aLength.
+ * aPos and aLength should already be initialized to the grid container
+ * containing block for this axis before calling this method.
+ */
+ void ToPositionAndLengthForAbsPos(const Tracks& aTracks, nscoord aGridOrigin,
+ nscoord* aPos, nscoord* aLength) const;
+
+ void Translate(int32_t aOffset) {
+ MOZ_ASSERT(IsDefinite());
+ mStart += aOffset;
+ mEnd += aOffset;
+ }
+
+ /** Swap the start/end sides of this range. */
+ void ReverseDirection(uint32_t aGridEnd) {
+ MOZ_ASSERT(IsDefinite());
+ MOZ_ASSERT(aGridEnd >= mEnd);
+ uint32_t newStart = aGridEnd - mEnd;
+ mEnd = aGridEnd - mStart;
+ mStart = newStart;
+ }
+
+ /**
+ * @note We'll use the signed member while resolving definite positions
+ * to line numbers (1-based), which may become negative for implicit lines
+ * to the top/left of the explicit grid. PlaceGridItems() then translates
+ * the whole grid to a 0,0 origin and we'll use the unsigned member from
+ * there on.
+ */
+ union {
+ uint32_t mStart;
+ int32_t mUntranslatedStart;
+ };
+ union {
+ uint32_t mEnd;
+ int32_t mUntranslatedEnd;
+ };
+
+ protected:
+ LineRange() : mStart(0), mEnd(0) {}
+};
+
+/**
+ * Helper class to construct a LineRange from translated lines.
+ * The ctor only accepts translated definite line numbers.
+ */
+struct nsGridContainerFrame::TranslatedLineRange : public LineRange {
+ TranslatedLineRange(uint32_t aStart, uint32_t aEnd) {
+ MOZ_ASSERT(aStart < aEnd && aEnd <= kTranslatedMaxLine);
+ mStart = aStart;
+ mEnd = aEnd;
+ }
+};
+
+/**
+ * A GridArea is the area in the grid for a grid item.
+ * The area is represented by two LineRanges, both of which can be auto
+ * (@see LineRange) in intermediate steps while the item is being placed.
+ * @see PlaceGridItems
+ */
+struct nsGridContainerFrame::GridArea {
+ GridArea(const LineRange& aCols, const LineRange& aRows)
+ : mCols(aCols), mRows(aRows) {}
+ bool IsDefinite() const { return mCols.IsDefinite() && mRows.IsDefinite(); }
+ LineRange& LineRangeForAxis(LogicalAxis aAxis) {
+ return aAxis == eLogicalAxisInline ? mCols : mRows;
+ }
+ const LineRange& LineRangeForAxis(LogicalAxis aAxis) const {
+ return aAxis == eLogicalAxisInline ? mCols : mRows;
+ }
+ LineRange mCols;
+ LineRange mRows;
+};
+
+struct nsGridContainerFrame::GridItemInfo {
+ /**
+ * Item state per axis.
+ */
+ enum StateBits : uint16_t {
+ // Does the item span a flex track?
+ eIsFlexing = 0x1,
+
+ // First or last baseline alignment preference. They are mutually exclusive.
+ // This does *NOT* represent the baseline alignment group. See the member
+ // variable for that.
+ // <https://drafts.csswg.org/css-align-3/#baseline-alignment-preference>
+ eFirstBaseline = 0x2,
+ eLastBaseline = 0x4,
+ eIsBaselineAligned = eFirstBaseline | eLastBaseline,
+
+ // One of e[Self|Content]Baseline is set when eIsBaselineAligned is true
+ eSelfBaseline = 0x8, // is it *-self:[last ]baseline alignment?
+ // Ditto *-content:[last ]baseline. Mutually exclusive w. eSelfBaseline.
+ eContentBaseline = 0x10,
+
+ // The baseline affects the margin or padding on the item's end side when
+ // this bit is set. In a grid-axis it's always set for eLastBaseline and
+ // always unset for eFirstBaseline. In a masonry-axis, it's set for
+ // baseline groups in the EndStretch set and unset for the StartStretch set.
+ eEndSideBaseline = 0x20,
+ eAllBaselineBits = eIsBaselineAligned | eSelfBaseline | eContentBaseline |
+ eEndSideBaseline,
+
+ // Should apply Automatic Minimum Size per:
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ eApplyAutoMinSize = 0x40,
+ // Clamp per https://drafts.csswg.org/css-grid/#min-size-auto
+ eClampMarginBoxMinSize = 0x80,
+ eIsSubgrid = 0x100,
+ // set on subgrids and items in subgrids if they are adjacent to the grid
+ // start/end edge (excluding grid-aligned abs.pos. frames)
+ eStartEdge = 0x200,
+ eEndEdge = 0x400,
+ eEdgeBits = eStartEdge | eEndEdge,
+ // Set if this item was auto-placed in this axis.
+ eAutoPlacement = 0x800,
+ // Set if this item is the last item in its track (masonry layout only)
+ eIsLastItemInMasonryTrack = 0x1000,
+ };
+
+ GridItemInfo(nsIFrame* aFrame, const GridArea& aArea);
+
+ GridItemInfo(const GridItemInfo& aOther)
+ : mFrame(aOther.mFrame), mArea(aOther.mArea) {
+ mBaselineOffset = aOther.mBaselineOffset;
+ mState = aOther.mState;
+ }
+
+ GridItemInfo& operator=(const GridItemInfo&) = delete;
+
+ static bool BaselineAlignmentAffectsEndSide(StateBits state) {
+ return state & StateBits::eEndSideBaseline;
+ }
+
+ /**
+ * Inhibit subgrid layout unless the item is placed in the first "track" in
+ * a parent masonry-axis, or has definite placement or spans all tracks in
+ * the parent grid-axis.
+ * TODO: this is stricter than what the Masonry proposal currently states
+ * (bug 1627581)
+ */
+ void MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent,
+ uint32_t aGridAxisTrackCount);
+
+ /**
+ * Inhibit subgridding in aAxis for this item.
+ */
+ void InhibitSubgrid(nsGridContainerFrame* aParent, LogicalAxis aAxis);
+
+ /**
+ * Return a copy of this item with its row/column data swapped.
+ */
+ GridItemInfo Transpose() const {
+ GridItemInfo info(mFrame, GridArea(mArea.mRows, mArea.mCols));
+ info.mState[eLogicalAxisBlock] = mState[eLogicalAxisInline];
+ info.mState[eLogicalAxisInline] = mState[eLogicalAxisBlock];
+ info.mBaselineOffset[eLogicalAxisBlock] =
+ mBaselineOffset[eLogicalAxisInline];
+ info.mBaselineOffset[eLogicalAxisInline] =
+ mBaselineOffset[eLogicalAxisBlock];
+ return info;
+ }
+
+ /** Swap the start/end sides in aAxis. */
+ inline void ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd);
+
+ // Is this item a subgrid in the given container axis?
+ bool IsSubgrid(LogicalAxis aAxis) const {
+ return mState[aAxis] & StateBits::eIsSubgrid;
+ }
+
+ // Is this item a subgrid in either axis?
+ bool IsSubgrid() const {
+ return IsSubgrid(eLogicalAxisInline) || IsSubgrid(eLogicalAxisBlock);
+ }
+
+ // Return the (inner) grid container frame associated with this subgrid item.
+ nsGridContainerFrame* SubgridFrame() const {
+ MOZ_ASSERT(IsSubgrid());
+ nsGridContainerFrame* gridFrame = GetGridContainerFrame(mFrame);
+ MOZ_ASSERT(gridFrame && gridFrame->IsSubgrid());
+ return gridFrame;
+ }
+
+ /**
+ * Adjust our grid areas to account for removed auto-fit tracks in aAxis.
+ */
+ void AdjustForRemovedTracks(LogicalAxis aAxis,
+ const nsTArray<uint32_t>& aNumRemovedTracks);
+
+ /**
+ * If the item is [align|justify]-self:[last ]baseline aligned in the given
+ * axis then set aBaselineOffset to the baseline offset and return aAlign.
+ * Otherwise, return a fallback alignment.
+ */
+ StyleAlignFlags GetSelfBaseline(StyleAlignFlags aAlign, LogicalAxis aAxis,
+ nscoord* aBaselineOffset) const {
+ MOZ_ASSERT(aAlign == StyleAlignFlags::BASELINE ||
+ aAlign == StyleAlignFlags::LAST_BASELINE);
+ if (!(mState[aAxis] & eSelfBaseline)) {
+ return aAlign == StyleAlignFlags::BASELINE ? StyleAlignFlags::SELF_START
+ : StyleAlignFlags::SELF_END;
+ }
+ *aBaselineOffset = mBaselineOffset[aAxis];
+ return aAlign;
+ }
+
+ // Return true if we should apply Automatic Minimum Size to this item.
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ // @note the caller should also check that the item spans at least one track
+ // that has a min track sizing function that is 'auto' before applying it.
+ bool ShouldApplyAutoMinSize(WritingMode aContainerWM,
+ LogicalAxis aContainerAxis,
+ nscoord aPercentageBasis) const {
+ const bool isInlineAxis = aContainerAxis == eLogicalAxisInline;
+ const auto* pos =
+ mFrame->IsTableWrapperFrame()
+ ? mFrame->PrincipalChildList().FirstChild()->StylePosition()
+ : mFrame->StylePosition();
+ const auto& size =
+ isInlineAxis ? pos->ISize(aContainerWM) : pos->BSize(aContainerWM);
+ // max-content and min-content should behave as initial value in block axis.
+ // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
+ // for block size dimension on sizing properties (e.g. height), so we
+ // treat it as `auto`.
+ bool isAuto = size.IsAuto() ||
+ (isInlineAxis ==
+ aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) &&
+ size.BehavesLikeInitialValueOnBlockAxis());
+ // NOTE: if we have a definite size then our automatic minimum size
+ // can't affect our size. Excluding these simplifies applying
+ // the clamping in the right cases later.
+ if (!isAuto && !::IsPercentOfIndefiniteSize(size, aPercentageBasis)) {
+ return false;
+ }
+ const auto& minSize = isInlineAxis ? pos->MinISize(aContainerWM)
+ : pos->MinBSize(aContainerWM);
+ // max-content and min-content should behave as initial value in block axis.
+ // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
+ // for block size dimension on sizing properties (e.g. height), so we
+ // treat it as `auto`.
+ isAuto = minSize.IsAuto() ||
+ (isInlineAxis ==
+ aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) &&
+ minSize.BehavesLikeInitialValueOnBlockAxis());
+ return isAuto && !mFrame->StyleDisplay()->IsScrollableOverflow();
+ }
+
+#ifdef DEBUG
+ void Dump() const;
+#endif
+
+ static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b) {
+ return a->mArea.mRows.mStart < b->mArea.mRows.mStart;
+ }
+
+ // Sorting functions for 'masonry-auto-flow:next'. We sort the items that
+ // were placed into the first track by the Grid placement algorithm first
+ // (to honor that placement). All other items will be placed by the Masonry
+ // layout algorithm (their Grid placement in the masonry axis is irrelevant).
+ static bool RowMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) {
+ return a->mArea.mRows.mStart == 0 && b->mArea.mRows.mStart != 0 &&
+ !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+ }
+ static bool ColMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) {
+ return a->mArea.mCols.mStart == 0 && b->mArea.mCols.mStart != 0 &&
+ !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+ }
+
+ // Sorting functions for 'masonry-auto-flow:definite-first'. Similar to
+ // the above, but here we also sort items with a definite item placement in
+ // the grid axis in track order before 'auto'-placed items. We also sort all
+ // continuations first since they use the same placement as their
+ // first-in-flow (we treat them as "definite" regardless of eAutoPlacement).
+ static bool RowMasonryDefiniteFirst(const GridItemInfo* a,
+ const GridItemInfo* b) {
+ bool isContinuationA = a->mFrame->GetPrevInFlow();
+ bool isContinuationB = b->mFrame->GetPrevInFlow();
+ if (isContinuationA != isContinuationB) {
+ return isContinuationA;
+ }
+ auto masonryA = a->mArea.mRows.mStart;
+ auto gridA = a->mState[eLogicalAxisInline] & StateBits::eAutoPlacement;
+ auto masonryB = b->mArea.mRows.mStart;
+ auto gridB = b->mState[eLogicalAxisInline] & StateBits::eAutoPlacement;
+ return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) &&
+ !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+ }
+ static bool ColMasonryDefiniteFirst(const GridItemInfo* a,
+ const GridItemInfo* b) {
+ MOZ_ASSERT(!a->mFrame->GetPrevInFlow() && !b->mFrame->GetPrevInFlow(),
+ "fragmentation not supported in inline axis");
+ auto masonryA = a->mArea.mCols.mStart;
+ auto gridA = a->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement;
+ auto masonryB = b->mArea.mCols.mStart;
+ auto gridB = b->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement;
+ return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) &&
+ !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+ }
+
+ // Return true if this items block size is dependent on the size of the
+ // container it is in.
+ bool IsBSizeDependentOnContainerSize(WritingMode aContainerWM) const {
+ const auto IsDependentOnContainerSize = [](const auto& size) -> bool {
+ return size.HasPercent() || size.IsMozAvailable();
+ };
+
+ const nsStylePosition* stylePos = mFrame->StylePosition();
+ bool isItemAutoSize =
+ IsDependentOnContainerSize(stylePos->BSize(aContainerWM)) ||
+ IsDependentOnContainerSize(stylePos->MinBSize(aContainerWM)) ||
+ IsDependentOnContainerSize(stylePos->MaxBSize(aContainerWM));
+
+ return isItemAutoSize;
+ }
+
+ nsIFrame* const mFrame;
+ GridArea mArea;
+
+ // Offset from the margin edge to the baseline (LogicalAxis index). It's from
+ // the start edge for first baseline sharing group, otherwise from the end
+ // edge.
+ // It's mutable since we update the value fairly late (just before reflowing
+ // the item).
+ mutable PerLogicalAxis<nscoord> mBaselineOffset;
+
+ // State bits per axis.
+ mutable PerLogicalAxis<StateBits> mState;
+};
+
+using GridItemInfo = nsGridContainerFrame::GridItemInfo;
+using ItemState = GridItemInfo::StateBits;
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ItemState)
+
+GridItemInfo::GridItemInfo(nsIFrame* aFrame, const GridArea& aArea)
+ : mFrame(aFrame), mArea(aArea), mBaselineOffset{0, 0} {
+ mState[eLogicalAxisBlock] =
+ StateBits(mArea.mRows.mStart == kAutoLine ? eAutoPlacement : 0);
+ mState[eLogicalAxisInline] =
+ StateBits(mArea.mCols.mStart == kAutoLine ? eAutoPlacement : 0);
+
+ if (auto* gridFrame = GetGridContainerFrame(mFrame)) {
+ auto parentWM = aFrame->GetParent()->GetWritingMode();
+ bool isOrthogonal = parentWM.IsOrthogonalTo(gridFrame->GetWritingMode());
+ if (gridFrame->IsColSubgrid()) {
+ mState[isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline] |=
+ StateBits::eIsSubgrid;
+ }
+ if (gridFrame->IsRowSubgrid()) {
+ mState[isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock] |=
+ StateBits::eIsSubgrid;
+ }
+ }
+}
+
+void GridItemInfo::ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd) {
+ mArea.LineRangeForAxis(aAxis).ReverseDirection(aGridEnd);
+ ItemState& state = mState[aAxis];
+ ItemState newState = state & ~ItemState::eEdgeBits;
+ if (state & ItemState::eStartEdge) {
+ newState |= ItemState::eEndEdge;
+ }
+ if (state & ItemState::eEndEdge) {
+ newState |= ItemState::eStartEdge;
+ }
+ state = newState;
+}
+
+void GridItemInfo::InhibitSubgrid(nsGridContainerFrame* aParent,
+ LogicalAxis aAxis) {
+ MOZ_ASSERT(IsSubgrid(aAxis));
+ auto bit = NS_STATE_GRID_IS_COL_SUBGRID;
+ if (aParent->GetWritingMode().IsOrthogonalTo(mFrame->GetWritingMode()) !=
+ (aAxis == eLogicalAxisBlock)) {
+ bit = NS_STATE_GRID_IS_ROW_SUBGRID;
+ }
+ MOZ_ASSERT(SubgridFrame()->HasAnyStateBits(bit));
+ SubgridFrame()->RemoveStateBits(bit);
+ mState[aAxis] &= StateBits(~StateBits::eIsSubgrid);
+}
+
+void GridItemInfo::MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent,
+ uint32_t aGridAxisTrackCount) {
+ if (IsSubgrid(eLogicalAxisInline) && aParent->IsMasonry(eLogicalAxisBlock) &&
+ mArea.mRows.mStart != 0 && mArea.mCols.Extent() != aGridAxisTrackCount &&
+ (mState[eLogicalAxisInline] & eAutoPlacement)) {
+ InhibitSubgrid(aParent, eLogicalAxisInline);
+ return;
+ }
+ if (IsSubgrid(eLogicalAxisBlock) && aParent->IsMasonry(eLogicalAxisInline) &&
+ mArea.mCols.mStart != 0 && mArea.mRows.Extent() != aGridAxisTrackCount &&
+ (mState[eLogicalAxisBlock] & eAutoPlacement)) {
+ InhibitSubgrid(aParent, eLogicalAxisBlock);
+ }
+}
+
+// Each subgrid stores this data about its items etc on a frame property.
+struct nsGridContainerFrame::Subgrid {
+ Subgrid(const GridArea& aArea, bool aIsOrthogonal, WritingMode aCBWM)
+ : mArea(aArea),
+ mGridColEnd(0),
+ mGridRowEnd(0),
+ mMarginBorderPadding(aCBWM),
+ mIsOrthogonal(aIsOrthogonal) {}
+
+ // Return the relevant line range for the subgrid column axis.
+ const LineRange& SubgridCols() const {
+ return mIsOrthogonal ? mArea.mRows : mArea.mCols;
+ }
+ // Return the relevant line range for the subgrid row axis.
+ const LineRange& SubgridRows() const {
+ return mIsOrthogonal ? mArea.mCols : mArea.mRows;
+ }
+
+ // The subgrid's items.
+ nsTArray<GridItemInfo> mGridItems;
+ // The subgrid's abs.pos. items.
+ nsTArray<GridItemInfo> mAbsPosItems;
+ // The subgrid's area as a grid item, i.e. in its parent's grid space.
+ GridArea mArea;
+ // The (inner) grid size for the subgrid, zero-based.
+ uint32_t mGridColEnd;
+ uint32_t mGridRowEnd;
+ // The margin+border+padding for the subgrid box in its parent grid's WM.
+ // (This also includes the size of any scrollbars.)
+ LogicalMargin mMarginBorderPadding;
+ // Does the subgrid frame have orthogonal writing-mode to its parent grid
+ // container?
+ bool mIsOrthogonal;
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, Subgrid)
+};
+using Subgrid = nsGridContainerFrame::Subgrid;
+
+void GridItemInfo::AdjustForRemovedTracks(
+ LogicalAxis aAxis, const nsTArray<uint32_t>& aNumRemovedTracks) {
+ const bool abspos = mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+ auto& lines = mArea.LineRangeForAxis(aAxis);
+ if (abspos) {
+ lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks);
+ } else {
+ lines.AdjustForRemovedTracks(aNumRemovedTracks);
+ }
+ if (IsSubgrid()) {
+ auto* subgrid = SubgridFrame()->GetProperty(Subgrid::Prop());
+ if (subgrid) {
+ auto& lines = subgrid->mArea.LineRangeForAxis(aAxis);
+ if (abspos) {
+ lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks);
+ } else {
+ lines.AdjustForRemovedTracks(aNumRemovedTracks);
+ }
+ }
+ }
+}
+
+/**
+ * Track size data for use by subgrids (which don't do sizing of their own
+ * in a subgridded axis). A non-subgrid container stores its resolved sizes,
+ * but only if it has any subgrid children. A subgrid always stores one.
+ * In a subgridded axis, we copy the parent's sizes (see CopyUsedTrackSizes).
+ *
+ * This struct us stored on a frame property, which may be null before the track
+ * sizing step for the given container. A null property is semantically
+ * equivalent to mCanResolveLineRangeSize being false in both axes.
+ * @note the axis used to access this data is in the grid container's own
+ * writing-mode, same as in other track-sizing functions.
+ */
+struct nsGridContainerFrame::UsedTrackSizes {
+ UsedTrackSizes() : mCanResolveLineRangeSize{false, false} {}
+
+ /**
+ * Setup mSizes by copying track sizes from aFrame's grid container
+ * parent when aAxis is subgridded (and recurse if the parent is a subgrid
+ * that doesn't have sizes yet), or by running the Track Sizing Algo when
+ * the axis is not subgridded (for a subgrid).
+ * Set mCanResolveLineRangeSize[aAxis] to true once we have obtained
+ * sizes for an axis (if it's already true then this method is a NOP).
+ */
+ void ResolveTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis,
+ gfxContext& aRC);
+
+ /** Helper function for the above method */
+ void ResolveSubgridTrackSizesForAxis(nsGridContainerFrame* aFrame,
+ LogicalAxis aAxis, Subgrid* aSubgrid,
+ gfxContext& aRC,
+ nscoord aContentBoxSize);
+
+ // This only has valid sizes when mCanResolveLineRangeSize is true in
+ // the same axis. It may have zero tracks (a grid with only abs.pos.
+ // subgrids/items may have zero tracks).
+ PerLogicalAxis<nsTArray<TrackSize>> mSizes;
+ // True if mSizes can be used to resolve line range sizes in an axis.
+ PerLogicalAxis<bool> mCanResolveLineRangeSize;
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, UsedTrackSizes)
+};
+using UsedTrackSizes = nsGridContainerFrame::UsedTrackSizes;
+
+#ifdef DEBUG
+void nsGridContainerFrame::GridItemInfo::Dump() const {
+ auto Dump1 = [this](const char* aMsg, LogicalAxis aAxis) {
+ auto state = mState[aAxis];
+ if (!state) {
+ return;
+ }
+ printf("%s", aMsg);
+ if (state & ItemState::eEdgeBits) {
+ printf("subgrid-adjacent-edges(");
+ if (state & ItemState::eStartEdge) {
+ printf("start ");
+ }
+ if (state & ItemState::eEndEdge) {
+ printf("end");
+ }
+ printf(") ");
+ }
+ if (state & ItemState::eAutoPlacement) {
+ printf("masonry-auto ");
+ }
+ if (state & ItemState::eIsSubgrid) {
+ printf("subgrid ");
+ }
+ if (state & ItemState::eIsFlexing) {
+ printf("flexing ");
+ }
+ if (state & ItemState::eApplyAutoMinSize) {
+ printf("auto-min-size ");
+ }
+ if (state & ItemState::eClampMarginBoxMinSize) {
+ printf("clamp ");
+ }
+ if (state & ItemState::eIsLastItemInMasonryTrack) {
+ printf("last-in-track ");
+ }
+ if (state & ItemState::eFirstBaseline) {
+ printf("first baseline %s-alignment ",
+ (state & ItemState::eSelfBaseline) ? "self" : "content");
+ }
+ if (state & ItemState::eLastBaseline) {
+ printf("last baseline %s-alignment ",
+ (state & ItemState::eSelfBaseline) ? "self" : "content");
+ }
+ if (state & ItemState::eIsBaselineAligned) {
+ printf("%.2fpx", NSAppUnitsToFloatPixels(mBaselineOffset[aAxis],
+ AppUnitsPerCSSPixel()));
+ }
+ printf("\n");
+ };
+ printf("grid-row: %d %d\n", mArea.mRows.mStart, mArea.mRows.mEnd);
+ Dump1(" grid block-axis: ", eLogicalAxisBlock);
+ printf("grid-column: %d %d\n", mArea.mCols.mStart, mArea.mCols.mEnd);
+ Dump1(" grid inline-axis: ", eLogicalAxisInline);
+}
+#endif
+
+/**
+ * Encapsulates CSS track-sizing functions.
+ */
+struct nsGridContainerFrame::TrackSizingFunctions {
+ private:
+ TrackSizingFunctions(const GridTemplate& aTemplate,
+ const StyleImplicitGridTracks& aAutoSizing,
+ const Maybe<size_t>& aRepeatAutoIndex, bool aIsSubgrid)
+ : mTemplate(aTemplate),
+ mTrackListValues(aTemplate.TrackListValues()),
+ mAutoSizing(aAutoSizing),
+ mExplicitGridOffset(0),
+ mRepeatAutoStart(aRepeatAutoIndex.valueOr(0)),
+ mRepeatAutoEnd(mRepeatAutoStart),
+ mHasRepeatAuto(aRepeatAutoIndex.isSome()) {
+ MOZ_ASSERT(!mHasRepeatAuto || !aIsSubgrid,
+ "a track-list for a subgrid can't have an <auto-repeat> track");
+ if (!aIsSubgrid) {
+ ExpandNonRepeatAutoTracks();
+ }
+
+#ifdef DEBUG
+ if (mHasRepeatAuto) {
+ MOZ_ASSERT(mExpandedTracks.Length() >= 1);
+ const unsigned maxTrack = kMaxLine - 1;
+ // If the exanded tracks are out of range of the maximum track, we
+ // can't compare the repeat-auto start. It will be removed later during
+ // grid item placement in that situation.
+ if (mExpandedTracks.Length() < maxTrack) {
+ MOZ_ASSERT(mRepeatAutoStart < mExpandedTracks.Length());
+ }
+ }
+#endif
+ }
+
+ public:
+ TrackSizingFunctions(const GridTemplate& aGridTemplate,
+ const StyleImplicitGridTracks& aAutoSizing,
+ bool aIsSubgrid)
+ : TrackSizingFunctions(aGridTemplate, aAutoSizing,
+ aGridTemplate.RepeatAutoIndex(), aIsSubgrid) {}
+
+ private:
+ enum { ForSubgridFallbackTag };
+ TrackSizingFunctions(const GridTemplate& aGridTemplate,
+ const StyleImplicitGridTracks& aAutoSizing,
+ decltype(ForSubgridFallbackTag))
+ : TrackSizingFunctions(aGridTemplate, aAutoSizing, Nothing(),
+ /* aIsSubgrid */ true) {}
+
+ public:
+ /**
+ * This is used in a subgridded axis to resolve sizes before its parent's
+ * sizes are known for intrinsic sizing purposes. It copies the slice of
+ * the nearest non-subgridded axis' track sizing functions spanned by
+ * the subgrid.
+ *
+ * FIXME: this was written before there was a spec... the spec now says:
+ * "If calculating the layout of a grid item in this step depends on
+ * the available space in the block axis, assume the available space
+ * that it would have if any row with a definite max track sizing
+ * function had that size and all other rows were infinite."
+ * https://drafts.csswg.org/css-grid-2/#subgrid-sizing
+ */
+ static TrackSizingFunctions ForSubgridFallback(
+ nsGridContainerFrame* aSubgridFrame, const Subgrid* aSubgrid,
+ nsGridContainerFrame* aParentGridContainer, LogicalAxis aParentAxis) {
+ MOZ_ASSERT(aSubgrid);
+ MOZ_ASSERT(aSubgridFrame->IsSubgrid(aSubgrid->mIsOrthogonal
+ ? GetOrthogonalAxis(aParentAxis)
+ : aParentAxis));
+ nsGridContainerFrame* parent = aParentGridContainer;
+ auto parentAxis = aParentAxis;
+ LineRange range = aSubgrid->mArea.LineRangeForAxis(parentAxis);
+ // Find our nearest non-subgridded axis and use its track sizing functions.
+ while (parent->IsSubgrid(parentAxis)) {
+ const auto* parentSubgrid = parent->GetProperty(Subgrid::Prop());
+ auto* grandParent = parent->ParentGridContainerForSubgrid();
+ auto grandParentWM = grandParent->GetWritingMode();
+ bool isSameDirInAxis =
+ parent->GetWritingMode().ParallelAxisStartsOnSameSide(parentAxis,
+ grandParentWM);
+ if (MOZ_UNLIKELY(!isSameDirInAxis)) {
+ auto end = parentAxis == eLogicalAxisBlock ? parentSubgrid->mGridRowEnd
+ : parentSubgrid->mGridColEnd;
+ range.ReverseDirection(end);
+ // range is now in the same direction as the grand-parent's axis
+ }
+ auto grandParentAxis = parentSubgrid->mIsOrthogonal
+ ? GetOrthogonalAxis(parentAxis)
+ : parentAxis;
+ const auto& parentRange =
+ parentSubgrid->mArea.LineRangeForAxis(grandParentAxis);
+ range.Translate(parentRange.mStart);
+ // range is now in the grand-parent's coordinates
+ parentAxis = grandParentAxis;
+ parent = grandParent;
+ }
+ const auto* pos = parent->StylePosition();
+ const auto isInlineAxis = parentAxis == eLogicalAxisInline;
+ const auto& szf =
+ isInlineAxis ? pos->mGridTemplateRows : pos->mGridTemplateColumns;
+ const auto& autoSizing =
+ isInlineAxis ? pos->mGridAutoColumns : pos->mGridAutoRows;
+ return TrackSizingFunctions(szf, autoSizing, ForSubgridFallbackTag);
+ }
+
+ /**
+ * Initialize the number of auto-fill/fit tracks to use.
+ * This can be zero if no auto-fill/fit track was specified, or if the repeat
+ * begins after the maximum allowed track.
+ */
+ void InitRepeatTracks(const NonNegativeLengthPercentageOrNormal& aGridGap,
+ nscoord aMinSize, nscoord aSize, nscoord aMaxSize) {
+ const uint32_t maxTrack = kMaxLine - 1;
+ // Check for a repeat after the maximum allowed track.
+ if (MOZ_UNLIKELY(mRepeatAutoStart >= maxTrack)) {
+ mHasRepeatAuto = false;
+ mRepeatAutoStart = 0;
+ mRepeatAutoEnd = 0;
+ return;
+ }
+ uint32_t repeatTracks =
+ CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize) *
+ NumRepeatTracks();
+ // Clamp the number of repeat tracks to the maximum possible track.
+ repeatTracks = std::min(repeatTracks, maxTrack - mRepeatAutoStart);
+ SetNumRepeatTracks(repeatTracks);
+ // Blank out the removed flags for each of these tracks.
+ mRemovedRepeatTracks.SetLength(repeatTracks);
+ for (auto& track : mRemovedRepeatTracks) {
+ track = false;
+ }
+ }
+
+ uint32_t CalculateRepeatFillCount(
+ const NonNegativeLengthPercentageOrNormal& aGridGap, nscoord aMinSize,
+ nscoord aSize, nscoord aMaxSize) const {
+ if (!mHasRepeatAuto) {
+ return 0;
+ }
+ // At this point no tracks will have been collapsed, so the RepeatEndDelta
+ // should not be negative.
+ MOZ_ASSERT(RepeatEndDelta() >= 0);
+ // Note that this uses NumRepeatTracks and mRepeatAutoStart/End, although
+ // the result of this method is used to change those values to a fully
+ // expanded value. Spec quotes are from
+ // https://drafts.csswg.org/css-grid/#repeat-notation
+ const uint32_t numTracks = mExpandedTracks.Length() + RepeatEndDelta();
+ MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track");
+ if (MOZ_UNLIKELY(numTracks >= kMaxLine)) {
+ // The fixed tracks plus an entire repetition is either larger or as
+ // large as the maximum track, so we do not need to measure how many
+ // repetitions will fit. This also avoids needing to check for if
+ // kMaxLine - numTracks would underflow at the end where we clamp the
+ // result.
+ return 1;
+ }
+ nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize;
+ if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == 0) {
+ // "Otherwise, the specified track list repeats only once."
+ return 1;
+ }
+ nscoord repeatTrackSum = 0;
+ // Note that one repeat() track size is included in |sum| in this loop.
+ nscoord sum = 0;
+ const nscoord percentBasis = aSize;
+ for (uint32_t i = 0; i < numTracks; ++i) {
+ // "treating each track as its max track sizing function if that is
+ // definite or as its minimum track sizing function otherwise"
+ // https://drafts.csswg.org/css-grid/#valdef-repeat-auto-fill
+ const auto& sizingFunction = SizingFor(i);
+ const auto& maxCoord = sizingFunction.GetMax();
+ const auto* coord = &maxCoord;
+ if (!coord->IsBreadth()) {
+ coord = &sizingFunction.GetMin();
+ if (!coord->IsBreadth()) {
+ return 1;
+ }
+ }
+ nscoord trackSize = ::ResolveToDefiniteSize(*coord, percentBasis);
+ if (i >= mRepeatAutoStart && i < mRepeatAutoEnd) {
+ // Use a minimum 1px for the repeat() track-size.
+ if (trackSize < AppUnitsPerCSSPixel()) {
+ trackSize = AppUnitsPerCSSPixel();
+ }
+ repeatTrackSum += trackSize;
+ }
+ sum += trackSize;
+ }
+ nscoord gridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aSize);
+ if (numTracks > 1) {
+ // Add grid-gaps for all the tracks including the repeat() track.
+ sum += gridGap * (numTracks - 1);
+ }
+ // Calculate the max number of tracks that fits without overflow.
+ nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize;
+ nscoord spaceToFill = available - sum;
+ if (spaceToFill <= 0) {
+ // "if any number of repetitions would overflow, then 1 repetition"
+ return 1;
+ }
+ // Calculate the max number of tracks that fits without overflow.
+ // Since we already have one repetition in sum, we can simply add one grid
+ // gap for each element in the repeat.
+ div_t q = div(spaceToFill, repeatTrackSum + gridGap * NumRepeatTracks());
+ // The +1 here is for the one repeat track we already accounted for above.
+ uint32_t numRepeatTracks = q.quot + 1;
+ if (q.rem != 0 && maxFill == NS_UNCONSTRAINEDSIZE) {
+ // "Otherwise, if the grid container has a definite min size in
+ // the relevant axis, the number of repetitions is the largest possible
+ // positive integer that fulfills that minimum requirement."
+ ++numRepeatTracks; // one more to ensure the grid is at least min-size
+ }
+ // Clamp the number of repeat tracks so that the last line <= kMaxLine.
+ // (note that |numTracks| already includes one repeat() track)
+ MOZ_ASSERT(numTracks >= NumRepeatTracks());
+ const uint32_t maxRepeatTrackCount = kMaxLine - numTracks;
+ const uint32_t maxRepetitions = maxRepeatTrackCount / NumRepeatTracks();
+ return std::min(numRepeatTracks, maxRepetitions);
+ }
+
+ /**
+ * Compute the explicit grid end line number (in a zero-based grid).
+ * @param aGridTemplateAreasEnd 'grid-template-areas' end line in this axis
+ */
+ uint32_t ComputeExplicitGridEnd(uint32_t aGridTemplateAreasEnd) {
+ uint32_t end = NumExplicitTracks() + 1;
+ end = std::max(end, aGridTemplateAreasEnd);
+ end = std::min(end, uint32_t(kMaxLine));
+ return end;
+ }
+ const StyleTrackSize& SizingFor(uint32_t aTrackIndex) const {
+ static const StyleTrackSize kAutoTrackSize =
+ StyleTrackSize::Breadth(StyleTrackBreadth::Auto());
+ // |aIndex| is the relative index to mAutoSizing. A negative value means it
+ // is the last Nth element.
+ auto getImplicitSize = [this](int32_t aIndex) -> const StyleTrackSize& {
+ MOZ_ASSERT(!(mAutoSizing.Length() == 1 &&
+ mAutoSizing.AsSpan()[0] == kAutoTrackSize),
+ "It's impossible to have one track with auto value because we "
+ "filter out this case during parsing");
+
+ if (mAutoSizing.IsEmpty()) {
+ return kAutoTrackSize;
+ }
+
+ // If multiple track sizes are given, the pattern is repeated as necessary
+ // to find the size of the implicit tracks.
+ int32_t i = aIndex % int32_t(mAutoSizing.Length());
+ if (i < 0) {
+ i += mAutoSizing.Length();
+ }
+ return mAutoSizing.AsSpan()[i];
+ };
+
+ if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
+ // The last implicit grid track before the explicit grid receives the
+ // last specified size, and so on backwards. Therefore we pass the
+ // negative relative index to imply that we should get the implicit size
+ // from the last Nth specified grid auto size.
+ return getImplicitSize(int32_t(aTrackIndex) -
+ int32_t(mExplicitGridOffset));
+ }
+ uint32_t index = aTrackIndex - mExplicitGridOffset;
+ MOZ_ASSERT(mRepeatAutoStart <= mRepeatAutoEnd);
+
+ if (index >= mRepeatAutoStart) {
+ if (index < mRepeatAutoEnd) {
+ // Expand the repeat tracks.
+ const auto& indices = mExpandedTracks[mRepeatAutoStart];
+ const TrackListValue& value = mTrackListValues[indices.first];
+
+ // We expect the default to be used for all track repeats.
+ MOZ_ASSERT(indices.second == 0);
+
+ const auto& repeatTracks = value.AsTrackRepeat().track_sizes.AsSpan();
+
+ // Find the repeat track to use, skipping over any collapsed tracks.
+ const uint32_t finalRepeatIndex = (index - mRepeatAutoStart);
+ uint32_t repeatWithCollapsed = 0;
+ // NOTE: We need SizingFor before the final collapsed tracks are known.
+ // We know that it's invalid to have empty mRemovedRepeatTracks when
+ // there are any repeat tracks, so we can detect that situation here.
+ if (mRemovedRepeatTracks.IsEmpty()) {
+ repeatWithCollapsed = finalRepeatIndex;
+ } else {
+ // Count up through the repeat tracks, until we have seen
+ // finalRepeatIndex number of non-collapsed tracks.
+ for (uint32_t repeatNoCollapsed = 0;
+ repeatNoCollapsed < finalRepeatIndex; repeatWithCollapsed++) {
+ if (!mRemovedRepeatTracks[repeatWithCollapsed]) {
+ repeatNoCollapsed++;
+ }
+ }
+ // If we stopped iterating on a collapsed track, continue to the next
+ // non-collapsed track.
+ while (mRemovedRepeatTracks[repeatWithCollapsed]) {
+ repeatWithCollapsed++;
+ }
+ }
+ return repeatTracks[repeatWithCollapsed % repeatTracks.Length()];
+ } else {
+ // The index is after the repeat auto range, adjust it to skip over the
+ // repeat value. This will have no effect if there is no auto repeat,
+ // since then RepeatEndDelta will return zero.
+ index -= RepeatEndDelta();
+ }
+ }
+ if (index >= mExpandedTracks.Length()) {
+ return getImplicitSize(index - mExpandedTracks.Length());
+ }
+ auto& indices = mExpandedTracks[index];
+ const TrackListValue& value = mTrackListValues[indices.first];
+ if (value.IsTrackSize()) {
+ MOZ_ASSERT(indices.second == 0);
+ return value.AsTrackSize();
+ }
+ return value.AsTrackRepeat().track_sizes.AsSpan()[indices.second];
+ }
+ const StyleTrackBreadth& MaxSizingFor(uint32_t aTrackIndex) const {
+ return SizingFor(aTrackIndex).GetMax();
+ }
+ const StyleTrackBreadth& MinSizingFor(uint32_t aTrackIndex) const {
+ return SizingFor(aTrackIndex).GetMin();
+ }
+ uint32_t NumExplicitTracks() const {
+ return mExpandedTracks.Length() + RepeatEndDelta();
+ }
+ uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; }
+ // The difference between mExplicitGridEnd and mSizingFunctions.Length().
+ int32_t RepeatEndDelta() const {
+ return mHasRepeatAuto ? int32_t(NumRepeatTracks()) - 1 : 0;
+ }
+ void SetNumRepeatTracks(uint32_t aNumRepeatTracks) {
+ MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0);
+ mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks;
+ }
+
+ // Store mTrackListValues into mExpandedTracks with `repeat(INTEGER, ...)`
+ // tracks expanded.
+ void ExpandNonRepeatAutoTracks() {
+ for (size_t i = 0; i < mTrackListValues.Length(); ++i) {
+ auto& value = mTrackListValues[i];
+ if (value.IsTrackSize()) {
+ mExpandedTracks.EmplaceBack(i, 0);
+ continue;
+ }
+ auto& repeat = value.AsTrackRepeat();
+ if (!repeat.count.IsNumber()) {
+ MOZ_ASSERT(i == mRepeatAutoStart);
+ mRepeatAutoStart = mExpandedTracks.Length();
+ mRepeatAutoEnd = mRepeatAutoStart + repeat.track_sizes.Length();
+ mExpandedTracks.EmplaceBack(i, 0);
+ continue;
+ }
+ for (auto j : IntegerRange(repeat.count.AsNumber())) {
+ Unused << j;
+ size_t trackSizesCount = repeat.track_sizes.Length();
+ for (auto k : IntegerRange(trackSizesCount)) {
+ mExpandedTracks.EmplaceBack(i, k);
+ }
+ }
+ }
+ if (MOZ_UNLIKELY(mExpandedTracks.Length() > kMaxLine - 1)) {
+ mExpandedTracks.TruncateLength(kMaxLine - 1);
+ if (mHasRepeatAuto && mRepeatAutoStart > kMaxLine - 1) {
+ // The `repeat(auto-fill/fit)` track is outside the clamped grid.
+ mHasRepeatAuto = false;
+ }
+ }
+ }
+
+ // Some style data references, for easy access.
+ const GridTemplate& mTemplate;
+ const Span<const TrackListValue> mTrackListValues;
+ const StyleImplicitGridTracks& mAutoSizing;
+ // An array from expanded track sizes (without expanding auto-repeat, which is
+ // included just once at `mRepeatAutoStart`).
+ //
+ // Each entry contains two indices, the first into mTrackListValues, and a
+ // second one inside mTrackListValues' repeat value, if any, or zero
+ // otherwise.
+ nsTArray<std::pair<size_t, size_t>> mExpandedTracks;
+ // Offset from the start of the implicit grid to the first explicit track.
+ uint32_t mExplicitGridOffset;
+ // The index of the repeat(auto-fill/fit) track, or zero if there is none.
+ // Relative to mExplicitGridOffset (repeat tracks are explicit by definition).
+ uint32_t mRepeatAutoStart;
+ // The (hypothetical) index of the last such repeat() track.
+ uint32_t mRepeatAutoEnd;
+ // True if there is a specified repeat(auto-fill/fit) track.
+ bool mHasRepeatAuto;
+ // True if this track (relative to mRepeatAutoStart) is a removed auto-fit.
+ // Indexed relative to mExplicitGridOffset + mRepeatAutoStart.
+ nsTArray<bool> mRemovedRepeatTracks;
+};
+
+/**
+ * Utility class to find line names. It provides an interface to lookup line
+ * names with a dynamic number of repeat(auto-fill/fit) tracks taken into
+ * account.
+ */
+class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap {
+ public:
+ /**
+ * Create a LineNameMap.
+ * @param aStylePosition the style for the grid container
+ * @param aImplicitNamedAreas the implicit areas for the grid container
+ * @param aGridTemplate is the grid-template-rows/columns data for this axis
+ * @param aParentLineNameMap the parent grid's map parallel to this map, or
+ * null if this map isn't for a subgrid
+ * @param aRange the subgrid's range in the parent grid, or null
+ * @param aIsSameDirection true if our axis progresses in the same direction
+ * in the subgrid and parent
+ */
+ LineNameMap(const nsStylePosition* aStylePosition,
+ const ImplicitNamedAreas* aImplicitNamedAreas,
+ const TrackSizingFunctions& aTracks,
+ const LineNameMap* aParentLineNameMap, const LineRange* aRange,
+ bool aIsSameDirection)
+ : mStylePosition(aStylePosition),
+ mAreas(aImplicitNamedAreas),
+ mRepeatAutoStart(aTracks.mRepeatAutoStart),
+ mRepeatAutoEnd(aTracks.mRepeatAutoEnd),
+ mRepeatEndDelta(aTracks.RepeatEndDelta()),
+ mParentLineNameMap(aParentLineNameMap),
+ mRange(aRange),
+ mIsSameDirection(aIsSameDirection),
+ mHasRepeatAuto(aTracks.mHasRepeatAuto) {
+ if (MOZ_UNLIKELY(aRange)) { // subgrid case
+ mClampMinLine = 1;
+ mClampMaxLine = 1 + aRange->Extent();
+ MOZ_ASSERT(aTracks.mTemplate.IsSubgrid(), "Should be subgrid type");
+ ExpandRepeatLineNamesForSubgrid(*aTracks.mTemplate.AsSubgrid());
+ // we've expanded all subgrid auto-fill lines in
+ // ExpandRepeatLineNamesForSubgrid()
+ mRepeatAutoStart = 0;
+ mRepeatAutoEnd = mRepeatAutoStart;
+ mHasRepeatAuto = false;
+ } else {
+ mClampMinLine = kMinLine;
+ mClampMaxLine = kMaxLine;
+ if (mHasRepeatAuto) {
+ mTrackAutoRepeatLineNames =
+ aTracks.mTemplate.GetRepeatAutoValue()->line_names.AsSpan();
+ }
+ ExpandRepeatLineNames(aTracks);
+ }
+ if (mHasRepeatAuto) {
+ // We need mTemplateLinesEnd to be after all line names.
+ // mExpandedLineNames has one repetition of the repeat(auto-fit/fill)
+ // track name lists already, so we must subtract the number of repeat
+ // track name lists to get to the number of non-repeat tracks, minus 2
+ // because the first and last line name lists are shared with the
+ // preceding and following non-repeat line name lists. We then add
+ // mRepeatEndDelta to include the interior line name lists from repeat
+ // tracks.
+ mTemplateLinesEnd = mExpandedLineNames.Length() -
+ (mTrackAutoRepeatLineNames.Length() - 2) +
+ mRepeatEndDelta;
+ } else {
+ mTemplateLinesEnd = mExpandedLineNames.Length();
+ }
+ MOZ_ASSERT(mHasRepeatAuto || mRepeatEndDelta <= 0);
+ MOZ_ASSERT(!mHasRepeatAuto || aRange ||
+ (mExpandedLineNames.Length() >= 2 &&
+ mRepeatAutoStart <= mExpandedLineNames.Length()));
+ }
+
+ // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)`
+ // expanded for non-subgrid.
+ void ExpandRepeatLineNames(const TrackSizingFunctions& aTracks) {
+ auto lineNameLists = aTracks.mTemplate.LineNameLists(false);
+
+ const auto& trackListValues = aTracks.mTrackListValues;
+ const NameList* nameListToMerge = nullptr;
+ // NOTE(emilio): We rely on std::move clearing out the array.
+ SmallPointerArray<const NameList> names;
+ const uint32_t end =
+ std::min<uint32_t>(lineNameLists.Length(), mClampMaxLine + 1);
+ for (uint32_t i = 0; i < end; ++i) {
+ if (nameListToMerge) {
+ names.AppendElement(nameListToMerge);
+ nameListToMerge = nullptr;
+ }
+ names.AppendElement(&lineNameLists[i]);
+ if (i >= trackListValues.Length()) {
+ mExpandedLineNames.AppendElement(std::move(names));
+ continue;
+ }
+ const auto& value = trackListValues[i];
+ if (value.IsTrackSize()) {
+ mExpandedLineNames.AppendElement(std::move(names));
+ continue;
+ }
+ const auto& repeat = value.AsTrackRepeat();
+ if (!repeat.count.IsNumber()) {
+ const auto repeatNames = repeat.line_names.AsSpan();
+ // If the repeat was truncated due to more than kMaxLine tracks, then
+ // the repeat will no longer be set on mRepeatAutoStart).
+ MOZ_ASSERT(!mHasRepeatAuto ||
+ mRepeatAutoStart == mExpandedLineNames.Length());
+ MOZ_ASSERT(repeatNames.Length() >= 2);
+ for (const auto j : IntegerRange(repeatNames.Length() - 1)) {
+ names.AppendElement(&repeatNames[j]);
+ mExpandedLineNames.AppendElement(std::move(names));
+ }
+ nameListToMerge = &repeatNames[repeatNames.Length() - 1];
+ continue;
+ }
+ for (auto j : IntegerRange(repeat.count.AsNumber())) {
+ Unused << j;
+ if (nameListToMerge) {
+ names.AppendElement(nameListToMerge);
+ nameListToMerge = nullptr;
+ }
+ size_t trackSizesCount = repeat.track_sizes.Length();
+ auto repeatLineNames = repeat.line_names.AsSpan();
+ MOZ_ASSERT(repeatLineNames.Length() == trackSizesCount ||
+ repeatLineNames.Length() == trackSizesCount + 1);
+ for (auto k : IntegerRange(trackSizesCount)) {
+ names.AppendElement(&repeatLineNames[k]);
+ mExpandedLineNames.AppendElement(std::move(names));
+ }
+ if (repeatLineNames.Length() == trackSizesCount + 1) {
+ nameListToMerge = &repeatLineNames[trackSizesCount];
+ }
+ }
+ }
+
+ if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) {
+ mExpandedLineNames.TruncateLength(mClampMaxLine);
+ }
+ }
+
+ // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)`
+ // expanded, and all `repeat(...)` expanded for subgrid.
+ // https://drafts.csswg.org/css-grid/#resolved-track-list-subgrid
+ void ExpandRepeatLineNamesForSubgrid(
+ const StyleGenericLineNameList<StyleInteger>& aStyleLineNameList) {
+ const auto& lineNameList = aStyleLineNameList.line_names.AsSpan();
+ const uint32_t maxCount = mClampMaxLine + 1;
+ const uint32_t end = lineNameList.Length();
+ for (uint32_t i = 0; i < end && mExpandedLineNames.Length() < maxCount;
+ ++i) {
+ const auto& item = lineNameList[i];
+ if (item.IsLineNames()) {
+ // <line-names> case. Just copy it.
+ SmallPointerArray<const NameList> names;
+ names.AppendElement(&item.AsLineNames());
+ mExpandedLineNames.AppendElement(std::move(names));
+ continue;
+ }
+
+ MOZ_ASSERT(item.IsRepeat());
+ const auto& repeat = item.AsRepeat();
+ const auto repeatLineNames = repeat.line_names.AsSpan();
+
+ if (repeat.count.IsNumber()) {
+ // Clone all <line-names>+ (repeated by N) into
+ // |mExpandedLineNames|.
+ for (uint32_t repeatCount = 0;
+ repeatCount < (uint32_t)repeat.count.AsNumber(); ++repeatCount) {
+ for (const NameList& lineNames : repeatLineNames) {
+ SmallPointerArray<const NameList> names;
+ names.AppendElement(&lineNames);
+ mExpandedLineNames.AppendElement(std::move(names));
+ if (mExpandedLineNames.Length() >= maxCount) {
+ break;
+ }
+ }
+ }
+ continue;
+ }
+
+ MOZ_ASSERT(repeat.count.IsAutoFill(),
+ "RepeatCount of subgrid is number or auto-fill");
+
+ const size_t fillLen = repeatLineNames.Length();
+ const int32_t extraAutoFillLineCount =
+ mClampMaxLine -
+ (int32_t)aStyleLineNameList.expanded_line_names_length;
+ // Maximum possible number of repeat name lists.
+ // Note: |expanded_line_names_length| doesn't include auto repeat.
+ const uint32_t possibleRepeatLength =
+ std::max<int32_t>(0, extraAutoFillLineCount);
+ const uint32_t repeatRemainder = possibleRepeatLength % fillLen;
+
+ // Note: Expand 'auto-fill' names for subgrid for now since
+ // HasNameAt() only deals with auto-repeat **tracks** currently.
+ const size_t len = possibleRepeatLength - repeatRemainder;
+ for (size_t j = 0; j < len; ++j) {
+ SmallPointerArray<const NameList> names;
+ names.AppendElement(&repeatLineNames[j % fillLen]);
+ mExpandedLineNames.AppendElement(std::move(names));
+ if (mExpandedLineNames.Length() >= maxCount) {
+ break;
+ }
+ }
+ }
+
+ if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) {
+ mExpandedLineNames.TruncateLength(mClampMaxLine);
+ }
+ }
+
+ /**
+ * Find the aNth occurrence of aName, searching forward if aNth is positive,
+ * and in reverse if aNth is negative (aNth == 0 is invalid), starting from
+ * aFromIndex (not inclusive), and return a 1-based line number.
+ * Also take into account there is an unconditional match at the lines in
+ * aImplicitLines.
+ * Return zero if aNth occurrences can't be found. In that case, aNth has
+ * been decremented with the number of occurrences that were found (if any).
+ *
+ * E.g. to search for "A 2" forward from the start of the grid: aName is "A"
+ * aNth is 2 and aFromIndex is zero. To search for "A -2", aNth is -2 and
+ * aFromIndex is ExplicitGridEnd + 1 (which is the line "before" the last
+ * line when we're searching in reverse). For "span A 2", aNth is 2 when
+ * used on a grid-[row|column]-end property and -2 for a *-start property,
+ * and aFromIndex is the line (which we should skip) on the opposite property.
+ */
+ uint32_t FindNamedLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex,
+ const nsTArray<uint32_t>& aImplicitLines) const {
+ MOZ_ASSERT(aName);
+ MOZ_ASSERT(!aName->IsEmpty());
+ MOZ_ASSERT(aNth && *aNth != 0);
+ if (*aNth > 0) {
+ return FindLine(aName, aNth, aFromIndex, aImplicitLines);
+ }
+ int32_t nth = -*aNth;
+ int32_t line = RFindLine(aName, &nth, aFromIndex, aImplicitLines);
+ *aNth = -nth;
+ return line;
+ }
+
+ /**
+ * Return a set of lines in aImplicitLines which matches the area name aName
+ * on aSide. For example, for aName "a" and aSide being an end side, it
+ * returns the line numbers which would match "a-end" in the relevant axis.
+ * For subgrids it includes searching the relevant axis in all ancestor
+ * grids too (within this subgrid's spanned area). If an ancestor has
+ * opposite direction, we switch aSide to the opposite logical side so we
+ * match on the same physical side as the original subgrid we're resolving
+ * the name for.
+ */
+ void FindNamedAreas(nsAtom* aName, LogicalSide aSide,
+ nsTArray<uint32_t>& aImplicitLines) const {
+ // True if we're currently in a map that has the same direction as 'this'.
+ bool sameDirectionAsThis = true;
+ uint32_t min = !mParentLineNameMap ? 1 : mClampMinLine;
+ uint32_t max = mClampMaxLine;
+ for (auto* map = this; true;) {
+ uint32_t line = map->FindNamedArea(aName, aSide, min, max);
+ if (line > 0) {
+ if (MOZ_LIKELY(sameDirectionAsThis)) {
+ line -= min - 1;
+ } else {
+ line = max - line + 1;
+ }
+ aImplicitLines.AppendElement(line);
+ }
+ auto* parent = map->mParentLineNameMap;
+ if (!parent) {
+ if (MOZ_UNLIKELY(aImplicitLines.Length() > 1)) {
+ // Remove duplicates and sort in ascending order.
+ aImplicitLines.Sort();
+ for (size_t i = 0; i < aImplicitLines.Length(); ++i) {
+ uint32_t prev = aImplicitLines[i];
+ auto j = i + 1;
+ const auto start = j;
+ while (j < aImplicitLines.Length() && aImplicitLines[j] == prev) {
+ ++j;
+ }
+ if (j != start) {
+ aImplicitLines.RemoveElementsAt(start, j - start);
+ }
+ }
+ }
+ return;
+ }
+ if (MOZ_UNLIKELY(!map->mIsSameDirection)) {
+ aSide = GetOppositeSide(aSide);
+ sameDirectionAsThis = !sameDirectionAsThis;
+ }
+ min = map->TranslateToParentMap(min);
+ max = map->TranslateToParentMap(max);
+ if (min > max) {
+ MOZ_ASSERT(!map->mIsSameDirection);
+ std::swap(min, max);
+ }
+ map = parent;
+ }
+ }
+
+ /**
+ * Return true if any implicit named areas match aName, in this map or
+ * in any of our ancestor maps.
+ */
+ bool HasImplicitNamedArea(nsAtom* aName) const {
+ const auto* map = this;
+ do {
+ if (map->mAreas && map->mAreas->has(aName)) {
+ return true;
+ }
+ map = map->mParentLineNameMap;
+ } while (map);
+ return false;
+ }
+
+ // For generating line name data for devtools.
+ nsTArray<nsTArray<StyleCustomIdent>>
+ GetResolvedLineNamesForComputedGridTrackInfo() const {
+ nsTArray<nsTArray<StyleCustomIdent>> result;
+ for (auto& expandedLine : mExpandedLineNames) {
+ nsTArray<StyleCustomIdent> line;
+ for (auto* chunk : expandedLine) {
+ for (auto& name : chunk->AsSpan()) {
+ line.AppendElement(name);
+ }
+ }
+ result.AppendElement(std::move(line));
+ }
+ return result;
+ }
+
+ nsTArray<RefPtr<nsAtom>> GetExplicitLineNamesAtIndex(uint32_t aIndex) const {
+ nsTArray<RefPtr<nsAtom>> lineNames;
+ if (aIndex < mTemplateLinesEnd) {
+ const auto nameLists = GetLineNamesAt(aIndex);
+ for (const NameList* nameList : nameLists) {
+ for (const auto& name : nameList->AsSpan()) {
+ lineNames.AppendElement(name.AsAtom());
+ }
+ }
+ }
+ return lineNames;
+ }
+
+ const nsTArray<SmallPointerArray<const NameList>>& ExpandedLineNames() const {
+ return mExpandedLineNames;
+ }
+ const Span<const StyleOwnedSlice<StyleCustomIdent>>&
+ TrackAutoRepeatLineNames() const {
+ return mTrackAutoRepeatLineNames;
+ }
+ bool HasRepeatAuto() const { return mHasRepeatAuto; }
+ uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; }
+ uint32_t RepeatAutoStart() const { return mRepeatAutoStart; }
+
+ // The min/max line number (1-based) for clamping.
+ int32_t mClampMinLine;
+ int32_t mClampMaxLine;
+
+ private:
+ // Return true if this map represents a subgridded axis.
+ bool IsSubgridded() const { return mParentLineNameMap != nullptr; }
+
+ /**
+ * @see FindNamedLine, this function searches forward.
+ */
+ uint32_t FindLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex,
+ const nsTArray<uint32_t>& aImplicitLines) const {
+ MOZ_ASSERT(aNth && *aNth > 0);
+ int32_t nth = *aNth;
+ // For a subgrid we need to search to the end of the grid rather than
+ // the end of the local name list, since ancestors might match.
+ const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd;
+ uint32_t line;
+ uint32_t i = aFromIndex;
+ for (; i < end; i = line) {
+ line = i + 1;
+ if (Contains(i, aName) || aImplicitLines.Contains(line)) {
+ if (--nth == 0) {
+ return line;
+ }
+ }
+ }
+ for (auto implicitLine : aImplicitLines) {
+ if (implicitLine > i) {
+ // implicitLine is after the lines we searched above so it's last.
+ // (grid-template-areas has more tracks than
+ // grid-template-[rows|columns])
+ if (--nth == 0) {
+ return implicitLine;
+ }
+ }
+ }
+ MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
+ *aNth = nth;
+ return 0;
+ }
+
+ /**
+ * @see FindNamedLine, this function searches in reverse.
+ */
+ uint32_t RFindLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex,
+ const nsTArray<uint32_t>& aImplicitLines) const {
+ MOZ_ASSERT(aNth && *aNth > 0);
+ if (MOZ_UNLIKELY(aFromIndex == 0)) {
+ return 0; // There are no named lines beyond the start of the explicit
+ // grid.
+ }
+ --aFromIndex; // (shift aFromIndex so we can treat it as inclusive)
+ int32_t nth = *aNth;
+ // Implicit lines may be beyond the explicit grid so we match those
+ // first if it's within the mTemplateLinesEnd..aFromIndex range.
+ // aImplicitLines is presumed sorted.
+ // For a subgrid we need to search to the end of the grid rather than
+ // the end of the local name list, since ancestors might match.
+ const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd;
+ for (auto implicitLine : Reversed(aImplicitLines)) {
+ if (implicitLine <= end) {
+ break;
+ }
+ if (implicitLine < aFromIndex) {
+ if (--nth == 0) {
+ return implicitLine;
+ }
+ }
+ }
+ for (uint32_t i = std::min(aFromIndex, end); i; --i) {
+ if (Contains(i - 1, aName) || aImplicitLines.Contains(i)) {
+ if (--nth == 0) {
+ return i;
+ }
+ }
+ }
+ MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
+ *aNth = nth;
+ return 0;
+ }
+
+ // Return true if aName exists at aIndex in this map or any parent map.
+ bool Contains(uint32_t aIndex, nsAtom* aName) const {
+ const auto* map = this;
+ while (true) {
+ if (aIndex < map->mTemplateLinesEnd && map->HasNameAt(aIndex, aName)) {
+ return true;
+ }
+ auto* parent = map->mParentLineNameMap;
+ if (!parent) {
+ return false;
+ }
+ uint32_t line = map->TranslateToParentMap(aIndex + 1);
+ MOZ_ASSERT(line >= 1, "expected a 1-based line number");
+ aIndex = line - 1;
+ map = parent;
+ }
+ MOZ_ASSERT_UNREACHABLE("we always return from inside the loop above");
+ }
+
+ static bool Contains(Span<const StyleCustomIdent> aNames, nsAtom* aName) {
+ for (auto& name : aNames) {
+ if (name.AsAtom() == aName) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Return true if aName exists at aIndex in this map.
+ bool HasNameAt(const uint32_t aIndex, nsAtom* const aName) const {
+ const auto nameLists = GetLineNamesAt(aIndex);
+ for (const NameList* nameList : nameLists) {
+ if (Contains(nameList->AsSpan(), aName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Get the line names at an index.
+ // This accounts for auto repeat. The results may be spread over multiple name
+ // lists returned in the array, which is done to avoid unneccessarily copying
+ // the arrays to concatenate them.
+ SmallPointerArray<const NameList> GetLineNamesAt(
+ const uint32_t aIndex) const {
+ SmallPointerArray<const NameList> names;
+ // The index into mExpandedLineNames to use, if aIndex doesn't point to a
+ // name inside of a auto repeat.
+ uint32_t repeatAdjustedIndex = aIndex;
+ // Note: For subgrid, |mHasRepeatAuto| is always false because we have
+ // expanded it in the constructor of LineNameMap.
+ if (mHasRepeatAuto) {
+ // If the index is inside of the auto repeat, use the repeat line
+ // names. Otherwise, if the index is past the end of the repeat it must
+ // be adjusted to acount for the repeat tracks.
+ // mExpandedLineNames has the first and last line name lists from the
+ // repeat in it already, so we can just ignore aIndex == mRepeatAutoStart
+ // and treat when aIndex == mRepeatAutoEnd the same as any line after the
+ // the repeat.
+ const uint32_t maxRepeatLine = mTrackAutoRepeatLineNames.Length() - 1;
+ if (aIndex > mRepeatAutoStart && aIndex < mRepeatAutoEnd) {
+ // The index is inside the auto repeat. Calculate the lines to use,
+ // including the previous repetitions final names when we roll over
+ // from one repetition to the next.
+ const uint32_t repeatIndex =
+ (aIndex - mRepeatAutoStart) % maxRepeatLine;
+ if (repeatIndex == 0) {
+ // The index is at the start of a new repetition. The start of the
+ // first repetition is intentionally ignored above, so this will
+ // consider both the end of the previous repetition and the start
+ // the one that contains aIndex.
+ names.AppendElement(&mTrackAutoRepeatLineNames[maxRepeatLine]);
+ }
+ names.AppendElement(&mTrackAutoRepeatLineNames[repeatIndex]);
+ return names;
+ }
+ if (aIndex != mRepeatAutoStart && aIndex >= mRepeatAutoEnd) {
+ // Adjust the index to account for the line names of the repeat.
+ repeatAdjustedIndex -= mRepeatEndDelta;
+ repeatAdjustedIndex += mTrackAutoRepeatLineNames.Length() - 2;
+ }
+ }
+ MOZ_ASSERT(repeatAdjustedIndex < mExpandedLineNames.Length(),
+ "Incorrect repeatedAdjustedIndex");
+ MOZ_ASSERT(names.IsEmpty());
+ // The index is not inside the repeat tracks, or no repeat tracks exist.
+ const auto& nameLists = mExpandedLineNames[repeatAdjustedIndex];
+ for (const NameList* nameList : nameLists) {
+ names.AppendElement(nameList);
+ }
+ return names;
+ }
+
+ // Translate a subgrid line (1-based) to a parent line (1-based).
+ uint32_t TranslateToParentMap(uint32_t aLine) const {
+ if (MOZ_LIKELY(mIsSameDirection)) {
+ return aLine + mRange->mStart;
+ }
+ MOZ_ASSERT(mRange->mEnd + 1 >= aLine);
+ return mRange->mEnd - (aLine - 1) + 1;
+ }
+
+ /**
+ * Return the 1-based line that match aName in 'grid-template-areas'
+ * on the side aSide. Clamp the result to aMin..aMax but require
+ * that some part of the area is inside for it to match.
+ * Return zero if there is no match.
+ */
+ uint32_t FindNamedArea(nsAtom* aName, LogicalSide aSide, int32_t aMin,
+ int32_t aMax) const {
+ if (const NamedArea* area = FindNamedArea(aName)) {
+ int32_t start = IsBlock(aSide) ? area->rows.start : area->columns.start;
+ int32_t end = IsBlock(aSide) ? area->rows.end : area->columns.end;
+ if (IsStart(aSide)) {
+ if (start >= aMin) {
+ if (start <= aMax) {
+ return start;
+ }
+ } else if (end >= aMin) {
+ return aMin;
+ }
+ } else {
+ if (end <= aMax) {
+ if (end >= aMin) {
+ return end;
+ }
+ } else if (start <= aMax) {
+ return aMax;
+ }
+ }
+ }
+ return 0; // no match
+ }
+
+ /**
+ * A convenience method to lookup a name in 'grid-template-areas'.
+ * @return null if not found
+ */
+ const NamedArea* FindNamedArea(nsAtom* aName) const {
+ if (mStylePosition->mGridTemplateAreas.IsNone()) {
+ return nullptr;
+ }
+ const auto areas = mStylePosition->mGridTemplateAreas.AsAreas();
+ for (const NamedArea& area : areas->areas.AsSpan()) {
+ if (area.name.AsAtom() == aName) {
+ return &area;
+ }
+ }
+ return nullptr;
+ }
+
+ // Some style data references, for easy access.
+ const nsStylePosition* mStylePosition;
+ const ImplicitNamedAreas* mAreas;
+ // The expanded list of line-names. Each entry is usually a single NameList,
+ // but can be multiple in the case where repeat() expands to something that
+ // has a line name list at the end.
+ nsTArray<SmallPointerArray<const NameList>> mExpandedLineNames;
+ // The repeat(auto-fill/fit) track value, if any. (always empty for subgrid)
+ Span<const StyleOwnedSlice<StyleCustomIdent>> mTrackAutoRepeatLineNames;
+ // The index of the repeat(auto-fill/fit) track, or zero if there is none.
+ uint32_t mRepeatAutoStart;
+ // The index one past the end of the repeat(auto-fill/fit) tracks. Equal to
+ // mRepeatAutoStart if there are no repeat(auto-fill/fit) tracks.
+ uint32_t mRepeatAutoEnd;
+ // The total number of repeat tracks minus 1.
+ int32_t mRepeatEndDelta;
+ // The end of the line name lists with repeat(auto-fill/fit) tracks accounted
+ // for.
+ uint32_t mTemplateLinesEnd;
+
+ // The parent line map, or null if this map isn't for a subgrid.
+ const LineNameMap* mParentLineNameMap;
+ // The subgrid's range, or null if this map isn't for a subgrid.
+ const LineRange* mRange;
+ // True if the subgrid/parent axes progresses in the same direction.
+ const bool mIsSameDirection;
+
+ // True if there is a specified repeat(auto-fill/fit) track.
+ bool mHasRepeatAuto;
+};
+
+/**
+ * State for the tracks in one dimension.
+ */
+struct nsGridContainerFrame::Tracks {
+ explicit Tracks(LogicalAxis aAxis)
+ : mContentBoxSize(NS_UNCONSTRAINEDSIZE),
+ mGridGap(NS_UNCONSTRAINEDSIZE),
+ mStateUnion(TrackSize::StateBits{0}),
+ mAxis(aAxis),
+ mCanResolveLineRangeSize(false),
+ mIsMasonry(false) {
+ mBaselineSubtreeAlign[BaselineSharingGroup::First] = StyleAlignFlags::AUTO;
+ mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::AUTO;
+ mBaseline[BaselineSharingGroup::First] = NS_INTRINSIC_ISIZE_UNKNOWN;
+ mBaseline[BaselineSharingGroup::Last] = NS_INTRINSIC_ISIZE_UNKNOWN;
+ }
+
+ void Initialize(const TrackSizingFunctions& aFunctions,
+ const NonNegativeLengthPercentageOrNormal& aGridGap,
+ uint32_t aNumTracks, nscoord aContentBoxSize);
+
+ /**
+ * Return the union of the state bits for the tracks in aRange.
+ */
+ TrackSize::StateBits StateBitsForRange(const LineRange& aRange) const;
+
+ // Some data we collect for aligning baseline-aligned items.
+ struct ItemBaselineData {
+ uint32_t mBaselineTrack;
+ nscoord mBaseline;
+ nscoord mSize;
+ GridItemInfo* mGridItem;
+ static bool IsBaselineTrackLessThan(const ItemBaselineData& a,
+ const ItemBaselineData& b) {
+ return a.mBaselineTrack < b.mBaselineTrack;
+ }
+ };
+
+ /**
+ * Calculate baseline offsets for the given set of items.
+ * Helper for InitialzeItemBaselines.
+ */
+ void CalculateItemBaselines(nsTArray<ItemBaselineData>& aBaselineItems,
+ BaselineSharingGroup aBaselineGroup);
+
+ /**
+ * Initialize grid item baseline state and offsets.
+ */
+ void InitializeItemBaselines(GridReflowInput& aState,
+ nsTArray<GridItemInfo>& aGridItems);
+
+ /**
+ * A masonry axis has four baseline alignment sets and each set can have
+ * a first- and last-baseline alignment group, for a total of eight possible
+ * baseline alignment groups, as follows:
+ * set 1: the first item in each `start` or `stretch` grid track
+ * set 2: the last item in each `start` grid track
+ * set 3: the last item in each `end` or `stretch` grid track
+ * set 4: the first item in each `end` grid track
+ * (`start`/`end`/`stretch` refers to the relevant `align/justify-tracks`
+ * value of the (grid-axis) start track for the item) Baseline-alignment for
+ * set 1 and 2 always adjusts the item's padding or margin on the start side,
+ * and set 3 and 4 on the end side, for both first- and last-baseline groups
+ * in the set. (This is similar to regular grid which always adjusts
+ * first-baseline groups on the start side and last-baseline groups on the
+ * end-side. The crux is that those groups are always aligned to the track's
+ * start/end side respectively.)
+ */
+ struct BaselineAlignmentSet {
+ bool MatchTrackAlignment(StyleAlignFlags aTrackAlignment) const {
+ if (mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) {
+ return aTrackAlignment == StyleAlignFlags::START ||
+ (aTrackAlignment == StyleAlignFlags::STRETCH &&
+ mItemSet == BaselineAlignmentSet::FirstItems);
+ }
+ return aTrackAlignment == StyleAlignFlags::END ||
+ (aTrackAlignment == StyleAlignFlags::STRETCH &&
+ mItemSet == BaselineAlignmentSet::LastItems);
+ }
+
+ enum ItemSet { FirstItems, LastItems };
+ ItemSet mItemSet = FirstItems;
+ enum TrackAlignmentSet { StartStretch, EndStretch };
+ TrackAlignmentSet mTrackAlignmentSet = StartStretch;
+ };
+ void InitializeItemBaselinesInMasonryAxis(
+ GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
+ BaselineAlignmentSet aSet, const nsSize& aContainerSize,
+ nsTArray<nscoord>& aTrackSizes,
+ nsTArray<ItemBaselineData>& aFirstBaselineItems,
+ nsTArray<ItemBaselineData>& aLastBaselineItems);
+
+ /**
+ * Apply the additional alignment needed to align the baseline-aligned subtree
+ * the item belongs to within its baseline track.
+ */
+ void AlignBaselineSubtree(const GridItemInfo& aGridItem) const;
+
+ enum class TrackSizingPhase {
+ IntrinsicMinimums,
+ ContentBasedMinimums,
+ MaxContentMinimums,
+ IntrinsicMaximums,
+ MaxContentMaximums,
+ };
+
+ // Some data we collect on each item that spans more than one track for step 3
+ // and 4 of the Track Sizing Algorithm in ResolveIntrinsicSize below.
+ // https://w3c.github.io/csswg-drafts/css-grid-1/#algo-spanning-items
+ struct SpanningItemData final {
+ uint32_t mSpan;
+ TrackSize::StateBits mState;
+ LineRange mLineRange;
+ nscoord mMinSize;
+ nscoord mMinContentContribution;
+ nscoord mMaxContentContribution;
+ nsIFrame* mFrame;
+
+ static bool IsSpanLessThan(const SpanningItemData& a,
+ const SpanningItemData& b) {
+ return a.mSpan < b.mSpan;
+ }
+
+ template <TrackSizingPhase phase>
+ nscoord SizeContributionForPhase() const {
+ switch (phase) {
+ case TrackSizingPhase::IntrinsicMinimums:
+ return mMinSize;
+ case TrackSizingPhase::ContentBasedMinimums:
+ case TrackSizingPhase::IntrinsicMaximums:
+ return mMinContentContribution;
+ case TrackSizingPhase::MaxContentMinimums:
+ case TrackSizingPhase::MaxContentMaximums:
+ return mMaxContentContribution;
+ }
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase");
+ }
+
+#ifdef DEBUG
+ void Dump() const {
+ printf(
+ "SpanningItemData { mSpan: %d, mState: %d, mLineRange: (%d, %d), "
+ "mMinSize: %d, mMinContentContribution: %d, mMaxContentContribution: "
+ "%d, mFrame: %p\n",
+ mSpan, mState, mLineRange.mStart, mLineRange.mEnd, mMinSize,
+ mMinContentContribution, mMaxContentContribution, mFrame);
+ }
+#endif
+ };
+
+ using FitContentClamper =
+ std::function<bool(uint32_t aTrack, nscoord aMinSize, nscoord* aSize)>;
+
+ // Helper method for ResolveIntrinsicSize.
+ template <TrackSizingPhase phase>
+ bool GrowSizeForSpanningItems(
+ nsTArray<SpanningItemData>::iterator aIter,
+ nsTArray<SpanningItemData>::iterator aIterEnd,
+ nsTArray<uint32_t>& aTracks, nsTArray<TrackSize>& aPlan,
+ nsTArray<TrackSize>& aItemPlan, TrackSize::StateBits aSelector,
+ const FitContentClamper& aFitContentClamper = nullptr,
+ bool aNeedInfinitelyGrowableFlag = false);
+ /**
+ * Resolve Intrinsic Track Sizes.
+ * http://dev.w3.org/csswg/css-grid/#algo-content
+ */
+ void ResolveIntrinsicSize(GridReflowInput& aState,
+ nsTArray<GridItemInfo>& aGridItems,
+ const TrackSizingFunctions& aFunctions,
+ LineRange GridArea::*aRange,
+ nscoord aPercentageBasis,
+ SizingConstraint aConstraint);
+
+ /**
+ * Helper for ResolveIntrinsicSize. It implements step 1 "size tracks to fit
+ * non-spanning items" in the spec. Return true if the track has a <flex>
+ * max-sizing function, false otherwise.
+ */
+ bool ResolveIntrinsicSizeForNonSpanningItems(
+ GridReflowInput& aState, const TrackSizingFunctions& aFunctions,
+ nscoord aPercentageBasis, SizingConstraint aConstraint,
+ const LineRange& aRange, const GridItemInfo& aGridItem);
+
+ // Helper method that returns the track size to use in §11.5.1.2
+ // https://drafts.csswg.org/css-grid/#extra-space
+ template <TrackSizingPhase phase>
+ static nscoord StartSizeInDistribution(const TrackSize& aSize) {
+ switch (phase) {
+ case TrackSizingPhase::IntrinsicMinimums:
+ case TrackSizingPhase::ContentBasedMinimums:
+ case TrackSizingPhase::MaxContentMinimums:
+ return aSize.mBase;
+ case TrackSizingPhase::IntrinsicMaximums:
+ case TrackSizingPhase::MaxContentMaximums:
+ if (aSize.mLimit == NS_UNCONSTRAINEDSIZE) {
+ return aSize.mBase;
+ }
+ return aSize.mLimit;
+ }
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase");
+ }
+
+ /**
+ * Collect the tracks which are growable (matching aSelector) into
+ * aGrowableTracks, and return the amount of space that can be used
+ * to grow those tracks. This method implements CSS Grid §11.5.1.2.
+ * https://drafts.csswg.org/css-grid/#extra-space
+ */
+ template <TrackSizingPhase phase>
+ nscoord CollectGrowable(nscoord aAvailableSpace, const LineRange& aRange,
+ TrackSize::StateBits aSelector,
+ nsTArray<uint32_t>& aGrowableTracks) const {
+ MOZ_ASSERT(aAvailableSpace > 0, "why call me?");
+ nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1);
+ for (auto i : aRange.Range()) {
+ const TrackSize& sz = mSizes[i];
+ space -= StartSizeInDistribution<phase>(sz);
+ if (space <= 0) {
+ return 0;
+ }
+ if (sz.mState & aSelector) {
+ aGrowableTracks.AppendElement(i);
+ }
+ }
+ return aGrowableTracks.IsEmpty() ? 0 : space;
+ }
+
+ template <TrackSizingPhase phase>
+ void InitializeItemPlan(nsTArray<TrackSize>& aItemPlan,
+ const nsTArray<uint32_t>& aTracks) const {
+ for (uint32_t track : aTracks) {
+ auto& plan = aItemPlan[track];
+ const TrackSize& sz = mSizes[track];
+ plan.mBase = StartSizeInDistribution<phase>(sz);
+ bool unlimited = sz.mState & TrackSize::eInfinitelyGrowable;
+ plan.mLimit = unlimited ? NS_UNCONSTRAINEDSIZE : sz.mLimit;
+ plan.mState = sz.mState;
+ }
+ }
+
+ template <TrackSizingPhase phase>
+ void InitializePlan(nsTArray<TrackSize>& aPlan) const {
+ for (size_t i = 0, len = aPlan.Length(); i < len; ++i) {
+ auto& plan = aPlan[i];
+ const auto& sz = mSizes[i];
+ plan.mBase = StartSizeInDistribution<phase>(sz);
+ MOZ_ASSERT(phase == TrackSizingPhase::MaxContentMaximums ||
+ !(sz.mState & TrackSize::eInfinitelyGrowable),
+ "forgot to reset the eInfinitelyGrowable bit?");
+ plan.mState = sz.mState;
+ }
+ }
+
+ template <TrackSizingPhase phase>
+ void CopyPlanToSize(const nsTArray<TrackSize>& aPlan,
+ bool aNeedInfinitelyGrowableFlag = false) {
+ for (size_t i = 0, len = mSizes.Length(); i < len; ++i) {
+ const auto& plan = aPlan[i];
+ MOZ_ASSERT(plan.mBase >= 0);
+ auto& sz = mSizes[i];
+ switch (phase) {
+ case TrackSizingPhase::IntrinsicMinimums:
+ case TrackSizingPhase::ContentBasedMinimums:
+ case TrackSizingPhase::MaxContentMinimums:
+ sz.mBase = plan.mBase;
+ break;
+ case TrackSizingPhase::IntrinsicMaximums:
+ if (plan.mState & TrackSize::eModified) {
+ if (sz.mLimit == NS_UNCONSTRAINEDSIZE &&
+ aNeedInfinitelyGrowableFlag) {
+ sz.mState |= TrackSize::eInfinitelyGrowable;
+ }
+ sz.mLimit = plan.mBase;
+ }
+ break;
+ case TrackSizingPhase::MaxContentMaximums:
+ if (plan.mState & TrackSize::eModified) {
+ sz.mLimit = plan.mBase;
+ }
+ sz.mState &= ~TrackSize::eInfinitelyGrowable;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Grow the planned size for tracks in aGrowableTracks up to their limit
+ * and then freeze them (all aGrowableTracks must be unfrozen on entry).
+ * Subtract the space added from aAvailableSpace and return that.
+ */
+ nscoord GrowTracksToLimit(nscoord aAvailableSpace, nsTArray<TrackSize>& aPlan,
+ const nsTArray<uint32_t>& aGrowableTracks,
+ const FitContentClamper& aFitContentClamper) const {
+ MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0);
+ nscoord space = aAvailableSpace;
+ uint32_t numGrowable = aGrowableTracks.Length();
+ while (true) {
+ nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
+ for (uint32_t track : aGrowableTracks) {
+ TrackSize& sz = aPlan[track];
+ if (sz.IsFrozen()) {
+ continue;
+ }
+ nscoord newBase = sz.mBase + spacePerTrack;
+ nscoord limit = sz.mLimit;
+ if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) &&
+ aFitContentClamper)) {
+ // Clamp the limit to the fit-content() size, for §12.5.2 step 5/6.
+ aFitContentClamper(track, sz.mBase, &limit);
+ }
+ if (newBase > limit) {
+ nscoord consumed = limit - sz.mBase;
+ if (consumed > 0) {
+ space -= consumed;
+ sz.mBase = limit;
+ }
+ sz.mState |= TrackSize::eFrozen;
+ if (--numGrowable == 0) {
+ return space;
+ }
+ } else {
+ sz.mBase = newBase;
+ space -= spacePerTrack;
+ }
+ MOZ_ASSERT(space >= 0);
+ if (space == 0) {
+ return 0;
+ }
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("we don't exit the loop above except by return");
+ return 0;
+ }
+
+ /**
+ * Helper for GrowSelectedTracksUnlimited. For the set of tracks (S) that
+ * match aMinSizingSelector: if a track in S doesn't match aMaxSizingSelector
+ * then mark it with aSkipFlag. If all tracks in S were marked then unmark
+ * them. Return aNumGrowable minus the number of tracks marked. It is
+ * assumed that aPlan have no aSkipFlag set for tracks in aGrowableTracks
+ * on entry to this method.
+ */
+ static uint32_t MarkExcludedTracks(nsTArray<TrackSize>& aPlan,
+ uint32_t aNumGrowable,
+ const nsTArray<uint32_t>& aGrowableTracks,
+ TrackSize::StateBits aMinSizingSelector,
+ TrackSize::StateBits aMaxSizingSelector,
+ TrackSize::StateBits aSkipFlag) {
+ bool foundOneSelected = false;
+ bool foundOneGrowable = false;
+ uint32_t numGrowable = aNumGrowable;
+ for (uint32_t track : aGrowableTracks) {
+ TrackSize& sz = aPlan[track];
+ const auto state = sz.mState;
+ if (state & aMinSizingSelector) {
+ foundOneSelected = true;
+ if (state & aMaxSizingSelector) {
+ foundOneGrowable = true;
+ continue;
+ }
+ sz.mState |= aSkipFlag;
+ MOZ_ASSERT(numGrowable != 0);
+ --numGrowable;
+ }
+ }
+ // 12.5 "if there are no such tracks, then all affected tracks"
+ if (foundOneSelected && !foundOneGrowable) {
+ for (uint32_t track : aGrowableTracks) {
+ aPlan[track].mState &= ~aSkipFlag;
+ }
+ numGrowable = aNumGrowable;
+ }
+ return numGrowable;
+ }
+
+ /**
+ * Mark all tracks in aGrowableTracks with an eSkipGrowUnlimited bit if
+ * they *shouldn't* grow unlimited in §11.5.1.2.3 "Distribute space beyond
+ * growth limits" https://drafts.csswg.org/css-grid/#extra-space
+ * Return the number of tracks that are still growable.
+ */
+ template <TrackSizingPhase phase>
+ static uint32_t MarkExcludedTracks(nsTArray<TrackSize>& aPlan,
+ const nsTArray<uint32_t>& aGrowableTracks,
+ TrackSize::StateBits aSelector) {
+ uint32_t numGrowable = aGrowableTracks.Length();
+ if (phase == TrackSizingPhase::IntrinsicMaximums ||
+ phase == TrackSizingPhase::MaxContentMaximums) {
+ // "when handling any intrinsic growth limit: all affected tracks"
+ return numGrowable;
+ }
+ MOZ_ASSERT(aSelector == (aSelector & TrackSize::eIntrinsicMinSizing) &&
+ (aSelector & TrackSize::eMaxContentMinSizing),
+ "Should only get here for track sizing steps 2.1 to 2.3");
+ // Note that eMaxContentMinSizing is always included. We do those first:
+ numGrowable = MarkExcludedTracks(
+ aPlan, numGrowable, aGrowableTracks, TrackSize::eMaxContentMinSizing,
+ TrackSize::eMaxContentMaxSizing, TrackSize::eSkipGrowUnlimited1);
+ // Now mark min-content/auto min-sizing tracks if requested.
+ auto minOrAutoSelector = aSelector & ~TrackSize::eMaxContentMinSizing;
+ if (minOrAutoSelector) {
+ numGrowable = MarkExcludedTracks(
+ aPlan, numGrowable, aGrowableTracks, minOrAutoSelector,
+ TrackSize::eIntrinsicMaxSizing, TrackSize::eSkipGrowUnlimited2);
+ }
+ return numGrowable;
+ }
+
+ /**
+ * Increase the planned size for tracks in aGrowableTracks that aren't
+ * marked with a eSkipGrowUnlimited flag beyond their limit.
+ * This implements the "Distribute space beyond growth limits" step in
+ * https://drafts.csswg.org/css-grid/#distribute-extra-space
+ */
+ void GrowSelectedTracksUnlimited(
+ nscoord aAvailableSpace, nsTArray<TrackSize>& aPlan,
+ const nsTArray<uint32_t>& aGrowableTracks, uint32_t aNumGrowable,
+ const FitContentClamper& aFitContentClamper) const {
+ MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0 &&
+ aNumGrowable <= aGrowableTracks.Length());
+ nscoord space = aAvailableSpace;
+ DebugOnly<bool> didClamp = false;
+ while (aNumGrowable) {
+ nscoord spacePerTrack = std::max<nscoord>(space / aNumGrowable, 1);
+ for (uint32_t track : aGrowableTracks) {
+ TrackSize& sz = aPlan[track];
+ if (sz.mState & TrackSize::eSkipGrowUnlimited) {
+ continue; // an excluded track
+ }
+ nscoord delta = spacePerTrack;
+ nscoord newBase = sz.mBase + delta;
+ if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) &&
+ aFitContentClamper)) {
+ // Clamp newBase to the fit-content() size, for §12.5.2 step 5/6.
+ if (aFitContentClamper(track, sz.mBase, &newBase)) {
+ didClamp = true;
+ delta = newBase - sz.mBase;
+ MOZ_ASSERT(delta >= 0, "track size shouldn't shrink");
+ sz.mState |= TrackSize::eSkipGrowUnlimited1;
+ --aNumGrowable;
+ }
+ }
+ sz.mBase = newBase;
+ space -= delta;
+ MOZ_ASSERT(space >= 0);
+ if (space == 0) {
+ return;
+ }
+ }
+ }
+ MOZ_ASSERT(didClamp,
+ "we don't exit the loop above except by return, "
+ "unless we clamped some track's size");
+ }
+
+ /**
+ * Distribute aAvailableSpace to the planned base size for aGrowableTracks
+ * up to their limits, then distribute the remaining space beyond the limits.
+ */
+ template <TrackSizingPhase phase>
+ void DistributeToTrackSizes(nscoord aAvailableSpace,
+ nsTArray<TrackSize>& aPlan,
+ nsTArray<TrackSize>& aItemPlan,
+ nsTArray<uint32_t>& aGrowableTracks,
+ TrackSize::StateBits aSelector,
+ const FitContentClamper& aFitContentClamper) {
+ InitializeItemPlan<phase>(aItemPlan, aGrowableTracks);
+ nscoord space = GrowTracksToLimit(aAvailableSpace, aItemPlan,
+ aGrowableTracks, aFitContentClamper);
+ if (space > 0) {
+ uint32_t numGrowable =
+ MarkExcludedTracks<phase>(aItemPlan, aGrowableTracks, aSelector);
+ GrowSelectedTracksUnlimited(space, aItemPlan, aGrowableTracks,
+ numGrowable, aFitContentClamper);
+ }
+ for (uint32_t track : aGrowableTracks) {
+ nscoord& plannedSize = aPlan[track].mBase;
+ nscoord itemIncurredSize = aItemPlan[track].mBase;
+ if (plannedSize < itemIncurredSize) {
+ plannedSize = itemIncurredSize;
+ }
+ }
+ }
+
+ /**
+ * Distribute aAvailableSize to the tracks. This implements 12.6 at:
+ * http://dev.w3.org/csswg/css-grid/#algo-grow-tracks
+ */
+ void DistributeFreeSpace(nscoord aAvailableSize) {
+ const uint32_t numTracks = mSizes.Length();
+ if (MOZ_UNLIKELY(numTracks == 0 || aAvailableSize <= 0)) {
+ return;
+ }
+ if (aAvailableSize == NS_UNCONSTRAINEDSIZE) {
+ for (TrackSize& sz : mSizes) {
+ sz.mBase = sz.mLimit;
+ }
+ } else {
+ // Compute free space and count growable tracks.
+ nscoord space = aAvailableSize;
+ uint32_t numGrowable = numTracks;
+ for (const TrackSize& sz : mSizes) {
+ space -= sz.mBase;
+ MOZ_ASSERT(sz.mBase <= sz.mLimit);
+ if (sz.mBase == sz.mLimit) {
+ --numGrowable;
+ }
+ }
+ // Distribute the free space evenly to the growable tracks. If not exactly
+ // divisable the remainder is added to the leading tracks.
+ while (space > 0 && numGrowable) {
+ nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
+ for (uint32_t i = 0; i < numTracks && space > 0; ++i) {
+ TrackSize& sz = mSizes[i];
+ if (sz.mBase == sz.mLimit) {
+ continue;
+ }
+ nscoord newBase = sz.mBase + spacePerTrack;
+ if (newBase >= sz.mLimit) {
+ space -= sz.mLimit - sz.mBase;
+ sz.mBase = sz.mLimit;
+ --numGrowable;
+ } else {
+ space -= spacePerTrack;
+ sz.mBase = newBase;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Implements "12.7.1. Find the Size of an 'fr'".
+ * http://dev.w3.org/csswg/css-grid/#algo-find-fr-size
+ * (The returned value is a 'nscoord' divided by a factor - a floating type
+ * is used to avoid intermediary rounding errors.)
+ */
+ float FindFrUnitSize(const LineRange& aRange,
+ const nsTArray<uint32_t>& aFlexTracks,
+ const TrackSizingFunctions& aFunctions,
+ nscoord aSpaceToFill) const;
+
+ /**
+ * Implements the "find the used flex fraction" part of StretchFlexibleTracks.
+ * (The returned value is a 'nscoord' divided by a factor - a floating type
+ * is used to avoid intermediary rounding errors.)
+ */
+ float FindUsedFlexFraction(GridReflowInput& aState,
+ nsTArray<GridItemInfo>& aGridItems,
+ const nsTArray<uint32_t>& aFlexTracks,
+ const TrackSizingFunctions& aFunctions,
+ nscoord aAvailableSize) const;
+
+ /**
+ * Implements "12.7. Stretch Flexible Tracks"
+ * http://dev.w3.org/csswg/css-grid/#algo-flex-tracks
+ */
+ void StretchFlexibleTracks(GridReflowInput& aState,
+ nsTArray<GridItemInfo>& aGridItems,
+ const TrackSizingFunctions& aFunctions,
+ nscoord aAvailableSize);
+
+ /**
+ * Implements "12.3. Track Sizing Algorithm"
+ * http://dev.w3.org/csswg/css-grid/#algo-track-sizing
+ */
+ void CalculateSizes(GridReflowInput& aState,
+ nsTArray<GridItemInfo>& aGridItems,
+ const TrackSizingFunctions& aFunctions,
+ nscoord aContentBoxSize, LineRange GridArea::*aRange,
+ SizingConstraint aConstraint);
+
+ /**
+ * Apply 'align/justify-content', whichever is relevant for this axis.
+ * https://drafts.csswg.org/css-align-3/#propdef-align-content
+ */
+ void AlignJustifyContent(const nsStylePosition* aStyle,
+ StyleContentDistribution aAligmentStyleValue,
+ WritingMode aWM, nscoord aContentBoxSize,
+ bool aIsSubgridded);
+
+ nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const {
+ if (MOZ_UNLIKELY(mSizes.IsEmpty())) {
+ // https://drafts.csswg.org/css-grid/#grid-definition
+ // "... the explicit grid still contains one grid line in each axis."
+ MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid");
+ return nscoord(0);
+ }
+ MOZ_ASSERT(aLine <= mSizes.Length(), "mSizes is too small");
+ if (aSide == GridLineSide::BeforeGridGap) {
+ if (aLine == 0) {
+ return nscoord(0);
+ }
+ const TrackSize& sz = mSizes[aLine - 1];
+ return sz.mPosition + sz.mBase;
+ }
+ if (aLine == mSizes.Length()) {
+ return mContentBoxSize;
+ }
+ return mSizes[aLine].mPosition;
+ }
+
+ nscoord SumOfGridTracksAndGaps() {
+ return SumOfGridTracks() + SumOfGridGaps();
+ }
+
+ nscoord SumOfGridTracks() const {
+ nscoord result = 0;
+ for (const TrackSize& size : mSizes) {
+ result += size.mBase;
+ }
+ return result;
+ }
+
+ nscoord SumOfGridGaps() const {
+ auto len = mSizes.Length();
+ return MOZ_LIKELY(len > 1) ? (len - 1) * mGridGap : 0;
+ }
+
+ /**
+ * Break before aRow, i.e. set the eBreakBefore flag on aRow and set the grid
+ * gap before aRow to zero (and shift all rows after it by the removed gap).
+ */
+ void BreakBeforeRow(uint32_t aRow) {
+ MOZ_ASSERT(mAxis == eLogicalAxisBlock,
+ "Should only be fragmenting in the block axis (between rows)");
+ nscoord prevRowEndPos = 0;
+ if (aRow != 0) {
+ auto& prevSz = mSizes[aRow - 1];
+ prevRowEndPos = prevSz.mPosition + prevSz.mBase;
+ }
+ auto& sz = mSizes[aRow];
+ const nscoord gap = sz.mPosition - prevRowEndPos;
+ sz.mState |= TrackSize::eBreakBefore;
+ if (gap != 0) {
+ for (uint32_t i = aRow, len = mSizes.Length(); i < len; ++i) {
+ mSizes[i].mPosition -= gap;
+ }
+ }
+ }
+
+ /**
+ * Set the size of aRow to aSize and adjust the position of all rows after it.
+ */
+ void ResizeRow(uint32_t aRow, nscoord aNewSize) {
+ MOZ_ASSERT(mAxis == eLogicalAxisBlock,
+ "Should only be fragmenting in the block axis (between rows)");
+ MOZ_ASSERT(aNewSize >= 0);
+ auto& sz = mSizes[aRow];
+ nscoord delta = aNewSize - sz.mBase;
+ NS_WARNING_ASSERTION(delta != nscoord(0), "Useless call to ResizeRow");
+ sz.mBase = aNewSize;
+ const uint32_t numRows = mSizes.Length();
+ for (uint32_t r = aRow + 1; r < numRows; ++r) {
+ mSizes[r].mPosition += delta;
+ }
+ }
+
+ nscoord ResolveSize(const LineRange& aRange) const {
+ MOZ_ASSERT(mCanResolveLineRangeSize);
+ MOZ_ASSERT(aRange.Extent() > 0, "grid items cover at least one track");
+ nscoord pos, size;
+ aRange.ToPositionAndLength(mSizes, &pos, &size);
+ return size;
+ }
+
+#ifdef DEBUG
+ void Dump() const;
+#endif
+
+ CopyableAutoTArray<TrackSize, 32> mSizes;
+ nscoord mContentBoxSize;
+ nscoord mGridGap;
+ // The first(last)-baseline for the first(last) track in this axis.
+ PerBaseline<nscoord> mBaseline;
+ // The union of the track min/max-sizing state bits in this axis.
+ TrackSize::StateBits mStateUnion;
+ LogicalAxis mAxis;
+ // Used for aligning a baseline-aligned subtree of items. The only possible
+ // values are StyleAlignFlags::{START,END,CENTER,AUTO}. AUTO means there are
+ // no baseline-aligned items in any track in that axis.
+ // There is one alignment value for each BaselineSharingGroup.
+ PerBaseline<StyleAlignFlags> mBaselineSubtreeAlign;
+ // True if track positions and sizes are final in this axis.
+ bool mCanResolveLineRangeSize;
+ // True if this axis has masonry layout.
+ bool mIsMasonry;
+};
+
+#ifdef DEBUG
+void nsGridContainerFrame::Tracks::Dump() const {
+ printf("%zu %s %s ", mSizes.Length(), mIsMasonry ? "masonry" : "grid",
+ mAxis == eLogicalAxisBlock ? "rows" : "columns");
+ TrackSize::DumpStateBits(mStateUnion);
+ printf("\n");
+ for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
+ printf(" %d: ", i);
+ mSizes[i].Dump();
+ printf("\n");
+ }
+ double px = AppUnitsPerCSSPixel();
+ printf("Baselines: %.2fpx %2fpx\n",
+ mBaseline[BaselineSharingGroup::First] / px,
+ mBaseline[BaselineSharingGroup::Last] / px);
+ printf("Gap: %.2fpx\n", mGridGap / px);
+ printf("ContentBoxSize: %.2fpx\n", mContentBoxSize / px);
+}
+#endif
+
+/**
+ * Grid data shared by all continuations, owned by the first-in-flow.
+ * The data is initialized from the first-in-flow's GridReflowInput at
+ * the end of its reflow. Fragmentation will modify mRows.mSizes -
+ * the mPosition to remove the row gap at the break boundary, the mState
+ * by setting the eBreakBefore flag, and mBase is modified when we decide
+ * to grow a row. mOriginalRowData is setup by the first-in-flow and
+ * not modified after that. It's used for undoing the changes to mRows.
+ * mCols, mGridItems, mAbsPosItems are used for initializing the grid
+ * reflow input for continuations, see GridReflowInput::Initialize below.
+ */
+struct nsGridContainerFrame::SharedGridData {
+ SharedGridData()
+ : mCols(eLogicalAxisInline),
+ mRows(eLogicalAxisBlock),
+ mGenerateComputedGridInfo(false) {}
+ Tracks mCols;
+ Tracks mRows;
+ struct RowData {
+ nscoord mBase; // the original track size
+ nscoord mGap; // the original gap before a track
+ };
+ nsTArray<RowData> mOriginalRowData;
+ nsTArray<GridItemInfo> mGridItems;
+ nsTArray<GridItemInfo> mAbsPosItems;
+ bool mGenerateComputedGridInfo;
+
+ /**
+ * Only set on the first-in-flow. Continuations will Initialize() their
+ * GridReflowInput from it.
+ */
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedGridData)
+};
+
+struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput {
+ GridReflowInput(nsGridContainerFrame* aFrame, const ReflowInput& aRI)
+ : GridReflowInput(aFrame, *aRI.mRenderingContext, &aRI,
+ aRI.mStylePosition, aRI.GetWritingMode()) {}
+ GridReflowInput(nsGridContainerFrame* aFrame, gfxContext& aRC)
+ : GridReflowInput(aFrame, aRC, nullptr, aFrame->StylePosition(),
+ aFrame->GetWritingMode()) {}
+
+ /**
+ * Initialize our track sizes and grid item info using the shared
+ * state from aGridContainerFrame first-in-flow.
+ */
+ void InitializeForContinuation(nsGridContainerFrame* aGridContainerFrame,
+ nscoord aConsumedBSize) {
+ MOZ_ASSERT(aGridContainerFrame->GetPrevInFlow(),
+ "don't call this on the first-in-flow");
+ MOZ_ASSERT(mGridItems.IsEmpty() && mAbsPosItems.IsEmpty(),
+ "shouldn't have any item data yet");
+
+ // Get the SharedGridData from the first-in-flow. Also calculate the number
+ // of fragments before this so that we can figure out our start row below.
+ uint32_t fragment = 0;
+ nsIFrame* firstInFlow = aGridContainerFrame;
+ for (auto pif = aGridContainerFrame->GetPrevInFlow(); pif;
+ pif = pif->GetPrevInFlow()) {
+ ++fragment;
+ firstInFlow = pif;
+ }
+ mSharedGridData = firstInFlow->GetProperty(SharedGridData::Prop());
+ MOZ_ASSERT(mSharedGridData, "first-in-flow must have SharedGridData");
+
+ // Find the start row for this fragment and undo breaks after that row
+ // since the breaks might be different from the last reflow.
+ auto& rowSizes = mSharedGridData->mRows.mSizes;
+ const uint32_t numRows = rowSizes.Length();
+ mStartRow = numRows;
+ for (uint32_t row = 0, breakCount = 0; row < numRows; ++row) {
+ if (rowSizes[row].mState & TrackSize::eBreakBefore) {
+ if (fragment == ++breakCount) {
+ mStartRow = row;
+ mFragBStart = rowSizes[row].mPosition;
+ // Restore the original size for |row| and grid gaps / state after it.
+ const auto& origRowData = mSharedGridData->mOriginalRowData;
+ rowSizes[row].mBase = origRowData[row].mBase;
+ nscoord prevEndPos = rowSizes[row].mPosition + rowSizes[row].mBase;
+ while (++row < numRows) {
+ auto& sz = rowSizes[row];
+ const auto& orig = origRowData[row];
+ sz.mPosition = prevEndPos + orig.mGap;
+ sz.mBase = orig.mBase;
+ sz.mState &= ~TrackSize::eBreakBefore;
+ prevEndPos = sz.mPosition + sz.mBase;
+ }
+ break;
+ }
+ }
+ }
+ if (mStartRow == numRows ||
+ aGridContainerFrame->IsMasonry(eLogicalAxisBlock)) {
+ // All of the grid's rows fit inside of previous grid-container fragments,
+ // or it's a masonry axis.
+ mFragBStart = aConsumedBSize;
+ }
+
+ // Copy the shared track state.
+ // XXX consider temporarily swapping the array elements instead and swapping
+ // XXX them back after we're done reflowing, for better performance.
+ // XXX (bug 1252002)
+ mCols = mSharedGridData->mCols;
+ mRows = mSharedGridData->mRows;
+
+ if (firstInFlow->GetProperty(UsedTrackSizes::Prop())) {
+ auto* prop = aGridContainerFrame->GetProperty(UsedTrackSizes::Prop());
+ if (!prop) {
+ prop = new UsedTrackSizes();
+ aGridContainerFrame->SetProperty(UsedTrackSizes::Prop(), prop);
+ }
+ prop->mCanResolveLineRangeSize = {true, true};
+ prop->mSizes[eLogicalAxisInline].Assign(mCols.mSizes);
+ prop->mSizes[eLogicalAxisBlock].Assign(mRows.mSizes);
+ }
+
+ // Copy item data from each child's first-in-flow data in mSharedGridData.
+ // XXX NOTE: This is O(n^2) in the number of items. (bug 1252186)
+ mIter.Reset();
+ for (; !mIter.AtEnd(); mIter.Next()) {
+ nsIFrame* child = *mIter;
+ nsIFrame* childFirstInFlow = child->FirstInFlow();
+ DebugOnly<size_t> len = mGridItems.Length();
+ for (auto& itemInfo : mSharedGridData->mGridItems) {
+ if (itemInfo.mFrame == childFirstInFlow) {
+ auto item =
+ mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea));
+ // Copy the item's baseline data so that the item's last fragment can
+ // do 'last baseline' alignment if necessary.
+ item->mState[eLogicalAxisBlock] |=
+ itemInfo.mState[eLogicalAxisBlock] & ItemState::eAllBaselineBits;
+ item->mState[eLogicalAxisInline] |=
+ itemInfo.mState[eLogicalAxisInline] & ItemState::eAllBaselineBits;
+ item->mBaselineOffset[eLogicalAxisBlock] =
+ itemInfo.mBaselineOffset[eLogicalAxisBlock];
+ item->mBaselineOffset[eLogicalAxisInline] =
+ itemInfo.mBaselineOffset[eLogicalAxisInline];
+ item->mState[eLogicalAxisBlock] |=
+ itemInfo.mState[eLogicalAxisBlock] & ItemState::eAutoPlacement;
+ item->mState[eLogicalAxisInline] |=
+ itemInfo.mState[eLogicalAxisInline] & ItemState::eAutoPlacement;
+ break;
+ }
+ }
+ MOZ_ASSERT(mGridItems.Length() == len + 1, "can't find GridItemInfo");
+ }
+
+ // XXX NOTE: This is O(n^2) in the number of abs.pos. items. (bug 1252186)
+ const nsFrameList& absPosChildren = aGridContainerFrame->GetChildList(
+ aGridContainerFrame->GetAbsoluteListID());
+ for (auto f : absPosChildren) {
+ nsIFrame* childFirstInFlow = f->FirstInFlow();
+ DebugOnly<size_t> len = mAbsPosItems.Length();
+ for (auto& itemInfo : mSharedGridData->mAbsPosItems) {
+ if (itemInfo.mFrame == childFirstInFlow) {
+ mAbsPosItems.AppendElement(GridItemInfo(f, itemInfo.mArea));
+ break;
+ }
+ }
+ MOZ_ASSERT(mAbsPosItems.Length() == len + 1, "can't find GridItemInfo");
+ }
+
+ // Copy in the computed grid info state bit
+ if (mSharedGridData->mGenerateComputedGridInfo) {
+ aGridContainerFrame->AddStateBits(NS_STATE_GRID_COMPUTED_INFO);
+ }
+ }
+
+ /**
+ * Calculate our track sizes in the given axis.
+ */
+ void CalculateTrackSizesForAxis(LogicalAxis aAxis, const Grid& aGrid,
+ nscoord aCBSize,
+ SizingConstraint aConstraint);
+
+ /**
+ * Calculate our track sizes.
+ */
+ void CalculateTrackSizes(const Grid& aGrid, const LogicalSize& aContentBox,
+ SizingConstraint aConstraint);
+
+ /**
+ * Return the percentage basis for a grid item in its writing-mode.
+ * If aAxis is eLogicalAxisInline then we return NS_UNCONSTRAINEDSIZE in
+ * both axes since we know all track sizes are indefinite at this point
+ * (we calculate column sizes before row sizes). Otherwise, assert that
+ * column sizes are known and calculate the size for aGridItem.mArea.mCols
+ * and use NS_UNCONSTRAINEDSIZE in the other axis.
+ * @param aAxis the axis we're currently calculating track sizes for
+ */
+ LogicalSize PercentageBasisFor(LogicalAxis aAxis,
+ const GridItemInfo& aGridItem) const;
+
+ /**
+ * Return the containing block for a grid item occupying aArea.
+ */
+ LogicalRect ContainingBlockFor(const GridArea& aArea) const;
+
+ /**
+ * Return the containing block for an abs.pos. grid item occupying aArea.
+ * Any 'auto' lines in the grid area will be aligned with grid container
+ * containing block on that side.
+ * @param aGridOrigin the origin of the grid
+ * @param aGridCB the grid container containing block (its padding area)
+ */
+ LogicalRect ContainingBlockForAbsPos(const GridArea& aArea,
+ const LogicalPoint& aGridOrigin,
+ const LogicalRect& aGridCB) const;
+
+ /**
+ * Apply `align/justify-content` alignment in our masonry axis.
+ * This aligns the "masonry box" within our content box size.
+ */
+ void AlignJustifyContentInMasonryAxis(nscoord aMasonryBoxSize,
+ nscoord aContentBoxSize);
+ /**
+ * Apply `align/justify-tracks` alignment in our masonry axis.
+ */
+ void AlignJustifyTracksInMasonryAxis(const LogicalSize& aContentSize,
+ const nsSize& aContainerSize);
+
+ // Recursive helper for CollectSubgridItemsForAxis().
+ static void CollectSubgridItemsForAxisHelper(
+ LogicalAxis aAxis, WritingMode aContainerWM,
+ const LineRange& aRangeInAxis, const LineRange& aRangeInOppositeAxis,
+ const GridItemInfo& aItem, const nsTArray<GridItemInfo>& aItems,
+ nsTArray<GridItemInfo>& aResult) {
+ const auto oppositeAxis = GetOrthogonalAxis(aAxis);
+ bool itemIsSubgridInOppositeAxis = aItem.IsSubgrid(oppositeAxis);
+ auto subgridWM = aItem.mFrame->GetWritingMode();
+ bool isOrthogonal = subgridWM.IsOrthogonalTo(aContainerWM);
+ bool isSameDirInAxis =
+ subgridWM.ParallelAxisStartsOnSameSide(aAxis, aContainerWM);
+ bool isSameDirInOppositeAxis =
+ subgridWM.ParallelAxisStartsOnSameSide(oppositeAxis, aContainerWM);
+ if (isOrthogonal) {
+ // We'll Transpose the area below so these needs to be transposed as well.
+ std::swap(isSameDirInAxis, isSameDirInOppositeAxis);
+ }
+ uint32_t offsetInAxis = aRangeInAxis.mStart;
+ uint32_t gridEndInAxis = aRangeInAxis.Extent();
+ uint32_t offsetInOppositeAxis = aRangeInOppositeAxis.mStart;
+ uint32_t gridEndInOppositeAxis = aRangeInOppositeAxis.Extent();
+ for (const auto& subgridItem : aItems) {
+ auto newItem = aResult.AppendElement(
+ isOrthogonal ? subgridItem.Transpose() : subgridItem);
+ if (MOZ_UNLIKELY(!isSameDirInAxis)) {
+ newItem->ReverseDirection(aAxis, gridEndInAxis);
+ }
+ newItem->mArea.LineRangeForAxis(aAxis).Translate(offsetInAxis);
+ if (itemIsSubgridInOppositeAxis) {
+ if (MOZ_UNLIKELY(!isSameDirInOppositeAxis)) {
+ newItem->ReverseDirection(oppositeAxis, gridEndInOppositeAxis);
+ }
+ LineRange& range = newItem->mArea.LineRangeForAxis(oppositeAxis);
+ range.Translate(offsetInOppositeAxis);
+ }
+ if (newItem->IsSubgrid(aAxis)) {
+ auto* subgrid =
+ subgridItem.SubgridFrame()->GetProperty(Subgrid::Prop());
+ CollectSubgridItemsForAxisHelper(
+ aAxis, aContainerWM, newItem->mArea.LineRangeForAxis(aAxis),
+ newItem->mArea.LineRangeForAxis(oppositeAxis), *newItem,
+ subgrid->mGridItems, aResult);
+ }
+ }
+ }
+
+ // Copy all descendant items from all our subgrid children that are subgridded
+ // in aAxis recursively into aResult. All item grid area's and state are
+ // translated to our coordinates.
+ void CollectSubgridItemsForAxis(LogicalAxis aAxis,
+ nsTArray<GridItemInfo>& aResult) const {
+ for (const auto& item : mGridItems) {
+ if (item.IsSubgrid(aAxis)) {
+ const auto oppositeAxis = GetOrthogonalAxis(aAxis);
+ auto* subgrid = item.SubgridFrame()->GetProperty(Subgrid::Prop());
+ CollectSubgridItemsForAxisHelper(
+ aAxis, mWM, item.mArea.LineRangeForAxis(aAxis),
+ item.mArea.LineRangeForAxis(oppositeAxis), item,
+ subgrid->mGridItems, aResult);
+ }
+ }
+ }
+
+ /**
+ * Recursive helper for CopyBaselineMetricsToSubgridItems().
+ *
+ * @param aAxis The LogicalAxis for the axis whose baseline metrics we're
+ * copying here (with respect to the outermost parent grid's
+ * writing mode).
+ * @param aContainerWM The writing mode of that outermost parent grid.
+ * @param aSubgridFrame The subgrid whose subgrid-items we're considering
+ * in this recursive traversal (whose items we're copying over
+ * baseline-alignment metrics for).
+ * @param aContainerGridItems The outermost parent grid's array of
+ * GridItemInfo objects. (The final portion of this array is
+ * all for subgrid items, and that's the portion that we're
+ * recursively iterating over.)
+ * @param aContainerGridItemsIdx [in/out] The index for the item that we're
+ * currently considering in aContainerGridItemsIdx. When
+ * this function returns, this will be the index just beyond the
+ * last item that we handled here, i.e. the index of the next
+ * item to be handled.
+ */
+ static void CopyBaselineMetricsToSubgridItemsHelper(
+ LogicalAxis aAxis, WritingMode aContainerWM, nsIFrame* aSubgridFrame,
+ const nsTArray<GridItemInfo>& aContainerGridItems,
+ size_t& aContainerGridItemsIdx) {
+ // Get the canonical GridItemInfo structs for the grid items that live
+ // inside of aSubgridFrame:
+ Subgrid* subgridProp = aSubgridFrame->GetProperty(Subgrid::Prop());
+ nsTArray<GridItemInfo>& subgridItems = subgridProp->mGridItems;
+
+ // Use aSubgridFrame's writing-mode to determine subgridAxis.
+ // Grids & subgrids store various data on a per-LogicalAxis basis, with
+ // respect to their own WritingMode. Here, subgridAxis is aSubgridFrame's
+ // axis that maps to the same physical axis that aAxis does for the
+ // outermost parent grid.
+ auto subgridWM = aSubgridFrame->GetWritingMode();
+ bool isOrthogonal = subgridWM.IsOrthogonalTo(aContainerWM);
+ LogicalAxis subgridAxis = isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
+
+ // Do a parallel walk through (1) subgridItems and (2) the portion of
+ // aContainerGridItems that starts at offset aContainerGridItems,
+ // descending to traverse child subgrids own items as we encounter them in
+ // subgridItems. We expect to have an exact correspondence, because this
+ // is precisely how we built up this portion of aContainerGridItems in
+ // CollectSubgridItemsForAxis. (But if we happen to overstep the end of an
+ // array, or find a GridItemInfo for a frame that we don't expect, we
+ // gracefully bail out.)
+ for (auto& subgridItem : subgridItems) {
+ if (MOZ_UNLIKELY(aContainerGridItemsIdx >=
+ aContainerGridItems.Length())) {
+ // We failed to make the same traversal as CollectSubgridItemsForAxis;
+ // whoops! This shouldn't happen; but if it does, we gracefully bail
+ // out, instead of crashing.
+ MOZ_ASSERT_UNREACHABLE("Out-of-bounds aContainerGridItemsIdx");
+ return;
+ }
+ const auto& itemFromContainer =
+ aContainerGridItems[aContainerGridItemsIdx];
+ aContainerGridItemsIdx++;
+
+ if (MOZ_UNLIKELY(subgridItem.mFrame != itemFromContainer.mFrame)) {
+ // We failed to make the same traversal as CollectSubgridItemsForAxis;
+ // whoops! This shouldn't happen; but if it does, we gracefully bail
+ // out, instead of copying baseline-alignment data for the wrong frame.
+ MOZ_ASSERT_UNREACHABLE("Found unexpected frame during traversal");
+ return;
+ }
+
+ // This pattern of bits will be truthy if the item is baseline-aligned in
+ // this axis (in which case the exact pattern of bits will have some
+ // additional significance that doesn't matter here, but we do need to
+ // copy it over).
+ const auto baselineStateBits =
+ itemFromContainer.mState[aAxis] & ItemState::eAllBaselineBits;
+
+ if (subgridItem.IsSubgrid(subgridAxis)) {
+ // This item is in fact a nested subgrid. It shouldn't itself be
+ // baseline-aligned, but we need to make a recursive call to copy
+ // baseline metrics to its items.
+ MOZ_ASSERT(!baselineStateBits,
+ "subgrids themselves can't be baseline-aligned "
+ "(or self-aligned in any way) in their subgrid axis");
+ CopyBaselineMetricsToSubgridItemsHelper(
+ aAxis, aContainerWM, subgridItem.SubgridFrame(),
+ aContainerGridItems, aContainerGridItemsIdx);
+ } else if (baselineStateBits) {
+ // This item is a baseline-aligned grid item (in the subgrid that we're
+ // traversing). Copy over its baseline metrics.
+ subgridItem.mState[subgridAxis] |= baselineStateBits;
+ subgridItem.mBaselineOffset[subgridAxis] =
+ itemFromContainer.mBaselineOffset[aAxis];
+ }
+ }
+ }
+
+ /**
+ * This function here is responsible for propagating baseline-alignment
+ * metrics for subgrid-items from mGridItems over to the "canonical"
+ * GridItemInfo structs for those grid items (which live on the subgrid that
+ * owns them). The outermost parent grid *computes* those metrics as part of
+ * doing track sizing, but it does this using *temporary* GridItemInfo
+ * objects for any grid items that live in subgrids (aka subgrid items). So
+ * that's why we need to rescue this baseline-alignment information before
+ * those temporary objects are discarded.
+ *
+ * (The temporary subgrid-items all live at the end of mGridItems; they were
+ * appended there by CollectSubgridItemsForAxis(). So, it's important that
+ * we perform the exact same traversal that CollectSubgridItemsForAxis() did,
+ * in order to properly match up the temporary & canonical GridItemInfo
+ * objects for these subgrid items.)
+ */
+ // traversal that CollectSubgridItemsForAxis (and its recursive helper) does.
+ void CopyBaselineMetricsToSubgridItems(LogicalAxis aAxis,
+ size_t aOriginalLength) {
+ MOZ_ASSERT(aOriginalLength <= mGridItems.Length(),
+ "aOriginalLength is the length that mGridItems had *before* we "
+ "appended temporary copies of subgrid items to it, so it's not "
+ "possible for it to be more than the current length");
+
+ // This index 'subgridItemIdx' traverses the final portion of mGridItems,
+ // the portion that currently has temporary GridItemInfo structs that we
+ // built for the items that live in our subgrids. (Our caller is about to
+ // discard this temporary portion of mGridItems, and we're trying to
+ // transfer some baseline-alignment data to the canonical GridItemInfo
+ // structs before that happens.)
+ //
+ // Our recursive helper updates subgridItemIdx internally. When this index
+ // reaches mGridItems.Length(), we can stop looping; that means we've
+ // finished copying out all the data from these temporary structs.
+ size_t subgridItemIdx = aOriginalLength;
+
+ for (size_t i = 0;
+ (i < aOriginalLength && subgridItemIdx < mGridItems.Length()); i++) {
+ const auto& item = mGridItems[i];
+ if (item.IsSubgrid(aAxis)) {
+ CopyBaselineMetricsToSubgridItemsHelper(aAxis, mWM, item.SubgridFrame(),
+ mGridItems, subgridItemIdx);
+ }
+ }
+ }
+
+ Tracks& TracksFor(LogicalAxis aAxis) {
+ return aAxis == eLogicalAxisBlock ? mRows : mCols;
+ }
+ const Tracks& TracksFor(LogicalAxis aAxis) const {
+ return aAxis == eLogicalAxisBlock ? mRows : mCols;
+ }
+
+ CSSOrderAwareFrameIterator mIter;
+ const nsStylePosition* const mGridStyle;
+ Tracks mCols;
+ Tracks mRows;
+ TrackSizingFunctions mColFunctions;
+ TrackSizingFunctions mRowFunctions;
+ /**
+ * Info about each (normal flow) grid item.
+ */
+ nsTArray<GridItemInfo> mGridItems;
+ /**
+ * Info about each grid-aligned abs.pos. child.
+ */
+ nsTArray<GridItemInfo> mAbsPosItems;
+
+ /**
+ * @note mReflowInput may be null when using the 2nd ctor above. In this case
+ * we'll construct a dummy parent reflow input if we need it to calculate
+ * min/max-content contributions when sizing tracks.
+ */
+ const ReflowInput* const mReflowInput;
+ gfxContext& mRenderingContext;
+ nsGridContainerFrame* const mFrame;
+ SharedGridData* mSharedGridData; // [weak] owned by mFrame's first-in-flow.
+ /** Computed border+padding with mSkipSides applied. */
+ LogicalMargin mBorderPadding;
+ /**
+ * BStart of this fragment in "grid space" (i.e. the concatenation of content
+ * areas of all fragments). Equal to mRows.mSizes[mStartRow].mPosition,
+ * or, if this fragment starts after the last row, the ConsumedBSize().
+ */
+ nscoord mFragBStart;
+ /** The start row for this fragment. */
+ uint32_t mStartRow;
+ /**
+ * The start row for the next fragment, if any. If mNextFragmentStartRow ==
+ * mStartRow then there are no rows in this fragment.
+ */
+ uint32_t mNextFragmentStartRow;
+ /** Our tentative ApplySkipSides bits. */
+ LogicalSides mSkipSides;
+ const WritingMode mWM;
+ /** Initialized lazily, when we find the fragmentainer. */
+ bool mInFragmentainer;
+
+ private:
+ GridReflowInput(nsGridContainerFrame* aFrame, gfxContext& aRenderingContext,
+ const ReflowInput* aReflowInput,
+ const nsStylePosition* aGridStyle, const WritingMode& aWM)
+ : mIter(aFrame, FrameChildListID::Principal),
+ mGridStyle(aGridStyle),
+ mCols(eLogicalAxisInline),
+ mRows(eLogicalAxisBlock),
+ mColFunctions(mGridStyle->mGridTemplateColumns,
+ mGridStyle->mGridAutoColumns,
+ aFrame->IsSubgrid(eLogicalAxisInline)),
+ mRowFunctions(mGridStyle->mGridTemplateRows, mGridStyle->mGridAutoRows,
+ aFrame->IsSubgrid(eLogicalAxisBlock)),
+ mReflowInput(aReflowInput),
+ mRenderingContext(aRenderingContext),
+ mFrame(aFrame),
+ mSharedGridData(nullptr),
+ mBorderPadding(aWM),
+ mFragBStart(0),
+ mStartRow(0),
+ mNextFragmentStartRow(0),
+ mSkipSides(aFrame->GetWritingMode()),
+ mWM(aWM),
+ mInFragmentainer(false) {
+ MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == mFrame);
+ if (aReflowInput) {
+ mBorderPadding = aReflowInput->ComputedLogicalBorderPadding(mWM);
+ mSkipSides = aFrame->PreReflowBlockLevelLogicalSkipSides();
+ mBorderPadding.ApplySkipSides(mSkipSides);
+ }
+ mCols.mIsMasonry = aFrame->IsMasonry(eLogicalAxisInline);
+ mRows.mIsMasonry = aFrame->IsMasonry(eLogicalAxisBlock);
+ MOZ_ASSERT(!(mCols.mIsMasonry && mRows.mIsMasonry),
+ "can't have masonry layout in both axes");
+ }
+};
+
+using GridReflowInput = nsGridContainerFrame::GridReflowInput;
+
+/**
+ * The Grid implements grid item placement and the state of the grid -
+ * the size of the explicit/implicit grid, which cells are occupied etc.
+ */
+struct MOZ_STACK_CLASS nsGridContainerFrame::Grid {
+ explicit Grid(const Grid* aParentGrid = nullptr) : mParentGrid(aParentGrid) {}
+
+ /**
+ * Place all child frames into the grid and expand the (implicit) grid as
+ * needed. The allocated GridAreas are stored in the GridAreaProperty
+ * frame property on the child frame.
+ * @param aRepeatSizing the container's [min-|max-]*size - used to determine
+ * the number of repeat(auto-fill/fit) tracks.
+ */
+ void PlaceGridItems(GridReflowInput& aState,
+ const RepeatTrackSizingInput& aRepeatSizing);
+
+ void SubgridPlaceGridItems(GridReflowInput& aParentState, Grid* aParentGrid,
+ const GridItemInfo& aGridItem);
+
+ /**
+ * As above but for an abs.pos. child. Any 'auto' lines will be represented
+ * by kAutoLine in the LineRange result.
+ * @param aGridStart the first line in the final, but untranslated grid
+ * @param aGridEnd the last line in the final, but untranslated grid
+ */
+ LineRange ResolveAbsPosLineRange(const StyleGridLine& aStart,
+ const StyleGridLine& aEnd,
+ const LineNameMap& aNameMap,
+ LogicalAxis aAxis, uint32_t aExplicitGridEnd,
+ int32_t aGridStart, int32_t aGridEnd,
+ const nsStylePosition* aStyle);
+
+ /**
+ * Return a GridArea for abs.pos. item with non-auto lines placed at
+ * a definite line (1-based) with placement errors resolved. One or both
+ * positions may still be 'auto'.
+ * @param aChild the abs.pos. grid item to place
+ * @param aStyle the StylePosition() for the grid container
+ */
+ GridArea PlaceAbsPos(nsIFrame* aChild, const LineNameMap& aColLineNameMap,
+ const LineNameMap& aRowLineNameMap,
+ const nsStylePosition* aStyle);
+
+ /**
+ * Find the first column in row aLockedRow starting at aStartCol where aArea
+ * could be placed without overlapping other items. The returned column may
+ * cause aArea to overflow the current implicit grid bounds if placed there.
+ */
+ uint32_t FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow,
+ const GridArea* aArea) const;
+
+ /**
+ * Place aArea in the first column (in row aArea->mRows.mStart) starting at
+ * aStartCol without overlapping other items. The resulting aArea may
+ * overflow the current implicit grid bounds.
+ * @param aClampMaxColLine the maximum allowed column line number (zero-based)
+ * Pre-condition: aArea->mRows.IsDefinite() is true.
+ * Post-condition: aArea->IsDefinite() is true.
+ */
+ void PlaceAutoCol(uint32_t aStartCol, GridArea* aArea,
+ uint32_t aClampMaxColLine) const;
+
+ /**
+ * Find the first row in column aLockedCol starting at aStartRow where aArea
+ * could be placed without overlapping other items. The returned row may
+ * cause aArea to overflow the current implicit grid bounds if placed there.
+ */
+ uint32_t FindAutoRow(uint32_t aLockedCol, uint32_t aStartRow,
+ const GridArea* aArea) const;
+
+ /**
+ * Place aArea in the first row (in column aArea->mCols.mStart) starting at
+ * aStartRow without overlapping other items. The resulting aArea may
+ * overflow the current implicit grid bounds.
+ * @param aClampMaxRowLine the maximum allowed row line number (zero-based)
+ * Pre-condition: aArea->mCols.IsDefinite() is true.
+ * Post-condition: aArea->IsDefinite() is true.
+ */
+ void PlaceAutoRow(uint32_t aStartRow, GridArea* aArea,
+ uint32_t aClampMaxRowLine) const;
+
+ /**
+ * Place aArea in the first column starting at aStartCol,aStartRow without
+ * causing it to overlap other items or overflow mGridColEnd.
+ * If there's no such column in aStartRow, continue in position 1,aStartRow+1.
+ * @param aClampMaxColLine the maximum allowed column line number (zero-based)
+ * @param aClampMaxRowLine the maximum allowed row line number (zero-based)
+ * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true.
+ * Post-condition: aArea->IsDefinite() is true.
+ */
+ void PlaceAutoAutoInRowOrder(uint32_t aStartCol, uint32_t aStartRow,
+ GridArea* aArea, uint32_t aClampMaxColLine,
+ uint32_t aClampMaxRowLine) const;
+
+ /**
+ * Place aArea in the first row starting at aStartCol,aStartRow without
+ * causing it to overlap other items or overflow mGridRowEnd.
+ * If there's no such row in aStartCol, continue in position aStartCol+1,1.
+ * @param aClampMaxColLine the maximum allowed column line number (zero-based)
+ * @param aClampMaxRowLine the maximum allowed row line number (zero-based)
+ * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true.
+ * Post-condition: aArea->IsDefinite() is true.
+ */
+ void PlaceAutoAutoInColOrder(uint32_t aStartCol, uint32_t aStartRow,
+ GridArea* aArea, uint32_t aClampMaxColLine,
+ uint32_t aClampMaxRowLine) const;
+
+ /**
+ * Return aLine if it's inside the aMin..aMax range (inclusive),
+ * otherwise return kAutoLine.
+ */
+ static int32_t AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax) {
+ MOZ_ASSERT(aMin <= aMax);
+ if (aLine < aMin || aLine > aMax) {
+ return kAutoLine;
+ }
+ return aLine;
+ }
+
+ /**
+ * Inflate the implicit grid to include aArea.
+ * @param aArea may be definite or auto
+ */
+ void InflateGridFor(const GridArea& aArea) {
+ mGridColEnd = std::max(mGridColEnd, aArea.mCols.HypotheticalEnd());
+ mGridRowEnd = std::max(mGridRowEnd, aArea.mRows.HypotheticalEnd());
+ MOZ_ASSERT(mGridColEnd <= kTranslatedMaxLine &&
+ mGridRowEnd <= kTranslatedMaxLine);
+ }
+
+ /**
+ * Calculates the empty tracks in a repeat(auto-fit).
+ * @param aOutNumEmptyLines Outputs the number of tracks which are empty.
+ * @param aSizingFunctions Sizing functions for the relevant axis.
+ * @param aNumGridLines Number of grid lines for the relevant axis.
+ * @param aIsEmptyFunc Functor to check if a cell is empty. This should be
+ * mCellMap.IsColEmpty or mCellMap.IsRowEmpty, depending on the axis.
+ */
+ template <typename IsEmptyFuncT>
+ static Maybe<nsTArray<uint32_t>> CalculateAdjustForAutoFitElements(
+ uint32_t* aOutNumEmptyTracks, TrackSizingFunctions& aSizingFunctions,
+ uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc);
+
+ /**
+ * Return a line number for (non-auto) aLine, per:
+ * http://dev.w3.org/csswg/css-grid/#line-placement
+ * @param aLine style data for the line (must be non-auto)
+ * @param aNth a number of lines to find from aFromIndex, negative if the
+ * search should be in reverse order. In the case aLine has
+ * a specified line name, it's permitted to pass in zero which
+ * will be treated as one.
+ * @param aFromIndex the zero-based index to start counting from
+ * @param aLineNameList the explicit named lines
+ * @param aSide the axis+edge we're resolving names for (e.g. if we're
+ resolving a grid-row-start line, pass eLogicalSideBStart)
+ * @param aExplicitGridEnd the last line in the explicit grid
+ * @param aStyle the StylePosition() for the grid container
+ * @return a definite line (1-based), clamped to
+ * the mClampMinLine..mClampMaxLine range
+ */
+ int32_t ResolveLine(const StyleGridLine& aLine, int32_t aNth,
+ uint32_t aFromIndex, const LineNameMap& aNameMap,
+ LogicalSide aSide, uint32_t aExplicitGridEnd,
+ const nsStylePosition* aStyle);
+
+ /**
+ * Helper method for ResolveLineRange.
+ * @see ResolveLineRange
+ * @return a pair (start,end) of lines
+ */
+ typedef std::pair<int32_t, int32_t> LinePair;
+ LinePair ResolveLineRangeHelper(const StyleGridLine& aStart,
+ const StyleGridLine& aEnd,
+ const LineNameMap& aNameMap,
+ LogicalAxis aAxis, uint32_t aExplicitGridEnd,
+ const nsStylePosition* aStyle);
+
+ /**
+ * Return a LineRange based on the given style data. Non-auto lines
+ * are resolved to a definite line number (1-based) per:
+ * http://dev.w3.org/csswg/css-grid/#line-placement
+ * with placement errors corrected per:
+ * http://dev.w3.org/csswg/css-grid/#grid-placement-errors
+ * @param aStyle the StylePosition() for the grid container
+ * @param aStart style data for the start line
+ * @param aEnd style data for the end line
+ * @param aLineNameList the explicit named lines
+ * @param aAxis the axis we're resolving names in
+ * @param aExplicitGridEnd the last line in the explicit grid
+ * @param aStyle the StylePosition() for the grid container
+ */
+ LineRange ResolveLineRange(const StyleGridLine& aStart,
+ const StyleGridLine& aEnd,
+ const LineNameMap& aNameMap, LogicalAxis aAxis,
+ uint32_t aExplicitGridEnd,
+ const nsStylePosition* aStyle);
+
+ /**
+ * Return a GridArea with non-auto lines placed at a definite line (1-based)
+ * with placement errors resolved. One or both positions may still
+ * be 'auto'.
+ * @param aChild the grid item
+ * @param aStyle the StylePosition() for the grid container
+ */
+ GridArea PlaceDefinite(nsIFrame* aChild, const LineNameMap& aColLineNameMap,
+ const LineNameMap& aRowLineNameMap,
+ const nsStylePosition* aStyle);
+
+ bool HasImplicitNamedArea(nsAtom* aName) const {
+ return mAreas && mAreas->has(aName);
+ }
+
+ // Return true if aString ends in aSuffix and has at least one character
+ // before the suffix. Assign aIndex to where the suffix starts.
+ static bool IsNameWithSuffix(nsAtom* aString, const nsString& aSuffix,
+ uint32_t* aIndex) {
+ if (StringEndsWith(nsDependentAtomString(aString), aSuffix)) {
+ *aIndex = aString->GetLength() - aSuffix.Length();
+ return *aIndex != 0;
+ }
+ return false;
+ }
+
+ static bool IsNameWithEndSuffix(nsAtom* aString, uint32_t* aIndex) {
+ return IsNameWithSuffix(aString, u"-end"_ns, aIndex);
+ }
+
+ static bool IsNameWithStartSuffix(nsAtom* aString, uint32_t* aIndex) {
+ return IsNameWithSuffix(aString, u"-start"_ns, aIndex);
+ }
+
+ // Return the relevant parent LineNameMap for the given subgrid axis aAxis.
+ const LineNameMap* ParentLineMapForAxis(bool aIsOrthogonal,
+ LogicalAxis aAxis) const {
+ if (!mParentGrid) {
+ return nullptr;
+ }
+ bool isRows = aIsOrthogonal == (aAxis == eLogicalAxisInline);
+ return isRows ? mParentGrid->mRowNameMap : mParentGrid->mColNameMap;
+ }
+
+ void SetLineMaps(const LineNameMap* aColNameMap,
+ const LineNameMap* aRowNameMap) {
+ mColNameMap = aColNameMap;
+ mRowNameMap = aRowNameMap;
+ }
+
+ /**
+ * A CellMap holds state for each cell in the grid.
+ * It's row major. It's sparse in the sense that it only has enough rows to
+ * cover the last row that has a grid item. Each row only has enough entries
+ * to cover columns that are occupied *on that row*, i.e. it's not a full
+ * matrix covering the entire implicit grid. An absent Cell means that it's
+ * unoccupied by any grid item.
+ */
+ struct CellMap {
+ struct Cell {
+ constexpr Cell() : mIsOccupied(false) {}
+ bool mIsOccupied : 1;
+ };
+
+ void Fill(const GridArea& aGridArea) {
+ MOZ_ASSERT(aGridArea.IsDefinite());
+ MOZ_ASSERT(aGridArea.mRows.mStart < aGridArea.mRows.mEnd);
+ MOZ_ASSERT(aGridArea.mCols.mStart < aGridArea.mCols.mEnd);
+ const auto numRows = aGridArea.mRows.mEnd;
+ const auto numCols = aGridArea.mCols.mEnd;
+ mCells.EnsureLengthAtLeast(numRows);
+ for (auto i = aGridArea.mRows.mStart; i < numRows; ++i) {
+ nsTArray<Cell>& cellsInRow = mCells[i];
+ cellsInRow.EnsureLengthAtLeast(numCols);
+ for (auto j = aGridArea.mCols.mStart; j < numCols; ++j) {
+ cellsInRow[j].mIsOccupied = true;
+ }
+ }
+ }
+
+ uint32_t IsEmptyCol(uint32_t aCol) const {
+ for (auto& row : mCells) {
+ if (aCol < row.Length() && row[aCol].mIsOccupied) {
+ return false;
+ }
+ }
+ return true;
+ }
+ uint32_t IsEmptyRow(uint32_t aRow) const {
+ if (aRow >= mCells.Length()) {
+ return true;
+ }
+ for (const Cell& cell : mCells[aRow]) {
+ if (cell.mIsOccupied) {
+ return false;
+ }
+ }
+ return true;
+ }
+#ifdef DEBUG
+ void Dump() const {
+ const size_t numRows = mCells.Length();
+ for (size_t i = 0; i < numRows; ++i) {
+ const nsTArray<Cell>& cellsInRow = mCells[i];
+ const size_t numCols = cellsInRow.Length();
+ printf("%lu:\t", (unsigned long)i + 1);
+ for (size_t j = 0; j < numCols; ++j) {
+ printf(cellsInRow[j].mIsOccupied ? "X " : ". ");
+ }
+ printf("\n");
+ }
+ }
+#endif
+
+ nsTArray<nsTArray<Cell>> mCells;
+ };
+
+ /**
+ * State for each cell in the grid.
+ */
+ CellMap mCellMap;
+ /**
+ * @see HasImplicitNamedArea.
+ */
+ ImplicitNamedAreas* mAreas;
+ /**
+ * The last column grid line (1-based) in the explicit grid.
+ * (i.e. the number of explicit columns + 1)
+ */
+ uint32_t mExplicitGridColEnd;
+ /**
+ * The last row grid line (1-based) in the explicit grid.
+ * (i.e. the number of explicit rows + 1)
+ */
+ uint32_t mExplicitGridRowEnd;
+ // Same for the implicit grid, except these become zero-based after
+ // resolving definite lines.
+ uint32_t mGridColEnd;
+ uint32_t mGridRowEnd;
+
+ /**
+ * Offsets from the start of the implicit grid to the start of the translated
+ * explicit grid. They are zero if there are no implicit lines before 1,1.
+ * e.g. "grid-column: span 3 / 1" makes mExplicitGridOffsetCol = 3 and the
+ * corresponding GridArea::mCols will be 0 / 3 in the zero-based translated
+ * grid.
+ */
+ uint32_t mExplicitGridOffsetCol;
+ uint32_t mExplicitGridOffsetRow;
+
+ /**
+ * Our parent grid if any.
+ */
+ const Grid* mParentGrid;
+
+ /**
+ * Our LineNameMaps.
+ */
+ const LineNameMap* mColNameMap;
+ const LineNameMap* mRowNameMap;
+};
+
+/**
+ * Compute margin+border+padding for aGridItem.mFrame (a subgrid) and store it
+ * on its Subgrid property (and return that property).
+ * aPercentageBasis is in the grid item's writing-mode.
+ */
+static Subgrid* SubgridComputeMarginBorderPadding(
+ const GridItemInfo& aGridItem, const LogicalSize& aPercentageBasis) {
+ auto* subgridFrame = aGridItem.SubgridFrame();
+ auto cbWM = aGridItem.mFrame->GetParent()->GetWritingMode();
+ auto* subgrid = subgridFrame->GetProperty(Subgrid::Prop());
+ auto wm = subgridFrame->GetWritingMode();
+ auto pmPercentageBasis = cbWM.IsOrthogonalTo(wm) ? aPercentageBasis.BSize(wm)
+ : aPercentageBasis.ISize(wm);
+ SizeComputationInput sz(subgridFrame, nullptr, cbWM, pmPercentageBasis);
+ subgrid->mMarginBorderPadding =
+ sz.ComputedLogicalMargin(cbWM) + sz.ComputedLogicalBorderPadding(cbWM);
+
+ if (aGridItem.mFrame != subgridFrame) {
+ nsIScrollableFrame* scrollFrame = aGridItem.mFrame->GetScrollTargetFrame();
+ if (scrollFrame) {
+ MOZ_ASSERT(
+ sz.ComputedLogicalMargin(cbWM) == LogicalMargin(cbWM) &&
+ sz.ComputedLogicalBorder(cbWM) == LogicalMargin(cbWM),
+ "A scrolled inner frame should not have any margin or border!");
+
+ // Add the margin and border from the (outer) scroll frame.
+ SizeComputationInput szScrollFrame(aGridItem.mFrame, nullptr, cbWM,
+ pmPercentageBasis);
+ subgrid->mMarginBorderPadding +=
+ szScrollFrame.ComputedLogicalMargin(cbWM) +
+ szScrollFrame.ComputedLogicalBorder(cbWM);
+
+ nsMargin ssz = scrollFrame->GetActualScrollbarSizes();
+ subgrid->mMarginBorderPadding += LogicalMargin(cbWM, ssz);
+ }
+
+ if (aGridItem.mFrame->IsFieldSetFrame()) {
+ const auto* f = static_cast<nsFieldSetFrame*>(aGridItem.mFrame);
+ const auto* inner = f->GetInner();
+ auto wm = inner->GetWritingMode();
+ LogicalPoint pos = inner->GetLogicalPosition(aGridItem.mFrame->GetSize());
+ // The legend is always on the BStart side and it inflates the fieldset's
+ // "border area" size. The inner frame's b-start pos equals that size.
+ LogicalMargin offsets(wm, pos.B(wm), 0, 0, 0);
+ subgrid->mMarginBorderPadding += offsets.ConvertTo(cbWM, wm);
+ }
+ }
+ return subgrid;
+}
+
+static void CopyUsedTrackSizes(nsTArray<TrackSize>& aResult,
+ const nsGridContainerFrame* aUsedTrackSizesFrame,
+ const UsedTrackSizes* aUsedTrackSizes,
+ const nsGridContainerFrame* aSubgridFrame,
+ const Subgrid* aSubgrid,
+ LogicalAxis aSubgridAxis) {
+ MOZ_ASSERT(aSubgridFrame->ParentGridContainerForSubgrid() ==
+ aUsedTrackSizesFrame);
+ aResult.SetLength(aSubgridAxis == eLogicalAxisInline ? aSubgrid->mGridColEnd
+ : aSubgrid->mGridRowEnd);
+ auto parentAxis =
+ aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aSubgridAxis) : aSubgridAxis;
+ const auto& parentSizes = aUsedTrackSizes->mSizes[parentAxis];
+ MOZ_ASSERT(aUsedTrackSizes->mCanResolveLineRangeSize[parentAxis]);
+ if (parentSizes.IsEmpty()) {
+ return;
+ }
+ const auto& range = aSubgrid->mArea.LineRangeForAxis(parentAxis);
+ const auto cbwm = aUsedTrackSizesFrame->GetWritingMode();
+ const auto wm = aSubgridFrame->GetWritingMode();
+ // Recompute the MBP to resolve percentages against the resolved track sizes.
+ if (parentAxis == eLogicalAxisInline) {
+ // Find the subgrid's grid item frame in its parent grid container. This
+ // is usually the same as aSubgridFrame but it may also have a ScrollFrame,
+ // FieldSetFrame etc. We just loop until we see the first ancestor
+ // GridContainerFrame and pick the last frame we saw before that.
+ // Note that all subgrids are inside a parent (sub)grid container.
+ const nsIFrame* outerGridItemFrame = aSubgridFrame;
+ for (nsIFrame* parent = aSubgridFrame->GetParent();
+ parent != aUsedTrackSizesFrame; parent = parent->GetParent()) {
+ MOZ_ASSERT(!parent->IsGridContainerFrame());
+ outerGridItemFrame = parent;
+ }
+ auto sizeInAxis = range.ToLength(aUsedTrackSizes->mSizes[parentAxis]);
+ LogicalSize pmPercentageBasis =
+ aSubgrid->mIsOrthogonal ? LogicalSize(wm, nscoord(0), sizeInAxis)
+ : LogicalSize(wm, sizeInAxis, nscoord(0));
+ GridItemInfo info(const_cast<nsIFrame*>(outerGridItemFrame),
+ aSubgrid->mArea);
+ SubgridComputeMarginBorderPadding(info, pmPercentageBasis);
+ }
+ const LogicalMargin& mbp = aSubgrid->mMarginBorderPadding;
+ nscoord startMBP;
+ nscoord endMBP;
+ if (MOZ_LIKELY(cbwm.ParallelAxisStartsOnSameSide(parentAxis, wm))) {
+ startMBP = mbp.Start(parentAxis, cbwm);
+ endMBP = mbp.End(parentAxis, cbwm);
+ uint32_t i = range.mStart;
+ nscoord startPos = parentSizes[i].mPosition + startMBP;
+ for (auto& sz : aResult) {
+ sz = parentSizes[i++];
+ sz.mPosition -= startPos;
+ }
+ } else {
+ startMBP = mbp.End(parentAxis, cbwm);
+ endMBP = mbp.Start(parentAxis, cbwm);
+ uint32_t i = range.mEnd - 1;
+ const auto& parentEnd = parentSizes[i];
+ nscoord parentEndPos = parentEnd.mPosition + parentEnd.mBase - startMBP;
+ for (auto& sz : aResult) {
+ sz = parentSizes[i--];
+ sz.mPosition = parentEndPos - (sz.mPosition + sz.mBase);
+ }
+ }
+ auto& startTrack = aResult[0];
+ startTrack.mPosition = 0;
+ startTrack.mBase -= startMBP;
+ if (MOZ_UNLIKELY(startTrack.mBase < nscoord(0))) {
+ // Our MBP doesn't fit in the start track. Adjust the track position
+ // to maintain track alignment with our parent.
+ startTrack.mPosition = startTrack.mBase;
+ startTrack.mBase = nscoord(0);
+ }
+ auto& endTrack = aResult.LastElement();
+ endTrack.mBase -= endMBP;
+ if (MOZ_UNLIKELY(endTrack.mBase < nscoord(0))) {
+ endTrack.mBase = nscoord(0);
+ }
+}
+
+void nsGridContainerFrame::UsedTrackSizes::ResolveTrackSizesForAxis(
+ nsGridContainerFrame* aFrame, LogicalAxis aAxis, gfxContext& aRC) {
+ if (mCanResolveLineRangeSize[aAxis]) {
+ return;
+ }
+ if (!aFrame->IsSubgrid()) {
+ // We can't resolve sizes in this axis at this point. aFrame is the top grid
+ // container, which will store its final track sizes later once they're
+ // resolved in this axis (in GridReflowInput::CalculateTrackSizesForAxis).
+ // The single caller of this method only needs track sizes for
+ // calculating a CB size and it will treat it as indefinite when
+ // this happens.
+ return;
+ }
+ auto* parent = aFrame->ParentGridContainerForSubgrid();
+ auto* parentSizes = parent->GetUsedTrackSizes();
+ if (!parentSizes) {
+ parentSizes = new UsedTrackSizes();
+ parent->SetProperty(UsedTrackSizes::Prop(), parentSizes);
+ }
+ auto* subgrid = aFrame->GetProperty(Subgrid::Prop());
+ const auto parentAxis =
+ subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
+ parentSizes->ResolveTrackSizesForAxis(parent, parentAxis, aRC);
+ if (!parentSizes->mCanResolveLineRangeSize[parentAxis]) {
+ if (aFrame->IsSubgrid(aAxis)) {
+ ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC,
+ NS_UNCONSTRAINEDSIZE);
+ }
+ return;
+ }
+ if (aFrame->IsSubgrid(aAxis)) {
+ CopyUsedTrackSizes(mSizes[aAxis], parent, parentSizes, aFrame, subgrid,
+ aAxis);
+ mCanResolveLineRangeSize[aAxis] = true;
+ } else {
+ const auto& range = subgrid->mArea.LineRangeForAxis(parentAxis);
+ nscoord contentBoxSize = range.ToLength(parentSizes->mSizes[parentAxis]);
+ auto parentWM = aFrame->GetParent()->GetWritingMode();
+ contentBoxSize -=
+ subgrid->mMarginBorderPadding.StartEnd(parentAxis, parentWM);
+ contentBoxSize = std::max(nscoord(0), contentBoxSize);
+ ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC,
+ contentBoxSize);
+ }
+}
+
+void nsGridContainerFrame::UsedTrackSizes::ResolveSubgridTrackSizesForAxis(
+ nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid,
+ gfxContext& aRC, nscoord aContentBoxSize) {
+ GridReflowInput state(aFrame, aRC);
+ state.mGridItems = aSubgrid->mGridItems.Clone();
+ Grid grid;
+ grid.mGridColEnd = aSubgrid->mGridColEnd;
+ grid.mGridRowEnd = aSubgrid->mGridRowEnd;
+ state.CalculateTrackSizesForAxis(aAxis, grid, aContentBoxSize,
+ SizingConstraint::NoConstraint);
+ const auto& tracks = aAxis == eLogicalAxisInline ? state.mCols : state.mRows;
+ mSizes[aAxis].Assign(tracks.mSizes);
+ mCanResolveLineRangeSize[aAxis] = tracks.mCanResolveLineRangeSize;
+ MOZ_ASSERT(mCanResolveLineRangeSize[aAxis]);
+}
+
+void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis(
+ LogicalAxis aAxis, const Grid& aGrid, nscoord aContentBoxSize,
+ SizingConstraint aConstraint) {
+ auto& tracks = aAxis == eLogicalAxisInline ? mCols : mRows;
+ const auto& sizingFunctions =
+ aAxis == eLogicalAxisInline ? mColFunctions : mRowFunctions;
+ const auto& gapStyle = aAxis == eLogicalAxisInline ? mGridStyle->mColumnGap
+ : mGridStyle->mRowGap;
+ if (tracks.mIsMasonry) {
+ // See comment on nsGridContainerFrame::MasonryLayout().
+ tracks.Initialize(sizingFunctions, gapStyle, 2, aContentBoxSize);
+ tracks.mCanResolveLineRangeSize = true;
+ return;
+ }
+ uint32_t gridEnd =
+ aAxis == eLogicalAxisInline ? aGrid.mGridColEnd : aGrid.mGridRowEnd;
+ Maybe<TrackSizingFunctions> fallbackTrackSizing;
+
+ bool useParentGaps = false;
+ const bool isSubgriddedAxis = mFrame->IsSubgrid(aAxis);
+ if (MOZ_LIKELY(!isSubgriddedAxis)) {
+ tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentBoxSize);
+ } else {
+ tracks.mGridGap =
+ nsLayoutUtils::ResolveGapToLength(gapStyle, aContentBoxSize);
+ tracks.mContentBoxSize = aContentBoxSize;
+ const auto* subgrid = mFrame->GetProperty(Subgrid::Prop());
+ tracks.mSizes.SetLength(gridEnd);
+ auto* parent = mFrame->ParentGridContainerForSubgrid();
+ auto parentAxis = subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
+ const auto* parentSizes = parent->GetUsedTrackSizes();
+ if (parentSizes && parentSizes->mCanResolveLineRangeSize[parentAxis]) {
+ CopyUsedTrackSizes(tracks.mSizes, parent, parentSizes, mFrame, subgrid,
+ aAxis);
+ useParentGaps = gapStyle.IsNormal();
+ } else {
+ fallbackTrackSizing.emplace(TrackSizingFunctions::ForSubgridFallback(
+ mFrame, subgrid, parent, parentAxis));
+ tracks.Initialize(*fallbackTrackSizing, gapStyle, gridEnd,
+ aContentBoxSize);
+ }
+ }
+
+ // We run the Track Sizing Algorithm in non-subgridded axes, and in some
+ // cases in a subgridded axis when our parent track sizes aren't resolved yet.
+ if (MOZ_LIKELY(!isSubgriddedAxis) || fallbackTrackSizing.isSome()) {
+ const size_t origGridItemCount = mGridItems.Length();
+ const bool hasSubgridItems = mFrame->HasSubgridItems(aAxis);
+ if (hasSubgridItems) {
+ AutoTArray<GridItemInfo, 8> collectedItems;
+ CollectSubgridItemsForAxis(aAxis, collectedItems);
+ mGridItems.AppendElements(collectedItems);
+ }
+ tracks.CalculateSizes(
+ *this, mGridItems,
+ fallbackTrackSizing ? *fallbackTrackSizing : sizingFunctions,
+ aContentBoxSize,
+ aAxis == eLogicalAxisInline ? &GridArea::mCols : &GridArea::mRows,
+ aConstraint);
+
+ if (hasSubgridItems &&
+ StaticPrefs::layout_css_grid_subgrid_baselines_enabled()) {
+ // If any of the subgrid items are baseline-aligned, we've just recorded
+ // their baseline-alignment offsets in our own copy of their GridItemInfo
+ // structs. Before we get rid of those copies (via TruncateLength), we
+ // have to copy these offsets back to the subgrids' versions of the
+ // GridItemInfo structs.
+ //
+ // XXXdholbert This new behavior is behind a pref due to bug 1871719.
+ CopyBaselineMetricsToSubgridItems(aAxis, origGridItemCount);
+ }
+ mGridItems.TruncateLength(origGridItemCount);
+ }
+ if (isSubgriddedAxis) {
+ // XXXdholbert This is a bit hacky, but this is something that
+ // tracks.CalculateSizes does internally (unconditionally, if there are
+ // baseline-aligned items), and it seems like subgrids need to do it too,
+ // or else they hit the "unexpected baseline subtree alignment"
+ // fatal-assert when aligning their children with the baseline-alignment
+ // information that they received from the outer grid.
+ // (This might be entirely unnecessary? Aside from the default ::AUTO
+ // value, it looks like the ::First entry is always set to ::START and
+ // the ::Last entry is always set to ::END...)
+ tracks.mBaselineSubtreeAlign[BaselineSharingGroup::First] =
+ StyleAlignFlags::START;
+ tracks.mBaselineSubtreeAlign[BaselineSharingGroup::Last] =
+ StyleAlignFlags::END;
+ }
+
+ if (aContentBoxSize != NS_UNCONSTRAINEDSIZE) {
+ auto alignment = mGridStyle->UsedContentAlignment(tracks.mAxis);
+ tracks.AlignJustifyContent(mGridStyle, alignment, mWM, aContentBoxSize,
+ isSubgriddedAxis);
+ } else if (!useParentGaps) {
+ const nscoord gridGap = tracks.mGridGap;
+ nscoord pos = 0;
+ for (TrackSize& sz : tracks.mSizes) {
+ sz.mPosition = pos;
+ pos += sz.mBase + gridGap;
+ }
+ }
+
+ if (aConstraint == SizingConstraint::NoConstraint &&
+ (mFrame->HasSubgridItems() || mFrame->IsSubgrid())) {
+ mFrame->StoreUsedTrackSizes(aAxis, tracks.mSizes);
+ }
+
+ // positions and sizes are now final
+ tracks.mCanResolveLineRangeSize = true;
+}
+
+void nsGridContainerFrame::GridReflowInput::CalculateTrackSizes(
+ const Grid& aGrid, const LogicalSize& aContentBox,
+ SizingConstraint aConstraint) {
+ CalculateTrackSizesForAxis(eLogicalAxisInline, aGrid, aContentBox.ISize(mWM),
+ aConstraint);
+ CalculateTrackSizesForAxis(eLogicalAxisBlock, aGrid, aContentBox.BSize(mWM),
+ aConstraint);
+}
+
+// Align an item's margin box in its aAxis inside aCBSize.
+static void AlignJustifySelf(StyleAlignFlags aAlignment, LogicalAxis aAxis,
+ AlignJustifyFlags aFlags, nscoord aBaselineAdjust,
+ nscoord aCBSize, const ReflowInput& aRI,
+ const LogicalSize& aChildSize,
+ LogicalPoint* aPos) {
+ MOZ_ASSERT(aAlignment != StyleAlignFlags::AUTO,
+ "unexpected 'auto' "
+ "computed value for normal flow grid item");
+
+ // NOTE: this is the resulting frame offset (border box).
+ nscoord offset = CSSAlignUtils::AlignJustifySelf(
+ aAlignment, aAxis, aFlags, aBaselineAdjust, aCBSize, aRI, aChildSize);
+
+ // Set the position (aPos) for the requested alignment.
+ if (offset != 0) {
+ WritingMode wm = aRI.GetWritingMode();
+ nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm);
+ pos += MOZ_LIKELY(aFlags & AlignJustifyFlags::SameSide) ? offset : -offset;
+ }
+}
+
+static void AlignSelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
+ StyleAlignFlags aAlignSelf, nscoord aCBSize,
+ const WritingMode aCBWM, const ReflowInput& aRI,
+ const LogicalSize& aSize, AlignJustifyFlags aFlags,
+ LogicalPoint* aPos) {
+ AlignJustifyFlags flags = aFlags;
+ if (aAlignSelf & StyleAlignFlags::SAFE) {
+ flags |= AlignJustifyFlags::OverflowSafe;
+ }
+ aAlignSelf &= ~StyleAlignFlags::FLAG_BITS;
+
+ WritingMode childWM = aRI.GetWritingMode();
+ if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, childWM)) {
+ flags |= AlignJustifyFlags::SameSide;
+ }
+
+ // Grid's 'align-self' axis is never parallel to the container's inline axis.
+ if (aAlignSelf == StyleAlignFlags::LEFT ||
+ aAlignSelf == StyleAlignFlags::RIGHT) {
+ aAlignSelf = StyleAlignFlags::START;
+ }
+ if (MOZ_LIKELY(aAlignSelf == StyleAlignFlags::NORMAL)) {
+ aAlignSelf = StyleAlignFlags::STRETCH;
+ }
+
+ nscoord baselineAdjust = 0;
+ if (aAlignSelf == StyleAlignFlags::BASELINE ||
+ aAlignSelf == StyleAlignFlags::LAST_BASELINE) {
+ aAlignSelf = aGridItem.GetSelfBaseline(aAlignSelf, eLogicalAxisBlock,
+ &baselineAdjust);
+ // Adjust the baseline alignment value if the baseline affects the opposite
+ // side of what AlignJustifySelf expects.
+ auto state = aGridItem.mState[eLogicalAxisBlock];
+ if (aAlignSelf == StyleAlignFlags::LAST_BASELINE &&
+ !GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
+ aAlignSelf = StyleAlignFlags::BASELINE;
+ } else if (aAlignSelf == StyleAlignFlags::BASELINE &&
+ GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
+ aAlignSelf = StyleAlignFlags::LAST_BASELINE;
+ }
+ }
+
+ bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
+ LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock;
+ AlignJustifySelf(aAlignSelf, axis, flags, baselineAdjust, aCBSize, aRI, aSize,
+ aPos);
+}
+
+static void JustifySelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
+ StyleAlignFlags aJustifySelf, nscoord aCBSize,
+ const WritingMode aCBWM, const ReflowInput& aRI,
+ const LogicalSize& aSize, AlignJustifyFlags aFlags,
+ LogicalPoint* aPos) {
+ AlignJustifyFlags flags = aFlags;
+ if (aJustifySelf & StyleAlignFlags::SAFE) {
+ flags |= AlignJustifyFlags::OverflowSafe;
+ }
+ aJustifySelf &= ~StyleAlignFlags::FLAG_BITS;
+
+ WritingMode childWM = aRI.GetWritingMode();
+ if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, childWM)) {
+ flags |= AlignJustifyFlags::SameSide;
+ }
+
+ if (MOZ_LIKELY(aJustifySelf == StyleAlignFlags::NORMAL)) {
+ aJustifySelf = StyleAlignFlags::STRETCH;
+ }
+
+ nscoord baselineAdjust = 0;
+ // Grid's 'justify-self' axis is always parallel to the container's inline
+ // axis, so justify-self:left|right always applies.
+ if (aJustifySelf == StyleAlignFlags::LEFT) {
+ aJustifySelf =
+ aCBWM.IsBidiLTR() ? StyleAlignFlags::START : StyleAlignFlags::END;
+ } else if (aJustifySelf == StyleAlignFlags::RIGHT) {
+ aJustifySelf =
+ aCBWM.IsBidiLTR() ? StyleAlignFlags::END : StyleAlignFlags::START;
+ } else if (aJustifySelf == StyleAlignFlags::BASELINE ||
+ aJustifySelf == StyleAlignFlags::LAST_BASELINE) {
+ aJustifySelf = aGridItem.GetSelfBaseline(aJustifySelf, eLogicalAxisInline,
+ &baselineAdjust);
+ // Adjust the baseline alignment value if the baseline affects the opposite
+ // side of what AlignJustifySelf expects.
+ auto state = aGridItem.mState[eLogicalAxisInline];
+ if (aJustifySelf == StyleAlignFlags::LAST_BASELINE &&
+ !GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
+ aJustifySelf = StyleAlignFlags::BASELINE;
+ } else if (aJustifySelf == StyleAlignFlags::BASELINE &&
+ GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
+ aJustifySelf = StyleAlignFlags::LAST_BASELINE;
+ }
+ }
+
+ bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
+ LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
+ AlignJustifySelf(aJustifySelf, axis, flags, baselineAdjust, aCBSize, aRI,
+ aSize, aPos);
+}
+
+static StyleAlignFlags GetAlignJustifyValue(StyleAlignFlags aAlignment,
+ const WritingMode aWM,
+ const bool aIsAlign,
+ bool* aOverflowSafe) {
+ *aOverflowSafe = bool(aAlignment & StyleAlignFlags::SAFE);
+ aAlignment &= ~StyleAlignFlags::FLAG_BITS;
+
+ // Map some alignment values to 'start' / 'end'.
+ if (aAlignment == StyleAlignFlags::LEFT ||
+ aAlignment == StyleAlignFlags::RIGHT) {
+ if (aIsAlign) {
+ // Grid's 'align-content' axis is never parallel to the inline axis.
+ return StyleAlignFlags::START;
+ }
+ bool isStart = aWM.IsBidiLTR() == (aAlignment == StyleAlignFlags::LEFT);
+ return isStart ? StyleAlignFlags::START : StyleAlignFlags::END;
+ }
+ if (aAlignment == StyleAlignFlags::FLEX_START) {
+ return StyleAlignFlags::START; // same as 'start' for Grid
+ }
+ if (aAlignment == StyleAlignFlags::FLEX_END) {
+ return StyleAlignFlags::END; // same as 'end' for Grid
+ }
+ return aAlignment;
+}
+
+static Maybe<StyleAlignFlags> GetAlignJustifyFallbackIfAny(
+ const StyleContentDistribution& aDistribution, const WritingMode aWM,
+ const bool aIsAlign, bool* aOverflowSafe) {
+ // TODO: Eventually this should look at aDistribution's fallback alignment,
+ // see https://github.com/w3c/csswg-drafts/issues/1002.
+ if (aDistribution.primary == StyleAlignFlags::STRETCH ||
+ aDistribution.primary == StyleAlignFlags::SPACE_BETWEEN) {
+ return Some(StyleAlignFlags::START);
+ }
+ if (aDistribution.primary == StyleAlignFlags::SPACE_AROUND ||
+ aDistribution.primary == StyleAlignFlags::SPACE_EVENLY) {
+ return Some(StyleAlignFlags::CENTER);
+ }
+ return Nothing();
+}
+
+//----------------------------------------------------------------------
+
+// Frame class boilerplate
+// =======================
+
+NS_QUERYFRAME_HEAD(nsGridContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsGridContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsGridContainerFrame)
+
+nsContainerFrame* NS_NewGridContainerFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsGridContainerFrame(aStyle, aPresShell->GetPresContext());
+}
+
+//----------------------------------------------------------------------
+
+// nsGridContainerFrame Method Implementations
+// ===========================================
+
+/*static*/ const nsRect& nsGridContainerFrame::GridItemCB(nsIFrame* aChild) {
+ MOZ_ASSERT(aChild->IsAbsolutelyPositioned());
+ nsRect* cb = aChild->GetProperty(GridItemContainingBlockRect());
+ MOZ_ASSERT(cb,
+ "this method must only be called on grid items, and the grid "
+ "container should've reflowed this item by now and set up cb");
+ return *cb;
+}
+
+void nsGridContainerFrame::AddImplicitNamedAreasInternal(
+ LineNameList& aNameList,
+ nsGridContainerFrame::ImplicitNamedAreas*& aAreas) {
+ for (const auto& nameIdent : aNameList.AsSpan()) {
+ nsAtom* name = nameIdent.AsAtom();
+ uint32_t indexOfSuffix;
+ if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) ||
+ Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) {
+ // Extract the name that was found earlier.
+ nsDependentSubstring areaName(nsDependentAtomString(name), 0,
+ indexOfSuffix);
+
+ // Lazily create the ImplicitNamedAreas.
+ if (!aAreas) {
+ aAreas = new nsGridContainerFrame::ImplicitNamedAreas;
+ SetProperty(nsGridContainerFrame::ImplicitNamedAreasProperty(), aAreas);
+ }
+
+ RefPtr<nsAtom> name = NS_Atomize(areaName);
+ auto addPtr = aAreas->lookupForAdd(name);
+ if (!addPtr) {
+ if (!aAreas->add(addPtr, name,
+ nsGridContainerFrame::NamedArea{
+ StyleAtom(do_AddRef(name)), {0, 0}, {0, 0}})) {
+ MOZ_CRASH("OOM while adding grid name lists");
+ }
+ }
+ }
+ }
+}
+
+void nsGridContainerFrame::AddImplicitNamedAreas(
+ Span<LineNameList> aLineNameLists) {
+ // http://dev.w3.org/csswg/css-grid/#implicit-named-areas
+ // Note: recording these names for fast lookup later is just an optimization.
+ ImplicitNamedAreas* areas = GetImplicitNamedAreas();
+ const uint32_t len = std::min(aLineNameLists.Length(), size_t(kMaxLine));
+ for (uint32_t i = 0; i < len; ++i) {
+ AddImplicitNamedAreasInternal(aLineNameLists[i], areas);
+ }
+}
+
+void nsGridContainerFrame::AddImplicitNamedAreas(
+ Span<StyleLineNameListValue> aLineNameList) {
+ // http://dev.w3.org/csswg/css-grid/#implicit-named-areas
+ // Note: recording these names for fast lookup later is just an optimization.
+ uint32_t count = 0;
+ ImplicitNamedAreas* areas = GetImplicitNamedAreas();
+ for (const auto& nameList : aLineNameList) {
+ if (nameList.IsRepeat()) {
+ for (const auto& repeatNameList :
+ nameList.AsRepeat().line_names.AsSpan()) {
+ AddImplicitNamedAreasInternal(repeatNameList, areas);
+ ++count;
+ }
+ } else {
+ MOZ_ASSERT(nameList.IsLineNames());
+ AddImplicitNamedAreasInternal(nameList.AsLineNames(), areas);
+ ++count;
+ }
+
+ if (count >= size_t(kMaxLine)) {
+ break;
+ }
+ }
+}
+
+void nsGridContainerFrame::InitImplicitNamedAreas(
+ const nsStylePosition* aStyle) {
+ ImplicitNamedAreas* areas = GetImplicitNamedAreas();
+ if (areas) {
+ // Clear it, but reuse the hashtable itself for now. We'll remove it
+ // below if it isn't needed anymore.
+ areas->clear();
+ }
+ auto Add = [&](const GridTemplate& aTemplate, bool aIsSubgrid) {
+ AddImplicitNamedAreas(aTemplate.LineNameLists(aIsSubgrid));
+ for (auto& value : aTemplate.TrackListValues()) {
+ if (value.IsTrackRepeat()) {
+ AddImplicitNamedAreas(value.AsTrackRepeat().line_names.AsSpan());
+ }
+ }
+
+ if (aIsSubgrid && aTemplate.IsSubgrid()) {
+ // For subgrid, |aTemplate.LineNameLists(aIsSubgrid)| returns an empty
+ // list so we have to manually add each item.
+ AddImplicitNamedAreas(aTemplate.AsSubgrid()->line_names.AsSpan());
+ }
+ };
+ Add(aStyle->mGridTemplateColumns, IsSubgrid(eLogicalAxisInline));
+ Add(aStyle->mGridTemplateRows, IsSubgrid(eLogicalAxisBlock));
+ if (areas && areas->count() == 0) {
+ RemoveProperty(ImplicitNamedAreasProperty());
+ }
+}
+
+int32_t nsGridContainerFrame::Grid::ResolveLine(
+ const StyleGridLine& aLine, int32_t aNth, uint32_t aFromIndex,
+ const LineNameMap& aNameMap, LogicalSide aSide, uint32_t aExplicitGridEnd,
+ const nsStylePosition* aStyle) {
+ MOZ_ASSERT(!aLine.IsAuto());
+ int32_t line = 0;
+ if (aLine.LineName()->IsEmpty()) {
+ MOZ_ASSERT(aNth != 0, "css-grid 9.2: <integer> must not be zero.");
+ line = int32_t(aFromIndex) + aNth;
+ } else {
+ if (aNth == 0) {
+ // <integer> was omitted; treat it as 1.
+ aNth = 1;
+ }
+ bool isNameOnly = !aLine.is_span && aLine.line_num == 0;
+ if (isNameOnly) {
+ AutoTArray<uint32_t, 16> implicitLines;
+ aNameMap.FindNamedAreas(aLine.ident.AsAtom(), aSide, implicitLines);
+ if (!implicitLines.IsEmpty() ||
+ aNameMap.HasImplicitNamedArea(aLine.LineName())) {
+ // aName is a named area - look for explicit lines named
+ // <name>-start/-end depending on which side we're resolving.
+ // http://dev.w3.org/csswg/css-grid/#grid-placement-slot
+ nsAutoString lineName(nsDependentAtomString(aLine.LineName()));
+ if (IsStart(aSide)) {
+ lineName.AppendLiteral("-start");
+ } else {
+ lineName.AppendLiteral("-end");
+ }
+ RefPtr<nsAtom> name = NS_Atomize(lineName);
+ line = aNameMap.FindNamedLine(name, &aNth, aFromIndex, implicitLines);
+ }
+ }
+
+ if (line == 0) {
+ // If LineName() ends in -start/-end, try the prefix as a named area.
+ AutoTArray<uint32_t, 16> implicitLines;
+ uint32_t index;
+ bool useStart = IsNameWithStartSuffix(aLine.LineName(), &index);
+ if (useStart || IsNameWithEndSuffix(aLine.LineName(), &index)) {
+ auto side = MakeLogicalSide(
+ GetAxis(aSide), useStart ? eLogicalEdgeStart : eLogicalEdgeEnd);
+ RefPtr<nsAtom> name = NS_Atomize(nsDependentSubstring(
+ nsDependentAtomString(aLine.LineName()), 0, index));
+ aNameMap.FindNamedAreas(name, side, implicitLines);
+ }
+ line = aNameMap.FindNamedLine(aLine.LineName(), &aNth, aFromIndex,
+ implicitLines);
+ }
+
+ if (line == 0) {
+ MOZ_ASSERT(aNth != 0, "we found all N named lines but 'line' is zero!");
+ int32_t edgeLine;
+ if (aLine.is_span) {
+ // http://dev.w3.org/csswg/css-grid/#grid-placement-span-int
+ // 'span <custom-ident> N'
+ edgeLine = IsStart(aSide) ? 1 : aExplicitGridEnd;
+ } else {
+ // http://dev.w3.org/csswg/css-grid/#grid-placement-int
+ // '<custom-ident> N'
+ edgeLine = aNth < 0 ? 1 : aExplicitGridEnd;
+ }
+ // "If not enough lines with that name exist, all lines in the implicit
+ // grid are assumed to have that name..."
+ line = edgeLine + aNth;
+ }
+ }
+ // Note: at this point, 'line' might be outside of aNameMap's allowed range,
+ // [mClampMinLin, mClampMaxLine]. This is fine; we'll clamp once we've
+ // resolved *both* the start and end line -- in particular, we clamp in
+ // ResolveLineRange(). If we clamped here, it'd be premature -- if one line
+ // is definite and the other is specified as a span to some named line
+ // (i.e. we need to perform a name-search that starts from the definite
+ // line), then it matters whether we clamp the definite line before or after
+ // that search. See https://bugzilla.mozilla.org/show_bug.cgi?id=1800566#c6
+ // for more.
+ return line;
+}
+
+nsGridContainerFrame::Grid::LinePair
+nsGridContainerFrame::Grid::ResolveLineRangeHelper(
+ const StyleGridLine& aStart, const StyleGridLine& aEnd,
+ const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd,
+ const nsStylePosition* aStyle) {
+ MOZ_ASSERT(int32_t(kAutoLine) > kMaxLine);
+
+ if (aStart.is_span) {
+ if (aEnd.is_span || aEnd.IsAuto()) {
+ // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
+ if (aStart.LineName()->IsEmpty()) {
+ // span <integer> / span *
+ // span <integer> / auto
+ return LinePair(kAutoLine, aStart.line_num);
+ }
+ // span <custom-ident> / span *
+ // span <custom-ident> / auto
+ return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1?
+ }
+
+ uint32_t from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0;
+ auto end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap,
+ MakeLogicalSide(aAxis, eLogicalEdgeEnd),
+ aExplicitGridEnd, aStyle);
+ int32_t span = aStart.line_num == 0 ? 1 : aStart.line_num;
+ if (end <= 1) {
+ // The end is at or before the first explicit line, thus all lines before
+ // it match <custom-ident> since they're implicit.
+ int32_t start = std::max(end - span, aNameMap.mClampMinLine);
+ return LinePair(start, end);
+ }
+ auto start = ResolveLine(aStart, -span, end, aNameMap,
+ MakeLogicalSide(aAxis, eLogicalEdgeStart),
+ aExplicitGridEnd, aStyle);
+ return LinePair(start, end);
+ }
+
+ int32_t start = kAutoLine;
+ if (aStart.IsAuto()) {
+ if (aEnd.IsAuto()) {
+ // auto / auto
+ return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
+ }
+ if (aEnd.is_span) {
+ if (aEnd.LineName()->IsEmpty()) {
+ // auto / span <integer>
+ MOZ_ASSERT(aEnd.line_num != 0);
+ return LinePair(start, aEnd.line_num);
+ }
+ // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
+ // auto / span <custom-ident>
+ return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
+ }
+ } else {
+ uint32_t from = aStart.line_num < 0 ? aExplicitGridEnd + 1 : 0;
+ start = ResolveLine(aStart, aStart.line_num, from, aNameMap,
+ MakeLogicalSide(aAxis, eLogicalEdgeStart),
+ aExplicitGridEnd, aStyle);
+ if (aEnd.IsAuto()) {
+ // A "definite line / auto" should resolve the auto to 'span 1'.
+ // The error handling in ResolveLineRange will make that happen and also
+ // clamp the end line correctly if we return "start / start".
+ return LinePair(start, start);
+ }
+ }
+
+ uint32_t from;
+ int32_t nth = aEnd.line_num == 0 ? 1 : aEnd.line_num;
+ if (aEnd.is_span) {
+ if (MOZ_UNLIKELY(start < 0)) {
+ if (aEnd.LineName()->IsEmpty()) {
+ return LinePair(start, start + nth);
+ }
+ from = 0;
+ } else {
+ if (start >= int32_t(aExplicitGridEnd)) {
+ // The start is at or after the last explicit line, thus all lines
+ // after it match <custom-ident> since they're implicit.
+ return LinePair(start, std::min(start + nth, aNameMap.mClampMaxLine));
+ }
+ from = start;
+ }
+ } else {
+ from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0;
+ }
+ auto end = ResolveLine(aEnd, nth, from, aNameMap,
+ MakeLogicalSide(aAxis, eLogicalEdgeEnd),
+ aExplicitGridEnd, aStyle);
+ if (start == int32_t(kAutoLine)) {
+ // auto / definite line
+ start = std::max(aNameMap.mClampMinLine, end - 1);
+ }
+ return LinePair(start, end);
+}
+
+nsGridContainerFrame::LineRange nsGridContainerFrame::Grid::ResolveLineRange(
+ const StyleGridLine& aStart, const StyleGridLine& aEnd,
+ const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd,
+ const nsStylePosition* aStyle) {
+ LinePair r = ResolveLineRangeHelper(aStart, aEnd, aNameMap, aAxis,
+ aExplicitGridEnd, aStyle);
+ MOZ_ASSERT(r.second != int32_t(kAutoLine));
+
+ if (r.first == int32_t(kAutoLine)) {
+ // r.second is a span, clamp it to aNameMap.mClampMaxLine - 1 so that
+ // the returned range has a HypotheticalEnd <= aNameMap.mClampMaxLine.
+ // http://dev.w3.org/csswg/css-grid/#overlarge-grids
+ r.second = std::min(r.second, aNameMap.mClampMaxLine - 1);
+ } else {
+ // Clamp the lines to be within our limits, per
+ // https://www.w3.org/TR/css-grid-2/#overlarge-grids
+ // Note that our limits here might come from the [kMinLine, kMaxLine]
+ // extremes; or, they might just be the bounds of a subgrid's explicit
+ // grid. We use the same clamping approach either way, per
+ // https://www.w3.org/TR/css-grid-2/#subgrid-implicit ("using the same
+ // procedure as for clamping placement in an overly-large grid").
+ //
+ // Note that these two clamped() assignments might collapse our range to
+ // have both edges pointing at the same line (spanning 0 tracks); this
+ // might happen here if e.g. r.first were mClampMaxLine, and r.second gets
+ // clamped from some higher number down to mClampMaxLine. We'll handle this
+ // by shifting the inner line (r.first in this hypothetical) inwards by 1,
+ // in the #grid-placement-errors section; that achieves the outcome of
+ // the #overlarge-grids clamping spec text that says "its span must be
+ // truncated to 1" when clamping an item that was completely outside the
+ // limits.
+ r.first = clamped(r.first, aNameMap.mClampMinLine, aNameMap.mClampMaxLine);
+ r.second =
+ clamped(r.second, aNameMap.mClampMinLine, aNameMap.mClampMaxLine);
+
+ // Handle grid placement errors.
+ // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
+ if (r.first > r.second) {
+ std::swap(r.first, r.second);
+ } else if (r.first == r.second) {
+ // (This is #grid-placement-errors fixup, but it's also where we ensure
+ // that any #overlarge-grids fixup that we did above will end up
+ // truncating the range to a span of 1 rather than 0 -- i.e. sliding
+ // inwards if needed.)
+ if (MOZ_UNLIKELY(r.first == aNameMap.mClampMaxLine)) {
+ r.first = aNameMap.mClampMaxLine - 1;
+ }
+ r.second = r.first + 1;
+ }
+ }
+ return LineRange(r.first, r.second);
+}
+
+nsGridContainerFrame::GridArea nsGridContainerFrame::Grid::PlaceDefinite(
+ nsIFrame* aChild, const LineNameMap& aColLineNameMap,
+ const LineNameMap& aRowLineNameMap, const nsStylePosition* aStyle) {
+ const nsStylePosition* itemStyle = aChild->StylePosition();
+ return GridArea(
+ ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
+ aColLineNameMap, eLogicalAxisInline, mExplicitGridColEnd,
+ aStyle),
+ ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
+ aRowLineNameMap, eLogicalAxisBlock, mExplicitGridRowEnd,
+ aStyle));
+}
+
+nsGridContainerFrame::LineRange
+nsGridContainerFrame::Grid::ResolveAbsPosLineRange(
+ const StyleGridLine& aStart, const StyleGridLine& aEnd,
+ const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd,
+ int32_t aGridStart, int32_t aGridEnd, const nsStylePosition* aStyle) {
+ if (aStart.IsAuto()) {
+ if (aEnd.IsAuto()) {
+ return LineRange(kAutoLine, kAutoLine);
+ }
+ uint32_t from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0;
+ int32_t end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap,
+ MakeLogicalSide(aAxis, eLogicalEdgeEnd),
+ aExplicitGridEnd, aStyle);
+ if (aEnd.is_span) {
+ ++end;
+ }
+ // A line outside the existing grid is treated as 'auto' for abs.pos (10.1).
+ end = AutoIfOutside(end, aGridStart, aGridEnd);
+ return LineRange(kAutoLine, end);
+ }
+
+ if (aEnd.IsAuto()) {
+ uint32_t from = aStart.line_num < 0 ? aExplicitGridEnd + 1 : 0;
+ int32_t start = ResolveLine(aStart, aStart.line_num, from, aNameMap,
+ MakeLogicalSide(aAxis, eLogicalEdgeStart),
+ aExplicitGridEnd, aStyle);
+ if (aStart.is_span) {
+ start = std::max(aGridEnd - start, aGridStart);
+ }
+ start = AutoIfOutside(start, aGridStart, aGridEnd);
+ return LineRange(start, kAutoLine);
+ }
+
+ LineRange r =
+ ResolveLineRange(aStart, aEnd, aNameMap, aAxis, aExplicitGridEnd, aStyle);
+ if (r.IsAuto()) {
+ MOZ_ASSERT(aStart.is_span && aEnd.is_span,
+ "span / span is the only case "
+ "leading to IsAuto here -- we dealt with the other cases above");
+ // The second span was ignored per 9.2.1. For abs.pos., 10.1 says that this
+ // case should result in "auto / auto" unlike normal flow grid items.
+ return LineRange(kAutoLine, kAutoLine);
+ }
+
+ return LineRange(AutoIfOutside(r.mUntranslatedStart, aGridStart, aGridEnd),
+ AutoIfOutside(r.mUntranslatedEnd, aGridStart, aGridEnd));
+}
+
+nsGridContainerFrame::GridArea nsGridContainerFrame::Grid::PlaceAbsPos(
+ nsIFrame* aChild, const LineNameMap& aColLineNameMap,
+ const LineNameMap& aRowLineNameMap, const nsStylePosition* aStyle) {
+ const nsStylePosition* itemStyle = aChild->StylePosition();
+ int32_t gridColStart = 1 - mExplicitGridOffsetCol;
+ int32_t gridRowStart = 1 - mExplicitGridOffsetRow;
+ return GridArea(ResolveAbsPosLineRange(
+ itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
+ aColLineNameMap, eLogicalAxisInline, mExplicitGridColEnd,
+ gridColStart, mGridColEnd, aStyle),
+ ResolveAbsPosLineRange(
+ itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
+ aRowLineNameMap, eLogicalAxisBlock, mExplicitGridRowEnd,
+ gridRowStart, mGridRowEnd, aStyle));
+}
+
+uint32_t nsGridContainerFrame::Grid::FindAutoCol(uint32_t aStartCol,
+ uint32_t aLockedRow,
+ const GridArea* aArea) const {
+ const uint32_t extent = aArea->mCols.Extent();
+ const uint32_t iStart = aLockedRow;
+ const uint32_t iEnd = iStart + aArea->mRows.Extent();
+ uint32_t candidate = aStartCol;
+ for (uint32_t i = iStart; i < iEnd;) {
+ if (i >= mCellMap.mCells.Length()) {
+ break;
+ }
+ const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
+ const uint32_t len = cellsInRow.Length();
+ const uint32_t lastCandidate = candidate;
+ // Find the first gap in the current row that's at least 'extent' wide.
+ // ('gap' tracks how wide the current column gap is.)
+ for (uint32_t j = candidate, gap = 0; j < len && gap < extent; ++j) {
+ if (!cellsInRow[j].mIsOccupied) {
+ ++gap;
+ continue;
+ }
+ candidate = j + 1;
+ gap = 0;
+ }
+ if (lastCandidate < candidate && i != iStart) {
+ // Couldn't fit 'extent' tracks at 'lastCandidate' here so we must
+ // restart from the beginning with the new 'candidate'.
+ i = iStart;
+ } else {
+ ++i;
+ }
+ }
+ return candidate;
+}
+
+void nsGridContainerFrame::Grid::PlaceAutoCol(uint32_t aStartCol,
+ GridArea* aArea,
+ uint32_t aClampMaxColLine) const {
+ MOZ_ASSERT(aArea->mRows.IsDefinite() && aArea->mCols.IsAuto());
+ uint32_t col = FindAutoCol(aStartCol, aArea->mRows.mStart, aArea);
+ aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine);
+ MOZ_ASSERT(aArea->IsDefinite());
+}
+
+uint32_t nsGridContainerFrame::Grid::FindAutoRow(uint32_t aLockedCol,
+ uint32_t aStartRow,
+ const GridArea* aArea) const {
+ const uint32_t extent = aArea->mRows.Extent();
+ const uint32_t jStart = aLockedCol;
+ const uint32_t jEnd = jStart + aArea->mCols.Extent();
+ const uint32_t iEnd = mCellMap.mCells.Length();
+ uint32_t candidate = aStartRow;
+ // Find the first gap in the rows that's at least 'extent' tall.
+ // ('gap' tracks how tall the current row gap is.)
+ for (uint32_t i = candidate, gap = 0; i < iEnd && gap < extent; ++i) {
+ ++gap; // tentative, but we may reset it below if a column is occupied
+ const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
+ const uint32_t clampedJEnd = std::min<uint32_t>(jEnd, cellsInRow.Length());
+ // Check if the current row is unoccupied from jStart to jEnd.
+ for (uint32_t j = jStart; j < clampedJEnd; ++j) {
+ if (cellsInRow[j].mIsOccupied) {
+ // Couldn't fit 'extent' rows at 'candidate' here; we hit something
+ // at row 'i'. So, try the row after 'i' as our next candidate.
+ candidate = i + 1;
+ gap = 0;
+ break;
+ }
+ }
+ }
+ return candidate;
+}
+
+void nsGridContainerFrame::Grid::PlaceAutoRow(uint32_t aStartRow,
+ GridArea* aArea,
+ uint32_t aClampMaxRowLine) const {
+ MOZ_ASSERT(aArea->mCols.IsDefinite() && aArea->mRows.IsAuto());
+ uint32_t row = FindAutoRow(aArea->mCols.mStart, aStartRow, aArea);
+ aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine);
+ MOZ_ASSERT(aArea->IsDefinite());
+}
+
+void nsGridContainerFrame::Grid::PlaceAutoAutoInRowOrder(
+ uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea,
+ uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) const {
+ MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
+ const uint32_t colExtent = aArea->mCols.Extent();
+ const uint32_t gridRowEnd = mGridRowEnd;
+ const uint32_t gridColEnd = mGridColEnd;
+ uint32_t col = aStartCol;
+ uint32_t row = aStartRow;
+ for (; row < gridRowEnd; ++row) {
+ col = FindAutoCol(col, row, aArea);
+ if (col + colExtent <= gridColEnd) {
+ break;
+ }
+ col = 0;
+ }
+ MOZ_ASSERT(row < gridRowEnd || col == 0,
+ "expected column 0 for placing in a new row");
+ aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine);
+ aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine);
+ MOZ_ASSERT(aArea->IsDefinite());
+}
+
+void nsGridContainerFrame::Grid::PlaceAutoAutoInColOrder(
+ uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea,
+ uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) const {
+ MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
+ const uint32_t rowExtent = aArea->mRows.Extent();
+ const uint32_t gridRowEnd = mGridRowEnd;
+ const uint32_t gridColEnd = mGridColEnd;
+ uint32_t col = aStartCol;
+ uint32_t row = aStartRow;
+ for (; col < gridColEnd; ++col) {
+ row = FindAutoRow(col, row, aArea);
+ if (row + rowExtent <= gridRowEnd) {
+ break;
+ }
+ row = 0;
+ }
+ MOZ_ASSERT(col < gridColEnd || row == 0,
+ "expected row 0 for placing in a new column");
+ aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine);
+ aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine);
+ MOZ_ASSERT(aArea->IsDefinite());
+}
+
+template <typename IsEmptyFuncT>
+Maybe<nsTArray<uint32_t>>
+nsGridContainerFrame::Grid::CalculateAdjustForAutoFitElements(
+ uint32_t* const aOutNumEmptyLines, TrackSizingFunctions& aSizingFunctions,
+ uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc) {
+ Maybe<nsTArray<uint32_t>> trackAdjust;
+ uint32_t& numEmptyLines = *aOutNumEmptyLines;
+ numEmptyLines = 0;
+ if (aSizingFunctions.NumRepeatTracks() > 0) {
+ MOZ_ASSERT(aSizingFunctions.mHasRepeatAuto);
+ // Since this loop is concerned with just the repeat tracks, we
+ // iterate from 0..NumRepeatTracks() which is the natural range of
+ // mRemoveRepeatTracks. This means we have to add
+ // (mExplicitGridOffset + mRepeatAutoStart) to get a zero-based
+ // index for arrays like mCellMap/aIsEmptyFunc and trackAdjust. We'll then
+ // fill out the trackAdjust array for all the remaining lines.
+ const uint32_t repeatStart = (aSizingFunctions.mExplicitGridOffset +
+ aSizingFunctions.mRepeatAutoStart);
+ const uint32_t numRepeats = aSizingFunctions.NumRepeatTracks();
+ for (uint32_t i = 0; i < numRepeats; ++i) {
+ if (numEmptyLines) {
+ MOZ_ASSERT(trackAdjust.isSome());
+ (*trackAdjust)[repeatStart + i] = numEmptyLines;
+ }
+ if (aIsEmptyFunc(repeatStart + i)) {
+ ++numEmptyLines;
+ if (trackAdjust.isNothing()) {
+ trackAdjust.emplace(aNumGridLines);
+ trackAdjust->SetLength(aNumGridLines);
+ PodZero(trackAdjust->Elements(), trackAdjust->Length());
+ }
+
+ aSizingFunctions.mRemovedRepeatTracks[i] = true;
+ }
+ }
+ // Fill out the trackAdjust array for all the tracks after the repeats.
+ if (numEmptyLines) {
+ for (uint32_t line = repeatStart + numRepeats; line < aNumGridLines;
+ ++line) {
+ (*trackAdjust)[line] = numEmptyLines;
+ }
+ }
+ }
+
+ return trackAdjust;
+}
+
+void nsGridContainerFrame::Grid::SubgridPlaceGridItems(
+ GridReflowInput& aParentState, Grid* aParentGrid,
+ const GridItemInfo& aGridItem) {
+ MOZ_ASSERT(aGridItem.mArea.IsDefinite() ||
+ aGridItem.mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "the subgrid's lines should be resolved by now");
+ if (aGridItem.IsSubgrid(eLogicalAxisInline)) {
+ aParentState.mFrame->AddStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM);
+ }
+ if (aGridItem.IsSubgrid(eLogicalAxisBlock)) {
+ aParentState.mFrame->AddStateBits(NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
+ }
+ auto* childGrid = aGridItem.SubgridFrame();
+ const auto* pos = childGrid->StylePosition();
+ childGrid->NormalizeChildLists();
+ GridReflowInput state(childGrid, aParentState.mRenderingContext);
+ childGrid->InitImplicitNamedAreas(pos);
+
+ const bool isOrthogonal = aParentState.mWM.IsOrthogonalTo(state.mWM);
+ // Record the subgrid's GridArea in a frame property.
+ auto* subgrid = childGrid->GetProperty(Subgrid::Prop());
+ if (!subgrid) {
+ subgrid = new Subgrid(aGridItem.mArea, isOrthogonal, aParentState.mWM);
+ childGrid->SetProperty(Subgrid::Prop(), subgrid);
+ } else {
+ subgrid->mArea = aGridItem.mArea;
+ subgrid->mIsOrthogonal = isOrthogonal;
+ subgrid->mGridItems.Clear();
+ subgrid->mAbsPosItems.Clear();
+ }
+
+ // Abs.pos. subgrids may have kAutoLine in their area. Map those to the edge
+ // line in the parent's grid (zero-based line numbers).
+ if (MOZ_UNLIKELY(subgrid->mArea.mCols.mStart == kAutoLine)) {
+ subgrid->mArea.mCols.mStart = 0;
+ }
+ if (MOZ_UNLIKELY(subgrid->mArea.mCols.mEnd == kAutoLine)) {
+ subgrid->mArea.mCols.mEnd = aParentGrid->mGridColEnd - 1;
+ }
+ if (MOZ_UNLIKELY(subgrid->mArea.mRows.mStart == kAutoLine)) {
+ subgrid->mArea.mRows.mStart = 0;
+ }
+ if (MOZ_UNLIKELY(subgrid->mArea.mRows.mEnd == kAutoLine)) {
+ subgrid->mArea.mRows.mEnd = aParentGrid->mGridRowEnd - 1;
+ }
+
+ MOZ_ASSERT((subgrid->mArea.mCols.Extent() > 0 &&
+ subgrid->mArea.mRows.Extent() > 0) ||
+ state.mGridItems.IsEmpty(),
+ "subgrid needs at least one track for its items");
+
+ // The min/sz/max sizes are the input to the "repeat-to-fill" algorithm:
+ // https://drafts.csswg.org/css-grid/#auto-repeat
+ // They're only used for auto-repeat in a non-subgridded axis so we skip
+ // computing them otherwise.
+ RepeatTrackSizingInput repeatSizing(state.mWM);
+ if (!childGrid->IsColSubgrid() && state.mColFunctions.mHasRepeatAuto) {
+ repeatSizing.InitFromStyle(eLogicalAxisInline, state.mWM,
+ state.mFrame->Style());
+ }
+ if (!childGrid->IsRowSubgrid() && state.mRowFunctions.mHasRepeatAuto) {
+ repeatSizing.InitFromStyle(eLogicalAxisBlock, state.mWM,
+ state.mFrame->Style());
+ }
+
+ PlaceGridItems(state, repeatSizing);
+
+ subgrid->mGridItems = std::move(state.mGridItems);
+ subgrid->mAbsPosItems = std::move(state.mAbsPosItems);
+ subgrid->mGridColEnd = mGridColEnd;
+ subgrid->mGridRowEnd = mGridRowEnd;
+}
+
+void nsGridContainerFrame::Grid::PlaceGridItems(
+ GridReflowInput& aState, const RepeatTrackSizingInput& aSizes) {
+ MOZ_ASSERT(mCellMap.mCells.IsEmpty(), "unexpected entries in cell map");
+
+ mAreas = aState.mFrame->GetImplicitNamedAreas();
+
+ if (aState.mFrame->HasSubgridItems() || aState.mFrame->IsSubgrid()) {
+ if (auto* uts = aState.mFrame->GetUsedTrackSizes()) {
+ uts->mCanResolveLineRangeSize = {false, false};
+ uts->mSizes[eLogicalAxisInline].ClearAndRetainStorage();
+ uts->mSizes[eLogicalAxisBlock].ClearAndRetainStorage();
+ }
+ }
+
+ // SubgridPlaceGridItems will set these if we find any subgrid items.
+ aState.mFrame->RemoveStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM |
+ NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
+
+ // http://dev.w3.org/csswg/css-grid/#grid-definition
+ // Initialize the end lines of the Explicit Grid (mExplicitGridCol[Row]End).
+ // This is determined by the larger of the number of rows/columns defined
+ // by 'grid-template-areas' and the 'grid-template-rows'/'-columns', plus one.
+ // Also initialize the Implicit Grid (mGridCol[Row]End) to the same values.
+ // Note that this is for a grid with a 1,1 origin. We'll change that
+ // to a 0,0 based grid after placing definite lines.
+ const nsStylePosition* const gridStyle = aState.mGridStyle;
+ const auto* areas = gridStyle->mGridTemplateAreas.IsNone()
+ ? nullptr
+ : &*gridStyle->mGridTemplateAreas.AsAreas();
+ const LineNameMap* parentLineNameMap = nullptr;
+ const LineRange* subgridRange = nullptr;
+ bool subgridAxisIsSameDirection = true;
+ if (!aState.mFrame->IsColSubgrid()) {
+ aState.mColFunctions.InitRepeatTracks(
+ gridStyle->mColumnGap, aSizes.mMin.ISize(aState.mWM),
+ aSizes.mSize.ISize(aState.mWM), aSizes.mMax.ISize(aState.mWM));
+ uint32_t areaCols = areas ? areas->width + 1 : 1;
+ mExplicitGridColEnd = aState.mColFunctions.ComputeExplicitGridEnd(areaCols);
+ } else {
+ const auto* subgrid = aState.mFrame->GetProperty(Subgrid::Prop());
+ subgridRange = &subgrid->SubgridCols();
+ uint32_t extent = subgridRange->Extent();
+ mExplicitGridColEnd = extent + 1; // the grid is 1-based at this point
+ parentLineNameMap =
+ ParentLineMapForAxis(subgrid->mIsOrthogonal, eLogicalAxisInline);
+ auto parentWM =
+ aState.mFrame->ParentGridContainerForSubgrid()->GetWritingMode();
+ subgridAxisIsSameDirection =
+ aState.mWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, parentWM);
+ }
+ mGridColEnd = mExplicitGridColEnd;
+ LineNameMap colLineNameMap(gridStyle, mAreas, aState.mColFunctions,
+ parentLineNameMap, subgridRange,
+ subgridAxisIsSameDirection);
+
+ if (!aState.mFrame->IsRowSubgrid()) {
+ const Maybe<nscoord> containBSize = aState.mFrame->ContainIntrinsicBSize();
+ const nscoord repeatTrackSizingBSize = [&] {
+ // This clamping only applies to auto sizes.
+ if (containBSize &&
+ aSizes.mSize.BSize(aState.mWM) == NS_UNCONSTRAINEDSIZE) {
+ return NS_CSS_MINMAX(*containBSize, aSizes.mMin.BSize(aState.mWM),
+ aSizes.mMax.BSize(aState.mWM));
+ }
+ return aSizes.mSize.BSize(aState.mWM);
+ }();
+ aState.mRowFunctions.InitRepeatTracks(
+ gridStyle->mRowGap, aSizes.mMin.BSize(aState.mWM),
+ repeatTrackSizingBSize, aSizes.mMax.BSize(aState.mWM));
+ uint32_t areaRows = areas ? areas->strings.Length() + 1 : 1;
+ mExplicitGridRowEnd = aState.mRowFunctions.ComputeExplicitGridEnd(areaRows);
+ parentLineNameMap = nullptr;
+ subgridRange = nullptr;
+ } else {
+ const auto* subgrid = aState.mFrame->GetProperty(Subgrid::Prop());
+ subgridRange = &subgrid->SubgridRows();
+ uint32_t extent = subgridRange->Extent();
+ mExplicitGridRowEnd = extent + 1; // the grid is 1-based at this point
+ parentLineNameMap =
+ ParentLineMapForAxis(subgrid->mIsOrthogonal, eLogicalAxisBlock);
+ auto parentWM =
+ aState.mFrame->ParentGridContainerForSubgrid()->GetWritingMode();
+ subgridAxisIsSameDirection =
+ aState.mWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, parentWM);
+ }
+ mGridRowEnd = mExplicitGridRowEnd;
+ LineNameMap rowLineNameMap(gridStyle, mAreas, aState.mRowFunctions,
+ parentLineNameMap, subgridRange,
+ subgridAxisIsSameDirection);
+
+ const bool isSubgridOrItemInSubgrid =
+ aState.mFrame->IsSubgrid() || !!mParentGrid;
+ auto SetSubgridChildEdgeBits =
+ [this, isSubgridOrItemInSubgrid](GridItemInfo& aItem) -> void {
+ if (isSubgridOrItemInSubgrid) {
+ const auto& area = aItem.mArea;
+ if (area.mCols.mStart == 0) {
+ aItem.mState[eLogicalAxisInline] |= ItemState::eStartEdge;
+ }
+ if (area.mCols.mEnd == mGridColEnd) {
+ aItem.mState[eLogicalAxisInline] |= ItemState::eEndEdge;
+ }
+ if (area.mRows.mStart == 0) {
+ aItem.mState[eLogicalAxisBlock] |= ItemState::eStartEdge;
+ }
+ if (area.mRows.mEnd == mGridRowEnd) {
+ aItem.mState[eLogicalAxisBlock] |= ItemState::eEndEdge;
+ }
+ }
+ };
+
+ SetLineMaps(&colLineNameMap, &rowLineNameMap);
+
+ // http://dev.w3.org/csswg/css-grid/#line-placement
+ // Resolve definite positions per spec chap 9.2.
+ int32_t minCol = 1;
+ int32_t minRow = 1;
+ aState.mGridItems.ClearAndRetainStorage();
+ aState.mIter.Reset();
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ nsIFrame* child = *aState.mIter;
+ GridItemInfo* info = aState.mGridItems.AppendElement(GridItemInfo(
+ child,
+ PlaceDefinite(child, colLineNameMap, rowLineNameMap, gridStyle)));
+ MOZ_ASSERT(aState.mIter.ItemIndex() == aState.mGridItems.Length() - 1,
+ "ItemIndex() is broken");
+ GridArea& area = info->mArea;
+ if (area.mCols.IsDefinite()) {
+ minCol = std::min(minCol, area.mCols.mUntranslatedStart);
+ }
+ if (area.mRows.IsDefinite()) {
+ minRow = std::min(minRow, area.mRows.mUntranslatedStart);
+ }
+ }
+
+ // Translate the whole grid so that the top-/left-most area is at 0,0.
+ mExplicitGridOffsetCol = 1 - minCol; // minCol/Row is always <= 1, see above
+ mExplicitGridOffsetRow = 1 - minRow;
+ aState.mColFunctions.mExplicitGridOffset = mExplicitGridOffsetCol;
+ aState.mRowFunctions.mExplicitGridOffset = mExplicitGridOffsetRow;
+ const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
+ const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
+ const bool isRowMasonry = aState.mFrame->IsMasonry(eLogicalAxisBlock);
+ const bool isColMasonry = aState.mFrame->IsMasonry(eLogicalAxisInline);
+ const bool isMasonry = isColMasonry || isRowMasonry;
+ mGridColEnd += offsetToColZero;
+ mGridRowEnd += offsetToRowZero;
+ const uint32_t gridAxisTrackCount = isRowMasonry ? mGridColEnd : mGridRowEnd;
+ aState.mIter.Reset();
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
+ GridArea& area = item.mArea;
+ if (area.mCols.IsDefinite()) {
+ area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
+ area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
+ }
+ if (area.mRows.IsDefinite()) {
+ area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
+ area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
+ }
+ if (area.IsDefinite()) {
+ if (isMasonry) {
+ item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
+ }
+ if (item.IsSubgrid()) {
+ Grid grid(this);
+ grid.SubgridPlaceGridItems(aState, this, item);
+ }
+ mCellMap.Fill(area);
+ InflateGridFor(area);
+ SetSubgridChildEdgeBits(item);
+ }
+ }
+
+ // http://dev.w3.org/csswg/css-grid/#auto-placement-algo
+ // Step 1, place 'auto' items that have one definite position -
+ // definite row (column) for grid-auto-flow:row (column).
+ auto flowStyle = gridStyle->mGridAutoFlow;
+ const bool isRowOrder =
+ isMasonry ? isRowMasonry : !!(flowStyle & StyleGridAutoFlow::ROW);
+ const bool isSparse = !(flowStyle & StyleGridAutoFlow::DENSE);
+ uint32_t clampMaxColLine = colLineNameMap.mClampMaxLine + offsetToColZero;
+ uint32_t clampMaxRowLine = rowLineNameMap.mClampMaxLine + offsetToRowZero;
+ // We need 1 cursor per row (or column) if placement is sparse.
+ {
+ Maybe<nsTHashMap<nsUint32HashKey, uint32_t>> cursors;
+ if (isSparse) {
+ cursors.emplace();
+ }
+ auto placeAutoMinorFunc =
+ isRowOrder ? &Grid::PlaceAutoCol : &Grid::PlaceAutoRow;
+ uint32_t clampMaxLine = isRowOrder ? clampMaxColLine : clampMaxRowLine;
+ aState.mIter.Reset();
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
+ GridArea& area = item.mArea;
+ LineRange& major = isRowOrder ? area.mRows : area.mCols;
+ LineRange& minor = isRowOrder ? area.mCols : area.mRows;
+ if (major.IsDefinite() && minor.IsAuto()) {
+ // Items with 'auto' in the minor dimension only.
+ const uint32_t cursor = isSparse ? cursors->Get(major.mStart) : 0;
+ (this->*placeAutoMinorFunc)(cursor, &area, clampMaxLine);
+ if (isMasonry) {
+ item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
+ }
+ if (item.IsSubgrid()) {
+ Grid grid(this);
+ grid.SubgridPlaceGridItems(aState, this, item);
+ }
+ mCellMap.Fill(area);
+ SetSubgridChildEdgeBits(item);
+ if (isSparse) {
+ cursors->InsertOrUpdate(major.mStart, minor.mEnd);
+ }
+ }
+ InflateGridFor(area); // Step 2, inflating for auto items too
+ }
+ }
+
+ // XXX NOTE possible spec issue.
+ // XXX It's unclear if the remaining major-dimension auto and
+ // XXX auto in both dimensions should use the same cursor or not,
+ // XXX https://www.w3.org/Bugs/Public/show_bug.cgi?id=16044
+ // XXX seems to indicate it shouldn't.
+ // XXX http://dev.w3.org/csswg/css-grid/#auto-placement-cursor
+ // XXX now says it should (but didn't in earlier versions)
+
+ // Step 3, place the remaining grid items
+ uint32_t cursorMajor = 0; // for 'dense' these two cursors will stay at 0,0
+ uint32_t cursorMinor = 0;
+ auto placeAutoMajorFunc =
+ isRowOrder ? &Grid::PlaceAutoRow : &Grid::PlaceAutoCol;
+ uint32_t clampMaxMajorLine = isRowOrder ? clampMaxRowLine : clampMaxColLine;
+ aState.mIter.Reset();
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
+ GridArea& area = item.mArea;
+ MOZ_ASSERT(*aState.mIter == item.mFrame,
+ "iterator out of sync with aState.mGridItems");
+ LineRange& major = isRowOrder ? area.mRows : area.mCols;
+ LineRange& minor = isRowOrder ? area.mCols : area.mRows;
+ if (major.IsAuto()) {
+ if (minor.IsDefinite()) {
+ // Items with 'auto' in the major dimension only.
+ if (isSparse) {
+ if (minor.mStart < cursorMinor) {
+ ++cursorMajor;
+ }
+ cursorMinor = minor.mStart;
+ }
+ (this->*placeAutoMajorFunc)(cursorMajor, &area, clampMaxMajorLine);
+ if (isSparse) {
+ cursorMajor = major.mStart;
+ }
+ } else {
+ // Items with 'auto' in both dimensions.
+ if (isRowOrder) {
+ PlaceAutoAutoInRowOrder(cursorMinor, cursorMajor, &area,
+ clampMaxColLine, clampMaxRowLine);
+ } else {
+ PlaceAutoAutoInColOrder(cursorMajor, cursorMinor, &area,
+ clampMaxColLine, clampMaxRowLine);
+ }
+ if (isSparse) {
+ cursorMajor = major.mStart;
+ cursorMinor = minor.mEnd;
+#ifdef DEBUG
+ uint32_t gridMajorEnd = isRowOrder ? mGridRowEnd : mGridColEnd;
+ uint32_t gridMinorEnd = isRowOrder ? mGridColEnd : mGridRowEnd;
+ MOZ_ASSERT(cursorMajor <= gridMajorEnd,
+ "we shouldn't need to place items further than 1 track "
+ "past the current end of the grid, in major dimension");
+ MOZ_ASSERT(cursorMinor <= gridMinorEnd,
+ "we shouldn't add implicit minor tracks for auto/auto");
+#endif
+ }
+ }
+ if (isMasonry) {
+ item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
+ }
+ if (item.IsSubgrid()) {
+ Grid grid(this);
+ grid.SubgridPlaceGridItems(aState, this, item);
+ }
+ mCellMap.Fill(area);
+ InflateGridFor(area);
+ SetSubgridChildEdgeBits(item);
+ // XXXmats it might be possible to optimize this a bit for masonry layout
+ // if this item was placed in the 2nd row && !isSparse, or the 1st row
+ // is full. Still gotta inflate the grid for all items though to make
+ // the grid large enough...
+ }
+ }
+
+ // Force all items into the 1st/2nd track and have span 1 in the masonry axis.
+ // (See comment on nsGridContainerFrame::MasonryLayout().)
+ if (isMasonry) {
+ auto masonryAxis = isRowMasonry ? eLogicalAxisBlock : eLogicalAxisInline;
+ aState.mIter.Reset();
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
+ auto& masonryRange = item.mArea.LineRangeForAxis(masonryAxis);
+ masonryRange.mStart = std::min(masonryRange.mStart, 1U);
+ masonryRange.mEnd = masonryRange.mStart + 1U;
+ }
+ }
+
+ if (aState.mFrame->IsAbsoluteContainer()) {
+ // 9.4 Absolutely-positioned Grid Items
+ // http://dev.w3.org/csswg/css-grid/#abspos-items
+ // We only resolve definite lines here; we'll align auto positions to the
+ // grid container later during reflow.
+ const nsFrameList& children =
+ aState.mFrame->GetChildList(aState.mFrame->GetAbsoluteListID());
+ const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
+ const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
+ // Untranslate the grid again temporarily while resolving abs.pos. lines.
+ AutoRestore<uint32_t> zeroOffsetGridColEnd(mGridColEnd);
+ AutoRestore<uint32_t> zeroOffsetGridRowEnd(mGridRowEnd);
+ mGridColEnd -= offsetToColZero;
+ mGridRowEnd -= offsetToRowZero;
+ aState.mAbsPosItems.ClearAndRetainStorage();
+ for (nsIFrame* child : children) {
+ GridItemInfo* info = aState.mAbsPosItems.AppendElement(GridItemInfo(
+ child,
+ PlaceAbsPos(child, colLineNameMap, rowLineNameMap, gridStyle)));
+ GridArea& area = info->mArea;
+ if (area.mCols.mUntranslatedStart != int32_t(kAutoLine)) {
+ area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
+ if (isColMasonry) {
+ // XXXmats clamp any non-auto line to 0 or 1. This is intended to
+ // allow authors to address the start/end of the masonry box.
+ // This is experimental at this point though and needs author feedback
+ // and spec work to sort out what is desired and how it should work.
+ // See https://github.com/w3c/csswg-drafts/issues/4650
+ area.mCols.mStart = std::min(area.mCols.mStart, 1U);
+ }
+ }
+ if (area.mCols.mUntranslatedEnd != int32_t(kAutoLine)) {
+ area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
+ if (isColMasonry) {
+ // ditto
+ area.mCols.mEnd = std::min(area.mCols.mEnd, 1U);
+ }
+ }
+ if (area.mRows.mUntranslatedStart != int32_t(kAutoLine)) {
+ area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
+ if (isRowMasonry) {
+ // ditto
+ area.mRows.mStart = std::min(area.mRows.mStart, 1U);
+ }
+ }
+ if (area.mRows.mUntranslatedEnd != int32_t(kAutoLine)) {
+ area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
+ if (isRowMasonry) {
+ // ditto
+ area.mRows.mEnd = std::min(area.mRows.mEnd, 1U);
+ }
+ }
+ if (isMasonry) {
+ info->MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
+ }
+
+ // An abs.pos. subgrid with placement auto/1 or -1/auto technically
+ // doesn't span any parent tracks. Inhibit subgridding in this case.
+ if (info->IsSubgrid(eLogicalAxisInline)) {
+ if (info->mArea.mCols.mStart == zeroOffsetGridColEnd.SavedValue() ||
+ info->mArea.mCols.mEnd == 0) {
+ info->InhibitSubgrid(aState.mFrame, eLogicalAxisInline);
+ }
+ }
+ if (info->IsSubgrid(eLogicalAxisBlock)) {
+ if (info->mArea.mRows.mStart == zeroOffsetGridRowEnd.SavedValue() ||
+ info->mArea.mRows.mEnd == 0) {
+ info->InhibitSubgrid(aState.mFrame, eLogicalAxisBlock);
+ }
+ }
+
+ if (info->IsSubgrid()) {
+ Grid grid(this);
+ grid.SubgridPlaceGridItems(aState, this, *info);
+ }
+ }
+ }
+
+ // Count empty 'auto-fit' tracks in the repeat() range.
+ // |colAdjust| will have a count for each line in the grid of how many
+ // tracks were empty between the start of the grid and that line.
+
+ Maybe<nsTArray<uint32_t>> colAdjust;
+ uint32_t numEmptyCols = 0;
+ if (aState.mColFunctions.mHasRepeatAuto &&
+ gridStyle->mGridTemplateColumns.GetRepeatAutoValue()->count.IsAutoFit()) {
+ const auto& cellMap = mCellMap;
+ colAdjust = CalculateAdjustForAutoFitElements(
+ &numEmptyCols, aState.mColFunctions, mGridColEnd + 1,
+ [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyCol(i); });
+ }
+
+ // Do similar work for the row tracks, with the same logic.
+ Maybe<nsTArray<uint32_t>> rowAdjust;
+ uint32_t numEmptyRows = 0;
+ if (aState.mRowFunctions.mHasRepeatAuto &&
+ gridStyle->mGridTemplateRows.GetRepeatAutoValue()->count.IsAutoFit()) {
+ const auto& cellMap = mCellMap;
+ rowAdjust = CalculateAdjustForAutoFitElements(
+ &numEmptyRows, aState.mRowFunctions, mGridRowEnd + 1,
+ [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyRow(i); });
+ }
+ MOZ_ASSERT((numEmptyCols > 0) == colAdjust.isSome());
+ MOZ_ASSERT((numEmptyRows > 0) == rowAdjust.isSome());
+ // Remove the empty 'auto-fit' tracks we found above, if any.
+ if (numEmptyCols || numEmptyRows) {
+ // Adjust the line numbers in the grid areas.
+ for (auto& item : aState.mGridItems) {
+ if (numEmptyCols) {
+ item.AdjustForRemovedTracks(eLogicalAxisInline, *colAdjust);
+ }
+ if (numEmptyRows) {
+ item.AdjustForRemovedTracks(eLogicalAxisBlock, *rowAdjust);
+ }
+ }
+ for (auto& item : aState.mAbsPosItems) {
+ if (numEmptyCols) {
+ item.AdjustForRemovedTracks(eLogicalAxisInline, *colAdjust);
+ }
+ if (numEmptyRows) {
+ item.AdjustForRemovedTracks(eLogicalAxisBlock, *rowAdjust);
+ }
+ }
+ // Adjust the grid size.
+ mGridColEnd -= numEmptyCols;
+ mExplicitGridColEnd -= numEmptyCols;
+ mGridRowEnd -= numEmptyRows;
+ mExplicitGridRowEnd -= numEmptyRows;
+ // Adjust the track mapping to unmap the removed tracks.
+ auto colRepeatCount = aState.mColFunctions.NumRepeatTracks();
+ aState.mColFunctions.SetNumRepeatTracks(colRepeatCount - numEmptyCols);
+ auto rowRepeatCount = aState.mRowFunctions.NumRepeatTracks();
+ aState.mRowFunctions.SetNumRepeatTracks(rowRepeatCount - numEmptyRows);
+ }
+
+ // Update the line boundaries of the implicit grid areas, if needed.
+ if (mAreas && aState.mFrame->HasAnyStateBits(NS_STATE_GRID_COMPUTED_INFO)) {
+ for (auto iter = mAreas->iter(); !iter.done(); iter.next()) {
+ auto& areaInfo = iter.get().value();
+
+ // Resolve the lines for the area. We use the name of the area as the
+ // name of the lines, knowing that the line placement algorithm will
+ // add the -start and -end suffixes as appropriate for layout.
+ StyleGridLine lineStartAndEnd;
+ lineStartAndEnd.ident._0 = areaInfo.name;
+
+ LineRange columnLines =
+ ResolveLineRange(lineStartAndEnd, lineStartAndEnd, colLineNameMap,
+ eLogicalAxisInline, mExplicitGridColEnd, gridStyle);
+
+ LineRange rowLines =
+ ResolveLineRange(lineStartAndEnd, lineStartAndEnd, rowLineNameMap,
+ eLogicalAxisBlock, mExplicitGridRowEnd, gridStyle);
+
+ // Put the resolved line indices back into the area structure.
+ areaInfo.columns.start = columnLines.mStart + mExplicitGridOffsetCol;
+ areaInfo.columns.end = columnLines.mEnd + mExplicitGridOffsetCol;
+ areaInfo.rows.start = rowLines.mStart + mExplicitGridOffsetRow;
+ areaInfo.rows.end = rowLines.mEnd + mExplicitGridOffsetRow;
+ }
+ }
+}
+
+void nsGridContainerFrame::Tracks::Initialize(
+ const TrackSizingFunctions& aFunctions,
+ const NonNegativeLengthPercentageOrNormal& aGridGap, uint32_t aNumTracks,
+ nscoord aContentBoxSize) {
+ mSizes.SetLength(aNumTracks);
+ PodZero(mSizes.Elements(), mSizes.Length());
+ for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
+ auto& sz = mSizes[i];
+ mStateUnion |= sz.Initialize(aContentBoxSize, aFunctions.SizingFor(i));
+ if (mIsMasonry) {
+ sz.mBase = aContentBoxSize;
+ sz.mLimit = aContentBoxSize;
+ }
+ }
+ mGridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aContentBoxSize);
+ mContentBoxSize = aContentBoxSize;
+}
+
+/**
+ * Reflow aChild in the given aAvailableSize.
+ */
+static nscoord MeasuringReflow(nsIFrame* aChild,
+ const ReflowInput* aReflowInput, gfxContext* aRC,
+ const LogicalSize& aAvailableSize,
+ const LogicalSize& aCBSize,
+ nscoord aIMinSizeClamp = NS_MAXSIZE,
+ nscoord aBMinSizeClamp = NS_MAXSIZE) {
+ MOZ_ASSERT(aChild->IsGridItem(), "aChild should be a grid item!");
+ auto* parent = static_cast<nsGridContainerFrame*>(aChild->GetParent());
+ nsPresContext* pc = aChild->PresContext();
+ Maybe<ReflowInput> dummyParentState;
+ const ReflowInput* rs = aReflowInput;
+ if (!aReflowInput) {
+ MOZ_ASSERT(!parent->HasAnyStateBits(NS_FRAME_IN_REFLOW));
+ dummyParentState.emplace(
+ pc, parent, aRC,
+ LogicalSize(parent->GetWritingMode(), 0, NS_UNCONSTRAINEDSIZE),
+ ReflowInput::InitFlag::DummyParentReflowInput);
+ rs = dummyParentState.ptr();
+ }
+#ifdef DEBUG
+ // This will suppress various ABSURD_SIZE warnings for this reflow.
+ parent->SetProperty(nsContainerFrame::DebugReflowingWithInfiniteISize(),
+ true);
+#endif
+ auto wm = aChild->GetWritingMode();
+ ComputeSizeFlags csFlags = ComputeSizeFlag::IsGridMeasuringReflow;
+ // Shrink-wrap grid items that will be aligned (rather than stretched) in
+ // their own inline axis.
+ if (!parent->GridItemShouldStretch(aChild, eLogicalAxisInline)) {
+ csFlags += ComputeSizeFlag::ShrinkWrap;
+ }
+ if (aAvailableSize.ISize(wm) == INFINITE_ISIZE_COORD) {
+ csFlags += ComputeSizeFlag::ShrinkWrap;
+ }
+ if (aIMinSizeClamp != NS_MAXSIZE) {
+ csFlags += ComputeSizeFlag::IClampMarginBoxMinSize;
+ }
+ if (aBMinSizeClamp != NS_MAXSIZE) {
+ csFlags += ComputeSizeFlag::BClampMarginBoxMinSize;
+ aChild->SetProperty(nsIFrame::BClampMarginBoxMinSizeProperty(),
+ aBMinSizeClamp);
+ } else {
+ aChild->RemoveProperty(nsIFrame::BClampMarginBoxMinSizeProperty());
+ }
+ ReflowInput childRI(pc, *rs, aChild, aAvailableSize, Some(aCBSize), {}, {},
+ csFlags);
+
+ // FIXME (perf): It would be faster to do this only if the previous reflow of
+ // the child was not a measuring reflow, and only if the child does some of
+ // the things that are affected by ComputeSizeFlag::IsGridMeasuringReflow.
+ childRI.SetBResize(true);
+ // Not 100% sure this is needed, but be conservative for now:
+ childRI.mFlags.mIsBResizeForPercentages = true;
+
+ ReflowOutput childSize(childRI);
+ nsReflowStatus childStatus;
+ const nsIFrame::ReflowChildFlags flags =
+ nsIFrame::ReflowChildFlags::NoMoveFrame |
+ nsIFrame::ReflowChildFlags::NoSizeView |
+ nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild;
+
+ bool found;
+ GridItemCachedBAxisMeasurement cachedMeasurement =
+ aChild->GetProperty(GridItemCachedBAxisMeasurement::Prop(), &found);
+ if (found && cachedMeasurement.IsValidFor(aChild, aCBSize)) {
+ childSize.BSize(wm) = cachedMeasurement.BSize();
+ childSize.ISize(wm) = aChild->ISize(wm);
+ nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm,
+ LogicalPoint(wm), nsSize(), flags);
+ GRID_LOG(
+ "[perf] MeasuringReflow accepted cached value=%d, child=%p, "
+ "aCBSize.ISize=%d",
+ cachedMeasurement.BSize(), aChild,
+ aCBSize.ISize(aChild->GetWritingMode()));
+ return cachedMeasurement.BSize();
+ }
+
+ parent->ReflowChild(aChild, pc, childSize, childRI, wm, LogicalPoint(wm),
+ nsSize(), flags, childStatus);
+ nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm,
+ LogicalPoint(wm), nsSize(), flags);
+#ifdef DEBUG
+ parent->RemoveProperty(nsContainerFrame::DebugReflowingWithInfiniteISize());
+#endif
+ if (!found &&
+ GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, aCBSize)) {
+ GridItemCachedBAxisMeasurement cachedMeasurement(aChild, aCBSize,
+ childSize.BSize(wm));
+ aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(),
+ cachedMeasurement);
+ GRID_LOG(
+ "[perf] MeasuringReflow created new cached value=%d, child=%p, "
+ "aCBSize.ISize=%d",
+ cachedMeasurement.BSize(), aChild,
+ aCBSize.ISize(aChild->GetWritingMode()));
+ } else if (found) {
+ if (GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, aCBSize)) {
+ cachedMeasurement.Update(aChild, aCBSize, childSize.BSize(wm));
+ GRID_LOG(
+ "[perf] MeasuringReflow rejected but updated cached value=%d, "
+ "child=%p, aCBSize.ISize=%d",
+ cachedMeasurement.BSize(), aChild,
+ aCBSize.ISize(aChild->GetWritingMode()));
+ aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(),
+ cachedMeasurement);
+ } else {
+ aChild->RemoveProperty(GridItemCachedBAxisMeasurement::Prop());
+ GRID_LOG(
+ "[perf] MeasuringReflow rejected and removed cached value, "
+ "child=%p",
+ aChild);
+ }
+ }
+
+ return childSize.BSize(wm);
+}
+
+/**
+ * Reflow aChild in the given aAvailableSize, using aNewContentBoxSize as its
+ * computed size in aChildAxis.
+ */
+static void PostReflowStretchChild(
+ nsIFrame* aChild, const ReflowInput& aReflowInput,
+ const LogicalSize& aAvailableSize, const LogicalSize& aCBSize,
+ LogicalAxis aChildAxis, const nscoord aNewContentBoxSize,
+ nscoord aIMinSizeClamp = NS_MAXSIZE, nscoord aBMinSizeClamp = NS_MAXSIZE) {
+ nsPresContext* pc = aChild->PresContext();
+ ComputeSizeFlags csFlags;
+ if (aIMinSizeClamp != NS_MAXSIZE) {
+ csFlags += ComputeSizeFlag::IClampMarginBoxMinSize;
+ }
+ if (aBMinSizeClamp != NS_MAXSIZE) {
+ csFlags += ComputeSizeFlag::BClampMarginBoxMinSize;
+ aChild->SetProperty(nsIFrame::BClampMarginBoxMinSizeProperty(),
+ aBMinSizeClamp);
+ } else {
+ aChild->RemoveProperty(nsIFrame::BClampMarginBoxMinSizeProperty());
+ }
+ ReflowInput ri(pc, aReflowInput, aChild, aAvailableSize, Some(aCBSize), {},
+ {}, csFlags);
+ if (aChildAxis == eLogicalAxisBlock) {
+ ri.SetComputedBSize(ri.ApplyMinMaxBSize(aNewContentBoxSize));
+ } else {
+ ri.SetComputedISize(ri.ApplyMinMaxISize(aNewContentBoxSize));
+ }
+ ReflowOutput childSize(ri);
+ nsReflowStatus childStatus;
+ const nsIFrame::ReflowChildFlags flags =
+ nsIFrame::ReflowChildFlags::NoMoveFrame |
+ nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild;
+ auto wm = aChild->GetWritingMode();
+ nsContainerFrame* parent = aChild->GetParent();
+ parent->ReflowChild(aChild, pc, childSize, ri, wm, LogicalPoint(wm), nsSize(),
+ flags, childStatus);
+ nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &ri, wm,
+ LogicalPoint(wm), nsSize(), flags);
+}
+
+/**
+ * Return the accumulated margin+border+padding in aAxis for aFrame (a subgrid)
+ * and its ancestor subgrids.
+ */
+static LogicalMargin SubgridAccumulatedMarginBorderPadding(
+ nsIFrame* aFrame, const Subgrid* aSubgrid, WritingMode aResultWM,
+ LogicalAxis aAxis) {
+ MOZ_ASSERT(aFrame->IsGridContainerFrame());
+ auto* subgridFrame = static_cast<nsGridContainerFrame*>(aFrame);
+ LogicalMargin result(aSubgrid->mMarginBorderPadding);
+ auto* parent = subgridFrame->ParentGridContainerForSubgrid();
+ auto subgridCBWM = parent->GetWritingMode();
+ auto childRange = aSubgrid->mArea.LineRangeForAxis(aAxis);
+ bool skipStartSide = false;
+ bool skipEndSide = false;
+ auto axis = aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
+ // If aFrame's parent is also a subgrid, then add its MBP on the edges that
+ // are adjacent (i.e. start or end in the same track), recursively.
+ // ("parent" refers to the grid-frame we're currently adding MBP for,
+ // and "grandParent" its parent, as we walk up the chain.)
+ while (parent->IsSubgrid(axis)) {
+ auto* parentSubgrid = parent->GetProperty(Subgrid::Prop());
+ auto* grandParent = parent->ParentGridContainerForSubgrid();
+ auto parentCBWM = grandParent->GetWritingMode();
+ if (parentCBWM.IsOrthogonalTo(subgridCBWM)) {
+ axis = GetOrthogonalAxis(axis);
+ }
+ const auto& parentRange = parentSubgrid->mArea.LineRangeForAxis(axis);
+ bool sameDir = parentCBWM.ParallelAxisStartsOnSameSide(axis, subgridCBWM);
+ if (sameDir) {
+ skipStartSide |= childRange.mStart != 0;
+ skipEndSide |= childRange.mEnd != parentRange.Extent();
+ } else {
+ skipEndSide |= childRange.mStart != 0;
+ skipStartSide |= childRange.mEnd != parentRange.Extent();
+ }
+ if (skipStartSide && skipEndSide) {
+ break;
+ }
+ auto mbp =
+ parentSubgrid->mMarginBorderPadding.ConvertTo(subgridCBWM, parentCBWM);
+ if (skipStartSide) {
+ mbp.Start(aAxis, subgridCBWM) = nscoord(0);
+ }
+ if (skipEndSide) {
+ mbp.End(aAxis, subgridCBWM) = nscoord(0);
+ }
+ result += mbp;
+ parent = grandParent;
+ childRange = parentRange;
+ }
+ return result.ConvertTo(aResultWM, subgridCBWM);
+}
+
+/**
+ * Return the [min|max]-content contribution of aChild to its parent (i.e.
+ * the child's margin-box) in aAxis.
+ */
+static nscoord ContentContribution(
+ const GridItemInfo& aGridItem, const GridReflowInput& aState,
+ gfxContext* aRC, WritingMode aCBWM, LogicalAxis aAxis,
+ const Maybe<LogicalSize>& aPercentageBasis, IntrinsicISizeType aConstraint,
+ nscoord aMinSizeClamp = NS_MAXSIZE, uint32_t aFlags = 0) {
+ nsIFrame* child = aGridItem.mFrame;
+
+ nscoord extraMargin = 0;
+ nsGridContainerFrame::Subgrid* subgrid = nullptr;
+ if (child->GetParent() != aState.mFrame) {
+ // |child| is a subgrid descendant, so it contributes its subgrids'
+ // margin+border+padding for any edge tracks that it spans.
+ auto* subgridFrame = child->GetParent();
+ subgrid = subgridFrame->GetProperty(Subgrid::Prop());
+ const auto itemEdgeBits = aGridItem.mState[aAxis] & ItemState::eEdgeBits;
+ if (itemEdgeBits) {
+ LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding(
+ subgridFrame, subgrid, aCBWM, aAxis);
+ if (itemEdgeBits & ItemState::eStartEdge) {
+ extraMargin += mbp.Start(aAxis, aCBWM);
+ }
+ if (itemEdgeBits & ItemState::eEndEdge) {
+ extraMargin += mbp.End(aAxis, aCBWM);
+ }
+ }
+ // It also contributes (half of) the subgrid's gap on its edges (if any)
+ // subtracted by the non-subgrid ancestor grid container's gap.
+ // Note that this can also be negative since it's considered a margin.
+ if (itemEdgeBits != ItemState::eEdgeBits) {
+ auto subgridAxis = aCBWM.IsOrthogonalTo(subgridFrame->GetWritingMode())
+ ? GetOrthogonalAxis(aAxis)
+ : aAxis;
+ auto& gapStyle = subgridAxis == eLogicalAxisBlock
+ ? subgridFrame->StylePosition()->mRowGap
+ : subgridFrame->StylePosition()->mColumnGap;
+ if (!gapStyle.IsNormal()) {
+ auto subgridExtent = subgridAxis == eLogicalAxisBlock
+ ? subgrid->mGridRowEnd
+ : subgrid->mGridColEnd;
+ if (subgridExtent > 1) {
+ nscoord subgridGap =
+ nsLayoutUtils::ResolveGapToLength(gapStyle, NS_UNCONSTRAINEDSIZE);
+ auto& tracks =
+ aAxis == eLogicalAxisBlock ? aState.mRows : aState.mCols;
+ auto gapDelta = subgridGap - tracks.mGridGap;
+ if (!itemEdgeBits) {
+ extraMargin += gapDelta;
+ } else {
+ extraMargin += gapDelta / 2;
+ }
+ }
+ }
+ }
+ }
+
+ PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
+ nscoord size = nsLayoutUtils::IntrinsicForAxis(
+ axis, aRC, child, aConstraint, aPercentageBasis,
+ aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED, aMinSizeClamp);
+ auto childWM = child->GetWritingMode();
+ const bool isOrthogonal = childWM.IsOrthogonalTo(aCBWM);
+ auto childAxis = isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
+ if (size == NS_INTRINSIC_ISIZE_UNKNOWN && childAxis == eLogicalAxisBlock) {
+ // We need to reflow the child to find its BSize contribution.
+ // XXX this will give mostly correct results for now (until bug 1174569).
+ nscoord availISize = INFINITE_ISIZE_COORD;
+ nscoord availBSize = NS_UNCONSTRAINEDSIZE;
+ // The next two variables are MinSizeClamp values in the child's axes.
+ nscoord iMinSizeClamp = NS_MAXSIZE;
+ nscoord bMinSizeClamp = NS_MAXSIZE;
+ LogicalSize cbSize(childWM, 0, NS_UNCONSTRAINEDSIZE);
+ // Below, we try to resolve the child's grid-area size in its inline-axis
+ // to use as the CB/Available size in the MeasuringReflow that follows.
+ if (child->GetParent() != aState.mFrame) {
+ // This item is a child of a subgrid descendant.
+ auto* subgridFrame =
+ static_cast<nsGridContainerFrame*>(child->GetParent());
+ MOZ_ASSERT(subgridFrame->IsGridContainerFrame());
+ auto* uts = subgridFrame->GetProperty(UsedTrackSizes::Prop());
+ if (!uts) {
+ uts = new UsedTrackSizes();
+ subgridFrame->SetProperty(UsedTrackSizes::Prop(), uts);
+ }
+ // The grid-item's inline-axis as expressed in the subgrid's WM.
+ auto subgridAxis = childWM.IsOrthogonalTo(subgridFrame->GetWritingMode())
+ ? eLogicalAxisBlock
+ : eLogicalAxisInline;
+ uts->ResolveTrackSizesForAxis(subgridFrame, subgridAxis, *aRC);
+ if (uts->mCanResolveLineRangeSize[subgridAxis]) {
+ auto* subgrid =
+ subgridFrame->GetProperty(nsGridContainerFrame::Subgrid::Prop());
+ const GridItemInfo* originalItem = nullptr;
+ for (const auto& item : subgrid->mGridItems) {
+ if (item.mFrame == child) {
+ originalItem = &item;
+ break;
+ }
+ }
+ MOZ_ASSERT(originalItem, "huh?");
+ const auto& range = originalItem->mArea.LineRangeForAxis(subgridAxis);
+ nscoord pos, sz;
+ range.ToPositionAndLength(uts->mSizes[subgridAxis], &pos, &sz);
+ if (childWM.IsOrthogonalTo(subgridFrame->GetWritingMode())) {
+ availBSize = sz;
+ cbSize.BSize(childWM) = sz;
+ if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
+ bMinSizeClamp = sz;
+ }
+ } else {
+ availISize = sz;
+ cbSize.ISize(childWM) = sz;
+ if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
+ iMinSizeClamp = sz;
+ }
+ }
+ }
+ } else if (aState.mCols.mCanResolveLineRangeSize) {
+ nscoord sz = aState.mCols.ResolveSize(aGridItem.mArea.mCols);
+ if (isOrthogonal) {
+ availBSize = sz;
+ cbSize.BSize(childWM) = sz;
+ if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
+ bMinSizeClamp = sz;
+ }
+ } else {
+ availISize = sz;
+ cbSize.ISize(childWM) = sz;
+ if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
+ iMinSizeClamp = sz;
+ }
+ }
+ }
+ if (isOrthogonal == (aAxis == eLogicalAxisInline)) {
+ bMinSizeClamp = aMinSizeClamp;
+ } else {
+ iMinSizeClamp = aMinSizeClamp;
+ }
+ LogicalSize availableSize(childWM, availISize, availBSize);
+ size = ::MeasuringReflow(child, aState.mReflowInput, aRC, availableSize,
+ cbSize, iMinSizeClamp, bMinSizeClamp);
+ size += child->GetLogicalUsedMargin(childWM).BStartEnd(childWM);
+ nscoord overflow = size - aMinSizeClamp;
+ if (MOZ_UNLIKELY(overflow > 0)) {
+ nscoord contentSize = child->ContentBSize(childWM);
+ nscoord newContentSize = std::max(nscoord(0), contentSize - overflow);
+ // XXXmats deal with percentages better, see bug 1300369 comment 27.
+ size -= contentSize - newContentSize;
+ }
+ }
+ MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
+ "baseline offset should be non-negative at this point");
+ MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
+ aGridItem.mBaselineOffset[aAxis] == nscoord(0),
+ "baseline offset should be zero when not baseline-aligned");
+ size += aGridItem.mBaselineOffset[aAxis];
+ size += extraMargin;
+ return std::max(size, 0);
+}
+
+struct CachedIntrinsicSizes {
+ Maybe<nscoord> mMinSize;
+ Maybe<nscoord> mMinContentContribution;
+ Maybe<nscoord> mMaxContentContribution;
+
+ // The item's percentage basis for intrinsic sizing purposes.
+ Maybe<LogicalSize> mPercentageBasis;
+
+ // "if the grid item spans only grid tracks that have a fixed max track
+ // sizing function, its automatic minimum size in that dimension is
+ // further clamped to less than or equal to the size necessary to fit its
+ // margin box within the resulting grid area (flooring at zero)"
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ // This is the clamp value to use for that:
+ nscoord mMinSizeClamp = NS_MAXSIZE;
+};
+
+static nscoord MinContentContribution(const GridItemInfo& aGridItem,
+ const GridReflowInput& aState,
+ gfxContext* aRC, WritingMode aCBWM,
+ LogicalAxis aAxis,
+ CachedIntrinsicSizes* aCache) {
+ if (aCache->mMinContentContribution.isSome()) {
+ return aCache->mMinContentContribution.value();
+ }
+ if (aCache->mPercentageBasis.isNothing()) {
+ aCache->mPercentageBasis.emplace(
+ aState.PercentageBasisFor(aAxis, aGridItem));
+ }
+ nscoord s = ContentContribution(
+ aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis,
+ IntrinsicISizeType::MinISize, aCache->mMinSizeClamp);
+ aCache->mMinContentContribution.emplace(s);
+ return s;
+}
+
+static nscoord MaxContentContribution(const GridItemInfo& aGridItem,
+ const GridReflowInput& aState,
+ gfxContext* aRC, WritingMode aCBWM,
+ LogicalAxis aAxis,
+ CachedIntrinsicSizes* aCache) {
+ if (aCache->mMaxContentContribution.isSome()) {
+ return aCache->mMaxContentContribution.value();
+ }
+ if (aCache->mPercentageBasis.isNothing()) {
+ aCache->mPercentageBasis.emplace(
+ aState.PercentageBasisFor(aAxis, aGridItem));
+ }
+ nscoord s = ContentContribution(
+ aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis,
+ IntrinsicISizeType::PrefISize, aCache->mMinSizeClamp);
+ aCache->mMaxContentContribution.emplace(s);
+ return s;
+}
+
+// Computes the min-size contribution for a grid item, as defined at
+// https://drafts.csswg.org/css-grid/#min-size-contribution
+static nscoord MinSize(const GridItemInfo& aGridItem,
+ const GridReflowInput& aState, gfxContext* aRC,
+ WritingMode aCBWM, LogicalAxis aAxis,
+ CachedIntrinsicSizes* aCache) {
+ if (aCache->mMinSize.isSome()) {
+ return aCache->mMinSize.value();
+ }
+ nsIFrame* child = aGridItem.mFrame;
+ PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
+ const nsStylePosition* stylePos = child->StylePosition();
+ StyleSize sizeStyle =
+ axis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight;
+
+ auto ourInlineAxis = child->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ // max-content and min-content should behave as initial value in block axis.
+ // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
+ // for block size dimension on sizing properties (e.g. height), so we
+ // treat it as `auto`.
+ if (axis != ourInlineAxis && sizeStyle.BehavesLikeInitialValueOnBlockAxis()) {
+ sizeStyle = StyleSize::Auto();
+ }
+
+ if (!sizeStyle.IsAuto() && !sizeStyle.HasPercent()) {
+ nscoord s =
+ MinContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, aCache);
+ aCache->mMinSize.emplace(s);
+ return s;
+ }
+
+ if (aCache->mPercentageBasis.isNothing()) {
+ aCache->mPercentageBasis.emplace(
+ aState.PercentageBasisFor(aAxis, aGridItem));
+ }
+
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ // This calculates the min-content contribution from either a definite
+ // min-width (or min-height depending on aAxis), or the "specified /
+ // transferred size" for min-width:auto if overflow == visible (as min-width:0
+ // otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values
+ // (which results in always taking the "content size" part below).
+ MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
+ "baseline offset should be non-negative at this point");
+ MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
+ aGridItem.mBaselineOffset[aAxis] == nscoord(0),
+ "baseline offset should be zero when not baseline-aligned");
+ nscoord sz = aGridItem.mBaselineOffset[aAxis] +
+ nsLayoutUtils::MinSizeContributionForAxis(
+ axis, aRC, child, IntrinsicISizeType::MinISize,
+ *aCache->mPercentageBasis);
+ const StyleSize& style =
+ axis == eAxisHorizontal ? stylePos->mMinWidth : stylePos->mMinHeight;
+ // max-content and min-content should behave as initial value in block axis.
+ // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
+ // for block size dimension on sizing properties (e.g. height), so we
+ // treat it as `auto`.
+ const bool inInlineAxis = axis == ourInlineAxis;
+ const bool isAuto =
+ style.IsAuto() ||
+ (!inInlineAxis && style.BehavesLikeInitialValueOnBlockAxis());
+ if ((inInlineAxis && nsIFrame::ToExtremumLength(style)) ||
+ (isAuto && !child->StyleDisplay()->IsScrollableOverflow())) {
+ // Now calculate the "content size" part and return whichever is smaller.
+ MOZ_ASSERT(isAuto || sz == NS_UNCONSTRAINEDSIZE);
+ sz = std::min(sz, ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis,
+ aCache->mPercentageBasis,
+ IntrinsicISizeType::MinISize,
+ aCache->mMinSizeClamp,
+ nsLayoutUtils::MIN_INTRINSIC_ISIZE));
+ }
+ aCache->mMinSize.emplace(sz);
+ return sz;
+}
+
+void nsGridContainerFrame::Tracks::CalculateSizes(
+ GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
+ const TrackSizingFunctions& aFunctions, nscoord aContentBoxSize,
+ LineRange GridArea::*aRange, SizingConstraint aConstraint) {
+ nscoord percentageBasis = aContentBoxSize;
+ if (percentageBasis == NS_UNCONSTRAINEDSIZE) {
+ percentageBasis = 0;
+ }
+ InitializeItemBaselines(aState, aGridItems);
+ ResolveIntrinsicSize(aState, aGridItems, aFunctions, aRange, percentageBasis,
+ aConstraint);
+ if (aConstraint != SizingConstraint::MinContent) {
+ nscoord freeSpace = aContentBoxSize;
+ if (freeSpace != NS_UNCONSTRAINEDSIZE) {
+ freeSpace -= SumOfGridGaps();
+ }
+ DistributeFreeSpace(freeSpace);
+ StretchFlexibleTracks(aState, aGridItems, aFunctions, freeSpace);
+ }
+}
+
+TrackSize::StateBits nsGridContainerFrame::Tracks::StateBitsForRange(
+ const LineRange& aRange) const {
+ MOZ_ASSERT(!aRange.IsAuto(), "must have a definite range");
+ TrackSize::StateBits state = TrackSize::StateBits{0};
+ for (auto i : aRange.Range()) {
+ state |= mSizes[i].mState;
+ }
+ return state;
+}
+
+static void AddSubgridContribution(TrackSize& aSize,
+ nscoord aMarginBorderPadding) {
+ if (aSize.mState & TrackSize::eIntrinsicMinSizing) {
+ aSize.mBase = std::max(aSize.mBase, aMarginBorderPadding);
+ aSize.mLimit = std::max(aSize.mLimit, aSize.mBase);
+ }
+ // XXX maybe eFlexMaxSizing too?
+ // (once we implement https://github.com/w3c/csswg-drafts/issues/2177)
+ if (aSize.mState &
+ (TrackSize::eIntrinsicMaxSizing | TrackSize::eFitContent)) {
+ aSize.mLimit = std::max(aSize.mLimit, aMarginBorderPadding);
+ }
+}
+
+bool nsGridContainerFrame::Tracks::ResolveIntrinsicSizeForNonSpanningItems(
+ GridReflowInput& aState, const TrackSizingFunctions& aFunctions,
+ nscoord aPercentageBasis, SizingConstraint aConstraint,
+ const LineRange& aRange, const GridItemInfo& aGridItem) {
+ gfxContext* rc = &aState.mRenderingContext;
+ WritingMode wm = aState.mWM;
+ CachedIntrinsicSizes cache;
+ TrackSize& sz = mSizes[aRange.mStart];
+
+ // min sizing
+ if (sz.mState & TrackSize::eAutoMinSizing) {
+ nscoord s;
+ // Check if we need to apply "Automatic Minimum Size" and cache it.
+ if (aGridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) {
+ aGridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize;
+ // Clamp it if it's spanning a definite track max-sizing function.
+ if (TrackSize::IsDefiniteMaxSizing(sz.mState)) {
+ cache.mMinSizeClamp = aFunctions.MaxSizingFor(aRange.mStart)
+ .AsBreadth()
+ .Resolve(aPercentageBasis);
+ aGridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize;
+ }
+ if (aConstraint != SizingConstraint::MaxContent) {
+ s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
+ } else {
+ s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
+ }
+ } else {
+ s = MinSize(aGridItem, aState, rc, wm, mAxis, &cache);
+ }
+ sz.mBase = std::max(sz.mBase, s);
+ } else if (sz.mState & TrackSize::eMinContentMinSizing) {
+ auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
+ sz.mBase = std::max(sz.mBase, s);
+ } else if (sz.mState & TrackSize::eMaxContentMinSizing) {
+ auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
+ sz.mBase = std::max(sz.mBase, s);
+ }
+
+ // max sizing
+ if (sz.mState & TrackSize::eMinContentMaxSizing) {
+ auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
+ if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
+ sz.mLimit = s;
+ } else {
+ sz.mLimit = std::max(sz.mLimit, s);
+ }
+ } else if (sz.mState &
+ (TrackSize::eAutoMaxSizing | TrackSize::eMaxContentMaxSizing)) {
+ auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
+ if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
+ sz.mLimit = s;
+ } else {
+ sz.mLimit = std::max(sz.mLimit, s);
+ }
+ if (MOZ_UNLIKELY(sz.mState & TrackSize::eFitContent)) {
+ // Clamp mLimit to the fit-content() size, for §12.5.1.
+ nscoord fitContentClamp = aFunctions.SizingFor(aRange.mStart)
+ .AsFitContent()
+ .AsBreadth()
+ .Resolve(aPercentageBasis);
+ sz.mLimit = std::min(sz.mLimit, fitContentClamp);
+ }
+ }
+
+ if (sz.mLimit < sz.mBase) {
+ sz.mLimit = sz.mBase;
+ }
+
+ return sz.mState & TrackSize::eFlexMaxSizing;
+}
+
+void nsGridContainerFrame::Tracks::CalculateItemBaselines(
+ nsTArray<ItemBaselineData>& aBaselineItems,
+ BaselineSharingGroup aBaselineGroup) {
+ if (aBaselineItems.IsEmpty()) {
+ return;
+ }
+
+ // Sort the collected items on their baseline track.
+ std::sort(aBaselineItems.begin(), aBaselineItems.end(),
+ ItemBaselineData::IsBaselineTrackLessThan);
+
+ MOZ_ASSERT(mSizes.Length() > 0, "having an item implies at least one track");
+ const uint32_t lastTrack = mSizes.Length() - 1;
+ nscoord maxBaseline = 0;
+ nscoord maxDescent = 0;
+ uint32_t currentTrack = kAutoLine; // guaranteed to not match any item
+ uint32_t trackStartIndex = 0;
+ for (uint32_t i = 0, len = aBaselineItems.Length(); true; ++i) {
+ // Find the maximum baseline and descent in the current track.
+ if (i != len) {
+ const ItemBaselineData& item = aBaselineItems[i];
+ if (currentTrack == item.mBaselineTrack) {
+ maxBaseline = std::max(maxBaseline, item.mBaseline);
+ maxDescent = std::max(maxDescent, item.mSize - item.mBaseline);
+ continue;
+ }
+ }
+ // Iterate the current track again and update the baseline offsets making
+ // all items baseline-aligned within this group in this track.
+ for (uint32_t j = trackStartIndex; j < i; ++j) {
+ const ItemBaselineData& item = aBaselineItems[j];
+ item.mGridItem->mBaselineOffset[mAxis] = maxBaseline - item.mBaseline;
+ MOZ_ASSERT(item.mGridItem->mBaselineOffset[mAxis] >= 0);
+ }
+ if (i != 0) {
+ // Store the size of this baseline-aligned subtree.
+ mSizes[currentTrack].mBaselineSubtreeSize[aBaselineGroup] =
+ maxBaseline + maxDescent;
+ // Record the first(last) baseline for the first(last) track.
+ if (currentTrack == 0 && aBaselineGroup == BaselineSharingGroup::First) {
+ mBaseline[aBaselineGroup] = maxBaseline;
+ }
+ if (currentTrack == lastTrack &&
+ aBaselineGroup == BaselineSharingGroup::Last) {
+ mBaseline[aBaselineGroup] = maxBaseline;
+ }
+ }
+ if (i == len) {
+ break;
+ }
+ // Initialize data for the next track with baseline-aligned items.
+ const ItemBaselineData& item = aBaselineItems[i];
+ currentTrack = item.mBaselineTrack;
+ trackStartIndex = i;
+ maxBaseline = item.mBaseline;
+ maxDescent = item.mSize - item.mBaseline;
+ }
+}
+
+void nsGridContainerFrame::Tracks::InitializeItemBaselines(
+ GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems) {
+ MOZ_ASSERT(!mIsMasonry);
+ if (aState.mFrame->IsSubgrid(mAxis)) {
+ // A grid container's subgridded axis doesn't have a baseline.
+ return;
+ }
+
+ nsTArray<ItemBaselineData> firstBaselineItems;
+ nsTArray<ItemBaselineData> lastBaselineItems;
+ const WritingMode containerWM = aState.mWM;
+ ComputedStyle* containerStyle = aState.mFrame->Style();
+
+ for (GridItemInfo& gridItem : aGridItems) {
+ if (gridItem.IsSubgrid(mAxis)) {
+ // A subgrid itself is never baseline-aligned.
+ continue;
+ }
+
+ nsIFrame* child = gridItem.mFrame;
+ uint32_t baselineTrack = kAutoLine;
+ auto state = ItemState(0);
+ const auto childWM = child->GetWritingMode();
+
+ const bool isOrthogonal = containerWM.IsOrthogonalTo(childWM);
+ const bool isInlineAxis = mAxis == eLogicalAxisInline; // i.e. columns
+
+ // XXX update the line below to include orthogonal grid/table boxes
+ // XXX since they have baselines in both dimensions. And flexbox with
+ // XXX reversed main/cross axis?
+ const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal;
+ if (itemHasBaselineParallelToTrack) {
+ // [align|justify]-self:[last ]baseline.
+ auto selfAlignment =
+ isOrthogonal
+ ? child->StylePosition()->UsedJustifySelf(containerStyle)._0
+ : child->StylePosition()->UsedAlignSelf(containerStyle)._0;
+ selfAlignment &= ~StyleAlignFlags::FLAG_BITS;
+ if (selfAlignment == StyleAlignFlags::BASELINE) {
+ state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline;
+ const GridArea& area = gridItem.mArea;
+ baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
+ } else if (selfAlignment == StyleAlignFlags::LAST_BASELINE) {
+ state |= ItemState::eLastBaseline | ItemState::eSelfBaseline;
+ const GridArea& area = gridItem.mArea;
+ baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
+ }
+
+ // [align|justify]-content:[last ]baseline.
+ // https://drafts.csswg.org/css-align-3/#baseline-align-content
+ // "[...] and its computed 'align-self' or 'justify-self' (whichever
+ // affects its block axis) is 'stretch' or 'self-start' ('self-end').
+ // For this purpose, the 'start', 'end', 'flex-start', and 'flex-end'
+ // values of 'align-self' are treated as either 'self-start' or
+ // 'self-end', whichever they end up equivalent to.
+ auto alignContent = child->StylePosition()->mAlignContent.primary;
+ alignContent &= ~StyleAlignFlags::FLAG_BITS;
+ if (alignContent == StyleAlignFlags::BASELINE ||
+ alignContent == StyleAlignFlags::LAST_BASELINE) {
+ const auto selfAlignEdge = alignContent == StyleAlignFlags::BASELINE
+ ? StyleAlignFlags::SELF_START
+ : StyleAlignFlags::SELF_END;
+ bool validCombo = selfAlignment == StyleAlignFlags::NORMAL ||
+ selfAlignment == StyleAlignFlags::STRETCH ||
+ selfAlignment == selfAlignEdge;
+ if (!validCombo) {
+ // We're doing alignment in the axis that's orthogonal to mAxis here.
+ LogicalAxis alignAxis = GetOrthogonalAxis(mAxis);
+ // |sameSide| is true if the container's start side in this axis is
+ // the same as the child's start side, in the child's parallel axis.
+ bool sameSide =
+ containerWM.ParallelAxisStartsOnSameSide(alignAxis, childWM);
+ if (selfAlignment == StyleAlignFlags::LEFT) {
+ selfAlignment = !isInlineAxis || containerWM.IsBidiLTR()
+ ? StyleAlignFlags::START
+ : StyleAlignFlags::END;
+ } else if (selfAlignment == StyleAlignFlags::RIGHT) {
+ selfAlignment = isInlineAxis && containerWM.IsBidiLTR()
+ ? StyleAlignFlags::END
+ : StyleAlignFlags::START;
+ }
+
+ if (selfAlignment == StyleAlignFlags::START ||
+ selfAlignment == StyleAlignFlags::FLEX_START) {
+ validCombo =
+ sameSide == (alignContent == StyleAlignFlags::BASELINE);
+ } else if (selfAlignment == StyleAlignFlags::END ||
+ selfAlignment == StyleAlignFlags::FLEX_END) {
+ validCombo =
+ sameSide == (alignContent == StyleAlignFlags::LAST_BASELINE);
+ }
+ }
+ if (validCombo) {
+ const GridArea& area = gridItem.mArea;
+ if (alignContent == StyleAlignFlags::BASELINE) {
+ state |= ItemState::eFirstBaseline | ItemState::eContentBaseline;
+ baselineTrack =
+ isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
+ } else if (alignContent == StyleAlignFlags::LAST_BASELINE) {
+ state |= ItemState::eLastBaseline | ItemState::eContentBaseline;
+ baselineTrack =
+ (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
+ }
+ }
+ }
+ }
+
+ if (state & ItemState::eIsBaselineAligned) {
+ // XXXmats if |child| is a descendant of a subgrid then the metrics
+ // below needs to account for the accumulated MPB somehow...
+
+ // XXX available size issue
+ LogicalSize avail(childWM, INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE);
+ auto* rc = &aState.mRenderingContext;
+ // XXX figure out if we can avoid/merge this reflow with the main reflow.
+ // XXX (after bug 1174569 is sorted out)
+ //
+ // XXX How should we handle percentage padding here? (bug 1330866)
+ // XXX (see ::ContentContribution and how it deals with percentages)
+ // XXX What if the true baseline after line-breaking differs from this
+ // XXX hypothetical baseline based on an infinite inline size?
+ // XXX Maybe we should just call ::ContentContribution here instead?
+ // XXX For now we just pass an unconstrined-bsize CB:
+ LogicalSize cbSize(childWM, 0, NS_UNCONSTRAINEDSIZE);
+ ::MeasuringReflow(child, aState.mReflowInput, rc, avail, cbSize);
+
+ nsGridContainerFrame* grid = do_QueryFrame(child);
+ auto frameSize =
+ isInlineAxis ? child->ISize(containerWM) : child->BSize(containerWM);
+ auto margin = child->GetLogicalUsedMargin(containerWM);
+ auto alignSize =
+ frameSize + (isInlineAxis ? margin.IStartEnd(containerWM)
+ : margin.BStartEnd(containerWM));
+
+ Maybe<nscoord> baseline;
+ auto baselineSharingGroup = state & ItemState::eFirstBaseline
+ ? BaselineSharingGroup::First
+ : BaselineSharingGroup::Last;
+ if (grid) {
+ baseline.emplace((isOrthogonal == isInlineAxis)
+ ? grid->GetBBaseline(baselineSharingGroup)
+ : grid->GetIBaseline(baselineSharingGroup));
+ } else {
+ baseline = child->GetNaturalBaselineBOffset(
+ childWM, baselineSharingGroup, BaselineExportContext::Other);
+
+ if (!baseline) {
+ // If baseline alignment is specified on a grid item whose size in
+ // that axis depends on the size of an intrinsically-sized track, that
+ // item does not participate in baseline alignment, and instead uses
+ // its fallback alignment as if that were originally specified.
+ // https://drafts.csswg.org/css-grid-1/#row-align
+
+ // Check if the item crosses any tracks that are intrinsically sized.
+ auto range = gridItem.mArea.LineRangeForAxis(mAxis).Range();
+ auto isTrackAutoSize =
+ std::find_if(range.begin(), range.end(), [&](auto track) {
+ constexpr auto intrinsicSizeFlags =
+ TrackSize::eIntrinsicMinSizing |
+ TrackSize::eIntrinsicMaxSizing | TrackSize::eFitContent |
+ TrackSize::eFlexMaxSizing;
+ return (mSizes[track].mState & intrinsicSizeFlags) != 0;
+ }) != range.end();
+
+ // If either the track or the item is not auto sized, then the item
+ // participates in baseline alignment.
+ if (!isTrackAutoSize ||
+ !gridItem.IsBSizeDependentOnContainerSize(containerWM)) {
+ baseline.emplace(Baseline::SynthesizeBOffsetFromBorderBox(
+ child, containerWM, baselineSharingGroup));
+ }
+ }
+ }
+
+ if (baseline) {
+ nscoord finalBaseline = *baseline;
+ NS_ASSERTION(finalBaseline != NS_INTRINSIC_ISIZE_UNKNOWN,
+ "about to use an unknown baseline");
+
+ if (baselineSharingGroup == BaselineSharingGroup::First) {
+ finalBaseline += isInlineAxis ? margin.IStart(containerWM)
+ : margin.BStart(containerWM);
+
+ } else {
+ finalBaseline += isInlineAxis ? margin.IEnd(containerWM)
+ : margin.BEnd(containerWM);
+ state |= ItemState::eEndSideBaseline;
+ }
+
+ auto& baselineItems =
+ (baselineSharingGroup == BaselineSharingGroup::First)
+ ? firstBaselineItems
+ : lastBaselineItems;
+ baselineItems.AppendElement(ItemBaselineData{
+ baselineTrack, finalBaseline, alignSize, &gridItem});
+ } else {
+ state &= ~ItemState::eAllBaselineBits;
+ }
+ }
+
+ MOZ_ASSERT(
+ (state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) !=
+ (ItemState::eFirstBaseline | ItemState::eLastBaseline),
+ "first/last baseline bits are mutually exclusive");
+ MOZ_ASSERT(
+ (state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)) !=
+ (ItemState::eSelfBaseline | ItemState::eContentBaseline),
+ "*-self and *-content baseline bits are mutually exclusive");
+ MOZ_ASSERT(
+ !(state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) ==
+ !(state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)),
+ "first/last bit requires self/content bit and vice versa");
+
+ gridItem.mState[mAxis] |= state;
+ gridItem.mBaselineOffset[mAxis] = nscoord(0);
+ }
+
+ if (firstBaselineItems.IsEmpty() && lastBaselineItems.IsEmpty()) {
+ return;
+ }
+
+ // TODO: CSS Align spec issue - how to align a baseline subtree in a track?
+ // https://lists.w3.org/Archives/Public/www-style/2016May/0141.html
+ mBaselineSubtreeAlign[BaselineSharingGroup::First] = StyleAlignFlags::START;
+ mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::END;
+
+ CalculateItemBaselines(firstBaselineItems, BaselineSharingGroup::First);
+ CalculateItemBaselines(lastBaselineItems, BaselineSharingGroup::Last);
+}
+
+// TODO: we store the wrong baseline group offset in some cases (bug 1632200)
+void nsGridContainerFrame::Tracks::InitializeItemBaselinesInMasonryAxis(
+ GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
+ BaselineAlignmentSet aSet, const nsSize& aContainerSize,
+ nsTArray<nscoord>& aTrackSizes,
+ nsTArray<ItemBaselineData>& aFirstBaselineItems,
+ nsTArray<ItemBaselineData>& aLastBaselineItems) {
+ MOZ_ASSERT(mIsMasonry);
+ WritingMode wm = aState.mWM;
+ ComputedStyle* containerSC = aState.mFrame->Style();
+ for (GridItemInfo& gridItem : aGridItems) {
+ if (gridItem.IsSubgrid(mAxis)) {
+ // A subgrid itself is never baseline-aligned.
+ continue;
+ }
+ const auto& area = gridItem.mArea;
+ if (aSet.mItemSet == BaselineAlignmentSet::LastItems) {
+ // NOTE: eIsLastItemInMasonryTrack is set also if the item is the ONLY
+ // item in its track; the eIsBaselineAligned check excludes it though
+ // since it participates in the start baseline groups in that case.
+ //
+ // XXX what if it's the only item in THAT baseline group?
+ // XXX should it participate in the last-item group instead then
+ // if there are more baseline-aligned items there?
+ if (!(gridItem.mState[mAxis] & ItemState::eIsLastItemInMasonryTrack) ||
+ (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) {
+ continue;
+ }
+ } else {
+ if (area.LineRangeForAxis(mAxis).mStart > 0 ||
+ (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) {
+ continue;
+ }
+ }
+ auto trackAlign =
+ aState.mGridStyle
+ ->UsedTracksAlignment(
+ mAxis, area.LineRangeForAxis(GetOrthogonalAxis(mAxis)).mStart)
+ .primary;
+ if (!aSet.MatchTrackAlignment(trackAlign)) {
+ continue;
+ }
+
+ nsIFrame* child = gridItem.mFrame;
+ uint32_t baselineTrack = kAutoLine;
+ auto state = ItemState(0);
+ auto childWM = child->GetWritingMode();
+ const bool isOrthogonal = wm.IsOrthogonalTo(childWM);
+ const bool isInlineAxis = mAxis == eLogicalAxisInline; // i.e. columns
+ // XXX update the line below to include orthogonal grid/table boxes
+ // XXX since they have baselines in both dimensions. And flexbox with
+ // XXX reversed main/cross axis?
+ const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal;
+ if (itemHasBaselineParallelToTrack) {
+ const auto* pos = child->StylePosition();
+ // [align|justify]-self:[last ]baseline.
+ auto selfAlignment = pos->UsedSelfAlignment(mAxis, containerSC);
+ selfAlignment &= ~StyleAlignFlags::FLAG_BITS;
+ if (selfAlignment == StyleAlignFlags::BASELINE) {
+ state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline;
+ baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
+ } else if (selfAlignment == StyleAlignFlags::LAST_BASELINE) {
+ state |= ItemState::eLastBaseline | ItemState::eSelfBaseline;
+ baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
+ } else {
+ // [align|justify]-content:[last ]baseline.
+ auto childAxis = isOrthogonal ? GetOrthogonalAxis(mAxis) : mAxis;
+ auto alignContent = pos->UsedContentAlignment(childAxis).primary;
+ alignContent &= ~StyleAlignFlags::FLAG_BITS;
+ if (alignContent == StyleAlignFlags::BASELINE) {
+ state |= ItemState::eFirstBaseline | ItemState::eContentBaseline;
+ baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
+ } else if (alignContent == StyleAlignFlags::LAST_BASELINE) {
+ state |= ItemState::eLastBaseline | ItemState::eContentBaseline;
+ baselineTrack =
+ (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
+ }
+ }
+ }
+
+ if (state & ItemState::eIsBaselineAligned) {
+ // XXXmats if |child| is a descendant of a subgrid then the metrics
+ // below needs to account for the accumulated MPB somehow...
+
+ nscoord baseline;
+ nsGridContainerFrame* grid = do_QueryFrame(child);
+ if (state & ItemState::eFirstBaseline) {
+ if (grid) {
+ if (isOrthogonal == isInlineAxis) {
+ baseline = grid->GetBBaseline(BaselineSharingGroup::First);
+ } else {
+ baseline = grid->GetIBaseline(BaselineSharingGroup::First);
+ }
+ }
+ if (grid || nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) {
+ NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN,
+ "about to use an unknown baseline");
+ auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
+ nscoord alignSize;
+ LogicalPoint pos =
+ child->GetLogicalNormalPosition(wm, aContainerSize);
+ baseline += pos.Pos(mAxis, wm);
+ if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) {
+ state |= ItemState::eEndSideBaseline;
+ // Convert to distance from the track end.
+ baseline =
+ aTrackSizes[gridItem.mArea
+ .LineRangeForAxis(GetOrthogonalAxis(mAxis))
+ .mStart] -
+ baseline;
+ }
+ alignSize = frameSize;
+ aFirstBaselineItems.AppendElement(ItemBaselineData(
+ {baselineTrack, baseline, alignSize, &gridItem}));
+ } else {
+ state &= ~ItemState::eAllBaselineBits;
+ }
+ } else {
+ if (grid) {
+ if (isOrthogonal == isInlineAxis) {
+ baseline = grid->GetBBaseline(BaselineSharingGroup::Last);
+ } else {
+ baseline = grid->GetIBaseline(BaselineSharingGroup::Last);
+ }
+ }
+ if (grid || nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) {
+ NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN,
+ "about to use an unknown baseline");
+ auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
+ auto m = child->GetLogicalUsedMargin(wm);
+ if (!grid &&
+ aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) {
+ // Convert to distance from border-box end.
+ state |= ItemState::eEndSideBaseline;
+ LogicalPoint pos =
+ child->GetLogicalNormalPosition(wm, aContainerSize);
+ baseline += pos.Pos(mAxis, wm);
+ baseline =
+ aTrackSizes[gridItem.mArea
+ .LineRangeForAxis(GetOrthogonalAxis(mAxis))
+ .mStart] -
+ baseline;
+ } else if (grid && aSet.mTrackAlignmentSet ==
+ BaselineAlignmentSet::StartStretch) {
+ // Convert to distance from border-box start.
+ baseline = frameSize - baseline;
+ }
+ if (aSet.mItemSet == BaselineAlignmentSet::LastItems &&
+ aSet.mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) {
+ LogicalPoint pos =
+ child->GetLogicalNormalPosition(wm, aContainerSize);
+ baseline += pos.B(wm);
+ }
+ if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) {
+ state |= ItemState::eEndSideBaseline;
+ }
+ auto descent =
+ baseline + ((state & ItemState::eEndSideBaseline)
+ ? (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm))
+ : (isInlineAxis ? m.IStart(wm) : m.BStart(wm)));
+ auto alignSize =
+ frameSize + (isInlineAxis ? m.IStartEnd(wm) : m.BStartEnd(wm));
+ aLastBaselineItems.AppendElement(
+ ItemBaselineData({baselineTrack, descent, alignSize, &gridItem}));
+ } else {
+ state &= ~ItemState::eAllBaselineBits;
+ }
+ }
+ }
+ MOZ_ASSERT(
+ (state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) !=
+ (ItemState::eFirstBaseline | ItemState::eLastBaseline),
+ "first/last baseline bits are mutually exclusive");
+ MOZ_ASSERT(
+ (state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)) !=
+ (ItemState::eSelfBaseline | ItemState::eContentBaseline),
+ "*-self and *-content baseline bits are mutually exclusive");
+ MOZ_ASSERT(
+ !(state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) ==
+ !(state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)),
+ "first/last bit requires self/content bit and vice versa");
+ gridItem.mState[mAxis] |= state;
+ gridItem.mBaselineOffset[mAxis] = nscoord(0);
+ }
+
+ CalculateItemBaselines(aFirstBaselineItems, BaselineSharingGroup::First);
+ CalculateItemBaselines(aLastBaselineItems, BaselineSharingGroup::Last);
+
+ // TODO: make sure the mBaselines (i.e. the baselines we export from
+ // the grid container) are offset from the correct container edge.
+ // Also, which of the baselines do we pick to export exactly?
+
+ MOZ_ASSERT(aFirstBaselineItems.Length() != 1 ||
+ aFirstBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0,
+ "a baseline group that contains only one item should not "
+ "produce a non-zero item baseline offset");
+ MOZ_ASSERT(aLastBaselineItems.Length() != 1 ||
+ aLastBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0,
+ "a baseline group that contains only one item should not "
+ "produce a non-zero item baseline offset");
+}
+
+void nsGridContainerFrame::Tracks::AlignBaselineSubtree(
+ const GridItemInfo& aGridItem) const {
+ if (mIsMasonry) {
+ return;
+ }
+ auto state = aGridItem.mState[mAxis];
+ if (!(state & ItemState::eIsBaselineAligned)) {
+ return;
+ }
+ const GridArea& area = aGridItem.mArea;
+ int32_t baselineTrack;
+ const bool isFirstBaseline = state & ItemState::eFirstBaseline;
+ if (isFirstBaseline) {
+ baselineTrack =
+ mAxis == eLogicalAxisBlock ? area.mRows.mStart : area.mCols.mStart;
+ } else {
+ baselineTrack =
+ (mAxis == eLogicalAxisBlock ? area.mRows.mEnd : area.mCols.mEnd) - 1;
+ }
+ const TrackSize& sz = mSizes[baselineTrack];
+ auto baselineGroup = isFirstBaseline ? BaselineSharingGroup::First
+ : BaselineSharingGroup::Last;
+ nscoord delta = sz.mBase - sz.mBaselineSubtreeSize[baselineGroup];
+ const auto subtreeAlign = mBaselineSubtreeAlign[baselineGroup];
+ if (subtreeAlign == StyleAlignFlags::START) {
+ if (state & ItemState::eLastBaseline) {
+ aGridItem.mBaselineOffset[mAxis] += delta;
+ }
+ } else if (subtreeAlign == StyleAlignFlags::END) {
+ if (isFirstBaseline) {
+ aGridItem.mBaselineOffset[mAxis] += delta;
+ }
+ } else if (subtreeAlign == StyleAlignFlags::CENTER) {
+ aGridItem.mBaselineOffset[mAxis] += delta / 2;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected baseline subtree alignment");
+ }
+}
+
+template <nsGridContainerFrame::Tracks::TrackSizingPhase phase>
+bool nsGridContainerFrame::Tracks::GrowSizeForSpanningItems(
+ nsTArray<SpanningItemData>::iterator aIter,
+ nsTArray<SpanningItemData>::iterator aIterEnd, nsTArray<uint32_t>& aTracks,
+ nsTArray<TrackSize>& aPlan, nsTArray<TrackSize>& aItemPlan,
+ TrackSize::StateBits aSelector, const FitContentClamper& aFitContentClamper,
+ bool aNeedInfinitelyGrowableFlag) {
+ constexpr bool isMaxSizingPhase =
+ phase == TrackSizingPhase::IntrinsicMaximums ||
+ phase == TrackSizingPhase::MaxContentMaximums;
+ bool needToUpdateSizes = false;
+ InitializePlan<phase>(aPlan);
+ for (; aIter != aIterEnd; ++aIter) {
+ const SpanningItemData& item = *aIter;
+ if (!(item.mState & aSelector)) {
+ continue;
+ }
+ if (isMaxSizingPhase) {
+ for (auto i : item.mLineRange.Range()) {
+ aPlan[i].mState |= TrackSize::eModified;
+ }
+ }
+ nscoord space = item.SizeContributionForPhase<phase>();
+ if (space <= 0) {
+ continue;
+ }
+ aTracks.ClearAndRetainStorage();
+ space = CollectGrowable<phase>(space, item.mLineRange, aSelector, aTracks);
+ if (space > 0) {
+ DistributeToTrackSizes<phase>(space, aPlan, aItemPlan, aTracks, aSelector,
+ aFitContentClamper);
+ needToUpdateSizes = true;
+ }
+ }
+ if (isMaxSizingPhase) {
+ needToUpdateSizes = true;
+ }
+ if (needToUpdateSizes) {
+ CopyPlanToSize<phase>(aPlan, aNeedInfinitelyGrowableFlag);
+ }
+ return needToUpdateSizes;
+}
+
+void nsGridContainerFrame::Tracks::ResolveIntrinsicSize(
+ GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
+ const TrackSizingFunctions& aFunctions, LineRange GridArea::*aRange,
+ nscoord aPercentageBasis, SizingConstraint aConstraint) {
+ // Resolve Intrinsic Track Sizes
+ // https://w3c.github.io/csswg-drafts/css-grid-1/#algo-content
+ // We're also setting eIsFlexing on the item state here to speed up
+ // FindUsedFlexFraction later.
+
+ gfxContext* rc = &aState.mRenderingContext;
+ WritingMode wm = aState.mWM;
+
+ // Data we accumulate when grouping similar sized spans together.
+ struct PerSpanData {
+ uint32_t mItemCountWithSameSpan = 0;
+ TrackSize::StateBits mStateBits = TrackSize::StateBits{0};
+ };
+ AutoTArray<PerSpanData, 16> perSpanData;
+
+ nsTArray<SpanningItemData> spanningItems;
+ uint32_t maxSpan = 0; // max span of items in `spanningItems`.
+
+ // Setup track selector for step 3.2:
+ const auto contentBasedMinSelector =
+ aConstraint == SizingConstraint::MinContent
+ ? TrackSize::eIntrinsicMinSizing
+ : TrackSize::eMinOrMaxContentMinSizing;
+
+ // Setup track selector for step 3.3:
+ const auto maxContentMinSelector =
+ aConstraint == SizingConstraint::MaxContent
+ ? (TrackSize::eMaxContentMinSizing | TrackSize::eAutoMinSizing)
+ : TrackSize::eMaxContentMinSizing;
+
+ const auto orthogonalAxis = GetOrthogonalAxis(mAxis);
+ const bool isMasonryInOtherAxis = aState.mFrame->IsMasonry(orthogonalAxis);
+
+ for (auto& gridItem : aGridItems) {
+ MOZ_ASSERT(!(gridItem.mState[mAxis] &
+ (ItemState::eApplyAutoMinSize | ItemState::eIsFlexing |
+ ItemState::eClampMarginBoxMinSize)),
+ "Why are any of these bits set already?");
+
+ const GridArea& area = gridItem.mArea;
+ const LineRange& lineRange = area.*aRange;
+
+ // If we have masonry layout in the other axis then skip this item unless
+ // it's in the first masonry track, or has definite placement in this axis,
+ // or spans all tracks in this axis (since that implies it will be placed
+ // at line 1 regardless of layout results of other items).
+ if (isMasonryInOtherAxis &&
+ gridItem.mArea.LineRangeForAxis(orthogonalAxis).mStart != 0 &&
+ (gridItem.mState[mAxis] & ItemState::eAutoPlacement) &&
+ gridItem.mArea.LineRangeForAxis(mAxis).Extent() != mSizes.Length()) {
+ continue;
+ }
+
+ uint32_t span = lineRange.Extent();
+ if (MOZ_UNLIKELY(gridItem.mState[mAxis] & ItemState::eIsSubgrid)) {
+ auto itemWM = gridItem.mFrame->GetWritingMode();
+ auto percentageBasis = aState.PercentageBasisFor(mAxis, gridItem);
+
+ if (percentageBasis.ISize(itemWM) == NS_UNCONSTRAINEDSIZE) {
+ percentageBasis.ISize(itemWM) = nscoord(0);
+ }
+
+ if (percentageBasis.BSize(itemWM) == NS_UNCONSTRAINEDSIZE) {
+ percentageBasis.BSize(itemWM) = nscoord(0);
+ }
+
+ auto* subgrid =
+ SubgridComputeMarginBorderPadding(gridItem, percentageBasis);
+ LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding(
+ gridItem.SubgridFrame(), subgrid, wm, mAxis);
+
+ if (span == 1) {
+ AddSubgridContribution(mSizes[lineRange.mStart],
+ mbp.StartEnd(mAxis, wm));
+ } else {
+ AddSubgridContribution(mSizes[lineRange.mStart], mbp.Start(mAxis, wm));
+ AddSubgridContribution(mSizes[lineRange.mEnd - 1], mbp.End(mAxis, wm));
+ }
+ continue;
+ }
+
+ if (span == 1) {
+ // Step 2. Size tracks to fit non-spanning items.
+ if (ResolveIntrinsicSizeForNonSpanningItems(aState, aFunctions,
+ aPercentageBasis, aConstraint,
+ lineRange, gridItem)) {
+ gridItem.mState[mAxis] |= ItemState::eIsFlexing;
+ }
+ } else {
+ TrackSize::StateBits state = StateBitsForRange(lineRange);
+
+ // Check if we need to apply "Automatic Minimum Size" and cache it.
+ if ((state & TrackSize::eAutoMinSizing) &&
+ !(state & TrackSize::eFlexMaxSizing) &&
+ gridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) {
+ gridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize;
+ }
+
+ if (state & TrackSize::eFlexMaxSizing) {
+ gridItem.mState[mAxis] |= ItemState::eIsFlexing;
+ } else if (state & (TrackSize::eIntrinsicMinSizing |
+ TrackSize::eIntrinsicMaxSizing)) {
+ // Collect data for Step 3.
+ maxSpan = std::max(maxSpan, span);
+ if (span >= perSpanData.Length()) {
+ perSpanData.SetLength(2 * span);
+ }
+
+ perSpanData[span].mItemCountWithSameSpan++;
+ perSpanData[span].mStateBits |= state;
+
+ CachedIntrinsicSizes cache;
+
+ // Calculate data for "Automatic Minimum Size" clamping, if needed.
+ if (TrackSize::IsDefiniteMaxSizing(state) &&
+ (gridItem.mState[mAxis] & ItemState::eApplyAutoMinSize)) {
+ nscoord minSizeClamp = 0;
+ for (auto i : lineRange.Range()) {
+ minSizeClamp += aFunctions.MaxSizingFor(i).AsBreadth().Resolve(
+ aPercentageBasis);
+ }
+ minSizeClamp += mGridGap * (span - 1);
+ cache.mMinSizeClamp = minSizeClamp;
+ gridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize;
+ }
+
+ // Collect the various grid item size contributions we need.
+ nscoord minSize = 0;
+ if (state & TrackSize::eIntrinsicMinSizing) { // for 3.1
+ minSize = MinSize(gridItem, aState, rc, wm, mAxis, &cache);
+ }
+ nscoord minContent = 0;
+ if (state & (contentBasedMinSelector | // for 3.2
+ TrackSize::eIntrinsicMaxSizing)) { // for 3.5
+ minContent =
+ MinContentContribution(gridItem, aState, rc, wm, mAxis, &cache);
+ }
+ nscoord maxContent = 0;
+ if (state & (maxContentMinSelector | // for 3.3
+ TrackSize::eAutoOrMaxContentMaxSizing)) { // for 3.6
+ maxContent =
+ MaxContentContribution(gridItem, aState, rc, wm, mAxis, &cache);
+ }
+
+ spanningItems.AppendElement(
+ SpanningItemData({span, state, lineRange, minSize, minContent,
+ maxContent, gridItem.mFrame}));
+ }
+ }
+
+ MOZ_ASSERT(!(gridItem.mState[mAxis] & ItemState::eClampMarginBoxMinSize) ||
+ (gridItem.mState[mAxis] & ItemState::eApplyAutoMinSize),
+ "clamping only applies to Automatic Minimum Size");
+ }
+
+ // Step 3 - Increase sizes to accommodate spanning items crossing
+ // content-sized tracks.
+ if (maxSpan) {
+ auto fitContentClamper = [&aFunctions, aPercentageBasis](uint32_t aTrack,
+ nscoord aMinSize,
+ nscoord* aSize) {
+ nscoord fitContentLimit = ::ResolveToDefiniteSize(
+ aFunctions.MaxSizingFor(aTrack), aPercentageBasis);
+ if (*aSize > fitContentLimit) {
+ *aSize = std::max(aMinSize, fitContentLimit);
+ return true;
+ }
+ return false;
+ };
+
+ // Sort the collected items on span length, shortest first. There's no need
+ // for a stable sort here since the sizing isn't order dependent within
+ // a group of items with the same span length.
+ std::sort(spanningItems.begin(), spanningItems.end(),
+ SpanningItemData::IsSpanLessThan);
+
+ nsTArray<uint32_t> tracks(maxSpan);
+ nsTArray<TrackSize> plan(mSizes.Length());
+ plan.SetLength(mSizes.Length());
+ nsTArray<TrackSize> itemPlan(mSizes.Length());
+ itemPlan.SetLength(mSizes.Length());
+ // Start / end iterator for items of the same span length:
+ auto spanGroupStart = spanningItems.begin();
+ auto spanGroupEnd = spanGroupStart;
+ const auto end = spanningItems.end();
+ for (; spanGroupStart != end; spanGroupStart = spanGroupEnd) {
+ const uint32_t span = spanGroupStart->mSpan;
+ spanGroupEnd = spanGroupStart + perSpanData[span].mItemCountWithSameSpan;
+ TrackSize::StateBits stateBitsForSpan = perSpanData[span].mStateBits;
+ bool updatedBase = false; // Did we update any mBase in step 3.1..3.3?
+ TrackSize::StateBits selector(TrackSize::eIntrinsicMinSizing);
+ if (stateBitsForSpan & selector) {
+ // Step 3.1 MinSize to intrinsic min-sizing.
+ updatedBase =
+ GrowSizeForSpanningItems<TrackSizingPhase::IntrinsicMinimums>(
+ spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector);
+ }
+
+ selector = contentBasedMinSelector;
+ if (stateBitsForSpan & selector) {
+ // Step 3.2 MinContentContribution to min-/max-content (and 'auto' when
+ // sizing under a min-content constraint) min-sizing.
+ updatedBase |=
+ GrowSizeForSpanningItems<TrackSizingPhase::ContentBasedMinimums>(
+ spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector);
+ }
+
+ selector = maxContentMinSelector;
+ if (stateBitsForSpan & selector) {
+ // Step 3.3 MaxContentContribution to max-content (and 'auto' when
+ // sizing under a max-content constraint) min-sizing.
+ updatedBase |=
+ GrowSizeForSpanningItems<TrackSizingPhase::MaxContentMinimums>(
+ spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector);
+ }
+
+ if (updatedBase) {
+ // Step 3.4
+ for (TrackSize& sz : mSizes) {
+ if (sz.mBase > sz.mLimit) {
+ sz.mLimit = sz.mBase;
+ }
+ }
+ }
+
+ selector = TrackSize::eIntrinsicMaxSizing;
+ if (stateBitsForSpan & selector) {
+ const bool willRunStep3_6 =
+ stateBitsForSpan & TrackSize::eAutoOrMaxContentMaxSizing;
+ // Step 3.5 MinContentContribution to intrinsic max-sizing.
+ GrowSizeForSpanningItems<TrackSizingPhase::IntrinsicMaximums>(
+ spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector,
+ fitContentClamper, willRunStep3_6);
+
+ if (willRunStep3_6) {
+ // Step 2.6 MaxContentContribution to max-content max-sizing.
+ selector = TrackSize::eAutoOrMaxContentMaxSizing;
+ GrowSizeForSpanningItems<TrackSizingPhase::MaxContentMaximums>(
+ spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector,
+ fitContentClamper);
+ }
+ }
+ }
+ }
+
+ // Step 5 - If any track still has an infinite growth limit, set its growth
+ // limit to its base size.
+ for (TrackSize& sz : mSizes) {
+ if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
+ sz.mLimit = sz.mBase;
+ }
+ }
+}
+
+float nsGridContainerFrame::Tracks::FindFrUnitSize(
+ const LineRange& aRange, const nsTArray<uint32_t>& aFlexTracks,
+ const TrackSizingFunctions& aFunctions, nscoord aSpaceToFill) const {
+ MOZ_ASSERT(aSpaceToFill > 0 && !aFlexTracks.IsEmpty());
+ float flexFactorSum = 0.0f;
+ nscoord leftOverSpace = aSpaceToFill;
+ for (auto i : aRange.Range()) {
+ const TrackSize& sz = mSizes[i];
+ if (sz.mState & TrackSize::eFlexMaxSizing) {
+ flexFactorSum += aFunctions.MaxSizingFor(i).AsFr();
+ } else {
+ leftOverSpace -= sz.mBase;
+ if (leftOverSpace <= 0) {
+ return 0.0f;
+ }
+ }
+ }
+ bool restart;
+ float hypotheticalFrSize;
+ nsTArray<uint32_t> flexTracks(aFlexTracks.Clone());
+ uint32_t numFlexTracks = flexTracks.Length();
+ do {
+ restart = false;
+ hypotheticalFrSize = leftOverSpace / std::max(flexFactorSum, 1.0f);
+ for (uint32_t i = 0, len = flexTracks.Length(); i < len; ++i) {
+ uint32_t track = flexTracks[i];
+ if (track == kAutoLine) {
+ continue; // Track marked as inflexible in a prev. iter of this loop.
+ }
+ float flexFactor = aFunctions.MaxSizingFor(track).AsFr();
+ const nscoord base = mSizes[track].mBase;
+ if (flexFactor * hypotheticalFrSize < base) {
+ // 12.7.1.4: Treat this track as inflexible.
+ flexTracks[i] = kAutoLine;
+ flexFactorSum -= flexFactor;
+ leftOverSpace -= base;
+ --numFlexTracks;
+ if (numFlexTracks == 0 || leftOverSpace <= 0) {
+ return 0.0f;
+ }
+ restart = true;
+ // break; XXX (bug 1176621 comment 16) measure which is more common
+ }
+ }
+ } while (restart);
+ return hypotheticalFrSize;
+}
+
+float nsGridContainerFrame::Tracks::FindUsedFlexFraction(
+ GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
+ const nsTArray<uint32_t>& aFlexTracks,
+ const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) const {
+ if (aAvailableSize != NS_UNCONSTRAINEDSIZE) {
+ // Use all of the grid tracks and a 'space to fill' of the available space.
+ const TranslatedLineRange range(0, mSizes.Length());
+ return FindFrUnitSize(range, aFlexTracks, aFunctions, aAvailableSize);
+ }
+
+ // The used flex fraction is the maximum of:
+ // ... each flexible track's base size divided by its flex factor (which is
+ // floored at 1).
+ float fr = 0.0f;
+ for (uint32_t track : aFlexTracks) {
+ float flexFactor = aFunctions.MaxSizingFor(track).AsFr();
+ float possiblyDividedBaseSize = (flexFactor > 1.0f)
+ ? mSizes[track].mBase / flexFactor
+ : mSizes[track].mBase;
+ fr = std::max(fr, possiblyDividedBaseSize);
+ }
+ WritingMode wm = aState.mWM;
+ gfxContext* rc = &aState.mRenderingContext;
+ // ... the result of 'finding the size of an fr' for each item that spans
+ // a flex track with its max-content contribution as 'space to fill'
+ for (const GridItemInfo& item : aGridItems) {
+ if (item.mState[mAxis] & ItemState::eIsFlexing) {
+ // XXX optimize: bug 1194446
+ auto pb = Some(aState.PercentageBasisFor(mAxis, item));
+ nscoord spaceToFill = ContentContribution(item, aState, rc, wm, mAxis, pb,
+ IntrinsicISizeType::PrefISize);
+ const LineRange& range =
+ mAxis == eLogicalAxisInline ? item.mArea.mCols : item.mArea.mRows;
+ MOZ_ASSERT(range.Extent() >= 1);
+ const auto spannedGaps = range.Extent() - 1;
+ if (spannedGaps > 0) {
+ spaceToFill -= mGridGap * spannedGaps;
+ }
+ if (spaceToFill <= 0) {
+ continue;
+ }
+ // ... and all its spanned tracks as input.
+ nsTArray<uint32_t> itemFlexTracks;
+ for (auto i : range.Range()) {
+ if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
+ itemFlexTracks.AppendElement(i);
+ }
+ }
+ float itemFr =
+ FindFrUnitSize(range, itemFlexTracks, aFunctions, spaceToFill);
+ fr = std::max(fr, itemFr);
+ }
+ }
+ return fr;
+}
+
+void nsGridContainerFrame::Tracks::StretchFlexibleTracks(
+ GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
+ const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) {
+ if (aAvailableSize <= 0) {
+ return;
+ }
+ nsTArray<uint32_t> flexTracks(mSizes.Length());
+ for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
+ if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
+ flexTracks.AppendElement(i);
+ }
+ }
+ if (flexTracks.IsEmpty()) {
+ return;
+ }
+ nscoord minSize = 0;
+ nscoord maxSize = NS_UNCONSTRAINEDSIZE;
+ if (aState.mReflowInput) {
+ auto* ri = aState.mReflowInput;
+ minSize = mAxis == eLogicalAxisBlock ? ri->ComputedMinBSize()
+ : ri->ComputedMinISize();
+ maxSize = mAxis == eLogicalAxisBlock ? ri->ComputedMaxBSize()
+ : ri->ComputedMaxISize();
+ }
+ Maybe<CopyableAutoTArray<TrackSize, 32>> origSizes;
+ bool applyMinMax = (minSize != 0 || maxSize != NS_UNCONSTRAINEDSIZE) &&
+ aAvailableSize == NS_UNCONSTRAINEDSIZE;
+ // We iterate twice at most. The 2nd time if the grid size changed after
+ // applying a min/max-size (can only occur if aAvailableSize is indefinite).
+ while (true) {
+ float fr = FindUsedFlexFraction(aState, aGridItems, flexTracks, aFunctions,
+ aAvailableSize);
+ if (fr != 0.0f) {
+ for (uint32_t i : flexTracks) {
+ float flexFactor = aFunctions.MaxSizingFor(i).AsFr();
+ nscoord flexLength = NSToCoordRound(flexFactor * fr);
+ nscoord& base = mSizes[i].mBase;
+ if (flexLength > base) {
+ if (applyMinMax && origSizes.isNothing()) {
+ origSizes.emplace(mSizes);
+ }
+ base = flexLength;
+ }
+ }
+ }
+ if (applyMinMax) {
+ applyMinMax = false;
+ // https://drafts.csswg.org/css-grid/#algo-flex-tracks
+ // "If using this flex fraction would cause the grid to be smaller than
+ // the grid container’s min-width/height (or larger than the grid
+ // container’s max-width/height), then redo this step, treating the free
+ // space as definite [...]"
+ const auto sumOfGridGaps = SumOfGridGaps();
+ nscoord newSize = SumOfGridTracks() + sumOfGridGaps;
+ if (newSize > maxSize) {
+ aAvailableSize = maxSize;
+ } else if (newSize < minSize) {
+ aAvailableSize = minSize;
+ }
+ if (aAvailableSize != NS_UNCONSTRAINEDSIZE) {
+ aAvailableSize = std::max(0, aAvailableSize - sumOfGridGaps);
+ // Restart with the original track sizes and definite aAvailableSize.
+ if (origSizes.isSome()) {
+ mSizes = std::move(*origSizes);
+ origSizes.reset();
+ } // else, no mSizes[].mBase were changed above so it's still correct
+ if (aAvailableSize == 0) {
+ break; // zero available size wouldn't change any sizes though...
+ }
+ continue;
+ }
+ }
+ break;
+ }
+}
+
+void nsGridContainerFrame::Tracks::AlignJustifyContent(
+ const nsStylePosition* aStyle, StyleContentDistribution aAligmentStyleValue,
+ WritingMode aWM, nscoord aContentBoxSize, bool aIsSubgriddedAxis) {
+ const bool isAlign = mAxis == eLogicalAxisBlock;
+ // Align-/justify-content doesn't apply in a subgridded axis.
+ // Gap properties do apply though so we need to stretch/position the tracks
+ // to center-align the gaps with the parent's gaps.
+ if (MOZ_UNLIKELY(aIsSubgriddedAxis)) {
+ auto& gap = isAlign ? aStyle->mRowGap : aStyle->mColumnGap;
+ if (gap.IsNormal()) {
+ return;
+ }
+ auto len = mSizes.Length();
+ if (len <= 1) {
+ return;
+ }
+ // This stores the gap deltas between the subgrid gap and the gaps in
+ // the used track sizes (as encoded in its tracks' mPosition):
+ nsTArray<nscoord> gapDeltas;
+ const size_t numGaps = len - 1;
+ gapDeltas.SetLength(numGaps);
+ for (size_t i = 0; i < numGaps; ++i) {
+ TrackSize& sz1 = mSizes[i];
+ TrackSize& sz2 = mSizes[i + 1];
+ nscoord currentGap = sz2.mPosition - (sz1.mPosition + sz1.mBase);
+ gapDeltas[i] = mGridGap - currentGap;
+ }
+ // Recompute the tracks' size/position so that they end up with
+ // a subgrid-gap centered on the original track gap.
+ nscoord currentPos = mSizes[0].mPosition;
+ nscoord lastHalfDelta(0);
+ for (size_t i = 0; i < numGaps; ++i) {
+ TrackSize& sz = mSizes[i];
+ nscoord delta = gapDeltas[i];
+ nscoord halfDelta;
+ nscoord roundingError = NSCoordDivRem(delta, 2, &halfDelta);
+ auto newSize = sz.mBase - (halfDelta + roundingError) - lastHalfDelta;
+ lastHalfDelta = halfDelta;
+ // If the gap delta (in particular 'halfDelta + lastHalfDelta') is larger
+ // than the current track size, newSize can be negative. Don't let the new
+ // track size (mBase) be negative.
+ sz.mBase = std::max(newSize, 0);
+ sz.mPosition = currentPos;
+ currentPos += newSize + mGridGap;
+ }
+ auto& lastTrack = mSizes.LastElement();
+ auto newSize = lastTrack.mBase - lastHalfDelta;
+ lastTrack.mBase = std::max(newSize, 0);
+ lastTrack.mPosition = currentPos;
+ return;
+ }
+
+ if (mSizes.IsEmpty()) {
+ return;
+ }
+
+ bool overflowSafe;
+ auto alignment = ::GetAlignJustifyValue(aAligmentStyleValue.primary, aWM,
+ isAlign, &overflowSafe);
+ if (alignment == StyleAlignFlags::NORMAL) {
+ alignment = StyleAlignFlags::STRETCH;
+ // we may need a fallback for 'stretch' below
+ aAligmentStyleValue = {alignment};
+ }
+
+ // Compute the free space and count auto-sized tracks.
+ size_t numAutoTracks = 0;
+ nscoord space;
+ if (alignment != StyleAlignFlags::START) {
+ nscoord trackSizeSum = 0;
+ if (aIsSubgriddedAxis) {
+ numAutoTracks = mSizes.Length();
+ } else {
+ for (const TrackSize& sz : mSizes) {
+ trackSizeSum += sz.mBase;
+ if (sz.mState & TrackSize::eAutoMaxSizing) {
+ ++numAutoTracks;
+ }
+ }
+ }
+ space = aContentBoxSize - trackSizeSum - SumOfGridGaps();
+ // Use the fallback value instead when applicable.
+ if (space < 0 ||
+ (alignment == StyleAlignFlags::SPACE_BETWEEN && mSizes.Length() == 1)) {
+ auto fallback = ::GetAlignJustifyFallbackIfAny(aAligmentStyleValue, aWM,
+ isAlign, &overflowSafe);
+ if (fallback) {
+ alignment = *fallback;
+ }
+ }
+ if (space == 0 || (space < 0 && overflowSafe)) {
+ // XXX check that this makes sense also for [last ]baseline (bug 1151204).
+ alignment = StyleAlignFlags::START;
+ }
+ }
+
+ // Optimize the cases where we just need to set each track's position.
+ nscoord pos = 0;
+ bool distribute = true;
+ if (alignment == StyleAlignFlags::BASELINE ||
+ alignment == StyleAlignFlags::LAST_BASELINE) {
+ NS_WARNING("NYI: 'first/last baseline' (bug 1151204)"); // XXX
+ alignment = StyleAlignFlags::START;
+ }
+ if (alignment == StyleAlignFlags::START) {
+ distribute = false;
+ } else if (alignment == StyleAlignFlags::END) {
+ pos = space;
+ distribute = false;
+ } else if (alignment == StyleAlignFlags::CENTER) {
+ pos = space / 2;
+ distribute = false;
+ } else if (alignment == StyleAlignFlags::STRETCH) {
+ distribute = numAutoTracks != 0;
+ }
+ if (!distribute) {
+ for (TrackSize& sz : mSizes) {
+ sz.mPosition = pos;
+ pos += sz.mBase + mGridGap;
+ }
+ return;
+ }
+
+ // Distribute free space to/between tracks and set their position.
+ MOZ_ASSERT(space > 0, "should've handled that on the fallback path above");
+ nscoord between, roundingError;
+ if (alignment == StyleAlignFlags::STRETCH) {
+ MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above");
+ // The outer loop typically only runs once - it repeats only in a masonry
+ // axis when some stretchable items reach their `max-size`.
+ // It's O(n^2) worst case; if all items are stretchable with a `max-size`
+ // and exactly one item reaches its `max-size` each round.
+ while (space) {
+ pos = 0;
+ nscoord spacePerTrack;
+ roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack);
+ space = 0;
+ for (TrackSize& sz : mSizes) {
+ sz.mPosition = pos;
+ if (!(sz.mState & TrackSize::eAutoMaxSizing)) {
+ pos += sz.mBase + mGridGap;
+ continue;
+ }
+ nscoord stretch = spacePerTrack;
+ if (roundingError) {
+ roundingError -= 1;
+ stretch += 1;
+ }
+ nscoord newBase = sz.mBase + stretch;
+ if (mIsMasonry && (sz.mState & TrackSize::eClampToLimit)) {
+ auto clampedSize = std::min(newBase, sz.mLimit);
+ auto sizeOverLimit = newBase - clampedSize;
+ if (sizeOverLimit > 0) {
+ newBase = clampedSize;
+ sz.mState &= ~(sz.mState & TrackSize::eAutoMaxSizing);
+ // This repeats the outer loop to distribute the superfluous space:
+ space += sizeOverLimit;
+ if (--numAutoTracks == 0) {
+ // ... except if we don't have any stretchable items left.
+ space = 0;
+ }
+ }
+ }
+ sz.mBase = newBase;
+ pos += newBase + mGridGap;
+ }
+ }
+ MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
+ return;
+ }
+ if (alignment == StyleAlignFlags::SPACE_BETWEEN) {
+ MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above");
+ roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between);
+ } else if (alignment == StyleAlignFlags::SPACE_AROUND) {
+ roundingError = NSCoordDivRem(space, mSizes.Length(), &between);
+ pos = between / 2;
+ } else if (alignment == StyleAlignFlags::SPACE_EVENLY) {
+ roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between);
+ pos = between;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value");
+ between = 0; // just to avoid a compiler warning
+ roundingError = 0; // just to avoid a compiler warning
+ }
+ between += mGridGap;
+ for (TrackSize& sz : mSizes) {
+ sz.mPosition = pos;
+ nscoord spacing = between;
+ if (roundingError) {
+ roundingError -= 1;
+ spacing += 1;
+ }
+ pos += sz.mBase + spacing;
+ }
+ MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
+}
+
+void nsGridContainerFrame::LineRange::ToPositionAndLength(
+ const nsTArray<TrackSize>& aTrackSizes, nscoord* aPos,
+ nscoord* aLength) const {
+ MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
+ "expected a definite LineRange");
+ MOZ_ASSERT(mStart < mEnd);
+ nscoord startPos = aTrackSizes[mStart].mPosition;
+ const TrackSize& sz = aTrackSizes[mEnd - 1];
+ *aPos = startPos;
+ *aLength = (sz.mPosition + sz.mBase) - startPos;
+}
+
+nscoord nsGridContainerFrame::LineRange::ToLength(
+ const nsTArray<TrackSize>& aTrackSizes) const {
+ MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
+ "expected a definite LineRange");
+ MOZ_ASSERT(mStart < mEnd);
+ nscoord startPos = aTrackSizes[mStart].mPosition;
+ const TrackSize& sz = aTrackSizes[mEnd - 1];
+ return (sz.mPosition + sz.mBase) - startPos;
+}
+
+void nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos(
+ const Tracks& aTracks, nscoord aGridOrigin, nscoord* aPos,
+ nscoord* aLength) const {
+ // kAutoLine for abspos children contributes the corresponding edge
+ // of the grid container's padding-box.
+ if (mEnd == kAutoLine) {
+ if (mStart == kAutoLine) {
+ // done
+ } else {
+ const nscoord endPos = *aPos + *aLength;
+ auto side = mStart == aTracks.mSizes.Length()
+ ? GridLineSide::BeforeGridGap
+ : GridLineSide::AfterGridGap;
+ nscoord startPos = aTracks.GridLineEdge(mStart, side);
+ *aPos = aGridOrigin + startPos;
+ *aLength = std::max(endPos - *aPos, 0);
+ }
+ } else {
+ if (mStart == kAutoLine) {
+ auto side =
+ mEnd == 0 ? GridLineSide::AfterGridGap : GridLineSide::BeforeGridGap;
+ nscoord endPos = aTracks.GridLineEdge(mEnd, side);
+ *aLength = std::max(aGridOrigin + endPos, 0);
+ } else if (MOZ_LIKELY(mStart != mEnd)) {
+ nscoord pos;
+ ToPositionAndLength(aTracks.mSizes, &pos, aLength);
+ *aPos = aGridOrigin + pos;
+ } else {
+ // The grid area only covers removed 'auto-fit' tracks.
+ nscoord pos = aTracks.GridLineEdge(mStart, GridLineSide::BeforeGridGap);
+ *aPos = aGridOrigin + pos;
+ *aLength = nscoord(0);
+ }
+ }
+}
+
+LogicalSize nsGridContainerFrame::GridReflowInput::PercentageBasisFor(
+ LogicalAxis aAxis, const GridItemInfo& aGridItem) const {
+ auto wm = aGridItem.mFrame->GetWritingMode();
+ const auto* itemParent = aGridItem.mFrame->GetParent();
+ if (MOZ_UNLIKELY(itemParent != mFrame)) {
+ // The item comes from a descendant subgrid. Use the subgrid's
+ // used track sizes to resolve the grid area size, if present.
+ MOZ_ASSERT(itemParent->IsGridContainerFrame());
+ auto* subgridFrame = static_cast<const nsGridContainerFrame*>(itemParent);
+ MOZ_ASSERT(subgridFrame->IsSubgrid());
+ if (auto* uts = subgridFrame->GetUsedTrackSizes()) {
+ auto subgridWM = subgridFrame->GetWritingMode();
+ LogicalSize cbSize(subgridWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ if (!subgridFrame->IsSubgrid(eLogicalAxisInline) &&
+ uts->mCanResolveLineRangeSize[eLogicalAxisInline]) {
+ // NOTE: At this point aGridItem.mArea is in this->mFrame coordinates
+ // and thus may have been transposed. The range values in a non-
+ // subgridded axis still has its original values in subgridFrame's
+ // coordinates though.
+ auto rangeAxis = subgridWM.IsOrthogonalTo(mWM) ? eLogicalAxisBlock
+ : eLogicalAxisInline;
+ const auto& range = aGridItem.mArea.LineRangeForAxis(rangeAxis);
+ cbSize.ISize(subgridWM) =
+ range.ToLength(uts->mSizes[eLogicalAxisInline]);
+ }
+ if (!subgridFrame->IsSubgrid(eLogicalAxisBlock) &&
+ uts->mCanResolveLineRangeSize[eLogicalAxisBlock]) {
+ auto rangeAxis = subgridWM.IsOrthogonalTo(mWM) ? eLogicalAxisInline
+ : eLogicalAxisBlock;
+ const auto& range = aGridItem.mArea.LineRangeForAxis(rangeAxis);
+ cbSize.BSize(subgridWM) =
+ range.ToLength(uts->mSizes[eLogicalAxisBlock]);
+ }
+ return cbSize.ConvertTo(wm, subgridWM);
+ }
+
+ return LogicalSize(wm, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ if (aAxis == eLogicalAxisInline || !mCols.mCanResolveLineRangeSize) {
+ return LogicalSize(wm, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+ // Note: for now, we only resolve transferred percentages to row sizing.
+ // We may need to adjust these assertions once we implement bug 1300366.
+ MOZ_ASSERT(!mRows.mCanResolveLineRangeSize);
+ nscoord colSize = aGridItem.mArea.mCols.ToLength(mCols.mSizes);
+ nscoord rowSize = NS_UNCONSTRAINEDSIZE;
+ return !wm.IsOrthogonalTo(mWM) ? LogicalSize(wm, colSize, rowSize)
+ : LogicalSize(wm, rowSize, colSize);
+}
+
+LogicalRect nsGridContainerFrame::GridReflowInput::ContainingBlockFor(
+ const GridArea& aArea) const {
+ nscoord i, b, iSize, bSize;
+ MOZ_ASSERT(aArea.mCols.Extent() > 0, "grid items cover at least one track");
+ MOZ_ASSERT(aArea.mRows.Extent() > 0, "grid items cover at least one track");
+ aArea.mCols.ToPositionAndLength(mCols.mSizes, &i, &iSize);
+ aArea.mRows.ToPositionAndLength(mRows.mSizes, &b, &bSize);
+ return LogicalRect(mWM, i, b, iSize, bSize);
+}
+
+LogicalRect nsGridContainerFrame::GridReflowInput::ContainingBlockForAbsPos(
+ const GridArea& aArea, const LogicalPoint& aGridOrigin,
+ const LogicalRect& aGridCB) const {
+ nscoord i = aGridCB.IStart(mWM);
+ nscoord b = aGridCB.BStart(mWM);
+ nscoord iSize = aGridCB.ISize(mWM);
+ nscoord bSize = aGridCB.BSize(mWM);
+ aArea.mCols.ToPositionAndLengthForAbsPos(mCols, aGridOrigin.I(mWM), &i,
+ &iSize);
+ aArea.mRows.ToPositionAndLengthForAbsPos(mRows, aGridOrigin.B(mWM), &b,
+ &bSize);
+ return LogicalRect(mWM, i, b, iSize, bSize);
+}
+
+void nsGridContainerFrame::GridReflowInput::AlignJustifyContentInMasonryAxis(
+ nscoord aMasonryBoxSize, nscoord aContentBoxSize) {
+ if (aContentBoxSize == NS_UNCONSTRAINEDSIZE) {
+ aContentBoxSize = aMasonryBoxSize;
+ }
+ auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols;
+ MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2,
+ "unexpected masonry axis tracks");
+ const auto masonryAxis = masonryAxisTracks.mAxis;
+ const auto contentAlignment = mGridStyle->UsedContentAlignment(masonryAxis);
+ if (contentAlignment.primary == StyleAlignFlags::NORMAL ||
+ contentAlignment.primary == StyleAlignFlags::STRETCH) {
+ // Stretch the "masonry box" to the full content box if it's smaller.
+ nscoord cbSize = std::max(aMasonryBoxSize, aContentBoxSize);
+ for (auto& sz : masonryAxisTracks.mSizes) {
+ sz.mBase = cbSize;
+ }
+ return;
+ }
+
+ // Save our current track sizes; replace them with one track sized to
+ // the masonry box and align that within our content box.
+ auto savedTrackSizes(std::move(masonryAxisTracks.mSizes));
+ masonryAxisTracks.mSizes.AppendElement(savedTrackSizes[0]);
+ masonryAxisTracks.mSizes[0].mBase = aMasonryBoxSize;
+ masonryAxisTracks.AlignJustifyContent(mGridStyle, contentAlignment, mWM,
+ aContentBoxSize, false);
+ nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition;
+ // Restore the original track sizes...
+ masonryAxisTracks.mSizes = std::move(savedTrackSizes);
+ // ...then reposition and resize all of them to the aligned result.
+ for (auto& sz : masonryAxisTracks.mSizes) {
+ sz.mPosition = masonryBoxOffset;
+ sz.mBase = aMasonryBoxSize;
+ }
+}
+
+// Note: this is called after all items have been positioned/reflowed.
+// The masonry-axis tracks have the size of the "masonry box" at this point
+// and are positioned according to 'align/justify-content'.
+void nsGridContainerFrame::GridReflowInput::AlignJustifyTracksInMasonryAxis(
+ const LogicalSize& aContentSize, const nsSize& aContainerSize) {
+ auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols;
+ MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2,
+ "unexpected masonry axis tracks");
+ const auto masonryAxis = masonryAxisTracks.mAxis;
+ auto gridAxis = GetOrthogonalAxis(masonryAxis);
+ auto& gridAxisTracks = TracksFor(gridAxis);
+ AutoTArray<TrackSize, 32> savedSizes;
+ savedSizes.AppendElements(masonryAxisTracks.mSizes);
+ auto wm = mWM;
+ nscoord contentAreaStart = mBorderPadding.Start(masonryAxis, wm);
+ // The offset to the "masonry box" from our content-box start edge.
+ nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition;
+ nscoord alignmentContainerSize = masonryAxisTracks.mSizes[0].mBase;
+
+ for (auto i : IntegerRange(gridAxisTracks.mSizes.Length())) {
+ auto tracksAlignment = mGridStyle->UsedTracksAlignment(masonryAxis, i);
+ if (tracksAlignment.primary != StyleAlignFlags::START) {
+ masonryAxisTracks.mSizes.ClearAndRetainStorage();
+ for (const auto& item : mGridItems) {
+ if (item.mArea.LineRangeForAxis(gridAxis).mStart == i) {
+ const auto* child = item.mFrame;
+ LogicalRect rect = child->GetLogicalRect(wm, aContainerSize);
+ TrackSize sz = {0, 0, 0, {0, 0}, TrackSize::StateBits{0}};
+ const auto& margin = child->GetLogicalUsedMargin(wm);
+ sz.mPosition = rect.Start(masonryAxis, wm) -
+ margin.Start(masonryAxis, wm) - contentAreaStart;
+ sz.mBase =
+ rect.Size(masonryAxis, wm) + margin.StartEnd(masonryAxis, wm);
+ // Account for a align-self baseline offset on the end side.
+ // XXXmats hmm, it seems it would be a lot simpler to just store
+ // these baseline adjustments into the UsedMarginProperty instead
+ auto state = item.mState[masonryAxis];
+ if ((state & ItemState::eSelfBaseline) &&
+ (state & ItemState::eEndSideBaseline)) {
+ sz.mBase += item.mBaselineOffset[masonryAxis];
+ }
+ if (tracksAlignment.primary == StyleAlignFlags::STRETCH) {
+ const auto* pos = child->StylePosition();
+ auto itemAlignment =
+ pos->UsedSelfAlignment(masonryAxis, mFrame->Style());
+ if (child->StyleMargin()->HasAuto(masonryAxis, wm)) {
+ sz.mState |= TrackSize::eAutoMaxSizing;
+ sz.mState |= TrackSize::eItemHasAutoMargin;
+ } else if (pos->Size(masonryAxis, wm).IsAuto() &&
+ (itemAlignment == StyleAlignFlags::NORMAL ||
+ itemAlignment == StyleAlignFlags::STRETCH)) {
+ sz.mState |= TrackSize::eAutoMaxSizing;
+ sz.mState |= TrackSize::eItemStretchSize;
+ const auto& max = pos->MaxSize(masonryAxis, wm);
+ if (max.ConvertsToLength()) { // XXX deal with percentages
+ // XXX add in baselineOffset ? use actual frame size - content
+ // size?
+ nscoord boxSizingAdjust =
+ child->GetLogicalUsedBorderAndPadding(wm).StartEnd(
+ masonryAxis, wm);
+ if (pos->mBoxSizing == StyleBoxSizing::Border) {
+ boxSizingAdjust = 0;
+ }
+ sz.mLimit = nsLayoutUtils::ComputeBSizeValue(
+ aContentSize.Size(masonryAxis, wm), boxSizingAdjust,
+ max.AsLengthPercentage());
+ sz.mLimit += margin.StartEnd(masonryAxis, wm);
+ sz.mState |= TrackSize::eClampToLimit;
+ }
+ }
+ }
+ masonryAxisTracks.mSizes.AppendElement(std::move(sz));
+ }
+ }
+ masonryAxisTracks.AlignJustifyContent(mGridStyle, tracksAlignment, wm,
+ alignmentContainerSize, false);
+ auto iter = mGridItems.begin();
+ auto end = mGridItems.end();
+ // We limit the loop to the number of items we found in the current
+ // grid-axis axis track (in the outer loop) as an optimization.
+ for (auto r : IntegerRange(masonryAxisTracks.mSizes.Length())) {
+ GridItemInfo* item = nullptr;
+ auto& sz = masonryAxisTracks.mSizes[r];
+ // Find the next item in the current grid-axis axis track.
+ for (; iter != end; ++iter) {
+ if (iter->mArea.LineRangeForAxis(gridAxis).mStart == i) {
+ item = &*iter;
+ ++iter;
+ break;
+ }
+ }
+ nsIFrame* child = item->mFrame;
+ const auto childWM = child->GetWritingMode();
+ auto masonryChildAxis =
+ childWM.IsOrthogonalTo(wm) ? gridAxis : masonryAxis;
+ LogicalMargin margin = child->GetLogicalUsedMargin(childWM);
+ bool forceReposition = false;
+ if (sz.mState & TrackSize::eItemStretchSize) {
+ auto size = child->GetLogicalSize().Size(masonryChildAxis, childWM);
+ auto newSize = sz.mBase - margin.StartEnd(masonryChildAxis, childWM);
+ if (size != newSize) {
+ // XXX need to pass aIMinSizeClamp aBMinSizeClamp ?
+ LogicalSize cb =
+ ContainingBlockFor(item->mArea).Size(wm).ConvertTo(childWM, wm);
+ LogicalSize availableSize = cb;
+ cb.Size(masonryChildAxis, childWM) = alignmentContainerSize;
+ availableSize.Size(eLogicalAxisBlock, childWM) =
+ NS_UNCONSTRAINEDSIZE;
+ const auto& bp = child->GetLogicalUsedBorderAndPadding(childWM);
+ newSize -= bp.StartEnd(masonryChildAxis, childWM);
+ ::PostReflowStretchChild(child, *mReflowInput, availableSize, cb,
+ masonryChildAxis, newSize);
+ if (childWM.IsPhysicalRTL()) {
+ // The NormalPosition of this child is frame-size dependent so we
+ // need to reset its stored position below.
+ forceReposition = true;
+ }
+ }
+ } else if (sz.mState & TrackSize::eItemHasAutoMargin) {
+ // Re-compute the auto-margin(s) in the masonry axis.
+ auto size = child->GetLogicalSize().Size(masonryChildAxis, childWM);
+ auto spaceToFill = sz.mBase - size;
+ if (spaceToFill > nscoord(0)) {
+ const auto& marginStyle = child->StyleMargin();
+ if (marginStyle->mMargin.Start(masonryChildAxis, childWM)
+ .IsAuto()) {
+ if (marginStyle->mMargin.End(masonryChildAxis, childWM)
+ .IsAuto()) {
+ nscoord half;
+ nscoord roundingError = NSCoordDivRem(spaceToFill, 2, &half);
+ margin.Start(masonryChildAxis, childWM) = half;
+ margin.End(masonryChildAxis, childWM) = half + roundingError;
+ } else {
+ margin.Start(masonryChildAxis, childWM) = spaceToFill;
+ }
+ } else {
+ MOZ_ASSERT(
+ marginStyle->mMargin.End(masonryChildAxis, childWM).IsAuto());
+ margin.End(masonryChildAxis, childWM) = spaceToFill;
+ }
+ nsMargin* propValue =
+ child->GetProperty(nsIFrame::UsedMarginProperty());
+ if (propValue) {
+ *propValue = margin.GetPhysicalMargin(childWM);
+ } else {
+ child->AddProperty(
+ nsIFrame::UsedMarginProperty(),
+ new nsMargin(margin.GetPhysicalMargin(childWM)));
+ }
+ }
+ }
+ nscoord newPos = contentAreaStart + masonryBoxOffset + sz.mPosition +
+ margin.Start(masonryChildAxis, childWM);
+ LogicalPoint pos = child->GetLogicalNormalPosition(wm, aContainerSize);
+ auto delta = newPos - pos.Pos(masonryAxis, wm);
+ if (delta != 0 || forceReposition) {
+ LogicalPoint logicalDelta(wm);
+ logicalDelta.Pos(masonryAxis, wm) = delta;
+ child->MovePositionBy(wm, logicalDelta);
+ }
+ }
+ } else if (masonryBoxOffset != nscoord(0)) {
+ // TODO move placeholders too
+ auto delta = masonryBoxOffset;
+ LogicalPoint logicalDelta(wm);
+ logicalDelta.Pos(masonryAxis, wm) = delta;
+ for (const auto& item : mGridItems) {
+ if (item.mArea.LineRangeForAxis(gridAxis).mStart != i) {
+ continue;
+ }
+ item.mFrame->MovePositionBy(wm, logicalDelta);
+ }
+ }
+ }
+ masonryAxisTracks.mSizes = std::move(savedSizes);
+}
+
+/**
+ * Return a Fragmentainer object if we have a fragmentainer frame in our
+ * ancestor chain of containing block (CB) reflow inputs. We'll only
+ * continue traversing the ancestor chain as long as the CBs have
+ * the same writing-mode and have overflow:visible.
+ */
+Maybe<nsGridContainerFrame::Fragmentainer>
+nsGridContainerFrame::GetNearestFragmentainer(
+ const GridReflowInput& aState) const {
+ Maybe<nsGridContainerFrame::Fragmentainer> data;
+ const ReflowInput* gridRI = aState.mReflowInput;
+ if (!gridRI->IsInFragmentedContext()) {
+ return data;
+ }
+ WritingMode wm = aState.mWM;
+ const ReflowInput* cbRI = gridRI->mCBReflowInput;
+ for (; cbRI; cbRI = cbRI->mCBReflowInput) {
+ nsIScrollableFrame* sf = do_QueryFrame(cbRI->mFrame);
+ if (sf) {
+ break;
+ }
+ if (wm.IsOrthogonalTo(cbRI->GetWritingMode())) {
+ break;
+ }
+ LayoutFrameType frameType = cbRI->mFrame->Type();
+ if ((frameType == LayoutFrameType::Canvas &&
+ PresContext()->IsPaginated()) ||
+ frameType == LayoutFrameType::ColumnSet) {
+ data.emplace();
+ data->mIsTopOfPage = gridRI->mFlags.mIsTopOfPage;
+ if (gridRI->AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
+ data->mToFragmentainerEnd = aState.mFragBStart +
+ gridRI->AvailableBSize() -
+ aState.mBorderPadding.BStart(wm);
+ } else {
+ // This occurs when nsColumnSetFrame reflows its last column in
+ // unconstrained available block-size.
+ data->mToFragmentainerEnd = NS_UNCONSTRAINEDSIZE;
+ }
+ const auto numRows = aState.mRows.mSizes.Length();
+ data->mCanBreakAtStart =
+ numRows > 0 && aState.mRows.mSizes[0].mPosition > 0;
+ nscoord bSize = gridRI->ComputedBSize();
+ data->mIsAutoBSize = bSize == NS_UNCONSTRAINEDSIZE;
+ if (data->mIsAutoBSize) {
+ bSize = gridRI->ComputedMinBSize();
+ } else {
+ bSize = gridRI->ApplyMinMaxBSize(bSize);
+ }
+ nscoord gridEnd =
+ aState.mRows.GridLineEdge(numRows, GridLineSide::BeforeGridGap);
+ data->mCanBreakAtEnd = bSize > gridEnd && bSize > aState.mFragBStart;
+ break;
+ }
+ }
+ return data;
+}
+
+void nsGridContainerFrame::ReflowInFlowChild(
+ nsIFrame* aChild, const GridItemInfo* aGridItemInfo, nsSize aContainerSize,
+ const Maybe<nscoord>& aStretchBSize, const Fragmentainer* aFragmentainer,
+ const GridReflowInput& aState, const LogicalRect& aContentArea,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus) {
+ nsPresContext* pc = PresContext();
+ ComputedStyle* containerSC = Style();
+ WritingMode wm = aState.mReflowInput->GetWritingMode();
+ const bool isGridItem = !!aGridItemInfo;
+ MOZ_ASSERT(isGridItem == !aChild->IsPlaceholderFrame());
+ LogicalRect cb(wm);
+ WritingMode childWM = aChild->GetWritingMode();
+ bool isConstrainedBSize = false;
+ nscoord toFragmentainerEnd;
+ // The part of the child's grid area that's in previous container fragments.
+ nscoord consumedGridAreaBSize = 0;
+ const bool isOrthogonal = wm.IsOrthogonalTo(childWM);
+ if (MOZ_LIKELY(isGridItem)) {
+ MOZ_ASSERT(aGridItemInfo->mFrame == aChild);
+ const GridArea& area = aGridItemInfo->mArea;
+ MOZ_ASSERT(area.IsDefinite());
+ cb = aState.ContainingBlockFor(area);
+ if (aFragmentainer && !wm.IsOrthogonalTo(childWM)) {
+ // |gridAreaBOffset| is the offset of the child's grid area in this
+ // container fragment (if negative, that distance is the child CB size
+ // consumed in previous container fragments). Note that cb.BStart
+ // (initially) and aState.mFragBStart are in "global" grid coordinates
+ // (like all track positions).
+ nscoord gridAreaBOffset = cb.BStart(wm) - aState.mFragBStart;
+ consumedGridAreaBSize = std::max(0, -gridAreaBOffset);
+ cb.BStart(wm) = std::max(0, gridAreaBOffset);
+ if (aFragmentainer->mToFragmentainerEnd != NS_UNCONSTRAINEDSIZE) {
+ toFragmentainerEnd = aFragmentainer->mToFragmentainerEnd -
+ aState.mFragBStart - cb.BStart(wm);
+ toFragmentainerEnd = std::max(toFragmentainerEnd, 0);
+ isConstrainedBSize = true;
+ }
+ }
+ cb += aContentArea.Origin(wm);
+ aState.mRows.AlignBaselineSubtree(*aGridItemInfo);
+ aState.mCols.AlignBaselineSubtree(*aGridItemInfo);
+ // Setup [align|justify]-content:[last ]baseline related frame properties.
+ // These are added to the padding in SizeComputationInput::InitOffsets.
+ // (a negative value signals the value is for 'last baseline' and should be
+ // added to the (logical) end padding)
+ typedef const FramePropertyDescriptor<SmallValueHolder<nscoord>>* Prop;
+ auto SetProp = [aGridItemInfo, aChild](LogicalAxis aGridAxis, Prop aProp) {
+ auto state = aGridItemInfo->mState[aGridAxis];
+ auto baselineAdjust = (state & ItemState::eContentBaseline)
+ ? aGridItemInfo->mBaselineOffset[aGridAxis]
+ : nscoord(0);
+ if (baselineAdjust < nscoord(0)) {
+ // This happens when the subtree overflows its track.
+ // XXX spec issue? it's unclear how to handle this.
+ baselineAdjust = nscoord(0);
+ } else if (GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
+ baselineAdjust = -baselineAdjust;
+ }
+ if (baselineAdjust != nscoord(0)) {
+ aChild->SetProperty(aProp, baselineAdjust);
+ } else {
+ aChild->RemoveProperty(aProp);
+ }
+ };
+ SetProp(eLogicalAxisBlock,
+ isOrthogonal ? IBaselinePadProperty() : BBaselinePadProperty());
+ SetProp(eLogicalAxisInline,
+ isOrthogonal ? BBaselinePadProperty() : IBaselinePadProperty());
+ } else {
+ // By convention, for frames that perform CSS Box Alignment, we position
+ // placeholder children at the start corner of their alignment container,
+ // and in this case that's usually the grid's content-box.
+ // ("Usually" - the exception is when the grid *also* forms the
+ // abs.pos. containing block. In that case, the alignment container isn't
+ // the content-box -- it's some grid area instead. But that case doesn't
+ // require any special handling here, because we handle it later using a
+ // special flag (ReflowInput::InitFlag::StaticPosIsCBOrigin) which will make
+ // us ignore the placeholder's position entirely.)
+ cb = aContentArea;
+ aChild->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
+ }
+
+ LogicalSize reflowSize(cb.Size(wm));
+ if (isConstrainedBSize) {
+ reflowSize.BSize(wm) = toFragmentainerEnd;
+ }
+ LogicalSize childCBSize = reflowSize.ConvertTo(childWM, wm);
+
+ // Setup the ClampMarginBoxMinSize reflow flags and property, if needed.
+ ComputeSizeFlags csFlags;
+ if (aGridItemInfo) {
+ const auto childIAxisInWM =
+ isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
+ // Clamp during reflow if we're stretching in that axis.
+ if (GridItemShouldStretch(aChild, eLogicalAxisInline)) {
+ if (aGridItemInfo->mState[childIAxisInWM] &
+ ItemState::eClampMarginBoxMinSize) {
+ csFlags += ComputeSizeFlag::IClampMarginBoxMinSize;
+ }
+ } else {
+ csFlags += ComputeSizeFlag::ShrinkWrap;
+ }
+
+ const auto childBAxisInWM = GetOrthogonalAxis(childIAxisInWM);
+ if (GridItemShouldStretch(aChild, eLogicalAxisBlock) &&
+ aGridItemInfo->mState[childBAxisInWM] &
+ ItemState::eClampMarginBoxMinSize) {
+ csFlags += ComputeSizeFlag::BClampMarginBoxMinSize;
+ aChild->SetProperty(BClampMarginBoxMinSizeProperty(),
+ childCBSize.BSize(childWM));
+ } else {
+ aChild->RemoveProperty(BClampMarginBoxMinSizeProperty());
+ }
+
+ if ((aGridItemInfo->mState[childIAxisInWM] &
+ ItemState::eApplyAutoMinSize)) {
+ csFlags += ComputeSizeFlag::IApplyAutoMinSize;
+ }
+ }
+
+ if (!isConstrainedBSize) {
+ childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
+ }
+ LogicalSize percentBasis(cb.Size(wm).ConvertTo(childWM, wm));
+ ReflowInput childRI(pc, *aState.mReflowInput, aChild, childCBSize,
+ Some(percentBasis), {}, {}, csFlags);
+ childRI.mFlags.mIsTopOfPage =
+ aFragmentainer ? aFragmentainer->mIsTopOfPage : false;
+
+ // FIXME (perf): It would be faster to do this only if the previous reflow of
+ // the child was a measuring reflow, and only if the child does some of the
+ // things that are affected by ComputeSizeFlag::IsGridMeasuringReflow.
+ childRI.SetBResize(true);
+ childRI.mFlags.mIsBResizeForPercentages = true;
+
+ // If the child is stretching in its block axis, and we might be fragmenting
+ // it in that axis, then setup a frame property to tell
+ // nsBlockFrame::ComputeFinalSize the size.
+ if (isConstrainedBSize && !wm.IsOrthogonalTo(childWM)) {
+ const bool stretch = childRI.mStylePosition->BSize(childWM).IsAuto() &&
+ GridItemShouldStretch(aChild, eLogicalAxisBlock);
+ if (stretch) {
+ aChild->SetProperty(FragStretchBSizeProperty(), *aStretchBSize);
+ } else {
+ aChild->RemoveProperty(FragStretchBSizeProperty());
+ }
+ }
+
+ // We need the width of the child before we can correctly convert
+ // the writing-mode of its origin, so we reflow at (0, 0) using a dummy
+ // aContainerSize, and then pass the correct position to FinishReflowChild.
+ ReflowOutput childSize(childRI);
+ const nsSize dummyContainerSize;
+
+ ReflowChild(aChild, pc, childSize, childRI, childWM, LogicalPoint(childWM),
+ dummyContainerSize, ReflowChildFlags::Default, aStatus);
+ LogicalPoint childPos = cb.Origin(wm).ConvertTo(
+ childWM, wm, aContainerSize - childSize.PhysicalSize());
+ // Apply align/justify-self and reflow again if that affects the size.
+ if (MOZ_LIKELY(isGridItem)) {
+ LogicalSize size = childSize.Size(childWM); // from the ReflowChild()
+ auto applyItemSelfAlignment = [&](LogicalAxis aAxis, nscoord aCBSize) {
+ auto align =
+ childRI.mStylePosition->UsedSelfAlignment(aAxis, containerSC);
+ auto state = aGridItemInfo->mState[aAxis];
+ auto flags = AlignJustifyFlags::NoFlags;
+ if (IsMasonry(aAxis)) {
+ // In a masonry axis, we inhibit applying 'stretch' and auto-margins
+ // here since AlignJustifyTracksInMasonryAxis deals with that.
+ // The only other {align,justify}-{self,content} values that have an
+ // effect are '[last] baseline', the rest behave as 'start'.
+ if (MOZ_LIKELY(!(state & ItemState::eSelfBaseline))) {
+ align = {StyleAlignFlags::START};
+ } else {
+ auto group = (state & ItemState::eFirstBaseline)
+ ? BaselineSharingGroup::First
+ : BaselineSharingGroup::Last;
+ auto itemStart = aGridItemInfo->mArea.LineRangeForAxis(aAxis).mStart;
+ aCBSize = aState.TracksFor(aAxis)
+ .mSizes[itemStart]
+ .mBaselineSubtreeSize[group];
+ }
+ flags = AlignJustifyFlags::IgnoreAutoMargins;
+ } else if (state & ItemState::eContentBaseline) {
+ align = {(state & ItemState::eFirstBaseline)
+ ? StyleAlignFlags::SELF_START
+ : StyleAlignFlags::SELF_END};
+ }
+ if (aAxis == eLogicalAxisBlock) {
+ AlignSelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags,
+ &childPos);
+ } else {
+ JustifySelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags,
+ &childPos);
+ }
+ };
+ if (aStatus.IsComplete()) {
+ applyItemSelfAlignment(eLogicalAxisBlock,
+ cb.BSize(wm) - consumedGridAreaBSize);
+ }
+ applyItemSelfAlignment(eLogicalAxisInline, cb.ISize(wm));
+ } // else, nsAbsoluteContainingBlock.cpp will handle align/justify-self.
+
+ FinishReflowChild(aChild, pc, childSize, &childRI, childWM, childPos,
+ aContainerSize, ReflowChildFlags::ApplyRelativePositioning);
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild);
+}
+
+nscoord nsGridContainerFrame::ReflowInFragmentainer(
+ GridReflowInput& aState, const LogicalRect& aContentArea,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus,
+ Fragmentainer& aFragmentainer, const nsSize& aContainerSize) {
+ MOZ_ASSERT(aStatus.IsEmpty());
+ MOZ_ASSERT(aState.mReflowInput);
+
+ // Collect our grid items and sort them in row order. Collect placeholders
+ // and put them in a separate array.
+ nsTArray<const GridItemInfo*> sortedItems(aState.mGridItems.Length());
+ nsTArray<nsIFrame*> placeholders(aState.mAbsPosItems.Length());
+ aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll);
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ nsIFrame* child = *aState.mIter;
+ if (!child->IsPlaceholderFrame()) {
+ const GridItemInfo* info = &aState.mGridItems[aState.mIter.ItemIndex()];
+ sortedItems.AppendElement(info);
+ } else {
+ placeholders.AppendElement(child);
+ }
+ }
+ // NOTE: We don't need stable_sort here, except in Masonry layout. There are
+ // no dependencies on having content order between items on the same row in
+ // the code below in the non-Masonry case.
+ if (IsMasonry()) {
+ std::stable_sort(sortedItems.begin(), sortedItems.end(),
+ GridItemInfo::IsStartRowLessThan);
+ } else {
+ std::sort(sortedItems.begin(), sortedItems.end(),
+ GridItemInfo::IsStartRowLessThan);
+ }
+
+ // Reflow our placeholder children; they must all be complete.
+ for (auto child : placeholders) {
+ nsReflowStatus childStatus;
+ ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(),
+ &aFragmentainer, aState, aContentArea, aDesiredSize,
+ childStatus);
+ MOZ_ASSERT(childStatus.IsComplete(),
+ "nsPlaceholderFrame should never need to be fragmented");
+ }
+
+ // The available size for children - we'll set this to the edge of the last
+ // row in most cases below, but for now use the full size.
+ nscoord childAvailableSize = aFragmentainer.mToFragmentainerEnd;
+ const uint32_t startRow = aState.mStartRow;
+ const uint32_t numRows = aState.mRows.mSizes.Length();
+ bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone;
+ nscoord bpBEnd = aState.mBorderPadding.BEnd(aState.mWM);
+
+ // Set |endRow| to the first row that doesn't fit.
+ uint32_t endRow = numRows;
+ for (uint32_t row = startRow; row < numRows; ++row) {
+ auto& sz = aState.mRows.mSizes[row];
+ const nscoord bEnd = sz.mPosition + sz.mBase;
+ nscoord remainingAvailableSize = childAvailableSize - bEnd;
+ if (remainingAvailableSize < 0 ||
+ (isBDBClone && remainingAvailableSize < bpBEnd)) {
+ endRow = row;
+ break;
+ }
+ }
+
+ // Check for forced breaks on the items if available block-size for children
+ // is constrained. That is, ignore forced breaks if available block-size for
+ // children is unconstrained since our parent expected us to be fully
+ // complete.
+ bool isForcedBreak = false;
+ const bool avoidBreakInside = ShouldAvoidBreakInside(*aState.mReflowInput);
+ if (childAvailableSize != NS_UNCONSTRAINEDSIZE) {
+ const bool isTopOfPage = aFragmentainer.mIsTopOfPage;
+ for (const GridItemInfo* info : sortedItems) {
+ uint32_t itemStartRow = info->mArea.mRows.mStart;
+ if (itemStartRow == endRow) {
+ break;
+ }
+ const auto* disp = info->mFrame->StyleDisplay();
+ if (disp->BreakBefore()) {
+ // Propagate break-before on the first row to the container unless we're
+ // already at top-of-page.
+ if ((itemStartRow == 0 && !isTopOfPage) || avoidBreakInside) {
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return aState.mFragBStart;
+ }
+ if ((itemStartRow > startRow ||
+ (itemStartRow == startRow && !isTopOfPage)) &&
+ itemStartRow < endRow) {
+ endRow = itemStartRow;
+ isForcedBreak = true;
+ // reset any BREAK_AFTER we found on an earlier item
+ aStatus.Reset();
+ break; // we're done since the items are sorted in row order
+ }
+ }
+ uint32_t itemEndRow = info->mArea.mRows.mEnd;
+ if (disp->BreakAfter()) {
+ if (itemEndRow != numRows) {
+ if (itemEndRow > startRow && itemEndRow < endRow) {
+ endRow = itemEndRow;
+ isForcedBreak = true;
+ // No "break;" here since later items with break-after may have
+ // a shorter span.
+ }
+ } else {
+ // Propagate break-after on the last row to the container, we may
+ // still find a break-before on this row though (and reset aStatus).
+ aStatus.SetInlineLineBreakAfter(); // tentative
+ }
+ }
+ }
+
+ // Consume at least one row in each fragment until we have consumed them
+ // all. Except for the first row if there's a break opportunity before it.
+ if (startRow == endRow && startRow != numRows &&
+ (startRow != 0 || !aFragmentainer.mCanBreakAtStart)) {
+ ++endRow;
+ }
+
+ // Honor break-inside:avoid if we can't fit all rows.
+ if (avoidBreakInside && endRow < numRows) {
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return aState.mFragBStart;
+ }
+ }
+
+ // Calculate the block-size including this fragment.
+ nscoord bEndRow =
+ aState.mRows.GridLineEdge(endRow, GridLineSide::BeforeGridGap);
+ nscoord bSize;
+ if (aFragmentainer.mIsAutoBSize) {
+ // We only apply min-bsize once all rows are complete (when bsize is auto).
+ if (endRow < numRows) {
+ bSize = bEndRow;
+ auto clampedBSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput);
+ if (MOZ_UNLIKELY(clampedBSize != bSize)) {
+ // We apply max-bsize in all fragments though.
+ bSize = clampedBSize;
+ } else if (!isBDBClone) {
+ // The max-bsize won't make this fragment COMPLETE, so the block-end
+ // border will be in a later fragment.
+ bpBEnd = 0;
+ }
+ } else {
+ bSize = aState.mReflowInput->ApplyMinMaxBSize(bEndRow);
+ }
+ } else {
+ bSize = aState.mReflowInput->ApplyMinMaxBSize(
+ aState.mReflowInput->ComputedBSize());
+ }
+
+ // Check for overflow and set aStatus INCOMPLETE if so.
+ bool overflow = bSize + bpBEnd > childAvailableSize;
+ if (overflow) {
+ if (avoidBreakInside) {
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return aState.mFragBStart;
+ }
+ bool breakAfterLastRow = endRow == numRows && aFragmentainer.mCanBreakAtEnd;
+ if (breakAfterLastRow) {
+ MOZ_ASSERT(bEndRow < bSize, "bogus aFragmentainer.mCanBreakAtEnd");
+ nscoord availableSize = childAvailableSize;
+ if (isBDBClone) {
+ availableSize -= bpBEnd;
+ }
+ // Pretend we have at least 1px available size, otherwise we'll never make
+ // progress in consuming our bSize.
+ availableSize =
+ std::max(availableSize, aState.mFragBStart + AppUnitsPerCSSPixel());
+ // Fill the fragmentainer, but not more than our desired block-size and
+ // at least to the size of the last row (even if that overflows).
+ nscoord newBSize = std::min(bSize, availableSize);
+ newBSize = std::max(newBSize, bEndRow);
+ // If it's just the border+padding that is overflowing and we have
+ // box-decoration-break:clone then we are technically COMPLETE. There's
+ // no point in creating another zero-bsize fragment in this case.
+ if (newBSize < bSize || !isBDBClone) {
+ aStatus.SetIncomplete();
+ }
+ bSize = newBSize;
+ } else if (bSize <= bEndRow && startRow + 1 < endRow) {
+ if (endRow == numRows) {
+ // We have more than one row in this fragment, so we can break before
+ // the last row instead.
+ --endRow;
+ bEndRow =
+ aState.mRows.GridLineEdge(endRow, GridLineSide::BeforeGridGap);
+ bSize = bEndRow;
+ if (aFragmentainer.mIsAutoBSize) {
+ bSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput);
+ }
+ }
+ aStatus.SetIncomplete();
+ } else if (endRow < numRows) {
+ bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
+ } // else - no break opportunities.
+ } else {
+ // Even though our block-size fits we need to honor forced breaks, or if
+ // a row doesn't fit in an auto-sized container (unless it's constrained
+ // by a max-bsize which make us overflow-incomplete).
+ if (endRow < numRows &&
+ (isForcedBreak || (aFragmentainer.mIsAutoBSize && bEndRow == bSize))) {
+ bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
+ }
+ }
+
+ // If we can't fit all rows then we're at least overflow-incomplete.
+ if (endRow < numRows) {
+ childAvailableSize = bEndRow;
+ if (aStatus.IsComplete()) {
+ aStatus.SetOverflowIncomplete();
+ aStatus.SetNextInFlowNeedsReflow();
+ }
+ } else {
+ // Children always have the full size of the rows in this fragment.
+ childAvailableSize = std::max(childAvailableSize, bEndRow);
+ }
+
+ return ReflowRowsInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
+ aFragmentainer, aContainerSize, sortedItems,
+ startRow, endRow, bSize, childAvailableSize);
+}
+
+nscoord nsGridContainerFrame::ReflowRowsInFragmentainer(
+ GridReflowInput& aState, const LogicalRect& aContentArea,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus,
+ Fragmentainer& aFragmentainer, const nsSize& aContainerSize,
+ const nsTArray<const GridItemInfo*>& aSortedItems, uint32_t aStartRow,
+ uint32_t aEndRow, nscoord aBSize, nscoord aAvailableSize) {
+ FrameHashtable pushedItems;
+ FrameHashtable incompleteItems;
+ FrameHashtable overflowIncompleteItems;
+ Maybe<nsTArray<nscoord>> masonryAxisPos;
+ const auto rowCount = aState.mRows.mSizes.Length();
+ nscoord masonryAxisGap;
+ const auto wm = aState.mWM;
+ const bool isColMasonry = IsMasonry(eLogicalAxisInline);
+ if (isColMasonry) {
+ for (auto& sz : aState.mCols.mSizes) {
+ sz.mPosition = 0;
+ }
+ masonryAxisGap = nsLayoutUtils::ResolveGapToLength(
+ aState.mGridStyle->mColumnGap, aContentArea.ISize(wm));
+ aState.mCols.mGridGap = masonryAxisGap;
+ masonryAxisPos.emplace(rowCount);
+ masonryAxisPos->SetLength(rowCount);
+ PodZero(masonryAxisPos->Elements(), rowCount);
+ }
+ bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone;
+ bool didGrowRow = false;
+ // As we walk across rows, we track whether the current row is at the top
+ // of its grid-fragment, to help decide whether we can break before it. When
+ // this function starts, our row is at the top of the current fragment if:
+ // - we're starting with a nonzero row (i.e. we're a continuation)
+ // OR:
+ // - we're starting with the first row, & we're not allowed to break before
+ // it (which makes it effectively at the top of its grid-fragment).
+ bool isRowTopOfPage = aStartRow != 0 || !aFragmentainer.mCanBreakAtStart;
+ const bool isStartRowTopOfPage = isRowTopOfPage;
+ // Save our full available size for later.
+ const nscoord gridAvailableSize = aFragmentainer.mToFragmentainerEnd;
+ // Propagate the constrained size to our children.
+ aFragmentainer.mToFragmentainerEnd = aAvailableSize;
+ // Reflow the items in row order up to |aEndRow| and push items after that.
+ uint32_t row = 0;
+ // |i| is intentionally signed, so we can set it to -1 to restart the loop.
+ for (int32_t i = 0, len = aSortedItems.Length(); i < len; ++i) {
+ const GridItemInfo* const info = aSortedItems[i];
+ nsIFrame* child = info->mFrame;
+ row = info->mArea.mRows.mStart;
+ MOZ_ASSERT(child->GetPrevInFlow() ? row < aStartRow : row >= aStartRow,
+ "unexpected child start row");
+ if (row >= aEndRow) {
+ pushedItems.Insert(child);
+ continue;
+ }
+
+ bool rowCanGrow = false;
+ nscoord maxRowSize = 0;
+ if (row >= aStartRow) {
+ if (row > aStartRow) {
+ isRowTopOfPage = false;
+ }
+ // Can we grow this row? Only consider span=1 items per spec...
+ rowCanGrow = !didGrowRow && info->mArea.mRows.Extent() == 1;
+ if (rowCanGrow) {
+ auto& sz = aState.mRows.mSizes[row];
+ // and only min-/max-content rows or flex rows in an auto-sized
+ // container
+ rowCanGrow = (sz.mState & TrackSize::eMinOrMaxContentMinSizing) ||
+ ((sz.mState & TrackSize::eFlexMaxSizing) &&
+ aFragmentainer.mIsAutoBSize);
+ if (rowCanGrow) {
+ if (isBDBClone) {
+ maxRowSize = gridAvailableSize - aState.mBorderPadding.BEnd(wm);
+ } else {
+ maxRowSize = gridAvailableSize;
+ }
+ maxRowSize -= sz.mPosition;
+ // ...and only if there is space for it to grow.
+ rowCanGrow = maxRowSize > sz.mBase;
+ }
+ }
+ }
+
+ if (isColMasonry) {
+ const auto& cols = info->mArea.mCols;
+ MOZ_ASSERT((cols.mStart == 0 || cols.mStart == 1) && cols.Extent() == 1);
+ aState.mCols.mSizes[cols.mStart].mPosition = masonryAxisPos.ref()[row];
+ }
+
+ // aFragmentainer.mIsTopOfPage is propagated to the child reflow input.
+ // When it's false the child may request InlineBreak::Before. We set it
+ // to false when the row is growable (as determined in the CSS Grid
+ // Fragmentation spec) and there is a non-zero space between it and the
+ // fragmentainer end (that can be used to grow it). If the child reports
+ // a forced break in this case, we grow this row to fill the fragment and
+ // restart the loop. We also restart the loop with |aEndRow = row|
+ // (but without growing any row) for a InlineBreak::Before child if it spans
+ // beyond the last row in this fragment. This is to avoid fragmenting it.
+ // We only restart the loop once.
+ aFragmentainer.mIsTopOfPage = isRowTopOfPage && !rowCanGrow;
+ nsReflowStatus childStatus;
+ // Pass along how much to stretch this fragment, in case it's needed.
+ nscoord bSize =
+ aState.mRows.GridLineEdge(std::min(aEndRow, info->mArea.mRows.mEnd),
+ GridLineSide::BeforeGridGap) -
+ aState.mRows.GridLineEdge(std::max(aStartRow, row),
+ GridLineSide::AfterGridGap);
+ ReflowInFlowChild(child, info, aContainerSize, Some(bSize), &aFragmentainer,
+ aState, aContentArea, aDesiredSize, childStatus);
+ MOZ_ASSERT(childStatus.IsInlineBreakBefore() ||
+ !childStatus.IsFullyComplete() || !child->GetNextInFlow(),
+ "fully-complete reflow should destroy any NIFs");
+
+ if (childStatus.IsInlineBreakBefore()) {
+ MOZ_ASSERT(
+ !child->GetPrevInFlow(),
+ "continuations should never report InlineBreak::Before status");
+ MOZ_ASSERT(!aFragmentainer.mIsTopOfPage,
+ "got IsInlineBreakBefore() at top of page");
+ if (!didGrowRow) {
+ if (rowCanGrow) {
+ // Grow this row and restart with the next row as |aEndRow|.
+ aState.mRows.ResizeRow(row, maxRowSize);
+ if (aState.mSharedGridData) {
+ aState.mSharedGridData->mRows.ResizeRow(row, maxRowSize);
+ }
+ didGrowRow = true;
+ aEndRow = row + 1; // growing this row makes the next one not fit
+ i = -1; // i == 0 after the next loop increment
+ isRowTopOfPage = isStartRowTopOfPage;
+ overflowIncompleteItems.Clear();
+ incompleteItems.Clear();
+ nscoord bEndRow =
+ aState.mRows.GridLineEdge(aEndRow, GridLineSide::BeforeGridGap);
+ aFragmentainer.mToFragmentainerEnd = bEndRow;
+ if (aFragmentainer.mIsAutoBSize) {
+ aBSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
+ } else if (aStatus.IsIncomplete()) {
+ aBSize = aState.mReflowInput->ApplyMinMaxBSize(
+ aState.mReflowInput->ComputedBSize());
+ aBSize = std::min(bEndRow, aBSize);
+ }
+ continue;
+ }
+
+ if (!isRowTopOfPage) {
+ // We can break before this row - restart with it as the new end row.
+ aEndRow = row;
+ aBSize =
+ aState.mRows.GridLineEdge(aEndRow, GridLineSide::BeforeGridGap);
+ i = -1; // i == 0 after the next loop increment
+ isRowTopOfPage = isStartRowTopOfPage;
+ overflowIncompleteItems.Clear();
+ incompleteItems.Clear();
+ aStatus.SetIncomplete();
+ continue;
+ }
+ NS_ERROR("got InlineBreak::Before at top-of-page");
+ childStatus.Reset();
+ } else {
+ // We got InlineBreak::Before again after growing the row - this can
+ // happen if the child isn't splittable, e.g. some form controls.
+ childStatus.Reset();
+ if (child->GetNextInFlow()) {
+ // The child already has a fragment, so we know it's splittable.
+ childStatus.SetIncomplete();
+ } // else, report that it's complete
+ }
+ } else if (childStatus.IsInlineBreakAfter()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected child reflow status");
+ }
+
+ MOZ_ASSERT(!childStatus.IsInlineBreakBefore(),
+ "should've handled InlineBreak::Before above");
+ if (childStatus.IsIncomplete()) {
+ incompleteItems.Insert(child);
+ } else if (!childStatus.IsFullyComplete()) {
+ overflowIncompleteItems.Insert(child);
+ }
+ if (isColMasonry) {
+ auto childWM = child->GetWritingMode();
+ auto childAxis =
+ !childWM.IsOrthogonalTo(wm) ? eLogicalAxisInline : eLogicalAxisBlock;
+ auto normalPos = child->GetLogicalNormalPosition(wm, aContainerSize);
+ auto sz =
+ childAxis == eLogicalAxisBlock ? child->BSize() : child->ISize();
+ auto pos = normalPos.Pos(eLogicalAxisInline, wm) + sz +
+ child->GetLogicalUsedMargin(childWM).End(childAxis, childWM);
+ masonryAxisPos.ref()[row] =
+ pos + masonryAxisGap - aContentArea.Start(eLogicalAxisInline, wm);
+ }
+ }
+
+ // Record a break before |aEndRow|.
+ aState.mNextFragmentStartRow = aEndRow;
+ if (aEndRow < rowCount) {
+ aState.mRows.BreakBeforeRow(aEndRow);
+ if (aState.mSharedGridData) {
+ aState.mSharedGridData->mRows.BreakBeforeRow(aEndRow);
+ }
+ }
+
+ const bool childrenMoved = PushIncompleteChildren(
+ pushedItems, incompleteItems, overflowIncompleteItems);
+ if (childrenMoved && aStatus.IsComplete()) {
+ aStatus.SetOverflowIncomplete();
+ aStatus.SetNextInFlowNeedsReflow();
+ }
+ if (!pushedItems.IsEmpty()) {
+ AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
+ // NOTE since we messed with our child list here, we intentionally
+ // make aState.mIter invalid to avoid any use of it after this point.
+ aState.mIter.Invalidate();
+ }
+ if (!incompleteItems.IsEmpty()) {
+ // NOTE since we messed with our child list here, we intentionally
+ // make aState.mIter invalid to avoid any use of it after this point.
+ aState.mIter.Invalidate();
+ }
+
+ if (isColMasonry) {
+ nscoord maxSize = 0;
+ for (auto pos : masonryAxisPos.ref()) {
+ maxSize = std::max(maxSize, pos);
+ }
+ maxSize = std::max(nscoord(0), maxSize - masonryAxisGap);
+ aState.AlignJustifyContentInMasonryAxis(maxSize, aContentArea.ISize(wm));
+ }
+
+ return aBSize;
+}
+
+// Here's a brief overview of how Masonry layout is implemented:
+// We setup two synthetic tracks in the Masonry axis so that the Reflow code
+// can treat it the same as for normal grid layout. The first track is
+// fixed (during item placement/layout) at the content box start and contains
+// the start items for each grid-axis track. The second track contains
+// all other items and is moved to the position where we want to position
+// the currently laid out item (like a sliding window as we place items).
+// Once item layout is done, the tracks are resized to be the size of
+// the "masonry box", which is the offset from the content box start to
+// the margin-box end of the item that is furthest away (this happens in
+// AlignJustifyContentInMasonryAxis() called at the end of this method).
+// This is to prepare for AlignJustifyTracksInMasonryAxis, which is called
+// later by our caller.
+// Both tracks store their first-/last-baseline group offsets as usual.
+// The first-baseline of the start track, and the last-baseline of the last
+// track (if they exist) are exported as the grid container's baselines, or
+// we fall back to picking an item's baseline (all this is per normal grid
+// layout). There's a slight difference in which items belongs to which
+// group though - see InitializeItemBaselinesInMasonryAxis for details.
+// This method returns the "masonry box" size (in the masonry axis).
+nscoord nsGridContainerFrame::MasonryLayout(GridReflowInput& aState,
+ const LogicalRect& aContentArea,
+ SizingConstraint aConstraint,
+ ReflowOutput& aDesiredSize,
+ nsReflowStatus& aStatus,
+ Fragmentainer* aFragmentainer,
+ const nsSize& aContainerSize) {
+ using BaselineAlignmentSet = Tracks::BaselineAlignmentSet;
+
+ auto recordAutoPlacement = [this, &aState](GridItemInfo* aItem,
+ LogicalAxis aGridAxis) {
+ // When we're auto-placing an item in a continuation we need to record
+ // the placement in mSharedGridData.
+ if (MOZ_UNLIKELY(aState.mSharedGridData && GetPrevInFlow()) &&
+ (aItem->mState[aGridAxis] & ItemState::eAutoPlacement)) {
+ auto* child = aItem->mFrame;
+ MOZ_RELEASE_ASSERT(!child->GetPrevInFlow(),
+ "continuations should never be auto-placed");
+ for (auto& sharedItem : aState.mSharedGridData->mGridItems) {
+ if (sharedItem.mFrame == child) {
+ sharedItem.mArea.LineRangeForAxis(aGridAxis) =
+ aItem->mArea.LineRangeForAxis(aGridAxis);
+ MOZ_ASSERT(sharedItem.mState[aGridAxis] & ItemState::eAutoPlacement);
+ sharedItem.mState[aGridAxis] &= ~ItemState::eAutoPlacement;
+ break;
+ }
+ }
+ }
+ aItem->mState[aGridAxis] &= ~ItemState::eAutoPlacement;
+ };
+
+ // Collect our grid items and sort them in grid order.
+ nsTArray<GridItemInfo*> sortedItems(aState.mGridItems.Length());
+ aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll);
+ size_t absposIndex = 0;
+ const LogicalAxis masonryAxis =
+ IsMasonry(eLogicalAxisBlock) ? eLogicalAxisBlock : eLogicalAxisInline;
+ const auto wm = aState.mWM;
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ nsIFrame* child = *aState.mIter;
+ if (MOZ_LIKELY(!child->IsPlaceholderFrame())) {
+ GridItemInfo* item = &aState.mGridItems[aState.mIter.ItemIndex()];
+ sortedItems.AppendElement(item);
+ } else if (aConstraint == SizingConstraint::NoConstraint) {
+ // (we only collect placeholders in the NoConstraint case since they
+ // don't affect intrinsic sizing in any way)
+ GridItemInfo* item = nullptr;
+ auto* ph = static_cast<nsPlaceholderFrame*>(child);
+ if (ph->GetOutOfFlowFrame()->GetParent() == this) {
+ item = &aState.mAbsPosItems[absposIndex++];
+ MOZ_RELEASE_ASSERT(item->mFrame == ph->GetOutOfFlowFrame());
+ auto masonryStart = item->mArea.LineRangeForAxis(masonryAxis).mStart;
+ // If the item was placed by the author at line 1 (masonryStart == 0)
+ // then include it to be placed at the masonry-box start. If it's
+ // auto-placed and has an `auto` inset value in the masonry axis then
+ // we include it to be placed after the last grid item with the same
+ // grid-axis start track.
+ // XXXmats this is all a bit experimental at this point, pending a spec
+ if (masonryStart == 0 ||
+ (masonryStart == kAutoLine && item->mFrame->StylePosition()
+ ->mOffset.Start(masonryAxis, wm)
+ .IsAuto())) {
+ sortedItems.AppendElement(item);
+ } else {
+ item = nullptr;
+ }
+ }
+ if (!item) {
+ // It wasn't included above - just reflow it and be done with it.
+ nsReflowStatus childStatus;
+ ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr,
+ aState, aContentArea, aDesiredSize, childStatus);
+ }
+ }
+ }
+ const auto masonryAutoFlow = aState.mGridStyle->mMasonryAutoFlow;
+ const bool definiteFirst =
+ masonryAutoFlow.order == StyleMasonryItemOrder::DefiniteFirst;
+ if (masonryAxis == eLogicalAxisBlock) {
+ std::stable_sort(sortedItems.begin(), sortedItems.end(),
+ definiteFirst ? GridItemInfo::RowMasonryDefiniteFirst
+ : GridItemInfo::RowMasonryOrdered);
+ } else {
+ std::stable_sort(sortedItems.begin(), sortedItems.end(),
+ definiteFirst ? GridItemInfo::ColMasonryDefiniteFirst
+ : GridItemInfo::ColMasonryOrdered);
+ }
+
+ FrameHashtable pushedItems;
+ FrameHashtable incompleteItems;
+ FrameHashtable overflowIncompleteItems;
+ nscoord toFragmentainerEnd = nscoord_MAX;
+ nscoord fragStartPos = aState.mFragBStart;
+ const bool avoidBreakInside =
+ aFragmentainer && ShouldAvoidBreakInside(*aState.mReflowInput);
+ const bool isTopOfPageAtStart =
+ aFragmentainer && aFragmentainer->mIsTopOfPage;
+ if (aFragmentainer) {
+ toFragmentainerEnd = std::max(0, aFragmentainer->mToFragmentainerEnd);
+ }
+ const LogicalAxis gridAxis = GetOrthogonalAxis(masonryAxis);
+ const auto gridAxisTrackCount = aState.TracksFor(gridAxis).mSizes.Length();
+ auto& masonryTracks = aState.TracksFor(masonryAxis);
+ auto& masonrySizes = masonryTracks.mSizes;
+ MOZ_ASSERT(masonrySizes.Length() == 2);
+ for (auto& sz : masonrySizes) {
+ sz.mPosition = fragStartPos;
+ }
+ // The current running position for each grid-axis track where the next item
+ // should be positioned. When an item is placed we'll update the tracks it
+ // spans to the end of its margin box + 'gap'.
+ nsTArray<nscoord> currentPos(gridAxisTrackCount);
+ currentPos.SetLength(gridAxisTrackCount);
+ for (auto& sz : currentPos) {
+ sz = fragStartPos;
+ }
+ nsTArray<nscoord> lastPos(currentPos.Clone());
+ nsTArray<GridItemInfo*> lastItems(gridAxisTrackCount);
+ lastItems.SetLength(gridAxisTrackCount);
+ PodZero(lastItems.Elements(), gridAxisTrackCount);
+ const nscoord gap = nsLayoutUtils::ResolveGapToLength(
+ masonryAxis == eLogicalAxisBlock ? aState.mGridStyle->mRowGap
+ : aState.mGridStyle->mColumnGap,
+ masonryTracks.mContentBoxSize);
+ masonryTracks.mGridGap = gap;
+ uint32_t cursor = 0;
+ const auto containerToMasonryBoxOffset =
+ fragStartPos - aContentArea.Start(masonryAxis, wm);
+ const bool isPack = masonryAutoFlow.placement == StyleMasonryPlacement::Pack;
+ bool didAlignStartAlignedFirstItems = false;
+
+ // Return true if any of the lastItems in aRange are baseline-aligned in
+ // the masonry axis.
+ auto lastItemHasBaselineAlignment = [&](const LineRange& aRange) {
+ for (auto i : aRange.Range()) {
+ if (auto* child = lastItems[i] ? lastItems[i]->mFrame : nullptr) {
+ const auto& pos = child->StylePosition();
+ auto selfAlignment = pos->UsedSelfAlignment(masonryAxis, this->Style());
+ if (selfAlignment == StyleAlignFlags::BASELINE ||
+ selfAlignment == StyleAlignFlags::LAST_BASELINE) {
+ return true;
+ }
+ auto childAxis = masonryAxis;
+ if (child->GetWritingMode().IsOrthogonalTo(wm)) {
+ childAxis = gridAxis;
+ }
+ auto contentAlignment = pos->UsedContentAlignment(childAxis).primary;
+ if (contentAlignment == StyleAlignFlags::BASELINE ||
+ contentAlignment == StyleAlignFlags::LAST_BASELINE) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ // Resolve aItem's placement, unless it's definite already. Return its
+ // masonry axis position with that placement.
+ auto placeItem = [&](GridItemInfo* aItem) -> nscoord {
+ auto& masonryAxisRange = aItem->mArea.LineRangeForAxis(masonryAxis);
+ MOZ_ASSERT(masonryAxisRange.mStart != 0, "item placement is already final");
+ auto& gridAxisRange = aItem->mArea.LineRangeForAxis(gridAxis);
+ bool isAutoPlaced = aItem->mState[gridAxis] & ItemState::eAutoPlacement;
+ uint32_t start = isAutoPlaced ? 0 : gridAxisRange.mStart;
+ if (isAutoPlaced && !isPack) {
+ start = cursor;
+ isAutoPlaced = false;
+ }
+ const uint32_t extent = gridAxisRange.Extent();
+ if (start + extent > gridAxisTrackCount) {
+ // Note that this will only happen to auto-placed items since the grid is
+ // always wide enough to fit other items.
+ start = 0;
+ }
+ // This keeps track of the smallest `maxPosForRange` value that
+ // we discover in the loop below:
+ nscoord minPos = nscoord_MAX;
+ MOZ_ASSERT(extent <= gridAxisTrackCount);
+ const uint32_t iEnd = gridAxisTrackCount + 1 - extent;
+ for (uint32_t i = start; i < iEnd; ++i) {
+ // Find the max `currentPos` value for the tracks that we would span
+ // if we were to use `i` as our start track:
+ nscoord maxPosForRange = 0;
+ for (auto j = i, jEnd = j + extent; j < jEnd; ++j) {
+ maxPosForRange = std::max(currentPos[j], maxPosForRange);
+ }
+ if (maxPosForRange < minPos) {
+ minPos = maxPosForRange;
+ start = i;
+ }
+ if (!isAutoPlaced) {
+ break;
+ }
+ }
+ gridAxisRange.mStart = start;
+ gridAxisRange.mEnd = start + extent;
+ bool isFirstItem = true;
+ for (uint32_t i : gridAxisRange.Range()) {
+ if (lastItems[i]) {
+ isFirstItem = false;
+ break;
+ }
+ }
+ // If this is the first item in its spanned grid tracks, then place it in
+ // the first masonry track. Otherwise, place it in the second masonry track.
+ masonryAxisRange.mStart = isFirstItem ? 0 : 1;
+ masonryAxisRange.mEnd = masonryAxisRange.mStart + 1;
+ return minPos;
+ };
+
+ // Handle the resulting reflow status after reflowing aItem.
+ // This may set aStatus to BreakBefore which the caller is expected
+ // to handle by returning from MasonryLayout.
+ // @return true if this item should consume all remaining space
+ auto handleChildStatus = [&](GridItemInfo* aItem,
+ const nsReflowStatus& aChildStatus) {
+ bool result = false;
+ if (MOZ_UNLIKELY(aFragmentainer)) {
+ auto* child = aItem->mFrame;
+ if (!aChildStatus.IsComplete() || aChildStatus.IsInlineBreakBefore() ||
+ aChildStatus.IsInlineBreakAfter() ||
+ child->StyleDisplay()->BreakAfter()) {
+ if (!isTopOfPageAtStart && avoidBreakInside) {
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return result;
+ }
+ result = true;
+ }
+ if (aChildStatus.IsInlineBreakBefore()) {
+ aStatus.SetIncomplete();
+ pushedItems.Insert(child);
+ } else if (aChildStatus.IsIncomplete()) {
+ recordAutoPlacement(aItem, gridAxis);
+ aStatus.SetIncomplete();
+ incompleteItems.Insert(child);
+ } else if (!aChildStatus.IsFullyComplete()) {
+ recordAutoPlacement(aItem, gridAxis);
+ overflowIncompleteItems.Insert(child);
+ }
+ }
+ return result;
+ };
+
+ // @return the distance from the masonry-box start to the end of the margin-
+ // box of aChild
+ auto offsetToMarginBoxEnd = [&](nsIFrame* aChild) {
+ auto childWM = aChild->GetWritingMode();
+ auto childAxis = !childWM.IsOrthogonalTo(wm) ? masonryAxis : gridAxis;
+ auto normalPos = aChild->GetLogicalNormalPosition(wm, aContainerSize);
+ auto sz =
+ childAxis == eLogicalAxisBlock ? aChild->BSize() : aChild->ISize();
+ return containerToMasonryBoxOffset + normalPos.Pos(masonryAxis, wm) + sz +
+ aChild->GetLogicalUsedMargin(childWM).End(childAxis, childWM);
+ };
+
+ // Apply baseline alignment to items belonging to the given set.
+ nsTArray<Tracks::ItemBaselineData> firstBaselineItems;
+ nsTArray<Tracks::ItemBaselineData> lastBaselineItems;
+ auto applyBaselineAlignment = [&](BaselineAlignmentSet aSet) {
+ firstBaselineItems.ClearAndRetainStorage();
+ lastBaselineItems.ClearAndRetainStorage();
+ masonryTracks.InitializeItemBaselinesInMasonryAxis(
+ aState, aState.mGridItems, aSet, aContainerSize, currentPos,
+ firstBaselineItems, lastBaselineItems);
+
+ bool didBaselineAdjustment = false;
+ nsTArray<Tracks::ItemBaselineData>* baselineItems[] = {&firstBaselineItems,
+ &lastBaselineItems};
+ for (const auto* items : baselineItems) {
+ for (const auto& data : *items) {
+ GridItemInfo* item = data.mGridItem;
+ MOZ_ASSERT((item->mState[masonryAxis] & ItemState::eIsBaselineAligned));
+ nscoord baselineOffset = item->mBaselineOffset[masonryAxis];
+ if (baselineOffset == nscoord(0)) {
+ continue; // no adjustment needed for this item
+ }
+ didBaselineAdjustment = true;
+ auto* child = item->mFrame;
+ auto masonryAxisStart =
+ item->mArea.LineRangeForAxis(masonryAxis).mStart;
+ auto gridAxisRange = item->mArea.LineRangeForAxis(gridAxis);
+ masonrySizes[masonryAxisStart].mPosition =
+ aSet.mItemSet == BaselineAlignmentSet::LastItems
+ ? lastPos[gridAxisRange.mStart]
+ : fragStartPos;
+ bool consumeAllSpace = false;
+ const auto state = item->mState[masonryAxis];
+ if ((state & ItemState::eContentBaseline) ||
+ MOZ_UNLIKELY(aFragmentainer)) {
+ if (MOZ_UNLIKELY(aFragmentainer)) {
+ aFragmentainer->mIsTopOfPage =
+ isTopOfPageAtStart &&
+ masonrySizes[masonryAxisStart].mPosition == fragStartPos;
+ }
+ nsReflowStatus childStatus;
+ ReflowInFlowChild(child, item, aContainerSize, Nothing(),
+ aFragmentainer, aState, aContentArea, aDesiredSize,
+ childStatus);
+ consumeAllSpace = handleChildStatus(item, childStatus);
+ if (aStatus.IsInlineBreakBefore()) {
+ return false;
+ }
+ } else if (!(state & ItemState::eEndSideBaseline)) {
+ // `align/justify-self` baselines on the start side can be handled by
+ // just moving the frame (except in a fragmentainer in which case we
+ // reflow it above instead since it might make it INCOMPLETE).
+ LogicalPoint logicalDelta(wm);
+ logicalDelta.Pos(masonryAxis, wm) = baselineOffset;
+ child->MovePositionBy(wm, logicalDelta);
+ }
+ if ((state & ItemState::eEndSideBaseline) && !consumeAllSpace) {
+ // Account for an end-side baseline adjustment.
+ for (uint32_t i : gridAxisRange.Range()) {
+ currentPos[i] += baselineOffset;
+ }
+ } else {
+ nscoord pos = consumeAllSpace ? toFragmentainerEnd
+ : offsetToMarginBoxEnd(child);
+ pos += gap;
+ for (uint32_t i : gridAxisRange.Range()) {
+ currentPos[i] = pos;
+ }
+ }
+ }
+ }
+ return didBaselineAdjustment;
+ };
+
+ // Place and reflow items. We'll use two fake tracks in the masonry axis.
+ // The first contains items that were placed there by the regular grid
+ // placement algo (PlaceGridItems) and we may add some items here if there
+ // are still empty slots. The second track contains all other items.
+ // Both tracks always have the size of the content box in the masonry axis.
+ // The position of the first track is always at the start. The position
+ // of the second track is updated as we go to a position where we want
+ // the current item to be positioned.
+ for (GridItemInfo* item : sortedItems) {
+ auto* child = item->mFrame;
+ auto& masonryRange = item->mArea.LineRangeForAxis(masonryAxis);
+ auto& gridRange = item->mArea.LineRangeForAxis(gridAxis);
+ nsReflowStatus childStatus;
+ if (MOZ_UNLIKELY(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) {
+ auto contentArea = aContentArea;
+ nscoord pos = nscoord_MAX;
+ // XXXmats take mEnd into consideration...
+ if (gridRange.mStart == kAutoLine) {
+ for (auto p : currentPos) {
+ pos = std::min(p, pos);
+ }
+ } else if (gridRange.mStart < currentPos.Length()) {
+ pos = currentPos[gridRange.mStart];
+ } else if (currentPos.Length() > 0) {
+ pos = currentPos.LastElement();
+ }
+ if (pos == nscoord_MAX) {
+ pos = nscoord(0);
+ }
+ contentArea.Start(masonryAxis, wm) = pos;
+ child = child->GetPlaceholderFrame();
+ ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr,
+ aState, contentArea, aDesiredSize, childStatus);
+ } else {
+ MOZ_ASSERT(gridRange.Extent() > 0 &&
+ gridRange.Extent() <= gridAxisTrackCount);
+ MOZ_ASSERT((masonryRange.mStart == 0 || masonryRange.mStart == 1) &&
+ masonryRange.Extent() == 1);
+ if (masonryRange.mStart != 0) {
+ masonrySizes[1].mPosition = placeItem(item);
+ }
+
+ // If this is the first item NOT in the first track and if any of
+ // the grid-axis tracks we span has a baseline-aligned item then we
+ // need to do that baseline alignment now since it may affect
+ // the placement of this and later items.
+ if (!didAlignStartAlignedFirstItems &&
+ aConstraint == SizingConstraint::NoConstraint &&
+ masonryRange.mStart != 0 && lastItemHasBaselineAlignment(gridRange)) {
+ didAlignStartAlignedFirstItems = true;
+ if (applyBaselineAlignment({BaselineAlignmentSet::FirstItems,
+ BaselineAlignmentSet::StartStretch})) {
+ // Baseline alignment resized some items - redo our placement.
+ masonrySizes[1].mPosition = placeItem(item);
+ }
+ if (aStatus.IsInlineBreakBefore()) {
+ return fragStartPos;
+ }
+ }
+
+ for (uint32_t i : gridRange.Range()) {
+ lastItems[i] = item;
+ }
+ cursor = gridRange.mEnd;
+ if (cursor >= gridAxisTrackCount) {
+ cursor = 0;
+ }
+
+ nscoord pos;
+ if (aConstraint == SizingConstraint::NoConstraint) {
+ const auto* disp = child->StyleDisplay();
+ if (MOZ_UNLIKELY(aFragmentainer)) {
+ aFragmentainer->mIsTopOfPage =
+ isTopOfPageAtStart &&
+ masonrySizes[masonryRange.mStart].mPosition == fragStartPos;
+ if (!aFragmentainer->mIsTopOfPage &&
+ (disp->BreakBefore() ||
+ masonrySizes[masonryRange.mStart].mPosition >=
+ toFragmentainerEnd)) {
+ childStatus.SetInlineLineBreakBeforeAndReset();
+ }
+ }
+ if (!childStatus.IsInlineBreakBefore()) {
+ ReflowInFlowChild(child, item, aContainerSize, Nothing(),
+ aFragmentainer, aState, aContentArea, aDesiredSize,
+ childStatus);
+ }
+ bool consumeAllSpace = handleChildStatus(item, childStatus);
+ if (aStatus.IsInlineBreakBefore()) {
+ return fragStartPos;
+ }
+ pos =
+ consumeAllSpace ? toFragmentainerEnd : offsetToMarginBoxEnd(child);
+ } else {
+ LogicalSize percentBasis(
+ aState.PercentageBasisFor(eLogicalAxisInline, *item));
+ IntrinsicISizeType type = aConstraint == SizingConstraint::MaxContent
+ ? IntrinsicISizeType::PrefISize
+ : IntrinsicISizeType::MinISize;
+ auto sz =
+ ::ContentContribution(*item, aState, &aState.mRenderingContext, wm,
+ masonryAxis, Some(percentBasis), type);
+ pos = sz + masonrySizes[masonryRange.mStart].mPosition;
+ }
+ pos += gap;
+ for (uint32_t i : gridRange.Range()) {
+ lastPos[i] = currentPos[i];
+ currentPos[i] = pos;
+ }
+ }
+ }
+
+ // Do the remaining baseline alignment sets.
+ if (aConstraint == SizingConstraint::NoConstraint) {
+ for (auto*& item : lastItems) {
+ if (item) {
+ item->mState[masonryAxis] |= ItemState::eIsLastItemInMasonryTrack;
+ }
+ }
+ BaselineAlignmentSet baselineSets[] = {
+ {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::StartStretch},
+ {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::EndStretch},
+ {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::StartStretch},
+ {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::EndStretch},
+ };
+ for (uint32_t i = 0; i < ArrayLength(baselineSets); ++i) {
+ if (i == 0 && didAlignStartAlignedFirstItems) {
+ continue;
+ }
+ applyBaselineAlignment(baselineSets[i]);
+ }
+ }
+
+ const bool childrenMoved = PushIncompleteChildren(
+ pushedItems, incompleteItems, overflowIncompleteItems);
+ if (childrenMoved && aStatus.IsComplete()) {
+ aStatus.SetOverflowIncomplete();
+ aStatus.SetNextInFlowNeedsReflow();
+ }
+ if (!pushedItems.IsEmpty()) {
+ AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
+ // NOTE since we messed with our child list here, we intentionally
+ // make aState.mIter invalid to avoid any use of it after this point.
+ aState.mIter.Invalidate();
+ }
+ if (!incompleteItems.IsEmpty()) {
+ // NOTE since we messed with our child list here, we intentionally
+ // make aState.mIter invalid to avoid any use of it after this point.
+ aState.mIter.Invalidate();
+ }
+
+ nscoord masonryBoxSize = 0;
+ for (auto pos : currentPos) {
+ masonryBoxSize = std::max(masonryBoxSize, pos);
+ }
+ masonryBoxSize = std::max(nscoord(0), masonryBoxSize - gap);
+ if (aConstraint == SizingConstraint::NoConstraint) {
+ aState.AlignJustifyContentInMasonryAxis(masonryBoxSize,
+ masonryTracks.mContentBoxSize);
+ }
+ return masonryBoxSize;
+}
+
+nsGridContainerFrame* nsGridContainerFrame::ParentGridContainerForSubgrid()
+ const {
+ MOZ_ASSERT(IsSubgrid());
+ nsIFrame* p = GetParent();
+ while (p->GetContent() == GetContent()) {
+ p = p->GetParent();
+ }
+ MOZ_ASSERT(p->IsGridContainerFrame());
+ auto* parent = static_cast<nsGridContainerFrame*>(p);
+ MOZ_ASSERT(parent->HasSubgridItems());
+ return parent;
+}
+
+nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aState,
+ const LogicalRect& aContentArea,
+ const nsSize& aContainerSize,
+ ReflowOutput& aDesiredSize,
+ nsReflowStatus& aStatus) {
+ WritingMode wm = aState.mReflowInput->GetWritingMode();
+ nscoord bSize = aContentArea.BSize(wm);
+ MOZ_ASSERT(aState.mReflowInput);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ if (HidesContentForLayout()) {
+ return bSize;
+ }
+
+ OverflowAreas ocBounds;
+ nsReflowStatus ocStatus;
+ if (GetPrevInFlow()) {
+ ReflowOverflowContainerChildren(PresContext(), *aState.mReflowInput,
+ ocBounds, ReflowChildFlags::Default,
+ ocStatus, MergeSortedFrameListsFor);
+ }
+
+ Maybe<Fragmentainer> fragmentainer = GetNearestFragmentainer(aState);
+ // MasonryLayout() can only handle fragmentation in the masonry-axis,
+ // so we let ReflowInFragmentainer() deal with grid-axis fragmentation
+ // in the else-clause below.
+ if (IsMasonry() &&
+ !(IsMasonry(eLogicalAxisInline) && fragmentainer.isSome())) {
+ aState.mInFragmentainer = fragmentainer.isSome();
+ nscoord sz = MasonryLayout(
+ aState, aContentArea, SizingConstraint::NoConstraint, aDesiredSize,
+ aStatus, fragmentainer.ptrOr(nullptr), aContainerSize);
+ if (IsMasonry(eLogicalAxisBlock)) {
+ bSize = aState.mReflowInput->ComputedBSize();
+ if (bSize == NS_UNCONSTRAINEDSIZE) {
+ bSize = aState.mReflowInput->ApplyMinMaxBSize(sz);
+ }
+ }
+ } else if (MOZ_UNLIKELY(fragmentainer.isSome())) {
+ if (IsMasonry(eLogicalAxisInline) && !GetPrevInFlow()) {
+ // First we do an unconstrained reflow to resolve the item placement
+ // which is then kept as-is in the constrained reflow below.
+ MasonryLayout(aState, aContentArea, SizingConstraint::NoConstraint,
+ aDesiredSize, aStatus, nullptr, aContainerSize);
+ }
+ aState.mInFragmentainer = true;
+ bSize = ReflowInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
+ *fragmentainer, aContainerSize);
+ } else {
+ aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll);
+ for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
+ nsIFrame* child = *aState.mIter;
+ const GridItemInfo* info = nullptr;
+ if (!child->IsPlaceholderFrame()) {
+ info = &aState.mGridItems[aState.mIter.ItemIndex()];
+ }
+ ReflowInFlowChild(child, info, aContainerSize, Nothing(), nullptr, aState,
+ aContentArea, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsComplete(),
+ "child should be complete in unconstrained reflow");
+ }
+ }
+
+ // Merge overflow container bounds and status.
+ aDesiredSize.mOverflowAreas.UnionWith(ocBounds);
+ aStatus.MergeCompletionStatusFrom(ocStatus);
+
+ if (IsAbsoluteContainer()) {
+ const nsFrameList& children = GetChildList(GetAbsoluteListID());
+ if (!children.IsEmpty()) {
+ // 'gridOrigin' is the origin of the grid (the start of the first track),
+ // with respect to the grid container's padding-box (CB).
+ LogicalMargin pad(aState.mReflowInput->ComputedLogicalPadding(wm));
+ const LogicalPoint gridOrigin(wm, pad.IStart(wm), pad.BStart(wm));
+ const LogicalRect gridCB(wm, 0, 0,
+ aContentArea.ISize(wm) + pad.IStartEnd(wm),
+ bSize + pad.BStartEnd(wm));
+ const nsSize gridCBPhysicalSize = gridCB.Size(wm).GetPhysicalSize(wm);
+ size_t i = 0;
+ for (nsIFrame* child : children) {
+ MOZ_ASSERT(i < aState.mAbsPosItems.Length());
+ MOZ_ASSERT(aState.mAbsPosItems[i].mFrame == child);
+ GridArea& area = aState.mAbsPosItems[i].mArea;
+ LogicalRect itemCB =
+ aState.ContainingBlockForAbsPos(area, gridOrigin, gridCB);
+ // nsAbsoluteContainingBlock::Reflow uses physical coordinates.
+ nsRect* cb = child->GetProperty(GridItemContainingBlockRect());
+ if (!cb) {
+ cb = new nsRect;
+ child->SetProperty(GridItemContainingBlockRect(), cb);
+ }
+ *cb = itemCB.GetPhysicalRect(wm, gridCBPhysicalSize);
+ ++i;
+ }
+ // We pass a dummy rect as CB because each child has its own CB rect.
+ // The eIsGridContainerCB flag tells nsAbsoluteContainingBlock::Reflow to
+ // use those instead.
+ nsRect dummyRect;
+ AbsPosReflowFlags flags =
+ AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized
+ flags |= AbsPosReflowFlags::ConstrainHeight;
+ flags |= AbsPosReflowFlags::IsGridContainerCB;
+ GetAbsoluteContainingBlock()->Reflow(
+ this, PresContext(), *aState.mReflowInput, aStatus, dummyRect, flags,
+ &aDesiredSize.mOverflowAreas);
+ }
+ }
+ return bSize;
+}
+
+void nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ return;
+ }
+
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ if (IsFrameTreeTooDeep(aReflowInput, aDesiredSize, aStatus)) {
+ return;
+ }
+
+ NormalizeChildLists();
+
+#ifdef DEBUG
+ mDidPushItemsBitMayLie = false;
+ SanityCheckChildListsBeforeReflow();
+#endif // DEBUG
+
+ for (auto& perAxisBaseline : mBaseline) {
+ for (auto& baseline : perAxisBaseline) {
+ baseline = NS_INTRINSIC_ISIZE_UNKNOWN;
+ }
+ }
+
+ const nsStylePosition* stylePos = aReflowInput.mStylePosition;
+ auto prevInFlow = static_cast<nsGridContainerFrame*>(GetPrevInFlow());
+ if (MOZ_LIKELY(!prevInFlow)) {
+ InitImplicitNamedAreas(stylePos);
+ } else {
+ MOZ_ASSERT(prevInFlow->HasAnyStateBits(kIsSubgridBits) ==
+ HasAnyStateBits(kIsSubgridBits),
+ "continuations should have same kIsSubgridBits");
+ }
+ GridReflowInput gridReflowInput(this, aReflowInput);
+ if (gridReflowInput.mIter.ItemsAreAlreadyInOrder()) {
+ AddStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
+ } else {
+ RemoveStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
+ }
+ if (gridReflowInput.mIter.AtEnd() ||
+ aReflowInput.mStyleDisplay->IsContainLayout()) {
+ // We have no grid items, or we're layout-contained. So, we have no
+ // baseline, and our parent should synthesize a baseline if needed.
+ AddStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
+ } else {
+ RemoveStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
+ }
+ const nscoord computedBSize = aReflowInput.ComputedBSize();
+ const nscoord computedISize = aReflowInput.ComputedISize();
+ const WritingMode& wm = gridReflowInput.mWM;
+ const LogicalSize computedSize(wm, computedISize, computedBSize);
+
+ nscoord consumedBSize = 0;
+ nscoord bSize = 0;
+ if (MOZ_LIKELY(!prevInFlow)) {
+ Grid grid;
+ if (MOZ_LIKELY(!IsSubgrid())) {
+ RepeatTrackSizingInput repeatSizing(aReflowInput.ComputedMinSize(),
+ computedSize,
+ aReflowInput.ComputedMaxSize());
+ grid.PlaceGridItems(gridReflowInput, repeatSizing);
+ } else {
+ auto* subgrid = GetProperty(Subgrid::Prop());
+ MOZ_ASSERT(subgrid, "an ancestor forgot to call PlaceGridItems?");
+ gridReflowInput.mGridItems = subgrid->mGridItems.Clone();
+ gridReflowInput.mAbsPosItems = subgrid->mAbsPosItems.Clone();
+ grid.mGridColEnd = subgrid->mGridColEnd;
+ grid.mGridRowEnd = subgrid->mGridRowEnd;
+ }
+ // XXX Technically incorrect: 'contain-intrinsic-block-size: none' is
+ // treated as 0, ignoring our row sizes, when really we should use them but
+ // *they* should be computed as if we had no children. To be fixed in bug
+ // 1488878.
+ const Maybe<nscoord> containBSize =
+ aReflowInput.mFrame->ContainIntrinsicBSize();
+ const nscoord trackSizingBSize = [&] {
+ // This clamping only applies to auto sizes.
+ if (containBSize && computedBSize == NS_UNCONSTRAINEDSIZE) {
+ return aReflowInput.ApplyMinMaxBSize(*containBSize);
+ }
+ return computedBSize;
+ }();
+ const LogicalSize containLogicalSize(wm, computedISize, trackSizingBSize);
+ gridReflowInput.CalculateTrackSizes(grid, containLogicalSize,
+ SizingConstraint::NoConstraint);
+ if (containBSize) {
+ bSize = *containBSize;
+ } else {
+ if (IsMasonry(eLogicalAxisBlock)) {
+ bSize = computedBSize;
+ } else {
+ const auto& rowSizes = gridReflowInput.mRows.mSizes;
+ if (MOZ_LIKELY(!IsSubgrid(eLogicalAxisBlock))) {
+ // Note: we can't use GridLineEdge here since we haven't calculated
+ // the rows' mPosition yet (happens in AlignJustifyContent below).
+ for (const auto& sz : rowSizes) {
+ bSize += sz.mBase;
+ }
+ bSize += gridReflowInput.mRows.SumOfGridGaps();
+ } else if (computedBSize == NS_UNCONSTRAINEDSIZE) {
+ bSize = gridReflowInput.mRows.GridLineEdge(
+ rowSizes.Length(), GridLineSide::BeforeGridGap);
+ }
+ }
+ }
+ } else {
+ consumedBSize = CalcAndCacheConsumedBSize();
+ gridReflowInput.InitializeForContinuation(this, consumedBSize);
+ // XXX Technically incorrect: 'contain-intrinsic-block-size: none' is
+ // treated as 0, ignoring our row sizes, when really we should use them but
+ // *they* should be computed as if we had no children. To be fixed in bug
+ // 1488878.
+ if (Maybe<nscoord> containBSize =
+ aReflowInput.mFrame->ContainIntrinsicBSize()) {
+ bSize = *containBSize;
+ } else {
+ const uint32_t numRows = gridReflowInput.mRows.mSizes.Length();
+ bSize = gridReflowInput.mRows.GridLineEdge(numRows,
+ GridLineSide::AfterGridGap);
+ }
+ }
+ if (computedBSize == NS_UNCONSTRAINEDSIZE) {
+ bSize = aReflowInput.ApplyMinMaxBSize(bSize);
+ } else if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) {
+ nscoord contentBSize = aReflowInput.ApplyMinMaxBSize(bSize);
+ bSize = std::max(contentBSize, computedBSize);
+ } else {
+ bSize = computedBSize;
+ }
+ if (bSize != NS_UNCONSTRAINEDSIZE) {
+ bSize = std::max(bSize - consumedBSize, 0);
+ }
+ auto& bp = gridReflowInput.mBorderPadding;
+ LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm), computedISize,
+ bSize);
+
+ if (!prevInFlow) {
+ const auto& rowSizes = gridReflowInput.mRows.mSizes;
+ if (!IsRowSubgrid()) {
+ // Apply 'align-content' to the grid.
+ if (computedBSize == NS_UNCONSTRAINEDSIZE &&
+ stylePos->mRowGap.IsLengthPercentage() &&
+ stylePos->mRowGap.AsLengthPercentage().HasPercent()) {
+ // Re-resolve the row-gap now that we know our intrinsic block-size.
+ gridReflowInput.mRows.mGridGap =
+ nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, bSize);
+ }
+ if (!gridReflowInput.mRows.mIsMasonry) {
+ auto alignment = stylePos->mAlignContent;
+ gridReflowInput.mRows.AlignJustifyContent(stylePos, alignment, wm,
+ bSize, false);
+ }
+ } else {
+ if (computedBSize == NS_UNCONSTRAINEDSIZE) {
+ bSize = gridReflowInput.mRows.GridLineEdge(rowSizes.Length(),
+ GridLineSide::BeforeGridGap);
+ contentArea.BSize(wm) = std::max(bSize, nscoord(0));
+ }
+ }
+ // Save the final row sizes for use by subgrids, if needed.
+ if (HasSubgridItems() || IsSubgrid()) {
+ StoreUsedTrackSizes(eLogicalAxisBlock, rowSizes);
+ }
+ }
+
+ nsSize containerSize = contentArea.Size(wm).GetPhysicalSize(wm);
+ bool repositionChildren = false;
+ if (containerSize.width == NS_UNCONSTRAINEDSIZE && wm.IsVerticalRL()) {
+ // Note that writing-mode:vertical-rl is the only case where the block
+ // logical direction progresses in a negative physical direction, and
+ // therefore block-dir coordinate conversion depends on knowing the width
+ // of the coordinate space in order to translate between the logical and
+ // physical origins.
+ //
+ // A masonry axis size may be unconstrained, otherwise in a regular grid
+ // our intrinsic size is always known by now. We'll re-position
+ // the children below once our size is known.
+ repositionChildren = true;
+ containerSize.width = 0;
+ }
+ containerSize.width += bp.LeftRight(wm);
+ containerSize.height += bp.TopBottom(wm);
+
+ bSize = ReflowChildren(gridReflowInput, contentArea, containerSize,
+ aDesiredSize, aStatus);
+ bSize = std::max(bSize - consumedBSize, 0);
+
+ // Skip our block-end border if we're INCOMPLETE.
+ if (!aStatus.IsComplete() && !gridReflowInput.mSkipSides.BEnd() &&
+ StyleBorder()->mBoxDecorationBreak != StyleBoxDecorationBreak::Clone) {
+ bp.BEnd(wm) = nscoord(0);
+ }
+
+ LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm),
+ bSize + bp.BStartEnd(wm));
+ aDesiredSize.SetSize(wm, desiredSize);
+ nsRect frameRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height());
+ aDesiredSize.mOverflowAreas.UnionAllWith(frameRect);
+
+ if (repositionChildren) {
+ nsPoint physicalDelta(aDesiredSize.Width() - bp.LeftRight(wm), 0);
+ for (const auto& item : gridReflowInput.mGridItems) {
+ auto* child = item.mFrame;
+ child->MovePositionBy(physicalDelta);
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child);
+ }
+ }
+
+ if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
+ // Per spec, the grid area is included in a grid container's scrollable
+ // overflow region [1], as well as the padding on the end-edge sides that
+ // would satisfy the requirements of 'place-content: end' alignment [2].
+ //
+ // Note that we include the padding from all sides of the grid area, not
+ // just the end sides; this is fine because the grid area is relative to our
+ // content-box origin. The inflated bounds won't go beyond our padding-box
+ // edges on the start sides.
+ //
+ // The margin areas of grid item boxes are also included in the scrollable
+ // overflow region [2].
+ //
+ // [1] https://drafts.csswg.org/css-grid-1/#overflow
+ // [2] https://drafts.csswg.org/css-overflow-3/#scrollable
+
+ // Synthesize a grid area covering all columns and rows, and compute its
+ // rect relative to our border-box.
+ //
+ // Note: the grid columns and rows exist only if there is an explicit grid;
+ // or when an implicit grid is needed to place any grid items. See
+ // nsGridContainerFrame::Grid::PlaceGridItems().
+ const auto numCols =
+ static_cast<int32_t>(gridReflowInput.mCols.mSizes.Length());
+ const auto numRows =
+ static_cast<int32_t>(gridReflowInput.mRows.mSizes.Length());
+ if (numCols > 0 && numRows > 0) {
+ const GridArea gridArea(LineRange(0, numCols), LineRange(0, numRows));
+ const LogicalRect gridAreaRect =
+ gridReflowInput.ContainingBlockFor(gridArea) +
+ LogicalPoint(wm, bp.IStart(wm), bp.BStart(wm));
+
+ MOZ_ASSERT(bp == aReflowInput.ComputedLogicalPadding(wm),
+ "A scrolled inner frame shouldn't have any border!");
+ const LogicalMargin& padding = bp;
+ nsRect physicalGridAreaRectWithPadding =
+ gridAreaRect.GetPhysicalRect(wm, containerSize);
+ physicalGridAreaRectWithPadding.Inflate(padding.GetPhysicalMargin(wm));
+ aDesiredSize.mOverflowAreas.UnionAllWith(physicalGridAreaRectWithPadding);
+ }
+
+ nsRect gridItemMarginBoxBounds;
+ for (const auto& item : gridReflowInput.mGridItems) {
+ gridItemMarginBoxBounds =
+ gridItemMarginBoxBounds.Union(item.mFrame->GetMarginRect());
+ }
+ aDesiredSize.mOverflowAreas.UnionAllWith(gridItemMarginBoxBounds);
+ }
+
+ // TODO: fix align-tracks alignment in fragments
+ if ((IsMasonry(eLogicalAxisBlock) && !prevInFlow) ||
+ IsMasonry(eLogicalAxisInline)) {
+ gridReflowInput.AlignJustifyTracksInMasonryAxis(
+ contentArea.Size(wm), aDesiredSize.PhysicalSize());
+ }
+
+ // Convert INCOMPLETE -> OVERFLOW_INCOMPLETE and zero bsize if we're an OC.
+ if (HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ if (!aStatus.IsComplete()) {
+ aStatus.SetOverflowIncomplete();
+ aStatus.SetNextInFlowNeedsReflow();
+ }
+ bSize = 0;
+ desiredSize.BSize(wm) = bSize + bp.BStartEnd(wm);
+ aDesiredSize.SetSize(wm, desiredSize);
+ }
+
+ if (!gridReflowInput.mInFragmentainer) {
+ MOZ_ASSERT(gridReflowInput.mIter.IsValid());
+ auto sz = frameRect.Size();
+ CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
+ &gridReflowInput.mGridItems, gridReflowInput.mCols, 0,
+ gridReflowInput.mCols.mSizes.Length(), wm, sz,
+ bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm));
+ CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
+ &gridReflowInput.mGridItems, gridReflowInput.mRows, 0,
+ gridReflowInput.mRows.mSizes.Length(), wm, sz,
+ bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm));
+ } else {
+ // Only compute 'first baseline' if this fragment contains the first track.
+ // XXXmats maybe remove this condition? bug 1306499
+ BaselineSet baselines = BaselineSet::eNone;
+ if (gridReflowInput.mStartRow == 0 &&
+ gridReflowInput.mStartRow != gridReflowInput.mNextFragmentStartRow) {
+ baselines = BaselineSet::eFirst;
+ }
+ // Only compute 'last baseline' if this fragment contains the last track.
+ // XXXmats maybe remove this condition? bug 1306499
+ uint32_t len = gridReflowInput.mRows.mSizes.Length();
+ if (gridReflowInput.mStartRow != len &&
+ gridReflowInput.mNextFragmentStartRow == len) {
+ baselines = BaselineSet(baselines | BaselineSet::eLast);
+ }
+ Maybe<CSSOrderAwareFrameIterator> iter;
+ Maybe<nsTArray<GridItemInfo>> gridItems;
+ if (baselines != BaselineSet::eNone) {
+ // We need to create a new iterator and GridItemInfo array because we
+ // might have pushed some children at this point.
+ // Even if the gridReflowInput iterator is invalid we can reuse its
+ // state about order to optimize initialization of the new iterator.
+ // An ordered child list can't become unordered by pushing frames.
+ // An unordered list can become ordered in a number of cases, but we
+ // ignore that here and guess that the child list is still unordered.
+ // XXX this is O(n^2) in the number of items in this fragment: bug 1306705
+ using Filter = CSSOrderAwareFrameIterator::ChildFilter;
+ using Order = CSSOrderAwareFrameIterator::OrderState;
+ bool ordered = gridReflowInput.mIter.ItemsAreAlreadyInOrder();
+ auto orderState = ordered ? Order::Ordered : Order::Unordered;
+ iter.emplace(this, FrameChildListID::Principal, Filter::SkipPlaceholders,
+ orderState);
+ gridItems.emplace();
+ for (; !iter->AtEnd(); iter->Next()) {
+ auto child = **iter;
+ for (const auto& info : gridReflowInput.mGridItems) {
+ if (info.mFrame == child) {
+ gridItems->AppendElement(info);
+ }
+ }
+ }
+ }
+ auto sz = frameRect.Size();
+ CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
+ gridReflowInput.mCols, 0,
+ gridReflowInput.mCols.mSizes.Length(), wm, sz,
+ bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm));
+ CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
+ gridReflowInput.mRows, gridReflowInput.mStartRow,
+ gridReflowInput.mNextFragmentStartRow, wm, sz,
+ bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm));
+ }
+
+ if (HasAnyStateBits(NS_STATE_GRID_COMPUTED_INFO)) {
+ // This state bit will never be cleared, since reflow can be called
+ // multiple times in fragmented grids, and it's challenging to scope
+ // the bit to only that sequence of calls. This is relatively harmless
+ // since this bit is only set by accessing a ChromeOnly property, and
+ // therefore can't unduly slow down normal web browsing.
+
+ // Clear our GridFragmentInfo property, which might be holding a stale
+ // dom::Grid object built from previously-computed info. This will
+ // ensure that the next call to GetGridFragments will create a new one.
+ if (mozilla::dom::Grid* grid = TakeProperty(GridFragmentInfo())) {
+ grid->ForgetFrame();
+ }
+
+ // Now that we know column and row sizes and positions, set
+ // the ComputedGridTrackInfo and related properties
+
+ const auto* subgrid = GetProperty(Subgrid::Prop());
+ const auto* subgridColRange = subgrid && IsSubgrid(eLogicalAxisInline)
+ ? &subgrid->SubgridCols()
+ : nullptr;
+
+ LineNameMap colLineNameMap(
+ gridReflowInput.mGridStyle, GetImplicitNamedAreas(),
+ gridReflowInput.mColFunctions, nullptr, subgridColRange, true);
+ uint32_t colTrackCount = gridReflowInput.mCols.mSizes.Length();
+ nsTArray<nscoord> colTrackPositions(colTrackCount);
+ nsTArray<nscoord> colTrackSizes(colTrackCount);
+ nsTArray<uint32_t> colTrackStates(colTrackCount);
+ nsTArray<bool> colRemovedRepeatTracks(
+ gridReflowInput.mColFunctions.mRemovedRepeatTracks.Clone());
+ uint32_t col = 0;
+ for (const TrackSize& sz : gridReflowInput.mCols.mSizes) {
+ colTrackPositions.AppendElement(sz.mPosition);
+ colTrackSizes.AppendElement(sz.mBase);
+ bool isRepeat =
+ ((col >= gridReflowInput.mColFunctions.mRepeatAutoStart) &&
+ (col < gridReflowInput.mColFunctions.mRepeatAutoEnd));
+ colTrackStates.AppendElement(
+ isRepeat ? (uint32_t)mozilla::dom::GridTrackState::Repeat
+ : (uint32_t)mozilla::dom::GridTrackState::Static);
+
+ col++;
+ }
+ // Get the number of explicit tracks first. The order of argument evaluation
+ // is implementation-defined. We should be OK here because colTrackSizes is
+ // taken by rvalue, but computing the size first prevents any changes in the
+ // argument types of the constructor from breaking this.
+ const uint32_t numColExplicitTracks =
+ IsSubgrid(eLogicalAxisInline)
+ ? colTrackSizes.Length()
+ : gridReflowInput.mColFunctions.NumExplicitTracks();
+ ComputedGridTrackInfo* colInfo = new ComputedGridTrackInfo(
+ gridReflowInput.mColFunctions.mExplicitGridOffset, numColExplicitTracks,
+ 0, col, std::move(colTrackPositions), std::move(colTrackSizes),
+ std::move(colTrackStates), std::move(colRemovedRepeatTracks),
+ gridReflowInput.mColFunctions.mRepeatAutoStart,
+ colLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(),
+ IsSubgrid(eLogicalAxisInline), IsMasonry(eLogicalAxisInline));
+ SetProperty(GridColTrackInfo(), colInfo);
+
+ const auto* subgridRowRange = subgrid && IsSubgrid(eLogicalAxisBlock)
+ ? &subgrid->SubgridRows()
+ : nullptr;
+ LineNameMap rowLineNameMap(
+ gridReflowInput.mGridStyle, GetImplicitNamedAreas(),
+ gridReflowInput.mRowFunctions, nullptr, subgridRowRange, true);
+ uint32_t rowTrackCount = gridReflowInput.mRows.mSizes.Length();
+ nsTArray<nscoord> rowTrackPositions(rowTrackCount);
+ nsTArray<nscoord> rowTrackSizes(rowTrackCount);
+ nsTArray<uint32_t> rowTrackStates(rowTrackCount);
+ nsTArray<bool> rowRemovedRepeatTracks(
+ gridReflowInput.mRowFunctions.mRemovedRepeatTracks.Clone());
+ uint32_t row = 0;
+ for (const TrackSize& sz : gridReflowInput.mRows.mSizes) {
+ rowTrackPositions.AppendElement(sz.mPosition);
+ rowTrackSizes.AppendElement(sz.mBase);
+ bool isRepeat =
+ ((row >= gridReflowInput.mRowFunctions.mRepeatAutoStart) &&
+ (row < gridReflowInput.mRowFunctions.mRepeatAutoEnd));
+ rowTrackStates.AppendElement(
+ isRepeat ? (uint32_t)mozilla::dom::GridTrackState::Repeat
+ : (uint32_t)mozilla::dom::GridTrackState::Static);
+
+ row++;
+ }
+ // Get the number of explicit tracks first. The order of argument evaluation
+ // is implementation-defined. We should be OK here because colTrackSizes is
+ // taken by rvalue, but computing the size first prevents any changes in the
+ // argument types of the constructor from breaking this.
+ const uint32_t numRowExplicitTracks =
+ IsSubgrid(eLogicalAxisBlock)
+ ? rowTrackSizes.Length()
+ : gridReflowInput.mRowFunctions.NumExplicitTracks();
+ // Row info has to accommodate fragmentation of the grid, which may happen
+ // in later calls to Reflow. For now, presume that no more fragmentation
+ // will occur.
+ ComputedGridTrackInfo* rowInfo = new ComputedGridTrackInfo(
+ gridReflowInput.mRowFunctions.mExplicitGridOffset, numRowExplicitTracks,
+ gridReflowInput.mStartRow, row, std::move(rowTrackPositions),
+ std::move(rowTrackSizes), std::move(rowTrackStates),
+ std::move(rowRemovedRepeatTracks),
+ gridReflowInput.mRowFunctions.mRepeatAutoStart,
+ rowLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(),
+ IsSubgrid(eLogicalAxisBlock), IsMasonry(eLogicalAxisBlock));
+ SetProperty(GridRowTrackInfo(), rowInfo);
+
+ if (prevInFlow) {
+ // This frame is fragmenting rows from a previous frame, so patch up
+ // the prior GridRowTrackInfo with a new end row.
+
+ // FIXME: This can be streamlined and/or removed when bug 1151204 lands.
+
+ ComputedGridTrackInfo* priorRowInfo =
+ prevInFlow->GetProperty(GridRowTrackInfo());
+
+ // Adjust track positions based on the first track in this fragment.
+ if (priorRowInfo->mPositions.Length() >
+ priorRowInfo->mStartFragmentTrack) {
+ nscoord delta =
+ priorRowInfo->mPositions[priorRowInfo->mStartFragmentTrack];
+ for (nscoord& pos : priorRowInfo->mPositions) {
+ pos -= delta;
+ }
+ }
+
+ ComputedGridTrackInfo* revisedPriorRowInfo = new ComputedGridTrackInfo(
+ priorRowInfo->mNumLeadingImplicitTracks,
+ priorRowInfo->mNumExplicitTracks, priorRowInfo->mStartFragmentTrack,
+ gridReflowInput.mStartRow, std::move(priorRowInfo->mPositions),
+ std::move(priorRowInfo->mSizes), std::move(priorRowInfo->mStates),
+ std::move(priorRowInfo->mRemovedRepeatTracks),
+ priorRowInfo->mRepeatFirstTrack,
+ std::move(priorRowInfo->mResolvedLineNames), priorRowInfo->mIsSubgrid,
+ priorRowInfo->mIsMasonry);
+ prevInFlow->SetProperty(GridRowTrackInfo(), revisedPriorRowInfo);
+ }
+
+ // Generate the line info properties. We need to provide the number of
+ // repeat tracks produced in the reflow. Only explicit names are assigned
+ // to lines here; the mozilla::dom::GridLines class will later extract
+ // implicit names from grid areas and assign them to the appropriate lines.
+
+ auto& colFunctions = gridReflowInput.mColFunctions;
+
+ // Generate column lines first.
+ uint32_t capacity = gridReflowInput.mCols.mSizes.Length();
+ nsTArray<nsTArray<RefPtr<nsAtom>>> columnLineNames(capacity);
+ for (col = 0; col <= gridReflowInput.mCols.mSizes.Length(); col++) {
+ // Offset col by the explicit grid offset, to get the original names.
+ nsTArray<RefPtr<nsAtom>> explicitNames =
+ colLineNameMap.GetExplicitLineNamesAtIndex(
+ col - colFunctions.mExplicitGridOffset);
+
+ columnLineNames.EmplaceBack(std::move(explicitNames));
+ }
+ // Get the explicit names that follow a repeat auto declaration.
+ nsTArray<RefPtr<nsAtom>> colNamesFollowingRepeat;
+ nsTArray<RefPtr<nsAtom>> colBeforeRepeatAuto;
+ nsTArray<RefPtr<nsAtom>> colAfterRepeatAuto;
+ // Note: the following is only used for a non-subgridded axis.
+ if (colLineNameMap.HasRepeatAuto()) {
+ MOZ_ASSERT(!colFunctions.mTemplate.IsSubgrid());
+ // The line name list after the repeatAutoIndex holds the line names
+ // for the first explicit line after the repeat auto declaration.
+ uint32_t repeatAutoEnd = colLineNameMap.RepeatAutoStart() + 1;
+ for (auto* list : colLineNameMap.ExpandedLineNames()[repeatAutoEnd]) {
+ for (auto& name : list->AsSpan()) {
+ colNamesFollowingRepeat.AppendElement(name.AsAtom());
+ }
+ }
+ auto names = colLineNameMap.TrackAutoRepeatLineNames();
+ for (auto& name : names[0].AsSpan()) {
+ colBeforeRepeatAuto.AppendElement(name.AsAtom());
+ }
+ for (auto& name : names[1].AsSpan()) {
+ colAfterRepeatAuto.AppendElement(name.AsAtom());
+ }
+ }
+
+ ComputedGridLineInfo* columnLineInfo = new ComputedGridLineInfo(
+ std::move(columnLineNames), std::move(colBeforeRepeatAuto),
+ std::move(colAfterRepeatAuto), std::move(colNamesFollowingRepeat));
+ SetProperty(GridColumnLineInfo(), columnLineInfo);
+
+ // Generate row lines next.
+ auto& rowFunctions = gridReflowInput.mRowFunctions;
+ capacity = gridReflowInput.mRows.mSizes.Length();
+ nsTArray<nsTArray<RefPtr<nsAtom>>> rowLineNames(capacity);
+ for (row = 0; row <= gridReflowInput.mRows.mSizes.Length(); row++) {
+ // Offset row by the explicit grid offset, to get the original names.
+ nsTArray<RefPtr<nsAtom>> explicitNames =
+ rowLineNameMap.GetExplicitLineNamesAtIndex(
+ row - rowFunctions.mExplicitGridOffset);
+ rowLineNames.EmplaceBack(std::move(explicitNames));
+ }
+ // Get the explicit names that follow a repeat auto declaration.
+ nsTArray<RefPtr<nsAtom>> rowNamesFollowingRepeat;
+ nsTArray<RefPtr<nsAtom>> rowBeforeRepeatAuto;
+ nsTArray<RefPtr<nsAtom>> rowAfterRepeatAuto;
+ // Note: the following is only used for a non-subgridded axis.
+ if (rowLineNameMap.HasRepeatAuto()) {
+ MOZ_ASSERT(!rowFunctions.mTemplate.IsSubgrid());
+ // The line name list after the repeatAutoIndex holds the line names
+ // for the first explicit line after the repeat auto declaration.
+ uint32_t repeatAutoEnd = rowLineNameMap.RepeatAutoStart() + 1;
+ for (auto* list : rowLineNameMap.ExpandedLineNames()[repeatAutoEnd]) {
+ for (auto& name : list->AsSpan()) {
+ rowNamesFollowingRepeat.AppendElement(name.AsAtom());
+ }
+ }
+ auto names = rowLineNameMap.TrackAutoRepeatLineNames();
+ for (auto& name : names[0].AsSpan()) {
+ rowBeforeRepeatAuto.AppendElement(name.AsAtom());
+ }
+ for (auto& name : names[1].AsSpan()) {
+ rowAfterRepeatAuto.AppendElement(name.AsAtom());
+ }
+ }
+
+ ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo(
+ std::move(rowLineNames), std::move(rowBeforeRepeatAuto),
+ std::move(rowAfterRepeatAuto), std::move(rowNamesFollowingRepeat));
+ SetProperty(GridRowLineInfo(), rowLineInfo);
+
+ // Generate area info for explicit areas. Implicit areas are handled
+ // elsewhere.
+ if (!gridReflowInput.mGridStyle->mGridTemplateAreas.IsNone()) {
+ auto* areas = new StyleOwnedSlice<NamedArea>(
+ gridReflowInput.mGridStyle->mGridTemplateAreas.AsAreas()->areas);
+ SetProperty(ExplicitNamedAreasProperty(), areas);
+ } else {
+ RemoveProperty(ExplicitNamedAreasProperty());
+ }
+ }
+
+ if (!prevInFlow) {
+ SharedGridData* sharedGridData = GetProperty(SharedGridData::Prop());
+ if (!aStatus.IsFullyComplete()) {
+ if (!sharedGridData) {
+ sharedGridData = new SharedGridData;
+ SetProperty(SharedGridData::Prop(), sharedGridData);
+ }
+ sharedGridData->mCols.mSizes = std::move(gridReflowInput.mCols.mSizes);
+ sharedGridData->mCols.mContentBoxSize =
+ gridReflowInput.mCols.mContentBoxSize;
+ sharedGridData->mCols.mBaselineSubtreeAlign =
+ gridReflowInput.mCols.mBaselineSubtreeAlign;
+ sharedGridData->mCols.mIsMasonry = gridReflowInput.mCols.mIsMasonry;
+ sharedGridData->mRows.mSizes = std::move(gridReflowInput.mRows.mSizes);
+ // Save the original row grid sizes and gaps so we can restore them later
+ // in GridReflowInput::Initialize for the continuations.
+ auto& origRowData = sharedGridData->mOriginalRowData;
+ origRowData.ClearAndRetainStorage();
+ origRowData.SetCapacity(sharedGridData->mRows.mSizes.Length());
+ nscoord prevTrackEnd = 0;
+ for (auto& sz : sharedGridData->mRows.mSizes) {
+ SharedGridData::RowData data = {sz.mBase, sz.mPosition - prevTrackEnd};
+ origRowData.AppendElement(data);
+ prevTrackEnd = sz.mPosition + sz.mBase;
+ }
+ sharedGridData->mRows.mContentBoxSize =
+ gridReflowInput.mRows.mContentBoxSize;
+ sharedGridData->mRows.mBaselineSubtreeAlign =
+ gridReflowInput.mRows.mBaselineSubtreeAlign;
+ sharedGridData->mRows.mIsMasonry = gridReflowInput.mRows.mIsMasonry;
+ sharedGridData->mGridItems = std::move(gridReflowInput.mGridItems);
+ sharedGridData->mAbsPosItems = std::move(gridReflowInput.mAbsPosItems);
+
+ sharedGridData->mGenerateComputedGridInfo =
+ HasAnyStateBits(NS_STATE_GRID_COMPUTED_INFO);
+ } else if (sharedGridData && !GetNextInFlow()) {
+ RemoveProperty(SharedGridData::Prop());
+ }
+ }
+
+ FinishAndStoreOverflow(&aDesiredSize);
+}
+
+void nsGridContainerFrame::UpdateSubgridFrameState() {
+ nsFrameState oldBits = GetStateBits() & kIsSubgridBits;
+ nsFrameState newBits = ComputeSelfSubgridMasonryBits() & kIsSubgridBits;
+ if (newBits != oldBits) {
+ RemoveStateBits(kIsSubgridBits);
+ if (!newBits) {
+ RemoveProperty(Subgrid::Prop());
+ } else {
+ AddStateBits(newBits);
+ }
+ }
+}
+
+nsFrameState nsGridContainerFrame::ComputeSelfSubgridMasonryBits() const {
+ nsFrameState bits = nsFrameState(0);
+ const auto* pos = StylePosition();
+
+ // We can only have masonry layout in one axis.
+ if (pos->mGridTemplateRows.IsMasonry()) {
+ bits |= NS_STATE_GRID_IS_ROW_MASONRY;
+ } else if (pos->mGridTemplateColumns.IsMasonry()) {
+ bits |= NS_STATE_GRID_IS_COL_MASONRY;
+ }
+
+ // NOTE: The rest of this function is only relevant if we're a subgrid;
+ // hence, we return early as soon as we rule out that possibility.
+
+ // 'contain:layout/paint' makes us an "independent formatting context",
+ // which prevents us from being a subgrid in this case (but not always).
+ // We will also need to check our containing scroll frame for this property.
+ // https://drafts.csswg.org/css-display-3/#establish-an-independent-formatting-context
+ if (ShouldInhibitSubgridDueToIFC(this)) {
+ return bits;
+ }
+
+ // Skip over our scroll frame and such if we have it, to find our "parent
+ // grid", if we have one.
+
+ // After this loop, 'parent' will represent the parent of the outermost frame
+ // that shares our content node. (Normally this is just our parent frame, but
+ // if we're e.g. a scrolled frame, then this will be the parent of our
+ // wrapper-scrollable-frame.) If 'parent' turns out to be a grid container,
+ // then it's our "parent grid", and we could potentially be a subgrid of it.
+ auto* parent = GetParent();
+ while (parent && parent->GetContent() == GetContent()) {
+ // If we find our containing frame (e.g. our scroll frame) can't be a
+ // subgrid, then we can't be a subgrid, for the same reasons as above. This
+ // can happen when this frame is itself a grid item with "overflow:scroll"
+ // or similar.
+ if (ShouldInhibitSubgridDueToIFC(parent)) {
+ return bits;
+ }
+ parent = parent->GetParent();
+ }
+ const nsGridContainerFrame* parentGrid = do_QueryFrame(parent);
+ if (parentGrid) {
+ bool isOrthogonal =
+ GetWritingMode().IsOrthogonalTo(parent->GetWritingMode());
+ bool isColSubgrid = pos->mGridTemplateColumns.IsSubgrid();
+ // Subgridding a parent masonry axis makes us use masonry layout too,
+ // unless our other axis is a masonry axis.
+ if (isColSubgrid &&
+ parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_ROW_MASONRY
+ : NS_STATE_GRID_IS_COL_MASONRY)) {
+ isColSubgrid = false;
+ if (!HasAnyStateBits(NS_STATE_GRID_IS_ROW_MASONRY)) {
+ bits |= NS_STATE_GRID_IS_COL_MASONRY;
+ }
+ }
+ if (isColSubgrid) {
+ bits |= NS_STATE_GRID_IS_COL_SUBGRID;
+ }
+
+ bool isRowSubgrid = pos->mGridTemplateRows.IsSubgrid();
+ if (isRowSubgrid &&
+ parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_COL_MASONRY
+ : NS_STATE_GRID_IS_ROW_MASONRY)) {
+ isRowSubgrid = false;
+ if (!HasAnyStateBits(NS_STATE_GRID_IS_COL_MASONRY)) {
+ bits |= NS_STATE_GRID_IS_ROW_MASONRY;
+ }
+ }
+ if (isRowSubgrid) {
+ bits |= NS_STATE_GRID_IS_ROW_SUBGRID;
+ }
+ }
+ return bits;
+}
+
+void nsGridContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+
+ nsFrameState bits = nsFrameState(0);
+ if (MOZ_LIKELY(!aPrevInFlow)) {
+ bits = ComputeSelfSubgridMasonryBits();
+ } else {
+ bits = aPrevInFlow->GetStateBits() &
+ (NS_STATE_GRID_IS_ROW_MASONRY | NS_STATE_GRID_IS_COL_MASONRY |
+ kIsSubgridBits | NS_STATE_GRID_HAS_COL_SUBGRID_ITEM |
+ NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
+ }
+ AddStateBits(bits);
+}
+
+void nsGridContainerFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ nsContainerFrame::DidSetComputedStyle(aOldStyle);
+
+ if (!aOldStyle) {
+ return; // Init() already initialized the bits.
+ }
+ UpdateSubgridFrameState();
+}
+
+nscoord nsGridContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
+ IntrinsicISizeType aType) {
+ // Calculate the sum of column sizes under intrinsic sizing.
+ // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
+ NormalizeChildLists();
+ GridReflowInput state(this, *aRenderingContext);
+ InitImplicitNamedAreas(state.mGridStyle); // XXX optimize
+
+ // The min/sz/max sizes are the input to the "repeat-to-fill" algorithm:
+ // https://drafts.csswg.org/css-grid/#auto-repeat
+ // They're only used for auto-repeat so we skip computing them otherwise.
+ RepeatTrackSizingInput repeatSizing(state.mWM);
+ if (!IsColSubgrid() && state.mColFunctions.mHasRepeatAuto) {
+ repeatSizing.InitFromStyle(eLogicalAxisInline, state.mWM,
+ state.mFrame->Style());
+ }
+ if ((!IsRowSubgrid() && state.mRowFunctions.mHasRepeatAuto &&
+ !(state.mGridStyle->mGridAutoFlow & StyleGridAutoFlow::ROW)) ||
+ IsMasonry(eLogicalAxisInline)) {
+ // Only 'grid-auto-flow:column' can create new implicit columns, so that's
+ // the only case where our block-size can affect the number of columns.
+ // Masonry layout always depends on how many rows we have though.
+ repeatSizing.InitFromStyle(eLogicalAxisBlock, state.mWM,
+ state.mFrame->Style());
+ }
+
+ Grid grid;
+ if (MOZ_LIKELY(!IsSubgrid())) {
+ grid.PlaceGridItems(state, repeatSizing); // XXX optimize
+ } else {
+ auto* subgrid = GetProperty(Subgrid::Prop());
+ state.mGridItems = subgrid->mGridItems.Clone();
+ state.mAbsPosItems = subgrid->mAbsPosItems.Clone();
+ grid.mGridColEnd = subgrid->mGridColEnd;
+ grid.mGridRowEnd = subgrid->mGridRowEnd;
+ }
+
+ auto constraint = aType == IntrinsicISizeType::MinISize
+ ? SizingConstraint::MinContent
+ : SizingConstraint::MaxContent;
+ if (IsMasonry(eLogicalAxisInline)) {
+ ReflowOutput desiredSize(state.mWM);
+ nsSize containerSize;
+ LogicalRect contentArea(state.mWM);
+ nsReflowStatus status;
+ state.mRows.mSizes.SetLength(grid.mGridRowEnd);
+ state.CalculateTrackSizesForAxis(eLogicalAxisInline, grid,
+ NS_UNCONSTRAINEDSIZE, constraint);
+ return MasonryLayout(state, contentArea, constraint, desiredSize, status,
+ nullptr, containerSize);
+ }
+
+ if (grid.mGridColEnd == 0) {
+ return nscoord(0);
+ }
+
+ state.CalculateTrackSizesForAxis(eLogicalAxisInline, grid,
+ NS_UNCONSTRAINEDSIZE, constraint);
+
+ if (MOZ_LIKELY(!IsSubgrid())) {
+ return state.mCols.SumOfGridTracksAndGaps();
+ }
+ const auto& last = state.mCols.mSizes.LastElement();
+ return last.mPosition + last.mBase;
+}
+
+nscoord nsGridContainerFrame::GetMinISize(gfxContext* aRC) {
+ auto* f = static_cast<nsGridContainerFrame*>(FirstContinuation());
+ if (f != this) {
+ return f->GetMinISize(aRC);
+ }
+
+ DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
+ if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
+ Maybe<nscoord> containISize = ContainIntrinsicISize();
+ mCachedMinISize = containISize
+ ? *containISize
+ : IntrinsicISize(aRC, IntrinsicISizeType::MinISize);
+ }
+ return mCachedMinISize;
+}
+
+nscoord nsGridContainerFrame::GetPrefISize(gfxContext* aRC) {
+ auto* f = static_cast<nsGridContainerFrame*>(FirstContinuation());
+ if (f != this) {
+ return f->GetPrefISize(aRC);
+ }
+
+ DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
+ if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
+ Maybe<nscoord> containISize = ContainIntrinsicISize();
+ mCachedPrefISize = containISize
+ ? *containISize
+ : IntrinsicISize(aRC, IntrinsicISizeType::PrefISize);
+ }
+ return mCachedPrefISize;
+}
+
+void nsGridContainerFrame::MarkIntrinsicISizesDirty() {
+ mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ for (auto& perAxisBaseline : mBaseline) {
+ for (auto& baseline : perAxisBaseline) {
+ baseline = NS_INTRINSIC_ISIZE_UNKNOWN;
+ }
+ }
+ nsContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+void nsGridContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+ if (GetPrevInFlow()) {
+ DisplayOverflowContainers(aBuilder, aLists);
+ }
+
+ // Our children are all grid-level boxes, which behave the same as
+ // inline-blocks in painting, so their borders/backgrounds all go on
+ // the BlockBorderBackgrounds list.
+ typedef CSSOrderAwareFrameIterator::OrderState OrderState;
+ OrderState order =
+ HasAnyStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
+ ? OrderState::Ordered
+ : OrderState::Unordered;
+ CSSOrderAwareFrameIterator iter(
+ this, FrameChildListID::Principal,
+ CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, order);
+ const auto flags = DisplayFlagsForFlexOrGridItem();
+ for (; !iter.AtEnd(); iter.Next()) {
+ nsIFrame* child = *iter;
+ BuildDisplayListForChild(aBuilder, child, aLists, flags);
+ }
+}
+
+bool nsGridContainerFrame::DrainSelfOverflowList() {
+ return DrainAndMergeSelfOverflowList();
+}
+
+void nsGridContainerFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ NoteNewChildren(aListID, aFrameList);
+ nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
+}
+
+void nsGridContainerFrame::InsertFrames(
+ ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
+ NoteNewChildren(aListID, aFrameList);
+ nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+}
+
+void nsGridContainerFrame::RemoveFrame(DestroyContext& aContext,
+ ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
+
+#ifdef DEBUG
+ SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
+#endif
+
+ nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
+}
+
+StyleAlignFlags nsGridContainerFrame::CSSAlignmentForAbsPosChild(
+ const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
+ MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(),
+ "This method should only be called for abspos children");
+
+ StyleAlignFlags alignment =
+ (aLogicalAxis == eLogicalAxisInline)
+ ? aChildRI.mStylePosition->UsedJustifySelf(Style())._0
+ : aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
+
+ // Extract and strip the flag bits
+ StyleAlignFlags alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS;
+ alignment &= ~StyleAlignFlags::FLAG_BITS;
+
+ if (alignment == StyleAlignFlags::NORMAL) {
+ // "the 'normal' keyword behaves as 'start' on replaced
+ // absolutely-positioned boxes, and behaves as 'stretch' on all other
+ // absolutely-positioned boxes."
+ // https://drafts.csswg.org/css-align/#align-abspos
+ // https://drafts.csswg.org/css-align/#justify-abspos
+ alignment = aChildRI.mFrame->IsReplaced() ? StyleAlignFlags::START
+ : StyleAlignFlags::STRETCH;
+ } else if (alignment == StyleAlignFlags::FLEX_START) {
+ alignment = StyleAlignFlags::START;
+ } else if (alignment == StyleAlignFlags::FLEX_END) {
+ alignment = StyleAlignFlags::END;
+ } else if (alignment == StyleAlignFlags::LEFT ||
+ alignment == StyleAlignFlags::RIGHT) {
+ if (aLogicalAxis == eLogicalAxisInline) {
+ const bool isLeft = (alignment == StyleAlignFlags::LEFT);
+ WritingMode wm = GetWritingMode();
+ alignment = (isLeft == wm.IsBidiLTR()) ? StyleAlignFlags::START
+ : StyleAlignFlags::END;
+ } else {
+ alignment = StyleAlignFlags::START;
+ }
+ } else if (alignment == StyleAlignFlags::BASELINE) {
+ alignment = StyleAlignFlags::START;
+ } else if (alignment == StyleAlignFlags::LAST_BASELINE) {
+ alignment = StyleAlignFlags::END;
+ }
+
+ return (alignment | alignmentFlags);
+}
+
+nscoord nsGridContainerFrame::SynthesizeBaseline(
+ const FindItemInGridOrderResult& aGridOrderItem, LogicalAxis aAxis,
+ BaselineSharingGroup aGroup, const nsSize& aCBPhysicalSize, nscoord aCBSize,
+ WritingMode aCBWM) {
+ if (MOZ_UNLIKELY(!aGridOrderItem.mItem)) {
+ // No item in this fragment - synthesize a baseline from our border-box.
+ return ::SynthesizeBaselineFromBorderBox(aGroup, aCBWM, aAxis, aCBSize);
+ }
+
+ nsIFrame* child = aGridOrderItem.mItem->mFrame;
+ nsGridContainerFrame* grid = do_QueryFrame(child);
+ auto childWM = child->GetWritingMode();
+ bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
+ const LogicalAxis childAxis = isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
+ nscoord baseline;
+ nscoord start;
+ nscoord size;
+
+ if (aAxis == eLogicalAxisBlock) {
+ start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).B(aCBWM);
+ size = child->BSize(aCBWM);
+ if (grid && aGridOrderItem.mIsInEdgeTrack) {
+ baseline = isOrthogonal ? grid->GetIBaseline(aGroup)
+ : grid->GetBBaseline(aGroup);
+ } else if (!isOrthogonal && aGridOrderItem.mIsInEdgeTrack) {
+ // This assertion is mostly for documentation purposes; it must hold,
+ // given the checks in our 'if' statements. (We know aAxis is
+ // eLogicalAxisBlock, and isOrthogonal is false, which means childAxis
+ // must be eLogicalAxisBlock). If instead we got here with a childAxis of
+ // eLogicalAxisInline, then our call to
+ // Baseline::SynthesizeBaselineFromBorderBox might incorrectly think
+ // it makes sense to use a central baseline, in an axis where that
+ // doesn't make sense.
+ MOZ_ASSERT(childAxis == eLogicalAxisBlock, "unexpected childAxis");
+ baseline = child
+ ->GetNaturalBaselineBOffset(childWM, aGroup,
+ BaselineExportContext::Other)
+ .valueOrFrom([aGroup, child, childWM]() {
+ return Baseline::SynthesizeBOffsetFromBorderBox(
+ child, childWM, aGroup);
+ });
+ } else {
+ baseline =
+ ::SynthesizeBaselineFromBorderBox(aGroup, childWM, childAxis, size);
+ }
+ } else {
+ start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).I(aCBWM);
+ size = child->ISize(aCBWM);
+ if (grid && aGridOrderItem.mIsInEdgeTrack) {
+ baseline = isOrthogonal ? grid->GetBBaseline(aGroup)
+ : grid->GetIBaseline(aGroup);
+ } else if (isOrthogonal && aGridOrderItem.mIsInEdgeTrack) {
+ baseline = child
+ ->GetNaturalBaselineBOffset(childWM, aGroup,
+ BaselineExportContext::Other)
+ .valueOrFrom([aGroup, childWM, childAxis, size]() {
+ return ::SynthesizeBaselineFromBorderBox(
+ aGroup, childWM, childAxis, size);
+ });
+ } else {
+ baseline =
+ ::SynthesizeBaselineFromBorderBox(aGroup, childWM, childAxis, size);
+ }
+ }
+ return aGroup == BaselineSharingGroup::First
+ ? start + baseline
+ : aCBSize - start - size + baseline;
+}
+
+void nsGridContainerFrame::CalculateBaselines(
+ BaselineSet aBaselineSet, CSSOrderAwareFrameIterator* aIter,
+ const nsTArray<GridItemInfo>* aGridItems, const Tracks& aTracks,
+ uint32_t aFragmentStartTrack, uint32_t aFirstExcludedTrack, WritingMode aWM,
+ const nsSize& aCBPhysicalSize, nscoord aCBBorderPaddingStart,
+ nscoord aCBBorderPaddingEnd, nscoord aCBSize) {
+ const auto axis = aTracks.mAxis;
+ auto firstBaseline = aTracks.mBaseline[BaselineSharingGroup::First];
+ if (!(aBaselineSet & BaselineSet::eFirst)) {
+ mBaseline[axis][BaselineSharingGroup::First] =
+ ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::First, aWM,
+ axis, aCBSize);
+ } else if (firstBaseline == NS_INTRINSIC_ISIZE_UNKNOWN) {
+ FindItemInGridOrderResult gridOrderFirstItem = FindFirstItemInGridOrder(
+ *aIter, *aGridItems,
+ axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
+ axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
+ aFragmentStartTrack);
+ mBaseline[axis][BaselineSharingGroup::First] = SynthesizeBaseline(
+ gridOrderFirstItem, axis, BaselineSharingGroup::First, aCBPhysicalSize,
+ aCBSize, aWM);
+ } else {
+ // We have a 'first baseline' group in the start track in this fragment.
+ // Convert it from track to grid container border-box coordinates.
+ MOZ_ASSERT(!aGridItems->IsEmpty());
+ nscoord gapBeforeStartTrack =
+ aFragmentStartTrack == 0
+ ? aTracks.GridLineEdge(aFragmentStartTrack,
+ GridLineSide::AfterGridGap)
+ : nscoord(0); // no content gap at start of fragment
+ mBaseline[axis][BaselineSharingGroup::First] =
+ aCBBorderPaddingStart + gapBeforeStartTrack + firstBaseline;
+ }
+
+ auto lastBaseline = aTracks.mBaseline[BaselineSharingGroup::Last];
+ if (!(aBaselineSet & BaselineSet::eLast)) {
+ mBaseline[axis][BaselineSharingGroup::Last] =
+ ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::Last, aWM, axis,
+ aCBSize);
+ } else if (lastBaseline == NS_INTRINSIC_ISIZE_UNKNOWN) {
+ // For finding items for the 'last baseline' we need to create a reverse
+ // iterator ('aIter' is the forward iterator from the GridReflowInput).
+ using Iter = ReverseCSSOrderAwareFrameIterator;
+ auto orderState = aIter->ItemsAreAlreadyInOrder()
+ ? Iter::OrderState::Ordered
+ : Iter::OrderState::Unordered;
+ Iter iter(this, FrameChildListID::Principal,
+ Iter::ChildFilter::SkipPlaceholders, orderState);
+ iter.SetItemCount(aGridItems->Length());
+ FindItemInGridOrderResult gridOrderLastItem = FindLastItemInGridOrder(
+ iter, *aGridItems,
+ axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
+ axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
+ aFragmentStartTrack, aFirstExcludedTrack);
+ mBaseline[axis][BaselineSharingGroup::Last] =
+ SynthesizeBaseline(gridOrderLastItem, axis, BaselineSharingGroup::Last,
+ aCBPhysicalSize, aCBSize, aWM);
+ } else {
+ // We have a 'last baseline' group in the end track in this fragment.
+ // Convert it from track to grid container border-box coordinates.
+ MOZ_ASSERT(!aGridItems->IsEmpty());
+ auto borderBoxStartToEndOfEndTrack =
+ aCBBorderPaddingStart +
+ aTracks.GridLineEdge(aFirstExcludedTrack, GridLineSide::BeforeGridGap) -
+ aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::BeforeGridGap);
+ mBaseline[axis][BaselineSharingGroup::Last] =
+ (aCBSize - borderBoxStartToEndOfEndTrack) + lastBaseline;
+ }
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsGridContainerFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"GridContainer"_ns, aResult);
+}
+
+void nsGridContainerFrame::ExtraContainerFrameInfo(nsACString& aTo) const {
+ if (const void* const subgrid = GetProperty(Subgrid::Prop())) {
+ aTo += nsPrintfCString(" [subgrid=%p]", subgrid);
+ }
+}
+
+#endif
+
+/* static */ nsGridContainerFrame::FindItemInGridOrderResult
+nsGridContainerFrame::FindFirstItemInGridOrder(
+ CSSOrderAwareFrameIterator& aIter, const nsTArray<GridItemInfo>& aGridItems,
+ LineRange GridArea::*aMajor, LineRange GridArea::*aMinor,
+ uint32_t aFragmentStartTrack) {
+ FindItemInGridOrderResult result = {nullptr, false};
+ uint32_t minMajor = kTranslatedMaxLine + 1;
+ uint32_t minMinor = kTranslatedMaxLine + 1;
+ aIter.Reset();
+ for (; !aIter.AtEnd(); aIter.Next()) {
+ const GridItemInfo& item = aGridItems[aIter.ItemIndex()];
+ if ((item.mArea.*aMajor).mEnd <= aFragmentStartTrack) {
+ continue; // item doesn't span any track in this fragment
+ }
+ uint32_t major = (item.mArea.*aMajor).mStart;
+ uint32_t minor = (item.mArea.*aMinor).mStart;
+ if (major < minMajor || (major == minMajor && minor < minMinor)) {
+ minMajor = major;
+ minMinor = minor;
+ result.mItem = &item;
+ result.mIsInEdgeTrack = major == 0U;
+ }
+ }
+ return result;
+}
+
+/* static */ nsGridContainerFrame::FindItemInGridOrderResult
+nsGridContainerFrame::FindLastItemInGridOrder(
+ ReverseCSSOrderAwareFrameIterator& aIter,
+ const nsTArray<GridItemInfo>& aGridItems, LineRange GridArea::*aMajor,
+ LineRange GridArea::*aMinor, uint32_t aFragmentStartTrack,
+ uint32_t aFirstExcludedTrack) {
+ FindItemInGridOrderResult result = {nullptr, false};
+ int32_t maxMajor = -1;
+ int32_t maxMinor = -1;
+ aIter.Reset();
+ int32_t lastMajorTrack = int32_t(aFirstExcludedTrack) - 1;
+ for (; !aIter.AtEnd(); aIter.Next()) {
+ const GridItemInfo& item = aGridItems[aIter.ItemIndex()];
+ // Subtract 1 from the end line to get the item's last track index.
+ int32_t major = (item.mArea.*aMajor).mEnd - 1;
+ // Currently, this method is only called with aFirstExcludedTrack ==
+ // the first track in the next fragment, so we take the opportunity
+ // to assert this item really belongs to this fragment.
+ MOZ_ASSERT((item.mArea.*aMajor).mStart < aFirstExcludedTrack,
+ "found an item that belongs to some later fragment");
+ if (major < int32_t(aFragmentStartTrack)) {
+ continue; // item doesn't span any track in this fragment
+ }
+ int32_t minor = (item.mArea.*aMinor).mEnd - 1;
+ MOZ_ASSERT(minor >= 0 && major >= 0, "grid item must have span >= 1");
+ if (major > maxMajor || (major == maxMajor && minor > maxMinor)) {
+ maxMajor = major;
+ maxMinor = minor;
+ result.mItem = &item;
+ result.mIsInEdgeTrack = major == lastMajorTrack;
+ }
+ }
+ return result;
+}
+
+nsGridContainerFrame::UsedTrackSizes* nsGridContainerFrame::GetUsedTrackSizes()
+ const {
+ return GetProperty(UsedTrackSizes::Prop());
+}
+
+void nsGridContainerFrame::StoreUsedTrackSizes(
+ LogicalAxis aAxis, const nsTArray<TrackSize>& aSizes) {
+ auto* uts = GetUsedTrackSizes();
+ if (!uts) {
+ uts = new UsedTrackSizes();
+ SetProperty(UsedTrackSizes::Prop(), uts);
+ }
+ uts->mSizes[aAxis] = aSizes.Clone();
+ uts->mCanResolveLineRangeSize[aAxis] = true;
+ // XXX is resetting these bits necessary?
+ for (auto& sz : uts->mSizes[aAxis]) {
+ sz.mState &= ~(TrackSize::eFrozen | TrackSize::eSkipGrowUnlimited |
+ TrackSize::eInfinitelyGrowable);
+ }
+}
+
+#ifdef DEBUG
+void nsGridContainerFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ ChildListIDs supportedLists = {FrameChildListID::Principal};
+ // We don't handle the FrameChildListID::Backdrop frames in any way, but it
+ // only contains a placeholder for ::backdrop which is OK to not reflow (for
+ // now anyway).
+ supportedLists += FrameChildListID::Backdrop;
+ MOZ_ASSERT(supportedLists.contains(aListID), "unexpected child list");
+
+ return nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
+}
+
+void nsGridContainerFrame::TrackSize::DumpStateBits(StateBits aState) {
+ printf("min:");
+ if (aState & eAutoMinSizing) {
+ printf("auto-min ");
+ } else if (aState & eMinContentMinSizing) {
+ printf("min-content ");
+ } else if (aState & eMaxContentMinSizing) {
+ printf("max-content ");
+ }
+ printf(" max:");
+ if (aState & eAutoMaxSizing) {
+ printf("auto ");
+ } else if (aState & eMinContentMaxSizing) {
+ printf("min-content ");
+ } else if (aState & eMaxContentMaxSizing) {
+ printf("max-content ");
+ } else if (aState & eFlexMaxSizing) {
+ printf("flex ");
+ }
+ if (aState & eFrozen) {
+ printf("frozen ");
+ }
+ if (aState & eModified) {
+ printf("modified ");
+ }
+ if (aState & eBreakBefore) {
+ printf("break-before ");
+ }
+}
+
+void nsGridContainerFrame::TrackSize::Dump() const {
+ printf("mPosition=%d mBase=%d mLimit=%d ", mPosition, mBase, mLimit);
+ DumpStateBits(mState);
+}
+
+#endif // DEBUG
+
+bool nsGridContainerFrame::GridItemShouldStretch(const nsIFrame* aChild,
+ LogicalAxis aAxis) const {
+ MOZ_ASSERT(aChild->IsGridItem());
+
+ if (aChild->IsGridContainerFrame()) {
+ // The subgrid is always stretched in its subgridded dimensions.
+ // https://drafts.csswg.org/css-grid/#subgrid-box-alignment
+ const auto* gridContainer =
+ static_cast<const nsGridContainerFrame*>(aChild);
+ if (gridContainer->IsSubgrid(aAxis)) {
+ return true;
+ }
+ }
+
+ const auto wm = aChild->GetWritingMode();
+ if (aChild->StyleMargin()->HasAuto(aAxis, wm)) {
+ // Per https://drafts.csswg.org/css-grid/#auto-margins, any 'auto' margin in
+ // an axis disables the alignment property in that axis.
+ return false;
+ }
+
+ const auto cbwm = GetWritingMode();
+ const bool isOrthogonal = wm.IsOrthogonalTo(cbwm);
+ if (IsMasonry(isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis)) {
+ // The child is in the container's masonry-axis.
+ // AlignJustifyTracksInMasonryAxis will stretch it, so we don't report that
+ // here.
+ return false;
+ }
+
+ const auto* pos = aChild->StylePosition();
+ const auto alignment = (aAxis == eLogicalAxisInline) == !isOrthogonal
+ ? pos->UsedJustifySelf(Style())._0
+ : pos->UsedAlignSelf(Style())._0;
+ return alignment == StyleAlignFlags::NORMAL ||
+ alignment == StyleAlignFlags::STRETCH;
+}
+
+bool nsGridContainerFrame::ShouldInhibitSubgridDueToIFC(
+ const nsIFrame* aFrame) {
+ // Just checking for things that make us establish an independent formatting
+ // context (IFC) and hence prevent us from being a subgrid:
+ // * Out-of-flow (e.g. abspos) frames also establish an IFC. Note, our
+ // NS_FRAME_OUT_OF_FLOW bit potentially isn't set yet, so we check our style.
+ // * contain:layout and contain:paint each make us establish an IFC.
+ const auto* display = aFrame->StyleDisplay();
+ return display->IsAbsolutelyPositionedStyle() || display->IsContainLayout() ||
+ display->IsContainPaint();
+}
+
+nsGridContainerFrame* nsGridContainerFrame::GetGridContainerFrame(
+ nsIFrame* aFrame) {
+ nsGridContainerFrame* gridFrame = nullptr;
+
+ if (aFrame) {
+ nsIFrame* inner = aFrame;
+ if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) {
+ inner = static_cast<nsFieldSetFrame*>(aFrame)->GetInner();
+ }
+ // Since "Get" methods like GetInner and GetContentInsertionFrame can
+ // return null, we check the return values before dereferencing. Our
+ // calling pattern makes this unlikely, but we're being careful.
+ nsIFrame* insertionFrame =
+ inner ? inner->GetContentInsertionFrame() : nullptr;
+ nsIFrame* possibleGridFrame = insertionFrame ? insertionFrame : aFrame;
+ gridFrame = possibleGridFrame->IsGridContainerFrame()
+ ? static_cast<nsGridContainerFrame*>(possibleGridFrame)
+ : nullptr;
+ }
+ return gridFrame;
+}
+
+nsGridContainerFrame* nsGridContainerFrame::GetGridFrameWithComputedInfo(
+ nsIFrame* aFrame) {
+ nsGridContainerFrame* gridFrame = GetGridContainerFrame(aFrame);
+ if (!gridFrame) {
+ return nullptr;
+ }
+
+ auto HasComputedInfo = [](const nsGridContainerFrame& aFrame) -> bool {
+ return aFrame.HasProperty(GridColTrackInfo()) &&
+ aFrame.HasProperty(GridRowTrackInfo()) &&
+ aFrame.HasProperty(GridColumnLineInfo()) &&
+ aFrame.HasProperty(GridRowLineInfo());
+ };
+
+ if (HasComputedInfo(*gridFrame)) {
+ return gridFrame;
+ }
+
+ // Trigger a reflow that generates additional grid property data.
+ // Hold onto aFrame while we do this, in case reflow destroys it.
+ AutoWeakFrame weakFrameRef(gridFrame);
+
+ RefPtr<mozilla::PresShell> presShell = gridFrame->PresShell();
+ gridFrame->AddStateBits(NS_STATE_GRID_COMPUTED_INFO);
+ presShell->FrameNeedsReflow(gridFrame, IntrinsicDirty::None,
+ NS_FRAME_IS_DIRTY);
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ // If the weakFrameRef is no longer valid, then we must bail out.
+ if (!weakFrameRef.IsAlive()) {
+ return nullptr;
+ }
+
+ // This can happen if for some reason we ended up not reflowing, like in print
+ // preview under some circumstances.
+ if (MOZ_UNLIKELY(!HasComputedInfo(*gridFrame))) {
+ return nullptr;
+ }
+
+ return gridFrame;
+}
+
+// TODO: This is a rather dumb implementation of nsILineIterator, but it's
+// better than our pre-existing behavior. Ideally, we should probably use the
+// grid information to return a meaningful number of lines etc.
+bool nsGridContainerFrame::IsLineIteratorFlowRTL() { return false; }
+
+int32_t nsGridContainerFrame::GetNumLines() const {
+ return mFrames.GetLength();
+}
+
+Result<nsILineIterator::LineInfo, nsresult> nsGridContainerFrame::GetLine(
+ int32_t aLineNumber) {
+ if (aLineNumber < 0 || aLineNumber >= GetNumLines()) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ LineInfo rv;
+ nsIFrame* f = mFrames.FrameAt(aLineNumber);
+ rv.mLineBounds = f->GetRect();
+ rv.mFirstFrameOnLine = f;
+ rv.mNumFramesOnLine = 1;
+ return rv;
+}
+
+int32_t nsGridContainerFrame::FindLineContaining(nsIFrame* aFrame,
+ int32_t aStartLine) {
+ const int32_t index = mFrames.IndexOf(aFrame);
+ if (index < 0) {
+ return -1;
+ }
+ if (index < aStartLine) {
+ return -1;
+ }
+ return index;
+}
+
+NS_IMETHODIMP
+nsGridContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) {
+ *aIsReordered = false;
+ *aFirstVisual = nullptr;
+ *aLastVisual = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGridContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
+ nsIFrame** aFrameFound,
+ bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) {
+ const auto wm = GetWritingMode();
+ const LogicalPoint pos(wm, aPos, GetSize());
+
+ *aFrameFound = nullptr;
+ *aPosIsBeforeFirstFrame = true;
+ *aPosIsAfterLastFrame = false;
+
+ nsIFrame* f = mFrames.FrameAt(aLineNumber);
+ if (!f) {
+ return NS_OK;
+ }
+
+ auto rect = f->GetLogicalRect(wm, GetSize());
+ *aFrameFound = f;
+ *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm);
+ *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm);
+ return NS_OK;
+}
diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h
new file mode 100644
index 0000000000..d785d65a50
--- /dev/null
+++ b/layout/generic/nsGridContainerFrame.h
@@ -0,0 +1,662 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: grid | inline-grid" */
+
+#ifndef nsGridContainerFrame_h___
+#define nsGridContainerFrame_h___
+
+#include "mozilla/CSSOrderAwareFrameIterator.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/HashTable.h"
+#include "nsAtomHashKeys.h"
+#include "nsContainerFrame.h"
+#include "nsILineIterator.h"
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Grid;
+}
+} // namespace mozilla
+
+/**
+ * Factory function.
+ * @return a newly allocated nsGridContainerFrame (infallible)
+ */
+nsContainerFrame* NS_NewGridContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+/**
+ * The number of implicit / explicit tracks and their sizes.
+ */
+struct ComputedGridTrackInfo {
+ ComputedGridTrackInfo(
+ uint32_t aNumLeadingImplicitTracks, uint32_t aNumExplicitTracks,
+ uint32_t aStartFragmentTrack, uint32_t aEndFragmentTrack,
+ nsTArray<nscoord>&& aPositions, nsTArray<nscoord>&& aSizes,
+ nsTArray<uint32_t>&& aStates, nsTArray<bool>&& aRemovedRepeatTracks,
+ uint32_t aRepeatFirstTrack,
+ nsTArray<nsTArray<StyleCustomIdent>>&& aResolvedLineNames,
+ bool aIsSubgrid, bool aIsMasonry)
+ : mNumLeadingImplicitTracks(aNumLeadingImplicitTracks),
+ mNumExplicitTracks(aNumExplicitTracks),
+ mStartFragmentTrack(aStartFragmentTrack),
+ mEndFragmentTrack(aEndFragmentTrack),
+ mPositions(std::move(aPositions)),
+ mSizes(std::move(aSizes)),
+ mStates(std::move(aStates)),
+ mRemovedRepeatTracks(std::move(aRemovedRepeatTracks)),
+ mResolvedLineNames(std::move(aResolvedLineNames)),
+ mRepeatFirstTrack(aRepeatFirstTrack),
+ mIsSubgrid(aIsSubgrid),
+ mIsMasonry(aIsMasonry) {}
+ uint32_t mNumLeadingImplicitTracks;
+ uint32_t mNumExplicitTracks;
+ uint32_t mStartFragmentTrack;
+ uint32_t mEndFragmentTrack;
+ nsTArray<nscoord> mPositions;
+ nsTArray<nscoord> mSizes;
+ nsTArray<uint32_t> mStates;
+ // Indicates if a track has been collapsed. This will be populated for each
+ // track in the repeat(auto-fit) and repeat(auto-fill), even if there are no
+ // collapsed tracks.
+ nsTArray<bool> mRemovedRepeatTracks;
+ // Contains lists of all line name lists, including the name lists inside
+ // repeats. When a repeat(auto) track exists, the internal track names will
+ // appear once each in this array.
+ nsTArray<nsTArray<StyleCustomIdent>> mResolvedLineNames;
+ uint32_t mRepeatFirstTrack;
+ bool mIsSubgrid;
+ bool mIsMasonry;
+};
+
+struct ComputedGridLineInfo {
+ explicit ComputedGridLineInfo(
+ nsTArray<nsTArray<RefPtr<nsAtom>>>&& aNames,
+ const nsTArray<RefPtr<nsAtom>>& aNamesBefore,
+ const nsTArray<RefPtr<nsAtom>>& aNamesAfter,
+ nsTArray<RefPtr<nsAtom>>&& aNamesFollowingRepeat)
+ : mNames(std::move(aNames)),
+ mNamesBefore(aNamesBefore.Clone()),
+ mNamesAfter(aNamesAfter.Clone()),
+ mNamesFollowingRepeat(std::move(aNamesFollowingRepeat)) {}
+ nsTArray<nsTArray<RefPtr<nsAtom>>> mNames;
+ nsTArray<RefPtr<nsAtom>> mNamesBefore;
+ nsTArray<RefPtr<nsAtom>> mNamesAfter;
+ nsTArray<RefPtr<nsAtom>> mNamesFollowingRepeat;
+};
+} // namespace mozilla
+
+class nsGridContainerFrame final : public nsContainerFrame,
+ public nsILineIterator {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsGridContainerFrame)
+ NS_DECL_QUERYFRAME
+ using ComputedGridTrackInfo = mozilla::ComputedGridTrackInfo;
+ using ComputedGridLineInfo = mozilla::ComputedGridLineInfo;
+ using LogicalAxis = mozilla::LogicalAxis;
+ using BaselineSharingGroup = mozilla::BaselineSharingGroup;
+ using NamedArea = mozilla::StyleNamedArea;
+
+ template <typename T>
+ using PerBaseline = mozilla::EnumeratedArray<BaselineSharingGroup,
+ BaselineSharingGroup(2), T>;
+
+ template <typename T>
+ using PerLogicalAxis =
+ mozilla::EnumeratedArray<LogicalAxis, LogicalAxis(2), T>;
+
+ // nsIFrame overrides
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ void DidSetComputedStyle(ComputedStyle* aOldStyle) override;
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+ void MarkIntrinsicISizesDirty() override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const override {
+ if (StyleDisplay()->IsContainLayout() ||
+ HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) {
+ return Nothing{};
+ }
+ return mozilla::Some(GetBBaseline(aBaselineGroup));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+ void ExtraContainerFrameInfo(nsACString& aTo) const override;
+#endif
+
+ // nsContainerFrame overrides
+ bool DrainSelfOverflowList() override;
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override;
+ mozilla::StyleAlignFlags CSSAlignmentForAbsPosChild(
+ const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const override;
+
+#ifdef DEBUG
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+#endif
+
+ /**
+ * Return the containing block for aChild which MUST be an abs.pos. child
+ * of a grid container and that container must have been reflowed.
+ */
+ static const nsRect& GridItemCB(nsIFrame* aChild);
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(GridItemContainingBlockRect, nsRect)
+
+ /**
+ * These properties are created by a call to
+ * nsGridContainerFrame::GetGridFrameWithComputedInfo, typically from
+ * Element::GetGridFragments.
+ */
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(GridColTrackInfo, ComputedGridTrackInfo)
+ const ComputedGridTrackInfo* GetComputedTemplateColumns() {
+ const ComputedGridTrackInfo* info = GetProperty(GridColTrackInfo());
+ MOZ_ASSERT(info, "Property generation wasn't requested.");
+ return info;
+ }
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(GridRowTrackInfo, ComputedGridTrackInfo)
+ const ComputedGridTrackInfo* GetComputedTemplateRows() {
+ const ComputedGridTrackInfo* info = GetProperty(GridRowTrackInfo());
+ MOZ_ASSERT(info, "Property generation wasn't requested.");
+ return info;
+ }
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(GridColumnLineInfo, ComputedGridLineInfo)
+ const ComputedGridLineInfo* GetComputedTemplateColumnLines() {
+ const ComputedGridLineInfo* info = GetProperty(GridColumnLineInfo());
+ MOZ_ASSERT(info, "Property generation wasn't requested.");
+ return info;
+ }
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(GridRowLineInfo, ComputedGridLineInfo)
+ const ComputedGridLineInfo* GetComputedTemplateRowLines() {
+ const ComputedGridLineInfo* info = GetProperty(GridRowLineInfo());
+ MOZ_ASSERT(info, "Property generation wasn't requested.");
+ return info;
+ }
+
+ /**
+ * This property is set by the creation of a dom::Grid object, and cleared
+ * during GC unlink. Since the Grid object manages the lifecycle, the property
+ * itself is set without a destructor. The property is also cleared whenever
+ * new grid computed info is generated during reflow, ensuring that we aren't
+ * holding a stale dom::Grid object.
+ */
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(GridFragmentInfo, mozilla::dom::Grid)
+ mozilla::dom::Grid* GetGridFragmentInfo() {
+ return GetProperty(GridFragmentInfo());
+ }
+
+ using ImplicitNamedAreas =
+ mozilla::HashMap<mozilla::AtomHashKey, NamedArea, mozilla::AtomHashKey>;
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(ImplicitNamedAreasProperty,
+ ImplicitNamedAreas)
+ ImplicitNamedAreas* GetImplicitNamedAreas() const {
+ return GetProperty(ImplicitNamedAreasProperty());
+ }
+
+ using ExplicitNamedAreas = mozilla::StyleOwnedSlice<NamedArea>;
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(ExplicitNamedAreasProperty,
+ ExplicitNamedAreas)
+ ExplicitNamedAreas* GetExplicitNamedAreas() const {
+ return GetProperty(ExplicitNamedAreasProperty());
+ }
+
+ using nsContainerFrame::IsMasonry;
+
+ /** Return true if this frame has masonry layout in any axis. */
+ bool IsMasonry() const {
+ return HasAnyStateBits(NS_STATE_GRID_IS_ROW_MASONRY |
+ NS_STATE_GRID_IS_COL_MASONRY);
+ }
+
+ /** Return true if this frame is subgridded in its aAxis. */
+ bool IsSubgrid(LogicalAxis aAxis) const {
+ return HasAnyStateBits(aAxis == mozilla::eLogicalAxisBlock
+ ? NS_STATE_GRID_IS_ROW_SUBGRID
+ : NS_STATE_GRID_IS_COL_SUBGRID);
+ }
+ bool IsColSubgrid() const { return IsSubgrid(mozilla::eLogicalAxisInline); }
+ bool IsRowSubgrid() const { return IsSubgrid(mozilla::eLogicalAxisBlock); }
+ /** Return true if this frame is subgridded in any axis. */
+ bool IsSubgrid() const {
+ return HasAnyStateBits(NS_STATE_GRID_IS_ROW_SUBGRID |
+ NS_STATE_GRID_IS_COL_SUBGRID);
+ }
+
+ /** Return true if this frame has an item that is subgridded in our aAxis. */
+ bool HasSubgridItems(LogicalAxis aAxis) const {
+ return HasAnyStateBits(aAxis == mozilla::eLogicalAxisBlock
+ ? NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM
+ : NS_STATE_GRID_HAS_COL_SUBGRID_ITEM);
+ }
+ /** Return true if this frame has any subgrid items. */
+ bool HasSubgridItems() const {
+ return HasAnyStateBits(NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM |
+ NS_STATE_GRID_HAS_COL_SUBGRID_ITEM);
+ }
+ /**
+ * Return true if the grid item aChild should stretch in its aAxis (i.e. aAxis
+ * is in the aChild's writing-mode).
+ *
+ * Note: this method does *not* consider the grid item's aspect-ratio and
+ * natural size in the axis when the self-alignment value is 'normal' per
+ * https://drafts.csswg.org/css-grid/#grid-item-sizing
+ */
+ bool GridItemShouldStretch(const nsIFrame* aChild, LogicalAxis aAxis) const;
+
+ /**
+ * Returns true if aFrame forms an independent formatting context and hence
+ * should be inhibited from being a subgrid (i.e. if the used value of
+ * 'grid-template-{rows,columns}:subgrid' should be 'none').
+ * https://drafts.csswg.org/css-grid-2/#subgrid-listing
+ *
+ * (Note this only makes sense to call if aFrame is itself either a grid
+ * container frame or a wrapper frame for a grid container frame, e.g. a
+ * scroll container frame for a scrollable grid. Having said that, this is
+ * technically safe to call on any non-null frame.)
+ */
+ static bool ShouldInhibitSubgridDueToIFC(const nsIFrame* aFrame);
+
+ /**
+ * Return a container grid frame for the supplied frame, if available.
+ * @return nullptr if aFrame has no grid container.
+ */
+ static nsGridContainerFrame* GetGridContainerFrame(nsIFrame* aFrame);
+
+ /**
+ * Return a container grid frame, and ensure it has computed grid info
+ * @return nullptr if aFrame has no grid container, or frame was destroyed
+ * @note this might destroy layout/style data since it may flush layout
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static nsGridContainerFrame* GetGridFrameWithComputedInfo(nsIFrame* aFrame);
+
+ struct Subgrid;
+ struct UsedTrackSizes;
+ struct TrackSize;
+ struct GridItemInfo;
+ struct GridReflowInput;
+ struct FindItemInGridOrderResult {
+ // The first(last) item in (reverse) grid order.
+ const GridItemInfo* mItem;
+ // Does the above item span the first(last) track?
+ bool mIsInEdgeTrack;
+ };
+
+ /** Return our parent grid container; |this| MUST be a subgrid. */
+ nsGridContainerFrame* ParentGridContainerForSubgrid() const;
+
+ // https://drafts.csswg.org/css-sizing/#constraints
+ enum class SizingConstraint {
+ MinContent, // sizing under min-content constraint
+ MaxContent, // sizing under max-content constraint
+ NoConstraint // no constraint, used during Reflow
+ };
+
+ protected:
+ typedef mozilla::LogicalPoint LogicalPoint;
+ typedef mozilla::LogicalRect LogicalRect;
+ typedef mozilla::LogicalSize LogicalSize;
+ typedef mozilla::WritingMode WritingMode;
+ struct Grid;
+ struct GridArea;
+ class LineNameMap;
+ struct LineRange;
+ struct SharedGridData;
+ struct SubgridFallbackTrackSizingFunctions;
+ struct TrackSizingFunctions;
+ struct Tracks;
+ struct TranslatedLineRange;
+ friend nsContainerFrame* NS_NewGridContainerFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+ explicit nsGridContainerFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID),
+ mCachedMinISize(NS_INTRINSIC_ISIZE_UNKNOWN),
+ mCachedPrefISize(NS_INTRINSIC_ISIZE_UNKNOWN) {
+ for (auto& perAxisBaseline : mBaseline) {
+ for (auto& baseline : perAxisBaseline) {
+ baseline = NS_INTRINSIC_ISIZE_UNKNOWN;
+ }
+ }
+ }
+
+ /**
+ * XXX temporary - move the ImplicitNamedAreas stuff to the style system.
+ * The implicit area names that come from x-start .. x-end lines in
+ * grid-template-columns / grid-template-rows are stored in this frame
+ * property when needed, as a ImplicitNamedAreas* value.
+ */
+ void InitImplicitNamedAreas(const nsStylePosition* aStyle);
+
+ using LineNameList =
+ const mozilla::StyleOwnedSlice<mozilla::StyleCustomIdent>;
+ void AddImplicitNamedAreas(mozilla::Span<LineNameList>);
+ using StyleLineNameListValue =
+ const mozilla::StyleGenericLineNameListValue<mozilla::StyleInteger>;
+ void AddImplicitNamedAreas(mozilla::Span<StyleLineNameListValue>);
+
+ /**
+ * Reflow and place our children.
+ * @return the consumed size of all of this grid container's continuations
+ * so far including this frame
+ */
+ nscoord ReflowChildren(GridReflowInput& aState,
+ const LogicalRect& aContentArea,
+ const nsSize& aContainerSize,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus);
+
+ /**
+ * Helper for GetMinISize / GetPrefISize.
+ */
+ nscoord IntrinsicISize(gfxContext* aRenderingContext,
+ mozilla::IntrinsicISizeType aConstraint);
+
+ nscoord GetBBaseline(BaselineSharingGroup aBaselineGroup) const {
+ return mBaseline[mozilla::eLogicalAxisBlock][aBaselineGroup];
+ }
+ nscoord GetIBaseline(BaselineSharingGroup aBaselineGroup) const {
+ return mBaseline[mozilla::eLogicalAxisInline][aBaselineGroup];
+ }
+
+ /**
+ * Calculate this grid container's baselines.
+ * @param aBaselineSet which baseline(s) to derive from a baseline-group or
+ * items; a baseline not included is synthesized from the border-box instead.
+ * @param aFragmentStartTrack is the first track in this fragment in the same
+ * axis as aMajor. Pass zero if that's not the axis we're fragmenting in.
+ * @param aFirstExcludedTrack should be the first track in the next fragment
+ * or one beyond the final track in the last fragment, in aMajor's axis.
+ * Pass the number of tracks if that's not the axis we're fragmenting in.
+ */
+ enum BaselineSet : uint32_t {
+ eNone = 0x0,
+ eFirst = 0x1,
+ eLast = 0x2,
+ eBoth = eFirst | eLast,
+ };
+ void CalculateBaselines(BaselineSet aBaselineSet,
+ mozilla::CSSOrderAwareFrameIterator* aIter,
+ const nsTArray<GridItemInfo>* aGridItems,
+ const Tracks& aTracks, uint32_t aFragmentStartTrack,
+ uint32_t aFirstExcludedTrack, WritingMode aWM,
+ const nsSize& aCBPhysicalSize,
+ nscoord aCBBorderPaddingStart,
+ nscoord aCBBorderPaddingStartEnd, nscoord aCBSize);
+
+ /**
+ * Synthesize a Grid container baseline for aGroup.
+ */
+ nscoord SynthesizeBaseline(const FindItemInGridOrderResult& aGridOrderItem,
+ LogicalAxis aAxis, BaselineSharingGroup aGroup,
+ const nsSize& aCBPhysicalSize, nscoord aCBSize,
+ WritingMode aCBWM);
+ /**
+ * Find the first item in Grid Order in this fragment.
+ * https://drafts.csswg.org/css-grid/#grid-order
+ * @param aFragmentStartTrack is the first track in this fragment in the same
+ * axis as aMajor. Pass zero if that's not the axis we're fragmenting in.
+ */
+ static FindItemInGridOrderResult FindFirstItemInGridOrder(
+ mozilla::CSSOrderAwareFrameIterator& aIter,
+ const nsTArray<GridItemInfo>& aGridItems, LineRange GridArea::*aMajor,
+ LineRange GridArea::*aMinor, uint32_t aFragmentStartTrack);
+ /**
+ * Find the last item in Grid Order in this fragment.
+ * @param aFragmentStartTrack is the first track in this fragment in the same
+ * axis as aMajor. Pass zero if that's not the axis we're fragmenting in.
+ * @param aFirstExcludedTrack should be the first track in the next fragment
+ * or one beyond the final track in the last fragment, in aMajor's axis.
+ * Pass the number of tracks if that's not the axis we're fragmenting in.
+ */
+ static FindItemInGridOrderResult FindLastItemInGridOrder(
+ mozilla::ReverseCSSOrderAwareFrameIterator& aIter,
+ const nsTArray<GridItemInfo>& aGridItems, LineRange GridArea::*aMajor,
+ LineRange GridArea::*aMinor, uint32_t aFragmentStartTrack,
+ uint32_t aFirstExcludedTrack);
+
+ /**
+ * Update our NS_STATE_GRID_IS_COL/ROW_SUBGRID bits and related subgrid state
+ * on our entire continuation chain based on the current style.
+ * This is needed because grid-template-columns/rows style changes only
+ * trigger a reflow so we need to update this dynamically.
+ */
+ void UpdateSubgridFrameState();
+
+ /**
+ * Return the NS_STATE_GRID_IS_COL/ROW_SUBGRID and
+ * NS_STATE_GRID_IS_ROW/COL_MASONRY bits we ought to have.
+ */
+ nsFrameState ComputeSelfSubgridMasonryBits() const;
+
+ private:
+ // Helpers for ReflowChildren
+ struct Fragmentainer {
+ /**
+ * The distance from the first grid container fragment's block-axis content
+ * edge to the fragmentainer end.
+ */
+ nscoord mToFragmentainerEnd;
+ /**
+ * True if the current fragment is at the start of the fragmentainer.
+ */
+ bool mIsTopOfPage;
+ /**
+ * Is there a Class C break opportunity at the start content edge?
+ */
+ bool mCanBreakAtStart;
+ /**
+ * Is there a Class C break opportunity at the end content edge?
+ */
+ bool mCanBreakAtEnd;
+ /**
+ * Is the grid container's block-size unconstrained?
+ */
+ bool mIsAutoBSize;
+ };
+
+ mozilla::Maybe<nsGridContainerFrame::Fragmentainer> GetNearestFragmentainer(
+ const GridReflowInput& aState) const;
+
+ // @return the consumed size of all continuations so far including this frame
+ nscoord ReflowInFragmentainer(GridReflowInput& aState,
+ const LogicalRect& aContentArea,
+ ReflowOutput& aDesiredSize,
+ nsReflowStatus& aStatus,
+ Fragmentainer& aFragmentainer,
+ const nsSize& aContainerSize);
+
+ // Helper for ReflowInFragmentainer
+ // @return the consumed size of all continuations so far including this frame
+ nscoord ReflowRowsInFragmentainer(
+ GridReflowInput& aState, const LogicalRect& aContentArea,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus,
+ Fragmentainer& aFragmentainer, const nsSize& aContainerSize,
+ const nsTArray<const GridItemInfo*>& aItems, uint32_t aStartRow,
+ uint32_t aEndRow, nscoord aBSize, nscoord aAvailableSize);
+
+ // Helper for ReflowChildren / ReflowInFragmentainer
+ void ReflowInFlowChild(nsIFrame* aChild, const GridItemInfo* aGridItemInfo,
+ nsSize aContainerSize,
+ const mozilla::Maybe<nscoord>& aStretchBSize,
+ const Fragmentainer* aFragmentainer,
+ const GridReflowInput& aState,
+ const LogicalRect& aContentArea,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus);
+
+ /**
+ * Places and reflows items when we have masonry layout.
+ * It handles unconstrained reflow and also fragmentation when the row axis
+ * is the masonry axis. ReflowInFragmentainer handles the case when we're
+ * fragmenting and our row axis is a grid axis and it handles masonry layout
+ * in the column axis in that case.
+ * @return the intrinsic size in the masonry axis
+ */
+ nscoord MasonryLayout(GridReflowInput& aState,
+ const LogicalRect& aContentArea,
+ SizingConstraint aConstraint,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus,
+ Fragmentainer* aFragmentainer,
+ const nsSize& aContainerSize);
+
+ // Return the stored UsedTrackSizes, if any.
+ UsedTrackSizes* GetUsedTrackSizes() const;
+
+ // Store the given TrackSizes in aAxis on a UsedTrackSizes frame property.
+ void StoreUsedTrackSizes(LogicalAxis aAxis,
+ const nsTArray<TrackSize>& aSizes);
+
+ // The internal implementation for AddImplicitNamedAreas().
+ void AddImplicitNamedAreasInternal(LineNameList& aNameList,
+ ImplicitNamedAreas*& aAreas);
+
+ /**
+ * Cached values to optimize GetMinISize/GetPrefISize.
+ */
+ nscoord mCachedMinISize;
+ nscoord mCachedPrefISize;
+
+ // Our baselines, one per BaselineSharingGroup per axis.
+ PerLogicalAxis<PerBaseline<nscoord>> mBaseline;
+
+ public:
+ // A cached result for a grid item's block-axis measuring reflow. This
+ // cache prevents us from doing exponential reflows in cases of deeply
+ // nested grid frames.
+ //
+ // We store the cached value in the grid item's frame property table.
+ //
+ // We cache the following as a "key"
+ // - The size of the grid area in the item's inline axis
+ // - The item's block axis baseline padding
+ // ...and we cache the following as the "value",
+ // - The item's border-box BSize
+ class CachedBAxisMeasurement {
+ public:
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(Prop, CachedBAxisMeasurement)
+ CachedBAxisMeasurement(const nsIFrame* aFrame, const LogicalSize& aCBSize,
+ const nscoord aBSize)
+ : mKey(aFrame, aCBSize), mBSize(aBSize) {}
+
+ CachedBAxisMeasurement() = default;
+
+ bool IsValidFor(const nsIFrame* aFrame, const LogicalSize& aCBSize) const {
+ if (aFrame->IsSubtreeDirty()) {
+ return false;
+ }
+
+ if (!CanCacheMeasurement(aFrame, aCBSize)) {
+ return false;
+ }
+
+ return mKey == Key(aFrame, aCBSize);
+ }
+
+ static bool CanCacheMeasurement(const nsIFrame* aFrame,
+ const LogicalSize& aCBSize) {
+ return Key::CanHash(aFrame, aCBSize);
+ }
+
+ nscoord BSize() const { return mBSize; }
+
+ void Update(const nsIFrame* aFrame, const LogicalSize& aCBSize,
+ const nscoord aBSize) {
+ MOZ_ASSERT(CanCacheMeasurement(aFrame, aCBSize));
+ mKey.mHashKey = Key::GenerateHash(aFrame, aCBSize);
+ mBSize = aBSize;
+ }
+
+ private:
+ struct Key {
+ // mHashKey is generated by combining these 2 variables together
+ // 1. The containing block size in the item's inline axis used
+ // for measuring reflow
+ // 2. The item's baseline padding property
+ uint32_t mHashKey;
+
+ Key() = default;
+
+ Key(const nsIFrame* aFrame, const LogicalSize& aCBSize) {
+ MOZ_ASSERT(CanHash(aFrame, aCBSize));
+ mHashKey = GenerateHash(aFrame, aCBSize);
+ }
+
+ void UpdateHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) {
+ MOZ_ASSERT(CanHash(aFrame, aCBSize));
+ mHashKey = GenerateHash(aFrame, aCBSize);
+ }
+
+ static uint32_t GenerateHash(const nsIFrame* aFrame,
+ const LogicalSize& aCBSize) {
+ MOZ_ASSERT(CanHash(aFrame, aCBSize));
+
+ nscoord gridAreaISize = aCBSize.ISize(aFrame->GetWritingMode());
+ nscoord bBaselinePaddingProperty =
+ abs(aFrame->GetProperty(nsIFrame::BBaselinePadProperty()));
+
+ uint_fast8_t bitsNeededForISize = mozilla::FloorLog2(gridAreaISize) + 1;
+
+ return (gridAreaISize << (32 - bitsNeededForISize)) |
+ bBaselinePaddingProperty;
+ }
+
+ static bool CanHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) {
+ uint_fast8_t bitsNeededForISize =
+ mozilla::FloorLog2(aCBSize.ISize(aFrame->GetWritingMode())) + 1;
+
+ uint_fast8_t bitsNeededForBBaselinePadding =
+ mozilla::FloorLog2(
+ abs(aFrame->GetProperty(nsIFrame::BBaselinePadProperty()))) +
+ 1;
+
+ return bitsNeededForISize + bitsNeededForBBaselinePadding <= 32;
+ }
+
+ bool operator==(const Key& aOther) const {
+ return mHashKey == aOther.mHashKey;
+ }
+ };
+
+ Key mKey;
+ nscoord mBSize;
+ };
+
+ bool CanProvideLineIterator() const final { return true; }
+ nsILineIterator* GetLineIterator() final { return this; }
+ int32_t GetNumLines() const final;
+ bool IsLineIteratorFlowRTL() final;
+ mozilla::Result<LineInfo, nsresult> GetLine(int32_t aLineNumber) final;
+ int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) final;
+ NS_IMETHOD FindFrameAt(int32_t aLineNumber, nsPoint aPos,
+ nsIFrame** aFrameFound, bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) final;
+ NS_IMETHOD CheckLineOrder(int32_t aLine, bool* aIsReordered,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) final;
+};
+
+#endif /* nsGridContainerFrame_h___ */
diff --git a/layout/generic/nsHTMLCanvasFrame.cpp b/layout/generic/nsHTMLCanvasFrame.cpp
new file mode 100644
index 0000000000..07aeabec07
--- /dev/null
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -0,0 +1,534 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for the HTML <canvas> element */
+
+#include "nsHTMLCanvasFrame.h"
+
+#include "nsGkAtoms.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderCanvasRenderer.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/webgpu/CanvasContext.h"
+#include "nsDisplayList.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleUtil.h"
+#include "ActiveLayerTracker.h"
+
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+
+/* Helper for our nsIFrame::GetIntrinsicSize() impl. Takes the result of
+ * "GetCanvasSize()" as a parameter, which may help avoid redundant
+ * indirect calls to GetCanvasSize().
+ *
+ * @param aCanvasSizeInPx The canvas's size in CSS pixels, as returned
+ * by GetCanvasSize().
+ * @return The canvas's intrinsic size, as an IntrinsicSize object.
+ */
+static IntrinsicSize IntrinsicSizeFromCanvasSize(
+ const nsIntSize& aCanvasSizeInPx) {
+ return IntrinsicSize(
+ nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.width),
+ nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.height));
+}
+
+/* Helper for our nsIFrame::GetIntrinsicRatio() impl. Takes the result of
+ * "GetCanvasSize()" as a parameter, which may help avoid redundant
+ * indirect calls to GetCanvasSize().
+ *
+ * @return The canvas's intrinsic ratio.
+ */
+static AspectRatio IntrinsicRatioFromCanvasSize(
+ const nsIntSize& aCanvasSizeInPx) {
+ return AspectRatio::FromSize(aCanvasSizeInPx.width, aCanvasSizeInPx.height);
+}
+
+class nsDisplayCanvas final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayCanvas(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayCanvas);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCanvas)
+
+ NS_DISPLAY_DECL_NAME("nsDisplayCanvas", TYPE_CANVAS)
+
+ virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
+ HTMLCanvasElement* canvas = HTMLCanvasElement::FromNode(f->GetContent());
+ nsRegion result;
+ if (canvas->GetIsOpaque()) {
+ // OK, the entire region painted by the canvas is opaque. But what is
+ // that region? It's the canvas's "dest rect" (controlled by the
+ // object-fit/object-position CSS properties), clipped to the container's
+ // content box (which is what GetBounds() returns). So, we grab those
+ // rects and intersect them.
+ nsRect constraintRect = GetBounds(aBuilder, aSnap);
+
+ // Need intrinsic size & ratio, for ComputeObjectDestRect:
+ nsIntSize canvasSize = f->GetCanvasSize();
+ IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSize);
+ AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSize);
+
+ const nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
+ constraintRect, intrinsicSize, intrinsicRatio, f->StylePosition());
+ return nsRegion(destRect.Intersect(constraintRect));
+ }
+ return result;
+ }
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = true;
+ return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override {
+ HTMLCanvasElement* element =
+ static_cast<HTMLCanvasElement*>(mFrame->GetContent());
+ element->HandlePrintCallback(mFrame->PresContext());
+
+ if (element->IsOffscreen()) {
+ // If we are offscreen, then we either display via an ImageContainer
+ // which is updated asynchronously, likely from a worker thread, or a
+ // CompositableHandle managed inside the compositor process. There is
+ // nothing to paint until the owner attaches it.
+
+ element->FlushOffscreenCanvas();
+
+ nsHTMLCanvasFrame* canvasFrame = static_cast<nsHTMLCanvasFrame*>(mFrame);
+ nsIntSize canvasSizeInPx = canvasFrame->GetCanvasSize();
+ IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
+ AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
+ nsRect area = mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
+ area, intrinsicSize, intrinsicRatio, mFrame->StylePosition());
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ dest, mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ RefPtr<ImageContainer> container = element->GetImageContainer();
+ if (container) {
+ MOZ_ASSERT(container->IsAsync());
+ aManager->CommandBuilder().PushImage(this, container, aBuilder,
+ aResources, aSc, bounds, bounds);
+ return true;
+ }
+
+ return true;
+ }
+
+ switch (element->GetCurrentContextType()) {
+ case CanvasContextType::Canvas2D:
+ case CanvasContextType::WebGL1:
+ case CanvasContextType::WebGL2:
+ case CanvasContextType::WebGPU: {
+ bool isRecycled;
+ RefPtr<WebRenderCanvasData> canvasData =
+ aManager->CommandBuilder()
+ .CreateOrRecycleWebRenderUserData<WebRenderCanvasData>(
+ this, &isRecycled);
+ nsHTMLCanvasFrame* canvasFrame =
+ static_cast<nsHTMLCanvasFrame*>(mFrame);
+ if (!canvasFrame->UpdateWebRenderCanvasData(aDisplayListBuilder,
+ canvasData)) {
+ return true;
+ }
+ WebRenderCanvasRendererAsync* data = canvasData->GetCanvasRenderer();
+ MOZ_ASSERT(data);
+ data->UpdateCompositableClient();
+
+ // Push IFrame for async image pipeline.
+ // XXX Remove this once partial display list update is supported.
+
+ nsIntSize canvasSizeInPx = data->GetSize();
+ IntrinsicSize intrinsicSize =
+ IntrinsicSizeFromCanvasSize(canvasSizeInPx);
+ AspectRatio intrinsicRatio =
+ IntrinsicRatioFromCanvasSize(canvasSizeInPx);
+
+ nsRect area =
+ mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
+ area, intrinsicSize, intrinsicRatio, mFrame->StylePosition());
+
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ dest, mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ // We don't push a stacking context for this async image pipeline here.
+ // Instead, we do it inside the iframe that hosts the image. As a
+ // result, a bunch of the calculations normally done as part of that
+ // stacking context need to be done manually and pushed over to the
+ // parent side, where it will be done when we build the display list for
+ // the iframe. That happens in WebRenderCompositableHolder.s2);
+ aBuilder.PushIFrame(bounds, !BackfaceIsHidden(),
+ data->GetPipelineId().ref(),
+ /*ignoreMissingPipelines*/ false);
+
+ LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), bounds.Size());
+ auto filter = wr::ToImageRendering(mFrame->UsedImageRendering());
+ auto mixBlendMode = wr::MixBlendMode::Normal;
+ aManager->WrBridge()->AddWebRenderParentCommand(
+ OpUpdateAsyncImagePipeline(data->GetPipelineId().value(), scBounds,
+ wr::WrRotation::Degree0, filter,
+ mixBlendMode));
+ break;
+ }
+ case CanvasContextType::ImageBitmap: {
+ nsHTMLCanvasFrame* canvasFrame =
+ static_cast<nsHTMLCanvasFrame*>(mFrame);
+ nsIntSize canvasSizeInPx = canvasFrame->GetCanvasSize();
+ if (canvasSizeInPx.width <= 0 || canvasSizeInPx.height <= 0) {
+ return true;
+ }
+ bool isRecycled;
+ RefPtr<WebRenderCanvasData> canvasData =
+ aManager->CommandBuilder()
+ .CreateOrRecycleWebRenderUserData<WebRenderCanvasData>(
+ this, &isRecycled);
+ if (!canvasFrame->UpdateWebRenderCanvasData(aDisplayListBuilder,
+ canvasData)) {
+ canvasData->ClearImageContainer();
+ return true;
+ }
+
+ IntrinsicSize intrinsicSize =
+ IntrinsicSizeFromCanvasSize(canvasSizeInPx);
+ AspectRatio intrinsicRatio =
+ IntrinsicRatioFromCanvasSize(canvasSizeInPx);
+
+ nsRect area =
+ mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
+ area, intrinsicSize, intrinsicRatio, mFrame->StylePosition());
+
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ dest, mFrame->PresContext()->AppUnitsPerDevPixel());
+
+ aManager->CommandBuilder().PushImage(
+ this, canvasData->GetImageContainer(), aBuilder, aResources, aSc,
+ bounds, bounds);
+ break;
+ }
+ case CanvasContextType::NoContext:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown canvas context type");
+ }
+ return true;
+ }
+
+ // FirstContentfulPaint is supposed to ignore "white" canvases. We use
+ // MaybeModified (if GetContext() was called on the canvas) as a standin for
+ // "white"
+ virtual bool IsContentful() const override {
+ nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
+ HTMLCanvasElement* canvas = HTMLCanvasElement::FromNode(f->GetContent());
+ return canvas->MaybeModified();
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) override {
+ nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
+ HTMLCanvasElement* canvas = HTMLCanvasElement::FromNode(f->GetContent());
+
+ nsRect area = f->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ nsIntSize canvasSizeInPx = f->GetCanvasSize();
+
+ nsPresContext* presContext = f->PresContext();
+ canvas->HandlePrintCallback(presContext);
+
+ if (canvasSizeInPx.width <= 0 || canvasSizeInPx.height <= 0 ||
+ area.IsEmpty()) {
+ return;
+ }
+
+ IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
+ AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
+
+ nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
+ area, intrinsicSize, intrinsicRatio, f->StylePosition());
+
+ gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
+
+ if (RefPtr<layers::Image> image = canvas->GetAsImage()) {
+ gfxRect destGFXRect = presContext->AppUnitsToGfxUnits(dest);
+
+ // Transform the canvas into the right place
+ gfxPoint p = destGFXRect.TopLeft();
+ Matrix transform = Matrix::Translation(p.x, p.y);
+ transform.PreScale(destGFXRect.Width() / canvasSizeInPx.width,
+ destGFXRect.Height() / canvasSizeInPx.height);
+
+ aCtx->SetMatrix(
+ gfxUtils::SnapTransformTranslation(aCtx->CurrentMatrix(), nullptr));
+
+ RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
+ if (!surface || !surface->IsValid()) {
+ return;
+ }
+
+ transform = gfxUtils::SnapTransform(
+ transform, gfxRect(0, 0, canvasSizeInPx.width, canvasSizeInPx.height),
+ nullptr);
+ aCtx->Multiply(transform);
+
+ aCtx->GetDrawTarget()->FillRect(
+ Rect(0, 0, canvasSizeInPx.width, canvasSizeInPx.height),
+ SurfacePattern(surface, ExtendMode::CLAMP, Matrix(),
+ nsLayoutUtils::GetSamplingFilterForFrame(f)));
+ return;
+ }
+
+ if (canvas->IsOffscreen()) {
+ return;
+ }
+
+ RefPtr<CanvasRenderer> renderer = new CanvasRenderer();
+ if (!canvas->InitializeCanvasRenderer(aBuilder, renderer)) {
+ return;
+ }
+ renderer->FirePreTransactionCallback();
+ const auto snapshot = renderer->BorrowSnapshot();
+ if (!snapshot) {
+ return;
+ }
+ const auto& surface = snapshot->mSurf;
+ DrawTarget& dt = *aCtx->GetDrawTarget();
+ gfx::Rect destRect =
+ NSRectToSnappedRect(dest, presContext->AppUnitsPerDevPixel(), dt);
+
+ if (!renderer->YIsDown()) {
+ // Calculate y-coord that is as far below the bottom of destGFXRect as
+ // the origin was above the top, then reflect about that.
+ float y = destRect.Y() + destRect.YMost();
+ Matrix transform = Matrix::Translation(0.0f, y).PreScale(1.0f, -1.0f);
+ aCtx->Multiply(transform);
+ }
+
+ const auto& srcRect = surface->GetRect();
+ dt.DrawSurface(
+ surface, destRect,
+ Rect(float(srcRect.X()), float(srcRect.Y()), float(srcRect.Width()),
+ float(srcRect.Height())),
+ DrawSurfaceOptions(nsLayoutUtils::GetSamplingFilterForFrame(f)));
+
+ renderer->FireDidTransactionCallback();
+ renderer->ResetDirty();
+ }
+};
+
+nsIFrame* NS_NewHTMLCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsHTMLCanvasFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_QUERYFRAME_HEAD(nsHTMLCanvasFrame)
+ NS_QUERYFRAME_ENTRY(nsHTMLCanvasFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsHTMLCanvasFrame)
+
+void nsHTMLCanvasFrame::Destroy(DestroyContext& aContext) {
+ if (IsPrimaryFrame()) {
+ HTMLCanvasElement::FromNode(*mContent)->ResetPrintCallback();
+ }
+ nsContainerFrame::Destroy(aContext);
+}
+
+nsHTMLCanvasFrame::~nsHTMLCanvasFrame() = default;
+
+nsIntSize nsHTMLCanvasFrame::GetCanvasSize() const {
+ nsIntSize size(0, 0);
+ HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(GetContent());
+ if (canvas) {
+ size = canvas->GetSize();
+ MOZ_ASSERT(size.width >= 0 && size.height >= 0,
+ "we should've required <canvas> width/height attrs to be "
+ "unsigned (non-negative) values");
+ } else {
+ MOZ_ASSERT_UNREACHABLE("couldn't get canvas size");
+ }
+
+ return size;
+}
+
+/* virtual */
+nscoord nsHTMLCanvasFrame::GetMinISize(gfxContext* aRenderingContext) {
+ // XXX The caller doesn't account for constraints of the height,
+ // min-height, and max-height properties.
+ nscoord result;
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ result = *containISize;
+ } else {
+ bool vertical = GetWritingMode().IsVertical();
+ result = nsPresContext::CSSPixelsToAppUnits(
+ vertical ? GetCanvasSize().height : GetCanvasSize().width);
+ }
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ return result;
+}
+
+/* virtual */
+nscoord nsHTMLCanvasFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ // XXX The caller doesn't account for constraints of the height,
+ // min-height, and max-height properties.
+ nscoord result;
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ result = *containISize;
+ } else {
+ bool vertical = GetWritingMode().IsVertical();
+ result = nsPresContext::CSSPixelsToAppUnits(
+ vertical ? GetCanvasSize().height : GetCanvasSize().width);
+ }
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ return result;
+}
+
+/* virtual */
+IntrinsicSize nsHTMLCanvasFrame::GetIntrinsicSize() {
+ const auto containAxes = GetContainSizeAxes();
+ IntrinsicSize size = containAxes.IsBoth()
+ ? IntrinsicSize(0, 0)
+ : IntrinsicSizeFromCanvasSize(GetCanvasSize());
+ return FinishIntrinsicSize(containAxes, size);
+}
+
+/* virtual */
+AspectRatio nsHTMLCanvasFrame::GetIntrinsicRatio() const {
+ if (GetContainSizeAxes().IsAny()) {
+ return AspectRatio();
+ }
+
+ return IntrinsicRatioFromCanvasSize(GetCanvasSize());
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult nsHTMLCanvasFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ return {ComputeSizeWithIntrinsicDimensions(
+ aRenderingContext, aWM, GetIntrinsicSize(), GetAspectRatio(),
+ aCBSize, aMargin, aBorderPadding, aSizeOverrides, aFlags),
+ AspectRatioUsage::None};
+}
+
+void nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsHTMLCanvasFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE(
+ NS_FRAME_TRACE_CALLS,
+ ("enter nsHTMLCanvasFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow");
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ const LogicalSize finalSize = aReflowInput.ComputedSizeWithBorderPadding(wm);
+
+ aMetrics.SetSize(wm, finalSize);
+ aMetrics.SetOverflowAreasToDesiredBounds();
+ FinishAndStoreOverflow(&aMetrics);
+
+ // Reflow the single anon block child.
+ nsReflowStatus childStatus;
+ nsIFrame* childFrame = mFrames.FirstChild();
+ WritingMode childWM = childFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(childWM);
+ availSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
+ NS_ASSERTION(!childFrame->GetNextSibling(), "HTML canvas should have 1 kid");
+ ReflowOutput childDesiredSize(aReflowInput.GetWritingMode());
+ ReflowInput childReflowInput(aPresContext, aReflowInput, childFrame,
+ availSize);
+ ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowInput, 0,
+ 0, ReflowChildFlags::Default, childStatus, nullptr);
+ FinishReflowChild(childFrame, aPresContext, childDesiredSize,
+ &childReflowInput, 0, 0, ReflowChildFlags::Default);
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("exit nsHTMLCanvasFrame::Reflow: size=%d,%d",
+ aMetrics.ISize(wm), aMetrics.BSize(wm)));
+}
+
+bool nsHTMLCanvasFrame::UpdateWebRenderCanvasData(
+ nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
+ HTMLCanvasElement* element = static_cast<HTMLCanvasElement*>(GetContent());
+ return element->UpdateWebRenderCanvasData(aBuilder, aCanvasData);
+}
+
+void nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) return;
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ if (HidesContent()) {
+ DisplaySelectionOverlay(aBuilder, aLists.Content(),
+ nsISelectionDisplay::DISPLAY_IMAGES);
+ return;
+ }
+
+ uint32_t clipFlags =
+ nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())
+ ? 0
+ : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
+
+ DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
+ aBuilder, this, clipFlags);
+
+ aLists.Content()->AppendNewToTop<nsDisplayCanvas>(aBuilder, this);
+
+ DisplaySelectionOverlay(aBuilder, aLists.Content(),
+ nsISelectionDisplay::DISPLAY_IMAGES);
+}
+
+void nsHTMLCanvasFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ MOZ_ASSERT(mFrames.FirstChild(), "Must have our canvas content anon box");
+ MOZ_ASSERT(!mFrames.FirstChild()->GetNextSibling(),
+ "Must only have our canvas content anon box");
+ aResult.AppendElement(OwnedAnonBox(mFrames.FirstChild()));
+}
+
+void nsHTMLCanvasFrame::UnionChildOverflow(
+ mozilla::OverflowAreas& aOverflowAreas) {
+ // Our one child (the canvas content anon box) is unpainted and isn't relevant
+ // for child-overflow purposes. So we need to provide our own trivial impl to
+ // avoid receiving the child-considering impl that we would otherwise inherit.
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsHTMLCanvasFrame::AccessibleType() {
+ return a11y::eHTMLCanvasType;
+}
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsHTMLCanvasFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"HTMLCanvas"_ns, aResult);
+}
+#endif
diff --git a/layout/generic/nsHTMLCanvasFrame.h b/layout/generic/nsHTMLCanvasFrame.h
new file mode 100644
index 0000000000..86deec2775
--- /dev/null
+++ b/layout/generic/nsHTMLCanvasFrame.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for the HTML <canvas> element */
+
+#ifndef nsHTMLCanvasFrame_h___
+#define nsHTMLCanvasFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+class PresShell;
+namespace layers {
+class CanvasRenderer;
+class Layer;
+class LayerManager;
+class WebRenderCanvasData;
+} // namespace layers
+} // namespace mozilla
+
+class nsPresContext;
+
+nsIFrame* NS_NewHTMLCanvasFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+class nsHTMLCanvasFrame final : public nsContainerFrame {
+ public:
+ typedef mozilla::layers::CanvasRenderer CanvasRenderer;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::layers::WebRenderCanvasData WebRenderCanvasData;
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsHTMLCanvasFrame)
+
+ nsHTMLCanvasFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {}
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ void Destroy(DestroyContext&) override;
+
+ bool UpdateWebRenderCanvasData(nsDisplayListBuilder* aBuilder,
+ WebRenderCanvasData* aCanvasData);
+
+ /* get the size of the canvas's image */
+ nsIntSize GetCanvasSize() const;
+
+ virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+ virtual mozilla::IntrinsicSize GetIntrinsicSize() override;
+ mozilla::AspectRatio GetIntrinsicRatio() const override;
+
+ void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas) override;
+
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ // Inserted child content gets its frames parented by our child block
+ virtual nsContainerFrame* GetContentInsertionFrame() override {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ // Return the ::-moz-html-canvas-content anonymous box.
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+ protected:
+ virtual ~nsHTMLCanvasFrame();
+};
+
+#endif /* nsHTMLCanvasFrame_h___ */
diff --git a/layout/generic/nsHTMLParts.h b/layout/generic/nsHTMLParts.h
new file mode 100644
index 0000000000..e1d7ef4d2d
--- /dev/null
+++ b/layout/generic/nsHTMLParts.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* factory functions for rendering object classes */
+
+#ifndef nsHTMLParts_h___
+#define nsHTMLParts_h___
+
+#include "nscore.h"
+#include "nsFrameState.h"
+#include "nsISupports.h"
+
+class nsAtom;
+class nsCheckboxRadioFrame;
+class nsComboboxControlFrame;
+class nsContainerFrame;
+class nsIChannel;
+class nsIContent;
+class nsIFragmentContentSink;
+class nsIFrame;
+class nsIHTMLContentSink;
+class nsIURI;
+class nsListControlFrame;
+class nsNodeInfoManager;
+class nsTableColFrame;
+namespace mozilla {
+class ComputedStyle;
+class PresShell;
+class PrintedSheetFrame;
+class ViewportFrame;
+
+namespace dom {
+class Document;
+}
+} // namespace mozilla
+
+// Factory methods for creating html layout objects
+
+// Create a frame that supports "display: block" layout behavior
+class nsBlockFrame;
+nsBlockFrame* NS_NewBlockFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+// Special Generated Content Node. It contains text taken from an
+// attribute of its *grandparent* content node.
+nsresult NS_NewAttributeContent(nsNodeInfoManager* aNodeInfoManager,
+ int32_t aNameSpaceID, nsAtom* aAttrName,
+ nsAtom* aFallback, nsIContent** aResult);
+
+// Create a basic area frame but the GetFrameForPoint is overridden to always
+// return the option frame
+// By default, area frames will extend
+// their height to cover any children that "stick out".
+nsContainerFrame* NS_NewSelectsAreaFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+// Create a block formatting context blockframe
+nsBlockFrame* NS_NewBlockFormattingContext(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+nsIFrame* NS_NewBRFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+nsIFrame* NS_NewCommentFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+// <frame> and <iframe>
+nsIFrame* NS_NewSubDocumentFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+// <frameset>
+nsIFrame* NS_NewHTMLFramesetFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+mozilla::ViewportFrame* NS_NewViewportFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsCanvasFrame;
+nsCanvasFrame* NS_NewCanvasFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewImageFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsInlineFrame;
+nsInlineFrame* NS_NewInlineFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewTextFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewEmptyFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewWBRFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+nsBlockFrame* NS_NewColumnSetWrapperFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle,
+ nsFrameState aStateFlags);
+nsContainerFrame* NS_NewColumnSetFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle,
+ nsFrameState aStateFlags);
+
+class nsPageSequenceFrame;
+nsPageSequenceFrame* NS_NewPageSequenceFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+mozilla::PrintedSheetFrame* NS_NewPrintedSheetFrame(
+ mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle);
+
+class nsPageFrame;
+nsPageFrame* NS_NewPageFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsPageContentFrame;
+nsPageContentFrame* NS_NewPageContentFrame(
+ mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle,
+ already_AddRefed<const nsAtom> aPageName);
+nsIFrame* NS_NewPageBreakFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsFirstLetterFrame;
+nsFirstLetterFrame* NS_NewFirstLetterFrame(mozilla::PresShell*,
+ mozilla::ComputedStyle*);
+class nsFirstLetterFrame;
+nsFirstLetterFrame* NS_NewFloatingFirstLetterFrame(mozilla::PresShell*,
+ mozilla::ComputedStyle*);
+class nsFirstLineFrame;
+nsFirstLineFrame* NS_NewFirstLineFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+// forms
+nsContainerFrame* NS_NewGfxButtonControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsCheckboxRadioFrame* NS_NewCheckboxRadioFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewImageControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewHTMLButtonControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewFieldSetFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewFileControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewColorControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewTextControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsListControlFrame* NS_NewListControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsComboboxControlFrame* NS_NewComboboxControlFrame(
+ mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewProgressFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewMeterFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewRangeFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewNumberControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewDateTimeControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewSearchControlFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+// Table frame factories
+class nsTableWrapperFrame;
+nsTableWrapperFrame* NS_NewTableWrapperFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsTableFrame;
+nsTableFrame* NS_NewTableFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsTableColFrame* NS_NewTableColFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsTableColGroupFrame;
+nsTableColGroupFrame* NS_NewTableColGroupFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsTableRowFrame;
+nsTableRowFrame* NS_NewTableRowFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsTableRowGroupFrame;
+nsTableRowGroupFrame* NS_NewTableRowGroupFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+class nsTableCellFrame;
+nsTableCellFrame* NS_NewTableCellFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle,
+ nsTableFrame* aTableFrame);
+
+nsresult NS_NewHTMLContentSink(nsIHTMLContentSink** aInstancePtrResult,
+ mozilla::dom::Document* aDoc, nsIURI* aURL,
+ nsISupports* aContainer, // e.g. docshell
+ nsIChannel* aChannel);
+nsresult NS_NewHTMLFragmentContentSink(
+ nsIFragmentContentSink** aInstancePtrResult);
+nsresult NS_NewHTMLFragmentContentSink2(
+ nsIFragmentContentSink** aInstancePtrResult);
+
+#endif /* nsHTMLParts_h___ */
diff --git a/layout/generic/nsIAnonymousContentCreator.h b/layout/generic/nsIAnonymousContentCreator.h
new file mode 100644
index 0000000000..344d9f0096
--- /dev/null
+++ b/layout/generic/nsIAnonymousContentCreator.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * interface for rendering objects that manually create subtrees of
+ * anonymous content
+ */
+
+#ifndef nsIAnonymousContentCreator_h___
+#define nsIAnonymousContentCreator_h___
+
+#include "mozilla/AnonymousContentKey.h"
+
+#include "nsQueryFrame.h"
+#include "nsTArrayForwardDeclare.h"
+#include "X11UndefineNone.h"
+
+class nsIContent;
+
+/**
+ * Any source for anonymous content can implement this interface to provide it.
+ * HTML frames like nsFileControlFrame currently use this.
+ *
+ * @see nsCSSFrameConstructor
+ */
+class nsIAnonymousContentCreator {
+ public:
+ NS_DECL_QUERYFRAME_TARGET(nsIAnonymousContentCreator)
+
+ struct ContentInfo {
+ explicit ContentInfo(
+ nsIContent* aContent,
+ mozilla::AnonymousContentKey aKey = mozilla::AnonymousContentKey::None)
+ : mContent(aContent), mKey(aKey) {}
+
+ nsIContent* mContent;
+ mozilla::AnonymousContentKey mKey;
+ };
+
+ /**
+ * Creates "native" anonymous content and adds the created content to
+ * the aElements array. None of the returned elements can be nullptr.
+ *
+ * If the anonymous content creator sets the editable flag on some
+ * of the elements that it creates, the flag will be applied to the node
+ * upon being bound to the document.
+ *
+ * @note The returned elements are owned by this object. This object is
+ * responsible for calling UnbindFromTree on the elements it returned
+ * from CreateAnonymousContent when appropriate (i.e. before releasing
+ * them).
+ */
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) = 0;
+
+ /**
+ * Appends "native" anonymous children created by CreateAnonymousContent()
+ * to the given content list depending on the filter.
+ *
+ * @see nsIContent::GetChildren for set of values used for filter. Currently,
+ * eSkipPlaceholderContent is the only flag that any implementation of
+ * this method heeds.
+ */
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) = 0;
+};
+
+#endif
diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp
new file mode 100644
index 0000000000..81109151ff
--- /dev/null
+++ b/layout/generic/nsIFrame.cpp
@@ -0,0 +1,12724 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class of all rendering objects */
+
+#include "nsIFrame.h"
+
+#include <stdarg.h>
+#include <algorithm>
+
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/dom/CSSAnimation.h"
+#include "mozilla/dom/CSSTransition.h"
+#include "mozilla/dom/ContentVisibilityAutoStateChangeEvent.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/SelectionMovementUtils.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/SVGMaskFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/TextControlElement.h"
+#include "mozilla/ToString.h"
+#include "mozilla/Try.h"
+#include "mozilla/ViewportUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsFieldSetFrame.h"
+#include "nsFlexContainerFrame.h"
+#include "nsFocusManager.h"
+#include "nsFrameList.h"
+#include "nsPlaceholderFrame.h"
+#include "nsIBaseWindow.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRendering.h"
+#include "nsAtom.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsTableWrapperFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsStyleConsts.h"
+#include "mozilla/Logging.h"
+#include "nsLayoutUtils.h"
+#include "LayoutLogging.h"
+#include "mozilla/RestyleManager.h"
+#include "nsImageFrame.h"
+#include "nsInlineFrame.h"
+#include "nsFrameSelection.h"
+#include "nsGkAtoms.h"
+#include "nsGridContainerFrame.h"
+#include "nsGfxScrollFrame.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCanvasFrame.h"
+
+#include "nsFieldSetFrame.h"
+#include "nsFrameTraversal.h"
+#include "nsRange.h"
+#include "nsITextControlFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsIPercentBSizeObserver.h"
+#include "nsStyleStructInlines.h"
+
+#include "nsBidiPresUtils.h"
+#include "RubyUtils.h"
+#include "TextOverflow.h"
+#include "nsAnimationManager.h"
+
+// For triple-click pref
+#include "imgIRequest.h"
+#include "nsError.h"
+#include "nsContainerFrame.h"
+#include "nsBlockFrame.h"
+#include "nsDisplayList.h"
+#include "nsChangeHint.h"
+#include "nsSubDocumentFrame.h"
+#include "RetainedDisplayListBuilder.h"
+
+#include "gfxContext.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "ScrollSnap.h"
+#include "StickyScrollContainer.h"
+#include "nsFontInflationData.h"
+#include "nsRegion.h"
+#include "nsIFrameInlines.h"
+#include "nsStyleChangeList.h"
+#include "nsWindowSizes.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/CSSClipPathInstance.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoStyleSetInlines.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/SVGPathData.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "mozilla/layout/ScrollAnchorContainer.h"
+#include "nsPrintfCString.h"
+#include "ActiveLayerTracker.h"
+
+#include "nsITheme.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::layout;
+typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
+using nsStyleTransformMatrix::TransformReferenceBox;
+
+nsIFrame* nsILineIterator::LineInfo::GetLastFrameOnLine() const {
+ if (!mNumFramesOnLine) {
+ return nullptr; // empty line, not illegal
+ }
+ MOZ_ASSERT(mFirstFrameOnLine);
+ nsIFrame* maybeLastFrame = mFirstFrameOnLine;
+ for ([[maybe_unused]] int32_t i : IntegerRange(mNumFramesOnLine - 1)) {
+ maybeLastFrame = maybeLastFrame->GetNextSibling();
+ if (NS_WARN_IF(!maybeLastFrame)) {
+ return nullptr;
+ }
+ }
+ return maybeLastFrame;
+}
+
+#ifdef HAVE_64BIT_BUILD
+static_assert(sizeof(nsIFrame) == 120, "nsIFrame should remain small");
+#else
+static_assert(sizeof(void*) == 4, "Odd build config?");
+// FIXME(emilio): Investigate why win32 and android-arm32 have bigger sizes (80)
+// than Linux32 (76).
+static_assert(sizeof(nsIFrame) <= 80, "nsIFrame should remain small");
+#endif
+
+const mozilla::LayoutFrameType nsIFrame::sLayoutFrameTypes[kFrameClassCount] = {
+#define FRAME_ID(class_, type_, ...) mozilla::LayoutFrameType::type_,
+#define ABSTRACT_FRAME_ID(...)
+#include "mozilla/FrameIdList.h"
+#undef FRAME_ID
+#undef ABSTRACT_FRAME_ID
+};
+
+const nsIFrame::ClassFlags nsIFrame::sLayoutFrameClassFlags[kFrameClassCount] =
+ {
+#define FRAME_ID(class_, type_, flags_, ...) flags_,
+#define ABSTRACT_FRAME_ID(...)
+#include "mozilla/FrameIdList.h"
+#undef FRAME_ID
+#undef ABSTRACT_FRAME_ID
+};
+
+std::ostream& operator<<(std::ostream& aStream, const nsDirection& aDirection) {
+ return aStream << (aDirection == eDirNext ? "eDirNext" : "eDirPrevious");
+}
+
+struct nsContentAndOffset {
+ nsIContent* mContent = nullptr;
+ int32_t mOffset = 0;
+};
+
+#include "nsILineIterator.h"
+#include "prenv.h"
+
+// Utility function to set a nsRect-valued property table entry on aFrame,
+// reusing the existing storage if the property happens to be already set.
+template <typename T>
+static void SetOrUpdateRectValuedProperty(
+ nsIFrame* aFrame, FrameProperties::Descriptor<T> aProperty,
+ const nsRect& aNewValue) {
+ bool found;
+ nsRect* rectStorage = aFrame->GetProperty(aProperty, &found);
+ if (!found) {
+ rectStorage = new nsRect(aNewValue);
+ aFrame->AddProperty(aProperty, rectStorage);
+ } else {
+ *rectStorage = aNewValue;
+ }
+}
+
+FrameDestroyContext::~FrameDestroyContext() {
+ for (auto& content : mozilla::Reversed(mAnonymousContent)) {
+ mPresShell->NativeAnonymousContentRemoved(content);
+ content->UnbindFromTree();
+ }
+}
+
+// Formerly the nsIFrameDebug interface
+
+std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus) {
+ char complete = 'Y';
+ if (aStatus.IsIncomplete()) {
+ complete = 'N';
+ } else if (aStatus.IsOverflowIncomplete()) {
+ complete = 'O';
+ }
+
+ char brk = 'N';
+ if (aStatus.IsInlineBreakBefore()) {
+ brk = 'B';
+ } else if (aStatus.IsInlineBreakAfter()) {
+ brk = 'A';
+ }
+
+ aStream << "["
+ << "Complete=" << complete << ","
+ << "NIF=" << (aStatus.NextInFlowNeedsReflow() ? 'Y' : 'N') << ","
+ << "Break=" << brk << ","
+ << "FirstLetter=" << (aStatus.FirstLetterComplete() ? 'Y' : 'N')
+ << "]";
+ return aStream;
+}
+
+#ifdef DEBUG
+
+/**
+ * Note: the log module is created during library initialization which
+ * means that you cannot perform logging before then.
+ */
+mozilla::LazyLogModule nsIFrame::sFrameLogModule("frame");
+
+#endif
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(AbsoluteContainingBlockProperty,
+ nsAbsoluteContainingBlock)
+
+bool nsIFrame::HasAbsolutelyPositionedChildren() const {
+ return IsAbsoluteContainer() &&
+ GetAbsoluteContainingBlock()->HasAbsoluteFrames();
+}
+
+nsAbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const {
+ NS_ASSERTION(IsAbsoluteContainer(),
+ "The frame is not marked as an abspos container correctly");
+ nsAbsoluteContainingBlock* absCB =
+ GetProperty(AbsoluteContainingBlockProperty());
+ NS_ASSERTION(absCB,
+ "The frame is marked as an abspos container but doesn't have "
+ "the property");
+ return absCB;
+}
+
+void nsIFrame::MarkAsAbsoluteContainingBlock() {
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
+ NS_ASSERTION(!GetProperty(AbsoluteContainingBlockProperty()),
+ "Already has an abs-pos containing block property?");
+ NS_ASSERTION(!HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
+ "Already has NS_FRAME_HAS_ABSPOS_CHILDREN state bit?");
+ AddStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
+ SetProperty(AbsoluteContainingBlockProperty(),
+ new nsAbsoluteContainingBlock(GetAbsoluteListID()));
+}
+
+void nsIFrame::MarkAsNotAbsoluteContainingBlock() {
+ NS_ASSERTION(!HasAbsolutelyPositionedChildren(), "Think of the children!");
+ NS_ASSERTION(GetProperty(AbsoluteContainingBlockProperty()),
+ "Should have an abs-pos containing block property");
+ NS_ASSERTION(HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
+ "Should have NS_FRAME_HAS_ABSPOS_CHILDREN state bit");
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
+ RemoveStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
+ RemoveProperty(AbsoluteContainingBlockProperty());
+}
+
+bool nsIFrame::CheckAndClearPaintedState() {
+ bool result = HasAnyStateBits(NS_FRAME_PAINTED_THEBES);
+ RemoveStateBits(NS_FRAME_PAINTED_THEBES);
+
+ for (const auto& childList : ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ if (child->CheckAndClearPaintedState()) {
+ result = true;
+ }
+ }
+ }
+ return result;
+}
+
+bool nsIFrame::CheckAndClearDisplayListState() {
+ bool result = BuiltDisplayList();
+ SetBuiltDisplayList(false);
+
+ for (const auto& childList : ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ if (child->CheckAndClearDisplayListState()) {
+ result = true;
+ }
+ }
+ }
+ return result;
+}
+
+bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const {
+ if (!StyleVisibility()->IsVisible()) {
+ return false;
+ }
+
+ if (PresShell()->IsUnderHiddenEmbedderElement()) {
+ return false;
+ }
+
+ const nsIFrame* frame = this;
+ while (frame) {
+ nsView* view = frame->GetView();
+ if (view && view->GetVisibility() == ViewVisibility::Hide) {
+ return false;
+ }
+
+ if (frame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
+ return false;
+ }
+
+ // This method is used to determine if a frame is focusable, because it's
+ // called by nsIFrame::IsFocusable. `content-visibility: auto` should not
+ // force this frame to be unfocusable, so we only take into account
+ // `content-visibility: hidden` here.
+ if (this != frame &&
+ frame->HidesContent(IncludeContentVisibility::Hidden)) {
+ return false;
+ }
+
+ if (nsIFrame* parent = frame->GetParent()) {
+ frame = parent;
+ } else {
+ parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
+ if (!parent) break;
+
+ if ((aFlags & nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) == 0 &&
+ parent->PresContext()->IsChrome() &&
+ !frame->PresContext()->IsChrome()) {
+ break;
+ }
+
+ frame = parent;
+ }
+ }
+
+ return true;
+}
+
+void nsIFrame::FindCloserFrameForSelection(
+ const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) {
+ if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect,
+ aCurrentBestFrame->mXDistance,
+ aCurrentBestFrame->mYDistance)) {
+ aCurrentBestFrame->mFrame = this;
+ }
+}
+
+void nsIFrame::ElementStateChanged(mozilla::dom::ElementState aStates) {}
+
+void WeakFrame::Clear(mozilla::PresShell* aPresShell) {
+ if (aPresShell) {
+ aPresShell->RemoveWeakFrame(this);
+ }
+ mFrame = nullptr;
+}
+
+AutoWeakFrame::AutoWeakFrame(const WeakFrame& aOther)
+ : mPrev(nullptr), mFrame(nullptr) {
+ Init(aOther.GetFrame());
+}
+
+void AutoWeakFrame::Clear(mozilla::PresShell* aPresShell) {
+ if (aPresShell) {
+ aPresShell->RemoveAutoWeakFrame(this);
+ }
+ mFrame = nullptr;
+ mPrev = nullptr;
+}
+
+AutoWeakFrame::~AutoWeakFrame() {
+ Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
+}
+
+void AutoWeakFrame::Init(nsIFrame* aFrame) {
+ Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
+ mFrame = aFrame;
+ if (mFrame) {
+ mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
+ NS_WARNING_ASSERTION(presShell, "Null PresShell in AutoWeakFrame!");
+ if (presShell) {
+ presShell->AddAutoWeakFrame(this);
+ } else {
+ mFrame = nullptr;
+ }
+ }
+}
+
+void WeakFrame::Init(nsIFrame* aFrame) {
+ Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
+ mFrame = aFrame;
+ if (mFrame) {
+ mozilla::PresShell* presShell = mFrame->PresContext()->GetPresShell();
+ MOZ_ASSERT(presShell, "Null PresShell in WeakFrame!");
+ if (presShell) {
+ presShell->AddWeakFrame(this);
+ } else {
+ mFrame = nullptr;
+ }
+ }
+}
+
+nsIFrame* NS_NewEmptyFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsIFrame(aStyle, aPresShell->GetPresContext());
+}
+
+nsIFrame::~nsIFrame() {
+ MOZ_COUNT_DTOR(nsIFrame);
+
+ MOZ_ASSERT(GetVisibility() != Visibility::ApproximatelyVisible,
+ "Visible nsFrame is being destroyed");
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsIFrame)
+
+// Dummy operator delete. Will never be called, but must be defined
+// to satisfy some C++ ABIs.
+void nsIFrame::operator delete(void*, size_t) {
+ MOZ_CRASH("nsIFrame::operator delete should never be called");
+}
+
+NS_QUERYFRAME_HEAD(nsIFrame)
+ NS_QUERYFRAME_ENTRY(nsIFrame)
+NS_QUERYFRAME_TAIL_INHERITANCE_ROOT
+
+/////////////////////////////////////////////////////////////////////////////
+// nsIFrame
+
+static bool IsFontSizeInflationContainer(nsIFrame* aFrame,
+ const nsStyleDisplay* aStyleDisplay) {
+ /*
+ * Font size inflation is built around the idea that we're inflating
+ * the fonts for a pan-and-zoom UI so that when the user scales up a
+ * block or other container to fill the width of the device, the fonts
+ * will be readable. To do this, we need to pick what counts as a
+ * container.
+ *
+ * From a code perspective, the only hard requirement is that frames
+ * that are line participants (nsIFrame::IsLineParticipant) are never
+ * containers, since line layout assumes that the inflation is consistent
+ * within a line.
+ *
+ * This is not an imposition, since we obviously want a bunch of text
+ * (possibly with inline elements) flowing within a block to count the
+ * block (or higher) as its container.
+ *
+ * We also want form controls, including the text in the anonymous
+ * content inside of them, to match each other and the text next to
+ * them, so they and their anonymous content should also not be a
+ * container.
+ *
+ * However, because we can't reliably compute sizes across XUL during
+ * reflow, any XUL frame with a XUL parent is always a container.
+ *
+ * There are contexts where it would be nice if some blocks didn't
+ * count as a container, so that, for example, an indented quotation
+ * didn't end up with a smaller font size. However, it's hard to
+ * distinguish these situations where we really do want the indented
+ * thing to count as a container, so we don't try, and blocks are
+ * always containers.
+ */
+
+ // The root frame should always be an inflation container.
+ if (!aFrame->GetParent()) {
+ return true;
+ }
+
+ nsIContent* content = aFrame->GetContent();
+ if (content && content->IsInNativeAnonymousSubtree()) {
+ // Native anonymous content shouldn't be a font inflation root,
+ // except for the canvas custom content container.
+ nsCanvasFrame* canvas = aFrame->PresShell()->GetCanvasFrame();
+ return canvas && canvas->GetCustomContentContainer() == content;
+ }
+
+ LayoutFrameType frameType = aFrame->Type();
+ bool isInline =
+ aFrame->GetDisplay().IsInlineFlow() || RubyUtils::IsRubyBox(frameType) ||
+ (aStyleDisplay->IsFloatingStyle() &&
+ frameType == LayoutFrameType::Letter) ||
+ // Given multiple frames for the same node, only the
+ // outer one should be considered a container.
+ // (Important, e.g., for nsSelectsAreaFrame.)
+ (aFrame->GetParent()->GetContent() == content) ||
+ (content &&
+ // Form controls shouldn't become inflation containers.
+ (content->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup,
+ nsGkAtoms::select, nsGkAtoms::input,
+ nsGkAtoms::button, nsGkAtoms::textarea)));
+ NS_ASSERTION(!aFrame->IsLineParticipant() || isInline ||
+ // br frames and mathml frames report being line
+ // participants even when their position or display is
+ // set
+ aFrame->IsBrFrame() || aFrame->IsMathMLFrame(),
+ "line participants must not be containers");
+ return !isInline;
+}
+
+static void MaybeScheduleReflowSVGNonDisplayText(nsIFrame* aFrame) {
+ if (!aFrame->IsInSVGTextSubtree()) {
+ return;
+ }
+
+ // We need to ensure that any non-display SVGTextFrames get reflowed when a
+ // child text frame gets new style. Thus we need to schedule a reflow in
+ // |DidSetComputedStyle|. We also need to call it from |DestroyFrom|,
+ // because otherwise we won't get notified when style changes to
+ // "display:none".
+ SVGTextFrame* svgTextFrame = static_cast<SVGTextFrame*>(
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText));
+ nsIFrame* anonBlock = svgTextFrame->PrincipalChildList().FirstChild();
+
+ // Note that we must check NS_FRAME_FIRST_REFLOW on our SVGTextFrame's
+ // anonymous block frame rather than our aFrame, since NS_FRAME_FIRST_REFLOW
+ // may be set on us if we're a new frame that has been inserted after the
+ // document's first reflow. (In which case this DidSetComputedStyle call may
+ // be happening under frame construction under a Reflow() call.)
+ if (!anonBlock || anonBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ return;
+ }
+
+ if (!svgTextFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
+ svgTextFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)) {
+ return;
+ }
+
+ svgTextFrame->ScheduleReflowSVGNonDisplayText(
+ IntrinsicDirty::FrameAncestorsAndDescendants);
+}
+
+bool nsIFrame::IsPrimaryFrameOfRootOrBodyElement() const {
+ if (!IsPrimaryFrame()) {
+ return false;
+ }
+ nsIContent* content = GetContent();
+ Document* document = content->OwnerDoc();
+ return content == document->GetRootElement() ||
+ content == document->GetBodyElement();
+}
+
+bool nsIFrame::IsRenderedLegend() const {
+ if (auto* parent = GetParent(); parent && parent->IsFieldSetFrame()) {
+ return static_cast<nsFieldSetFrame*>(parent)->GetLegend() == this;
+ }
+ return false;
+}
+
+void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT(nsQueryFrame::FrameIID(mClass) == GetFrameId());
+ MOZ_ASSERT(!mContent, "Double-initing a frame?");
+
+ mContent = aContent;
+ mParent = aParent;
+ MOZ_DIAGNOSTIC_ASSERT(!mParent || PresShell() == mParent->PresShell());
+
+ if (aPrevInFlow) {
+ mWritingMode = aPrevInFlow->GetWritingMode();
+
+ // Copy some state bits from prev-in-flow (the bits that should apply
+ // throughout a continuation chain). The bits are sorted according to their
+ // order in nsFrameStateBits.h.
+
+ // clang-format off
+ AddStateBits(aPrevInFlow->GetStateBits() &
+ (NS_FRAME_GENERATED_CONTENT |
+ NS_FRAME_OUT_OF_FLOW |
+ NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN |
+ NS_FRAME_INDEPENDENT_SELECTION |
+ NS_FRAME_PART_OF_IBSPLIT |
+ NS_FRAME_MAY_BE_TRANSFORMED |
+ NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR));
+ // clang-format on
+
+ // Copy other bits in nsIFrame from prev-in-flow.
+ mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings();
+ } else {
+ PresContext()->ConstructedFrame();
+ }
+
+ if (GetParent()) {
+ if (MOZ_UNLIKELY(mContent == PresContext()->Document()->GetRootElement() &&
+ mContent == GetParent()->GetContent())) {
+ // Our content is the root element and we have the same content as our
+ // parent. That is, we are the internal anonymous frame of the root
+ // element. Copy the used mWritingMode from our parent because
+ // mDocElementContainingBlock gets its mWritingMode from <body>.
+ mWritingMode = GetParent()->GetWritingMode();
+ }
+
+ // Copy some state bits from our parent (the bits that should apply
+ // recursively throughout a subtree). The bits are sorted according to their
+ // order in nsFrameStateBits.h.
+
+ // clang-format off
+ AddStateBits(GetParent()->GetStateBits() &
+ (NS_FRAME_GENERATED_CONTENT |
+ NS_FRAME_INDEPENDENT_SELECTION |
+ NS_FRAME_IS_SVG_TEXT |
+ NS_FRAME_IN_POPUP |
+ NS_FRAME_IS_NONDISPLAY));
+ // clang-format on
+
+ if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) {
+ // Assume all frames in popups are visible.
+ IncApproximateVisibleCount();
+ }
+ }
+ if (aPrevInFlow) {
+ mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation();
+ mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation();
+ } else if (mContent) {
+ // It's fine to fetch the EffectSet for the style frame here because in the
+ // following code we take care of the case where animations may target
+ // a different frame.
+ EffectSet* effectSet = EffectSet::GetForStyleFrame(this);
+ if (effectSet) {
+ mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation();
+
+ if (effectSet->MayHaveTransformAnimation()) {
+ // If we are the inner table frame for display:table content, then
+ // transform animations should go on our parent frame (the table wrapper
+ // frame).
+ //
+ // We do this when initializing the child frame (table inner frame),
+ // because when initializng the table wrapper frame, we don't yet have
+ // access to its children so we can't tell if we have transform
+ // animations or not.
+ if (SupportsCSSTransforms()) {
+ mMayHaveTransformAnimation = true;
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ } else if (aParent && nsLayoutUtils::GetStyleFrame(aParent) == this) {
+ MOZ_ASSERT(
+ aParent->SupportsCSSTransforms(),
+ "Style frames that don't support transforms should have parents"
+ " that do");
+ aParent->mMayHaveTransformAnimation = true;
+ aParent->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+ }
+ }
+ }
+
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (disp->HasTransform(this)) {
+ // If 'transform' dynamically changes, RestyleManager takes care of
+ // updating this bit.
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+
+ if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) ||
+ !GetParent()
+#ifdef DEBUG
+ // We have assertions that check inflation invariants even when
+ // font size inflation is not enabled.
+ || true
+#endif
+ ) {
+ if (IsFontSizeInflationContainer(this, disp)) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER);
+ if (!GetParent() ||
+ // I'd use NS_FRAME_OUT_OF_FLOW, but it's not set yet.
+ disp->IsFloating(this) || disp->IsAbsolutelyPositioned(this) ||
+ GetParent()->IsFlexContainerFrame() ||
+ GetParent()->IsGridContainerFrame()) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+ }
+ NS_ASSERTION(
+ GetParent() || HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER),
+ "root frame should always be a container");
+ }
+
+ if (TrackingVisibility() && PresShell()->AssumeAllFramesVisible()) {
+ IncApproximateVisibleCount();
+ }
+
+ DidSetComputedStyle(nullptr);
+
+ // For a newly created frame, we need to update this frame's visibility state.
+ // Usually we update the state when the frame is restyled and has a
+ // VisibilityChange change hint but we don't generate any change hints for
+ // newly created frames.
+ // Note: We don't need to do this for placeholders since placeholders have
+ // different styles so that the styles don't have visibility:hidden even if
+ // the parent has visibility:hidden style. We also don't need to update the
+ // state when creating continuations because its visibility is the same as its
+ // prev-in-flow, and the animation code cares only primary frames.
+ if (!IsPlaceholderFrame() && !aPrevInFlow) {
+ UpdateVisibleDescendantsState();
+ }
+
+ if (!aPrevInFlow && HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ // We aren't going to get a reflow, so nothing else will call
+ // InvalidateRenderingObservers, we have to do it here.
+ SVGObserverUtils::InvalidateRenderingObservers(this);
+ }
+}
+
+void nsIFrame::InitPrimaryFrame() {
+ MOZ_ASSERT(IsPrimaryFrame());
+ HandlePrimaryFrameStyleChange(nullptr);
+}
+
+void nsIFrame::HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle) {
+ const nsStyleDisplay* disp = StyleDisplay();
+ const nsStyleDisplay* oldDisp =
+ aOldStyle ? aOldStyle->StyleDisplay() : nullptr;
+
+ const bool wasQueryContainer = oldDisp && oldDisp->IsQueryContainer();
+ const bool isQueryContainer = disp->IsQueryContainer();
+ if (wasQueryContainer != isQueryContainer) {
+ auto* pc = PresContext();
+ if (isQueryContainer) {
+ pc->RegisterContainerQueryFrame(this);
+ } else {
+ pc->UnregisterContainerQueryFrame(this);
+ }
+ }
+
+ const auto cv = disp->ContentVisibility(*this);
+ if (!oldDisp || oldDisp->ContentVisibility(*this) != cv) {
+ if (cv == StyleContentVisibility::Auto) {
+ PresShell()->RegisterContentVisibilityAutoFrame(this);
+ } else {
+ if (auto* element = Element::FromNodeOrNull(GetContent())) {
+ element->ClearContentRelevancy();
+ }
+ PresShell()->UnregisterContentVisibilityAutoFrame(this);
+ }
+ PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations();
+ }
+
+ HandleLastRememberedSize();
+}
+
+void nsIFrame::Destroy(DestroyContext& aContext) {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "destroy called on frame while scripts not blocked");
+ NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(),
+ "Frames should be removed before destruction.");
+ MOZ_ASSERT(!HasAbsolutelyPositionedChildren());
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
+ "NS_FRAME_PART_OF_IBSPLIT set on non-nsContainerFrame?");
+
+ MaybeScheduleReflowSVGNonDisplayText(this);
+
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+
+ const auto* disp = StyleDisplay();
+ if (disp->mPosition == StylePositionProperty::Sticky) {
+ if (auto* ssc =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(this)) {
+ ssc->RemoveFrame(this);
+ }
+ }
+
+ if (HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ if (nsPlaceholderFrame* placeholder = GetPlaceholderFrame()) {
+ placeholder->SetOutOfFlowFrame(nullptr);
+ }
+ }
+
+ nsPresContext* pc = PresContext();
+ mozilla::PresShell* ps = pc->GetPresShell();
+ if (IsPrimaryFrame()) {
+ if (disp->IsQueryContainer()) {
+ pc->UnregisterContainerQueryFrame(this);
+ }
+ if (disp->ContentVisibility(*this) == StyleContentVisibility::Auto) {
+ ps->UnregisterContentVisibilityAutoFrame(this);
+ }
+ // This needs to happen before we clear our Properties() table.
+ ActiveLayerTracker::TransferActivityToContent(this, mContent);
+ }
+
+ ScrollAnchorContainer* anchor = nullptr;
+ if (IsScrollAnchor(&anchor)) {
+ anchor->InvalidateAnchor();
+ }
+
+ if (HasCSSAnimations() || HasCSSTransitions() ||
+ // It's fine to look up the style frame here since if we're destroying the
+ // frames for display:table content we should be destroying both wrapper
+ // and inner frame.
+ EffectSet::GetForStyleFrame(this)) {
+ // If no new frame for this element is created by the end of the
+ // restyling process, stop animations and transitions for this frame
+ RestyleManager::AnimationsWithDestroyedFrame* adf =
+ pc->RestyleManager()->GetAnimationsWithDestroyedFrame();
+ // AnimationsWithDestroyedFrame only lives during the restyling process.
+ if (adf) {
+ adf->Put(mContent, mComputedStyle);
+ }
+ }
+
+ // Disable visibility tracking. Note that we have to do this before we clear
+ // frame properties and lose track of whether we were previously visible.
+ // XXX(seth): It'd be ideal to assert that we're already marked nonvisible
+ // here, but it's unfortunately tricky to guarantee in the face of things like
+ // frame reconstruction induced by style changes.
+ DisableVisibilityTracking();
+
+ // Ensure that we're not in the approximately visible list anymore.
+ ps->RemoveFrameFromApproximatelyVisibleList(this);
+
+ ps->NotifyDestroyingFrame(this);
+
+ if (HasAnyStateBits(NS_FRAME_EXTERNAL_REFERENCE)) {
+ ps->ClearFrameRefs(this);
+ }
+
+ nsView* view = GetView();
+ if (view) {
+ view->SetFrame(nullptr);
+ view->Destroy();
+ }
+
+ // Make sure that our deleted frame can't be returned from GetPrimaryFrame()
+ if (IsPrimaryFrame()) {
+ mContent->SetPrimaryFrame(nullptr);
+
+ // Pass the root of a generated content subtree (e.g. ::after/::before) to
+ // aPostDestroyData to unbind it after frame destruction is done.
+ if (HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) &&
+ mContent->IsRootOfNativeAnonymousSubtree()) {
+ aContext.AddAnonymousContent(mContent.forget());
+ }
+ }
+
+ // Remove all properties attached to the frame, to ensure any property
+ // destructors that need the frame pointer are handled properly.
+ RemoveAllProperties();
+
+ // Must retrieve the object ID before calling destructors, so the
+ // vtable is still valid.
+ //
+ // Note to future tweakers: having the method that returns the
+ // object size call the destructor will not avoid an indirect call;
+ // the compiler cannot devirtualize the call to the destructor even
+ // if it's from a method defined in the same class.
+
+ nsQueryFrame::FrameIID id = GetFrameId();
+ this->~nsIFrame();
+
+#ifdef DEBUG
+ {
+ nsIFrame* rootFrame = ps->GetRootFrame();
+ MOZ_ASSERT(rootFrame);
+ if (this != rootFrame) {
+ auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(rootFrame);
+ auto* data = builder ? builder->Data() : nullptr;
+
+ const bool inData =
+ data && (data->IsModified(this) || data->HasProps(this));
+
+ if (inData) {
+ DL_LOG(LogLevel::Warning, "Frame %p found in retained data", this);
+ }
+
+ MOZ_ASSERT(!inData, "Deleted frame in retained data!");
+ }
+ }
+#endif
+
+ // Now that we're totally cleaned out, we need to add ourselves to
+ // the presshell's recycler.
+ ps->FreeFrame(id, this);
+}
+
+std::pair<int32_t, int32_t> nsIFrame::GetOffsets() const {
+ return std::make_pair(0, 0);
+}
+
+static void CompareLayers(
+ const nsStyleImageLayers* aFirstLayers,
+ const nsStyleImageLayers* aSecondLayers,
+ const std::function<void(imgRequestProxy* aReq)>& aCallback) {
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, (*aFirstLayers)) {
+ const auto& image = aFirstLayers->mLayers[i].mImage;
+ if (!image.IsImageRequestType() || !image.IsResolved()) {
+ continue;
+ }
+
+ // aCallback is called when the style image in aFirstLayers is thought to
+ // be different with the corresponded one in aSecondLayers
+ if (!aSecondLayers || i >= aSecondLayers->mImageCount ||
+ (!aSecondLayers->mLayers[i].mImage.IsResolved() ||
+ image.GetImageRequest() !=
+ aSecondLayers->mLayers[i].mImage.GetImageRequest())) {
+ if (imgRequestProxy* req = image.GetImageRequest()) {
+ aCallback(req);
+ }
+ }
+ }
+}
+
+static void AddAndRemoveImageAssociations(
+ ImageLoader& aImageLoader, nsIFrame* aFrame,
+ const nsStyleImageLayers* aOldLayers,
+ const nsStyleImageLayers* aNewLayers) {
+ // If the old context had a background-image image, or mask-image image,
+ // and new context does not have the same image, clear the image load
+ // notifier (which keeps the image loading, if it still is) for the frame.
+ // We want to do this conservatively because some frames paint their
+ // backgrounds from some other frame's style data, and we don't want
+ // to clear those notifiers unless we have to. (They'll be reset
+ // when we paint, although we could miss a notification in that
+ // interval.)
+ if (aOldLayers && aFrame->HasImageRequest()) {
+ CompareLayers(aOldLayers, aNewLayers, [&](imgRequestProxy* aReq) {
+ aImageLoader.DisassociateRequestFromFrame(aReq, aFrame);
+ });
+ }
+
+ CompareLayers(aNewLayers, aOldLayers, [&](imgRequestProxy* aReq) {
+ aImageLoader.AssociateRequestToFrame(aReq, aFrame);
+ });
+}
+
+void nsIFrame::AddDisplayItem(nsDisplayItem* aItem) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDisplayItems.Contains(aItem));
+ mDisplayItems.AppendElement(aItem);
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->NotifyOfPossibleBoundsChange(PresShell(), mContent);
+ }
+#endif
+}
+
+bool nsIFrame::RemoveDisplayItem(nsDisplayItem* aItem) {
+ return mDisplayItems.RemoveElement(aItem);
+}
+
+bool nsIFrame::HasDisplayItems() { return !mDisplayItems.IsEmpty(); }
+
+bool nsIFrame::HasDisplayItem(nsDisplayItem* aItem) {
+ return mDisplayItems.Contains(aItem);
+}
+
+bool nsIFrame::HasDisplayItem(uint32_t aKey) {
+ for (nsDisplayItem* i : mDisplayItems) {
+ if (i->GetPerFrameKey() == aKey) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename Condition>
+static void DiscardDisplayItems(nsIFrame* aFrame, Condition aCondition) {
+ for (nsDisplayItem* i : aFrame->DisplayItems()) {
+ // Only discard items that are invalidated by this frame, as we're only
+ // guaranteed to rebuild those items. Table background items are created by
+ // the relevant table part, but have the cell frame as the primary frame,
+ // and we don't want to remove them if this is the cell.
+ if (aCondition(i) && i->FrameForInvalidation() == aFrame) {
+ i->SetCantBeReused();
+ }
+ }
+}
+
+static void DiscardOldItems(nsIFrame* aFrame) {
+ DiscardDisplayItems(aFrame,
+ [](nsDisplayItem* aItem) { return aItem->IsOldItem(); });
+}
+
+void nsIFrame::RemoveDisplayItemDataForDeletion() {
+ // Destroying a WebRenderUserDataTable can cause destruction of other objects
+ // which can remove frame properties in their destructor. If we delete a frame
+ // property it runs the destructor of the stored object in the middle of
+ // updating the frame property table, so if the destruction of that object
+ // causes another update to the frame property table it would leave the frame
+ // property table in an inconsistent state. So we remove it from the table and
+ // then destroy it. (bug 1530657)
+ WebRenderUserDataTable* userDataTable =
+ TakeProperty(WebRenderUserDataProperty::Key());
+ if (userDataTable) {
+ for (const auto& data : userDataTable->Values()) {
+ data->RemoveFromTable();
+ }
+ delete userDataTable;
+ }
+
+ if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
+ // Retained display lists are disabled, no need to update
+ // RetainedDisplayListData.
+ return;
+ }
+
+ auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this);
+ if (!builder) {
+ MOZ_ASSERT(DisplayItems().IsEmpty());
+ MOZ_ASSERT(!IsFrameModified());
+ return;
+ }
+
+ for (nsDisplayItem* i : DisplayItems()) {
+ if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) {
+ i->Frame()->MarkNeedsDisplayItemRebuild();
+ }
+ i->RemoveFrame(this);
+ }
+
+ DisplayItems().Clear();
+
+ nsAutoString name;
+#ifdef DEBUG_FRAME_DUMP
+ if (DL_LOG_TEST(LogLevel::Debug)) {
+ GetFrameName(name);
+ }
+#endif
+ DL_LOGV("Removing display item data for frame %p (%s)", this,
+ NS_ConvertUTF16toUTF8(name).get());
+
+ auto* data = builder->Data();
+ if (MayHaveWillChangeBudget()) {
+ // Keep the frame in list, so it can be removed from the will-change budget.
+ data->Flags(this) = RetainedDisplayListData::FrameFlag::HadWillChange;
+ } else {
+ data->Remove(this);
+ }
+}
+
+void nsIFrame::MarkNeedsDisplayItemRebuild() {
+ if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || IsFrameModified() ||
+ HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ // Skip frames that are already marked modified.
+ return;
+ }
+
+ if (Type() == LayoutFrameType::Placeholder) {
+ nsIFrame* oof = static_cast<nsPlaceholderFrame*>(this)->GetOutOfFlowFrame();
+ if (oof) {
+ oof->MarkNeedsDisplayItemRebuild();
+ }
+ // Do not mark placeholder frames modified.
+ return;
+ }
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->NotifyOfPossibleBoundsChange(PresShell(), mContent);
+ }
+#endif
+
+ nsIFrame* rootFrame = PresShell()->GetRootFrame();
+
+ if (rootFrame->IsFrameModified()) {
+ // The whole frame tree is modified.
+ return;
+ }
+
+ auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this);
+ if (!builder) {
+ MOZ_ASSERT(DisplayItems().IsEmpty());
+ return;
+ }
+
+ RetainedDisplayListData* data = builder->Data();
+ MOZ_ASSERT(data);
+
+ if (data->AtModifiedFrameLimit()) {
+ // This marks the whole frame tree modified.
+ // See |RetainedDisplayListBuilder::ShouldBuildPartial()|.
+ data->AddModifiedFrame(rootFrame);
+ return;
+ }
+
+ nsAutoString name;
+#ifdef DEBUG_FRAME_DUMP
+ if (DL_LOG_TEST(LogLevel::Debug)) {
+ GetFrameName(name);
+ }
+#endif
+
+ DL_LOGV("RDL - Rebuilding display items for frame %p (%s)", this,
+ NS_ConvertUTF16toUTF8(name).get());
+
+ data->AddModifiedFrame(this);
+
+ MOZ_ASSERT(
+ PresContext()->LayoutPhaseCount(nsLayoutPhase::DisplayListBuilding) == 0);
+
+ // Hopefully this is cheap, but we could use a frame state bit to note
+ // the presence of dependencies to speed it up.
+ for (nsDisplayItem* i : DisplayItems()) {
+ if (i->HasDeletedFrame() || i->Frame() == this) {
+ // Ignore the items with deleted frames, and the items with |this| as
+ // the primary frame.
+ continue;
+ }
+
+ if (i->GetDependentFrame() == this) {
+ // For items with |this| as a dependent frame, mark the primary frame
+ // for rebuild.
+ i->Frame()->MarkNeedsDisplayItemRebuild();
+ }
+ }
+}
+
+// Subclass hook for style post processing
+/* virtual */
+void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+#ifdef ACCESSIBILITY
+ // Don't notify for reconstructed frames here, since the frame is still being
+ // constructed at this point and so LocalAccessible::GetFrame() will return
+ // null. Style changes for reconstructed frames are handled in
+ // DocAccessible::PruneOrInsertSubtree.
+ if (aOldComputedStyle) {
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->NotifyOfComputedStyleChange(PresShell(), mContent);
+ }
+ }
+#endif
+
+ MaybeScheduleReflowSVGNonDisplayText(this);
+
+ Document* doc = PresContext()->Document();
+ ImageLoader* loader = doc->StyleImageLoader();
+ // Continuing text frame doesn't initialize its continuation pointer before
+ // reaching here for the first time, so we have to exclude text frames. This
+ // doesn't affect correctness because text can't match selectors.
+ //
+ // FIXME(emilio): We should consider fixing that.
+ //
+ // TODO(emilio): Can we avoid doing some / all of the image stuff when
+ // isNonTextFirstContinuation is false? We should consider doing this just for
+ // primary frames and pseudos, but the first-line reparenting code makes it
+ // all bad, should get around to bug 1465474 eventually :(
+ const bool isNonText = !IsTextFrame();
+ if (isNonText) {
+ mComputedStyle->StartImageLoads(*doc, aOldComputedStyle);
+ }
+
+ const nsStyleImageLayers* oldLayers =
+ aOldComputedStyle ? &aOldComputedStyle->StyleBackground()->mImage
+ : nullptr;
+ const nsStyleImageLayers* newLayers = &StyleBackground()->mImage;
+ AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);
+
+ oldLayers =
+ aOldComputedStyle ? &aOldComputedStyle->StyleSVGReset()->mMask : nullptr;
+ newLayers = &StyleSVGReset()->mMask;
+ AddAndRemoveImageAssociations(*loader, this, oldLayers, newLayers);
+
+ const nsStyleDisplay* disp = StyleDisplay();
+ bool handleStickyChange = false;
+ if (aOldComputedStyle) {
+ // Detect style changes that should trigger a scroll anchor adjustment
+ // suppression.
+ // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
+ bool needAnchorSuppression = false;
+
+ const nsStyleMargin* oldMargin = aOldComputedStyle->StyleMargin();
+ if (oldMargin->mMargin != StyleMargin()->mMargin) {
+ needAnchorSuppression = true;
+ }
+
+ const nsStylePadding* oldPadding = aOldComputedStyle->StylePadding();
+ if (oldPadding->mPadding != StylePadding()->mPadding) {
+ SetHasPaddingChange(true);
+ needAnchorSuppression = true;
+ }
+
+ const nsStyleDisplay* oldDisp = aOldComputedStyle->StyleDisplay();
+ if (oldDisp->mOverflowAnchor != disp->mOverflowAnchor) {
+ if (auto* container = ScrollAnchorContainer::FindFor(this)) {
+ container->InvalidateAnchor();
+ }
+ if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(this)) {
+ scrollableFrame->Anchor()->InvalidateAnchor();
+ }
+ }
+
+ if (mInScrollAnchorChain) {
+ const nsStylePosition* pos = StylePosition();
+ const nsStylePosition* oldPos = aOldComputedStyle->StylePosition();
+ if (!needAnchorSuppression &&
+ (oldPos->mOffset != pos->mOffset || oldPos->mWidth != pos->mWidth ||
+ oldPos->mMinWidth != pos->mMinWidth ||
+ oldPos->mMaxWidth != pos->mMaxWidth ||
+ oldPos->mHeight != pos->mHeight ||
+ oldPos->mMinHeight != pos->mMinHeight ||
+ oldPos->mMaxHeight != pos->mMaxHeight ||
+ oldDisp->mPosition != disp->mPosition ||
+ oldDisp->mTransform != disp->mTransform)) {
+ needAnchorSuppression = true;
+ }
+
+ if (needAnchorSuppression &&
+ StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
+ ScrollAnchorContainer::FindFor(this)->SuppressAdjustments();
+ }
+ }
+
+ if (disp->mPosition != oldDisp->mPosition) {
+ if (!disp->IsRelativelyOrStickyPositionedStyle() &&
+ oldDisp->IsRelativelyOrStickyPositionedStyle()) {
+ RemoveProperty(NormalPositionProperty());
+ }
+
+ handleStickyChange = disp->mPosition == StylePositionProperty::Sticky ||
+ oldDisp->mPosition == StylePositionProperty::Sticky;
+ }
+ if (disp->mScrollSnapAlign != oldDisp->mScrollSnapAlign) {
+ ScrollSnapUtils::PostPendingResnapFor(this);
+ }
+ if (aOldComputedStyle->IsRootElementStyle() &&
+ disp->mScrollSnapType != oldDisp->mScrollSnapType) {
+ if (nsIScrollableFrame* scrollableFrame =
+ PresShell()->GetRootScrollFrameAsScrollable()) {
+ scrollableFrame->PostPendingResnap();
+ }
+ }
+ if (StyleUIReset()->mMozSubtreeHiddenOnlyVisually &&
+ !aOldComputedStyle->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
+ PresShell::ClearMouseCapture(this);
+ }
+ } else { // !aOldComputedStyle
+ handleStickyChange = disp->mPosition == StylePositionProperty::Sticky;
+ }
+
+ if (handleStickyChange && !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) &&
+ !GetPrevInFlow()) {
+ // Note that we only add first continuations, but we really only
+ // want to add first continuation-or-ib-split-siblings. But since we don't
+ // yet know if we're a later part of a block-in-inline split, we'll just
+ // add later members of a block-in-inline split here, and then
+ // StickyScrollContainer will remove them later.
+ if (auto* ssc =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(this)) {
+ if (disp->mPosition == StylePositionProperty::Sticky) {
+ ssc->AddFrame(this);
+ } else {
+ ssc->RemoveFrame(this);
+ }
+ }
+ }
+
+ imgIRequest* oldBorderImage =
+ aOldComputedStyle
+ ? aOldComputedStyle->StyleBorder()->GetBorderImageRequest()
+ : nullptr;
+ imgIRequest* newBorderImage = StyleBorder()->GetBorderImageRequest();
+ // FIXME (Bug 759996): The following is no longer true.
+ // For border-images, we can't be as conservative (we need to set the
+ // new loaders if there has been any change) since the CalcDifference
+ // call depended on the result of GetComputedBorder() and that result
+ // depends on whether the image has loaded, start the image load now
+ // so that we'll get notified when it completes loading and can do a
+ // restyle. Otherwise, the image might finish loading from the
+ // network before we start listening to its notifications, and then
+ // we'll never know that it's finished loading. Likewise, we want to
+ // do this for freshly-created frames to prevent a similar race if the
+ // image loads between reflow (which can depend on whether the image
+ // is loaded) and paint. We also don't really care about any callers who try
+ // to paint borders with a different style, because they won't have the
+ // correct size for the border either.
+ if (oldBorderImage != newBorderImage) {
+ // stop and restart the image loading/notification
+ if (oldBorderImage && HasImageRequest()) {
+ loader->DisassociateRequestFromFrame(oldBorderImage, this);
+ }
+ if (newBorderImage) {
+ loader->AssociateRequestToFrame(newBorderImage, this);
+ }
+ }
+
+ auto GetShapeImageRequest = [](const ComputedStyle* aStyle) -> imgIRequest* {
+ if (!aStyle) {
+ return nullptr;
+ }
+ auto& shape = aStyle->StyleDisplay()->mShapeOutside;
+ if (!shape.IsImage()) {
+ return nullptr;
+ }
+ return shape.AsImage().GetImageRequest();
+ };
+
+ imgIRequest* oldShapeImage = GetShapeImageRequest(aOldComputedStyle);
+ imgIRequest* newShapeImage = GetShapeImageRequest(Style());
+ if (oldShapeImage != newShapeImage) {
+ if (oldShapeImage && HasImageRequest()) {
+ loader->DisassociateRequestFromFrame(oldShapeImage, this);
+ }
+ if (newShapeImage) {
+ loader->AssociateRequestToFrame(
+ newShapeImage, this,
+ ImageLoader::Flags::
+ RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking);
+ }
+ }
+
+ // SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with
+ // the first continuation so we need to check that in advance.
+ const bool isNonTextFirstContinuation = isNonText && !GetPrevContinuation();
+ if (isNonTextFirstContinuation) {
+ // Kick off loading of external SVG resources referenced from properties if
+ // any. This currently includes filter, clip-path, and mask.
+ SVGObserverUtils::InitiateResourceDocLoads(this);
+ }
+
+ // If the page contains markup that overrides text direction, and
+ // does not contain any characters that would activate the Unicode
+ // bidi algorithm, we need to call |SetBidiEnabled| on the pres
+ // context before reflow starts. See bug 115921.
+ if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
+ PresContext()->SetBidiEnabled();
+ }
+
+ // The following part is for caching offset-path:path(). We cache the
+ // flatten gfx path, so we don't have to rebuild and re-flattern it at
+ // each cycle if we have animations on offset-* with a fixed offset-path.
+ const StyleOffsetPath* oldPath =
+ aOldComputedStyle ? &aOldComputedStyle->StyleDisplay()->mOffsetPath
+ : nullptr;
+ const StyleOffsetPath& newPath = StyleDisplay()->mOffsetPath;
+ if (!oldPath || *oldPath != newPath) {
+ // FIXME: Bug 1837042. Cache all basic shapes.
+ if (newPath.IsPath()) {
+ RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
+ RefPtr<gfx::Path> path =
+ MotionPathUtils::BuildSVGPath(newPath.AsSVGPathData(), builder);
+ if (path) {
+ // The newPath could be path('') (i.e. empty path), so its gfx path
+ // could be nullptr, and so we only set property for a non-empty path.
+ SetProperty(nsIFrame::OffsetPathCache(), path.forget().take());
+ } else {
+ // May have an old cached path, so we have to delete it.
+ RemoveProperty(nsIFrame::OffsetPathCache());
+ }
+ } else if (oldPath) {
+ RemoveProperty(nsIFrame::OffsetPathCache());
+ }
+ }
+
+ if (IsPrimaryFrame()) {
+ MOZ_ASSERT(aOldComputedStyle);
+ HandlePrimaryFrameStyleChange(aOldComputedStyle);
+ }
+
+ RemoveStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS | NS_FRAME_SIMPLE_DISPLAYLIST);
+
+ mMayHaveRoundedCorners = true;
+}
+
+void nsIFrame::HandleLastRememberedSize() {
+ MOZ_ASSERT(IsPrimaryFrame());
+ // Storing a last remembered size requires contain-intrinsic-size, and using
+ // a previously stored last remembered size requires content-visibility.
+ if (!StaticPrefs::layout_css_contain_intrinsic_size_enabled() ||
+ !StaticPrefs::layout_css_content_visibility_enabled()) {
+ return;
+ }
+ auto* element = Element::FromNodeOrNull(mContent);
+ if (!element) {
+ return;
+ }
+ const WritingMode wm = GetWritingMode();
+ const nsStylePosition* stylePos = StylePosition();
+ bool canRememberBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto();
+ bool canRememberISize = stylePos->ContainIntrinsicISize(wm).HasAuto();
+ if (!canRememberBSize) {
+ element->RemoveLastRememberedBSize();
+ }
+ if (!canRememberISize) {
+ element->RemoveLastRememberedISize();
+ }
+ if ((canRememberBSize || canRememberISize) && !HidesContent()) {
+ bool isNonReplacedInline = IsLineParticipant() && !IsReplaced();
+ if (!isNonReplacedInline) {
+ PresContext()->Document()->ObserveForLastRememberedSize(*element);
+ return;
+ }
+ }
+ PresContext()->Document()->UnobserveForLastRememberedSize(*element);
+}
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aNewStyle.GetPseudoType() == mComputedStyle->GetPseudoType() ||
+ // ::first-line continuations are weird, this should probably be fixed via
+ // bug 1465474.
+ (mComputedStyle->GetPseudoType() == PseudoStyleType::firstLine &&
+ aNewStyle.GetPseudoType() == PseudoStyleType::mozLineFrame) ||
+ // ::first-letter continuations are broken, in particular floating ones,
+ // see bug 1490281. The construction code tries to fix this up after the
+ // fact, then restyling undoes it...
+ (mComputedStyle->GetPseudoType() == PseudoStyleType::mozText &&
+ aNewStyle.GetPseudoType() == PseudoStyleType::firstLetterContinuation) ||
+ (mComputedStyle->GetPseudoType() ==
+ PseudoStyleType::firstLetterContinuation &&
+ aNewStyle.GetPseudoType() == PseudoStyleType::mozText));
+}
+#endif
+
+void nsIFrame::ReparentFrameViewTo(nsViewManager* aViewManager,
+ nsView* aNewParentView) {
+ if (HasView()) {
+ if (IsMenuPopupFrame()) {
+ // This view must be parented by the root view, don't reparent it.
+ return;
+ }
+ nsView* view = GetView();
+ aViewManager->RemoveChild(view);
+
+ // The view will remember the Z-order and other attributes that have been
+ // set on it.
+ nsView* insertBefore =
+ nsLayoutUtils::FindSiblingViewFor(aNewParentView, this);
+ aViewManager->InsertChild(aNewParentView, view, insertBefore,
+ insertBefore != nullptr);
+ } else if (HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
+ for (const auto& childList : ChildLists()) {
+ // Iterate the child frames, and check each child frame to see if it has
+ // a view
+ for (nsIFrame* child : childList.mList) {
+ child->ReparentFrameViewTo(aViewManager, aNewParentView);
+ }
+ }
+ }
+}
+
+void nsIFrame::SyncFrameViewProperties(nsView* aView) {
+ if (!aView) {
+ aView = GetView();
+ if (!aView) {
+ return;
+ }
+ }
+
+ nsViewManager* vm = aView->GetViewManager();
+
+ // Make sure visibility is correct. This only affects nsSubDocumentFrame.
+ if (!SupportsVisibilityHidden()) {
+ // See if the view should be hidden or visible
+ ComputedStyle* sc = Style();
+ vm->SetViewVisibility(aView, sc->StyleVisibility()->IsVisible()
+ ? ViewVisibility::Show
+ : ViewVisibility::Hide);
+ }
+
+ const auto zIndex = ZIndex();
+ const bool autoZIndex = !zIndex;
+ vm->SetViewZIndex(aView, autoZIndex, zIndex.valueOr(0));
+}
+
+void nsIFrame::CreateView() {
+ MOZ_ASSERT(!HasView());
+
+ nsView* parentView = GetParent()->GetClosestView();
+ MOZ_ASSERT(parentView, "no parent with view");
+
+ nsViewManager* viewManager = parentView->GetViewManager();
+ MOZ_ASSERT(viewManager, "null view manager");
+
+ nsView* view = viewManager->CreateView(GetRect(), parentView);
+ SyncFrameViewProperties(view);
+
+ nsView* insertBefore = nsLayoutUtils::FindSiblingViewFor(parentView, this);
+ // we insert this view 'above' the insertBefore view, unless insertBefore is
+ // null, in which case we want to call with aAbove == false to insert at the
+ // beginning in document order
+ viewManager->InsertChild(parentView, view, insertBefore,
+ insertBefore != nullptr);
+
+ // REVIEW: Don't create a widget for fixed-pos elements anymore.
+ // ComputeRepaintRegionForCopy will calculate the right area to repaint
+ // when we scroll.
+ // Reparent views on any child frames (or their descendants) to this
+ // view. We can just call ReparentFrameViewTo on this frame because
+ // we know this frame has no view, so it will crawl the children. Also,
+ // we know that any descendants with views must have 'parentView' as their
+ // parent view.
+ ReparentFrameViewTo(viewManager, view);
+
+ // Remember our view
+ SetView(view);
+
+ NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
+ ("nsIFrame::CreateView: frame=%p view=%p", this, view));
+}
+
+/* virtual */
+nsMargin nsIFrame::GetUsedMargin() const {
+ nsMargin margin;
+ if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
+ IsInSVGTextSubtree()) {
+ return margin;
+ }
+
+ if (nsMargin* m = GetProperty(UsedMarginProperty())) {
+ margin = *m;
+ } else if (!StyleMargin()->GetMargin(margin)) {
+ // If we get here, our caller probably shouldn't be calling us...
+ NS_ERROR(
+ "Returning bogus 0-sized margin, because this margin "
+ "depends on layout & isn't cached!");
+ }
+ return margin;
+}
+
+/* virtual */
+nsMargin nsIFrame::GetUsedBorder() const {
+ if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
+ IsInSVGTextSubtree()) {
+ return {};
+ }
+
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (IsThemed(disp)) {
+ // Theme methods don't use const-ness.
+ auto* mutable_this = const_cast<nsIFrame*>(this);
+ nsPresContext* pc = PresContext();
+ LayoutDeviceIntMargin widgetBorder = pc->Theme()->GetWidgetBorder(
+ pc->DeviceContext(), mutable_this, disp->EffectiveAppearance());
+ return LayoutDevicePixel::ToAppUnits(widgetBorder,
+ pc->AppUnitsPerDevPixel());
+ }
+
+ return StyleBorder()->GetComputedBorder();
+}
+
+/* virtual */
+nsMargin nsIFrame::GetUsedPadding() const {
+ nsMargin padding;
+ if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
+ IsInSVGTextSubtree()) {
+ return padding;
+ }
+
+ const nsStyleDisplay* disp = StyleDisplay();
+ if (IsThemed(disp)) {
+ // Theme methods don't use const-ness.
+ nsIFrame* mutable_this = const_cast<nsIFrame*>(this);
+ nsPresContext* pc = PresContext();
+ LayoutDeviceIntMargin widgetPadding;
+ if (pc->Theme()->GetWidgetPadding(pc->DeviceContext(), mutable_this,
+ disp->EffectiveAppearance(),
+ &widgetPadding)) {
+ return LayoutDevicePixel::ToAppUnits(widgetPadding,
+ pc->AppUnitsPerDevPixel());
+ }
+ }
+
+ if (nsMargin* p = GetProperty(UsedPaddingProperty())) {
+ padding = *p;
+ } else if (!StylePadding()->GetPadding(padding)) {
+ // If we get here, our caller probably shouldn't be calling us...
+ NS_ERROR(
+ "Returning bogus 0-sized padding, because this padding "
+ "depends on layout & isn't cached!");
+ }
+ return padding;
+}
+
+nsIFrame::Sides nsIFrame::GetSkipSides() const {
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone) &&
+ !HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ return Sides();
+ }
+
+ // Convert the logical skip sides to physical sides using the frame's
+ // writing mode
+ WritingMode writingMode = GetWritingMode();
+ LogicalSides logicalSkip = GetLogicalSkipSides();
+ Sides skip;
+
+ if (logicalSkip.BStart()) {
+ if (writingMode.IsVertical()) {
+ skip |= writingMode.IsVerticalLR() ? SideBits::eLeft : SideBits::eRight;
+ } else {
+ skip |= SideBits::eTop;
+ }
+ }
+
+ if (logicalSkip.BEnd()) {
+ if (writingMode.IsVertical()) {
+ skip |= writingMode.IsVerticalLR() ? SideBits::eRight : SideBits::eLeft;
+ } else {
+ skip |= SideBits::eBottom;
+ }
+ }
+
+ if (logicalSkip.IStart()) {
+ if (writingMode.IsVertical()) {
+ skip |= SideBits::eTop;
+ } else {
+ skip |= writingMode.IsBidiLTR() ? SideBits::eLeft : SideBits::eRight;
+ }
+ }
+
+ if (logicalSkip.IEnd()) {
+ if (writingMode.IsVertical()) {
+ skip |= SideBits::eBottom;
+ } else {
+ skip |= writingMode.IsBidiLTR() ? SideBits::eRight : SideBits::eLeft;
+ }
+ }
+ return skip;
+}
+
+nsRect nsIFrame::GetPaddingRectRelativeToSelf() const {
+ nsMargin border = GetUsedBorder().ApplySkipSides(GetSkipSides());
+ nsRect r(0, 0, mRect.width, mRect.height);
+ r.Deflate(border);
+ return r;
+}
+
+nsRect nsIFrame::GetPaddingRect() const {
+ return GetPaddingRectRelativeToSelf() + GetPosition();
+}
+
+WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM,
+ nsIFrame* aSubFrame) const {
+ MOZ_ASSERT(aSelfWM == GetWritingMode());
+ WritingMode writingMode = aSelfWM;
+
+ if (StyleTextReset()->mUnicodeBidi == StyleUnicodeBidi::Plaintext) {
+ mozilla::intl::BidiEmbeddingLevel frameLevel =
+ nsBidiPresUtils::GetFrameBaseLevel(aSubFrame);
+ writingMode.SetDirectionFromBidiLevel(frameLevel);
+ }
+
+ return writingMode;
+}
+
+nsRect nsIFrame::GetMarginRect() const {
+ return GetMarginRectRelativeToSelf() + GetPosition();
+}
+
+nsRect nsIFrame::GetMarginRectRelativeToSelf() const {
+ nsMargin m = GetUsedMargin().ApplySkipSides(GetSkipSides());
+ nsRect r(0, 0, mRect.width, mRect.height);
+ r.Inflate(m);
+ return r;
+}
+
+bool nsIFrame::IsTransformed() const {
+ if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
+ MOZ_ASSERT(!IsCSSTransformed());
+ MOZ_ASSERT(!IsSVGTransformed());
+ return false;
+ }
+ return IsCSSTransformed() || IsSVGTransformed();
+}
+
+bool nsIFrame::IsCSSTransformed() const {
+ return HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) &&
+ (StyleDisplay()->HasTransform(this) || HasAnimationOfTransform());
+}
+
+bool nsIFrame::HasAnimationOfTransform() const {
+ return IsPrimaryFrame() &&
+ nsLayoutUtils::HasAnimationOfTransformAndMotionPath(this) &&
+ SupportsCSSTransforms();
+}
+
+bool nsIFrame::ChildrenHavePerspective(
+ const nsStyleDisplay* aStyleDisplay) const {
+ MOZ_ASSERT(aStyleDisplay == StyleDisplay());
+ return aStyleDisplay->HasPerspective(this);
+}
+
+bool nsIFrame::HasAnimationOfOpacity(EffectSet* aEffectSet) const {
+ return ((nsLayoutUtils::IsPrimaryStyleFrame(this) ||
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(this)
+ ->IsPrimaryFrame()) &&
+ nsLayoutUtils::HasAnimationOfPropertySet(
+ this, nsCSSPropertyIDSet::OpacityProperties(), aEffectSet));
+}
+
+bool nsIFrame::HasOpacityInternal(float aThreshold,
+ const nsStyleDisplay* aStyleDisplay,
+ const nsStyleEffects* aStyleEffects,
+ EffectSet* aEffectSet) const {
+ MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
+ if (aStyleEffects->mOpacity < aThreshold ||
+ aStyleDisplay->mWillChange.bits & StyleWillChangeBits::OPACITY) {
+ return true;
+ }
+
+ if (!mMayHaveOpacityAnimation) {
+ return false;
+ }
+
+ return HasAnimationOfOpacity(aEffectSet);
+}
+
+bool nsIFrame::IsSVGTransformed(gfx::Matrix* aOwnTransforms,
+ gfx::Matrix* aFromParentTransforms) const {
+ return false;
+}
+
+bool nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay,
+ const nsStyleEffects* aStyleEffects,
+ mozilla::EffectSet* aEffectSetForOpacity) const {
+ if (!HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
+ return false;
+ }
+ const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
+ if (disp->mTransformStyle != StyleTransformStyle::Preserve3d ||
+ !SupportsCSSTransforms()) {
+ return false;
+ }
+
+ // If we're all scroll frame, then all descendants will be clipped, so we
+ // can't preserve 3d.
+ if (IsScrollFrame()) {
+ return false;
+ }
+
+ const nsStyleEffects* effects = StyleEffectsWithOptionalParam(aStyleEffects);
+ if (HasOpacity(disp, effects, aEffectSetForOpacity)) {
+ return false;
+ }
+
+ return ShouldApplyOverflowClipping(disp) == PhysicalAxes::None &&
+ !GetClipPropClipRect(disp, effects, GetSize()) &&
+ !SVGIntegrationUtils::UsingEffectsForFrame(this) &&
+ !effects->HasMixBlendMode() &&
+ disp->mIsolation != StyleIsolation::Isolate;
+}
+
+bool nsIFrame::Combines3DTransformWithAncestors() const {
+ // Check these first as they are faster then both calls below and are we are
+ // likely to hit the early return (backface hidden is uncommon and
+ // GetReferenceFrame is a hot caller of this which only calls this if
+ // IsCSSTransformed is false).
+ if (!IsCSSTransformed() && !BackfaceIsHidden()) {
+ return false;
+ }
+ nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
+ return parent && parent->Extend3DContext();
+}
+
+bool nsIFrame::In3DContextAndBackfaceIsHidden() const {
+ // While both tests fail most of the time, test BackfaceIsHidden()
+ // first since it's likely to fail faster.
+ return BackfaceIsHidden() && Combines3DTransformWithAncestors();
+}
+
+bool nsIFrame::HasPerspective() const {
+ if (!IsCSSTransformed()) {
+ return false;
+ }
+ nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
+ if (!parent) {
+ return false;
+ }
+ return parent->ChildrenHavePerspective();
+}
+
+nsRect nsIFrame::GetContentRectRelativeToSelf() const {
+ nsMargin bp = GetUsedBorderAndPadding().ApplySkipSides(GetSkipSides());
+ nsRect r(0, 0, mRect.width, mRect.height);
+ r.Deflate(bp);
+ return r;
+}
+
+nsRect nsIFrame::GetContentRect() const {
+ return GetContentRectRelativeToSelf() + GetPosition();
+}
+
+bool nsIFrame::ComputeBorderRadii(const BorderRadius& aBorderRadius,
+ const nsSize& aFrameSize,
+ const nsSize& aBorderArea, Sides aSkipSides,
+ nscoord aRadii[8]) {
+ // Percentages are relative to whichever side they're on.
+ for (const auto i : mozilla::AllPhysicalHalfCorners()) {
+ const LengthPercentage& c = aBorderRadius.Get(i);
+ nscoord axis = HalfCornerIsX(i) ? aFrameSize.width : aFrameSize.height;
+ aRadii[i] = std::max(0, c.Resolve(axis));
+ }
+
+ if (aSkipSides.Top()) {
+ aRadii[eCornerTopLeftX] = 0;
+ aRadii[eCornerTopLeftY] = 0;
+ aRadii[eCornerTopRightX] = 0;
+ aRadii[eCornerTopRightY] = 0;
+ }
+
+ if (aSkipSides.Right()) {
+ aRadii[eCornerTopRightX] = 0;
+ aRadii[eCornerTopRightY] = 0;
+ aRadii[eCornerBottomRightX] = 0;
+ aRadii[eCornerBottomRightY] = 0;
+ }
+
+ if (aSkipSides.Bottom()) {
+ aRadii[eCornerBottomRightX] = 0;
+ aRadii[eCornerBottomRightY] = 0;
+ aRadii[eCornerBottomLeftX] = 0;
+ aRadii[eCornerBottomLeftY] = 0;
+ }
+
+ if (aSkipSides.Left()) {
+ aRadii[eCornerBottomLeftX] = 0;
+ aRadii[eCornerBottomLeftY] = 0;
+ aRadii[eCornerTopLeftX] = 0;
+ aRadii[eCornerTopLeftY] = 0;
+ }
+
+ // css3-background specifies this algorithm for reducing
+ // corner radii when they are too big.
+ bool haveRadius = false;
+ double ratio = 1.0f;
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ uint32_t hc1 = SideToHalfCorner(side, false, true);
+ uint32_t hc2 = SideToHalfCorner(side, true, true);
+ nscoord length =
+ SideIsVertical(side) ? aBorderArea.height : aBorderArea.width;
+ nscoord sum = aRadii[hc1] + aRadii[hc2];
+ if (sum) {
+ haveRadius = true;
+ // avoid floating point division in the normal case
+ if (length < sum) {
+ ratio = std::min(ratio, double(length) / sum);
+ }
+ }
+ }
+ if (ratio < 1.0) {
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ aRadii[corner] *= ratio;
+ }
+ }
+
+ return haveRadius;
+}
+
+void nsIFrame::AdjustBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets) {
+ auto AdjustOffset = [](const uint32_t aRadius, const nscoord aOffset) {
+ // Implement the cubic formula to adjust offset when aOffset > 0 and
+ // aRadius / aOffset < 1.
+ // https://drafts.csswg.org/css-shapes/#valdef-shape-box-margin-box
+ if (aOffset > 0) {
+ const double ratio = aRadius / double(aOffset);
+ if (ratio < 1.0) {
+ return nscoord(aOffset * (1.0 + std::pow(ratio - 1, 3)));
+ }
+ }
+ return aOffset;
+ };
+
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ const nscoord offset = aOffsets.Side(side);
+ const uint32_t hc1 = SideToHalfCorner(side, false, false);
+ const uint32_t hc2 = SideToHalfCorner(side, true, false);
+ if (aRadii[hc1] > 0) {
+ const nscoord offset1 = AdjustOffset(aRadii[hc1], offset);
+ aRadii[hc1] = std::max(0, aRadii[hc1] + offset1);
+ }
+ if (aRadii[hc2] > 0) {
+ const nscoord offset2 = AdjustOffset(aRadii[hc2], offset);
+ aRadii[hc2] = std::max(0, aRadii[hc2] + offset2);
+ }
+ }
+}
+
+static inline bool RadiiAreDefinitelyZero(const BorderRadius& aBorderRadius) {
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ if (!aBorderRadius.Get(corner).IsDefinitelyZero()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* virtual */
+bool nsIFrame::GetBorderRadii(const nsSize& aFrameSize,
+ const nsSize& aBorderArea, Sides aSkipSides,
+ nscoord aRadii[8]) const {
+ if (!mMayHaveRoundedCorners) {
+ memset(aRadii, 0, sizeof(nscoord) * 8);
+ return false;
+ }
+
+ if (IsThemed()) {
+ // When we're themed, the native theme code draws the border and
+ // background, and therefore it doesn't make sense to tell other
+ // code that's interested in border-radius that we have any radii.
+ //
+ // In an ideal world, we might have a way for the them to tell us an
+ // border radius, but since we don't, we're better off assuming
+ // zero.
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ aRadii[corner] = 0;
+ }
+ return false;
+ }
+
+ const auto& radii = StyleBorder()->mBorderRadius;
+ const bool hasRadii =
+ ComputeBorderRadii(radii, aFrameSize, aBorderArea, aSkipSides, aRadii);
+ if (!hasRadii) {
+ // TODO(emilio): Maybe we can just remove this bit and do the
+ // IsDefinitelyZero check unconditionally. That should still avoid most of
+ // the work, though maybe not the cache miss of going through the style and
+ // the border struct.
+ const_cast<nsIFrame*>(this)->mMayHaveRoundedCorners =
+ !RadiiAreDefinitelyZero(radii);
+ }
+ return hasRadii;
+}
+
+bool nsIFrame::GetBorderRadii(nscoord aRadii[8]) const {
+ nsSize sz = GetSize();
+ return GetBorderRadii(sz, sz, GetSkipSides(), aRadii);
+}
+
+bool nsIFrame::GetMarginBoxBorderRadii(nscoord aRadii[8]) const {
+ return GetBoxBorderRadii(aRadii, GetUsedMargin());
+}
+
+bool nsIFrame::GetPaddingBoxBorderRadii(nscoord aRadii[8]) const {
+ return GetBoxBorderRadii(aRadii, -GetUsedBorder());
+}
+
+bool nsIFrame::GetContentBoxBorderRadii(nscoord aRadii[8]) const {
+ return GetBoxBorderRadii(aRadii, -GetUsedBorderAndPadding());
+}
+
+bool nsIFrame::GetBoxBorderRadii(nscoord aRadii[8],
+ const nsMargin& aOffsets) const {
+ if (!GetBorderRadii(aRadii)) {
+ return false;
+ }
+ AdjustBorderRadii(aRadii, aOffsets);
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ if (aRadii[corner]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool nsIFrame::GetShapeBoxBorderRadii(nscoord aRadii[8]) const {
+ using Tag = StyleShapeOutside::Tag;
+ auto& shapeOutside = StyleDisplay()->mShapeOutside;
+ auto box = StyleShapeBox::MarginBox;
+ switch (shapeOutside.tag) {
+ case Tag::Image:
+ case Tag::None:
+ return false;
+ case Tag::Box:
+ box = shapeOutside.AsBox();
+ break;
+ case Tag::Shape:
+ box = shapeOutside.AsShape()._1;
+ break;
+ }
+
+ switch (box) {
+ case StyleShapeBox::ContentBox:
+ return GetContentBoxBorderRadii(aRadii);
+ case StyleShapeBox::PaddingBox:
+ return GetPaddingBoxBorderRadii(aRadii);
+ case StyleShapeBox::BorderBox:
+ return GetBorderRadii(aRadii);
+ case StyleShapeBox::MarginBox:
+ return GetMarginBoxBorderRadii(aRadii);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected box value");
+ return false;
+ }
+}
+
+ComputedStyle* nsIFrame::GetAdditionalComputedStyle(int32_t aIndex) const {
+ MOZ_ASSERT(aIndex >= 0, "invalid index number");
+ return nullptr;
+}
+
+void nsIFrame::SetAdditionalComputedStyle(int32_t aIndex,
+ ComputedStyle* aComputedStyle) {
+ MOZ_ASSERT(aIndex >= 0, "invalid index number");
+}
+
+nscoord nsIFrame::SynthesizeFallbackBaseline(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
+ const auto margin = GetLogicalUsedMargin(aWM);
+ NS_ASSERTION(!IsSubtreeDirty(), "frame must not be dirty");
+ if (aWM.IsCentralBaseline()) {
+ return (BSize(aWM) + GetLogicalUsedMargin(aWM).BEnd(aWM)) / 2;
+ }
+ // Baseline for inverted line content is the top (block-start) margin edge,
+ // as the frame is in effect "flipped" for alignment purposes.
+ if (aWM.IsLineInverted()) {
+ const auto marginStart = margin.BStart(aWM);
+ return aBaselineGroup == BaselineSharingGroup::First
+ ? -marginStart
+ : BSize(aWM) + marginStart;
+ }
+ // Otherwise, the bottom margin edge, per CSS2.1's definition of the
+ // 'baseline' value of 'vertical-align'.
+ const auto marginEnd = margin.BEnd(aWM);
+ return aBaselineGroup == BaselineSharingGroup::First ? BSize(aWM) + marginEnd
+ : -marginEnd;
+}
+
+nscoord nsIFrame::GetLogicalBaseline(WritingMode aWM) const {
+ return GetLogicalBaseline(aWM, GetDefaultBaselineSharingGroup(),
+ BaselineExportContext::LineLayout);
+}
+
+nscoord nsIFrame::GetLogicalBaseline(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ const auto result =
+ GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext)
+ .valueOrFrom([this, aWM, aBaselineGroup]() {
+ return SynthesizeFallbackBaseline(aWM, aBaselineGroup);
+ });
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return BSize(aWM) - result;
+ }
+ return result;
+}
+
+const nsFrameList& nsIFrame::GetChildList(ChildListID aListID) const {
+ if (IsAbsoluteContainer() && aListID == GetAbsoluteListID()) {
+ return GetAbsoluteContainingBlock()->GetChildList();
+ } else {
+ return nsFrameList::EmptyList();
+ }
+}
+
+void nsIFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
+ if (IsAbsoluteContainer()) {
+ const nsFrameList& absoluteList =
+ GetAbsoluteContainingBlock()->GetChildList();
+ absoluteList.AppendIfNonempty(aLists, GetAbsoluteListID());
+ }
+}
+
+AutoTArray<nsIFrame::ChildList, 4> nsIFrame::CrossDocChildLists() {
+ AutoTArray<ChildList, 4> childLists;
+ nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(this);
+ if (subdocumentFrame) {
+ // Descend into the subdocument
+ nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame();
+ if (root) {
+ childLists.EmplaceBack(
+ nsFrameList(root, nsLayoutUtils::GetLastSibling(root)),
+ FrameChildListID::Principal);
+ }
+ }
+
+ GetChildLists(&childLists);
+ return childLists;
+}
+
+nsIFrame::CaretBlockAxisMetrics nsIFrame::GetCaretBlockAxisMetrics(
+ mozilla::WritingMode aWM, const nsFontMetrics& aFM) const {
+ // Note(dshin): Ultimately, this does something highly similar (But still
+ // different) to `nsLayoutUtils::GetFirstLinePosition`.
+ const auto baseline = GetCaretBaseline();
+ nscoord ascent = 0, descent = 0;
+ ascent = aFM.MaxAscent();
+ descent = aFM.MaxDescent();
+ const nscoord height = ascent + descent;
+ if (aWM.IsVertical() && aWM.IsLineInverted()) {
+ return CaretBlockAxisMetrics{.mOffset = baseline - descent,
+ .mExtent = height};
+ }
+ return CaretBlockAxisMetrics{.mOffset = baseline - ascent, .mExtent = height};
+}
+
+const nsAtom* nsIFrame::ComputePageValue() const {
+ const nsAtom* value = nsGkAtoms::_empty;
+ const nsIFrame* frame = this;
+ // Find what CSS page name value this frame's subtree has, if any.
+ // Starting with this frame, check if a page name other than auto is present,
+ // and record it if so. Then, if the current frame is a container frame, find
+ // the first non-placeholder child and repeat.
+ // This will find the most deeply nested first in-flow child of this frame's
+ // subtree, and return its page name (with auto resolved if applicable, and
+ // subtrees with no page-names returning the empty atom rather than null).
+ do {
+ if (const nsAtom* maybePageName = frame->GetStylePageName()) {
+ value = maybePageName;
+ }
+ // Get the next frame to read from.
+ const nsIFrame* firstNonPlaceholderFrame = nullptr;
+ // If this is a container frame, inspect its in-flow children.
+ if (const nsContainerFrame* containerFrame = do_QueryFrame(frame)) {
+ for (const nsIFrame* childFrame : containerFrame->PrincipalChildList()) {
+ if (!childFrame->IsPlaceholderFrame()) {
+ firstNonPlaceholderFrame = childFrame;
+ break;
+ }
+ }
+ }
+ frame = firstNonPlaceholderFrame;
+ } while (frame);
+ return value;
+}
+
+Visibility nsIFrame::GetVisibility() const {
+ if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
+ return Visibility::Untracked;
+ }
+
+ bool isSet = false;
+ uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
+
+ MOZ_ASSERT(isSet,
+ "Should have a VisibilityStateProperty value "
+ "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
+
+ return visibleCount > 0 ? Visibility::ApproximatelyVisible
+ : Visibility::ApproximatelyNonVisible;
+}
+
+void nsIFrame::UpdateVisibilitySynchronously() {
+ mozilla::PresShell* presShell = PresShell();
+ if (!presShell) {
+ return;
+ }
+
+ if (presShell->AssumeAllFramesVisible()) {
+ presShell->EnsureFrameInApproximatelyVisibleList(this);
+ return;
+ }
+
+ bool visible = StyleVisibility()->IsVisible();
+ nsIFrame* f = GetParent();
+ nsRect rect = GetRectRelativeToSelf();
+ nsIFrame* rectFrame = this;
+ while (f && visible) {
+ nsIScrollableFrame* sf = do_QueryFrame(f);
+ if (sf) {
+ nsRect transformedRect =
+ nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f);
+ if (!sf->IsRectNearlyVisible(transformedRect)) {
+ visible = false;
+ break;
+ }
+
+ // In this code we're trying to synchronously update *approximate*
+ // visibility. (In the future we may update precise visibility here as
+ // well, which is why the method name does not contain 'approximate'.) The
+ // IsRectNearlyVisible() check above tells us that the rect we're checking
+ // is approximately visible within the scrollframe, but we still need to
+ // ensure that, even if it was scrolled into view, it'd be visible when we
+ // consider the rest of the document. To do that, we move transformedRect
+ // to be contained in the scrollport as best we can (it might not fit) to
+ // pretend that it was scrolled into view.
+ rect = transformedRect.MoveInsideAndClamp(sf->GetScrollPortRect());
+ rectFrame = f;
+ }
+ nsIFrame* parent = f->GetParent();
+ if (!parent) {
+ parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
+ if (parent && parent->PresContext()->IsChrome()) {
+ break;
+ }
+ }
+ f = parent;
+ }
+
+ if (visible) {
+ presShell->EnsureFrameInApproximatelyVisibleList(this);
+ } else {
+ presShell->RemoveFrameFromApproximatelyVisibleList(this);
+ }
+}
+
+void nsIFrame::EnableVisibilityTracking() {
+ if (HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
+ return; // Nothing to do.
+ }
+
+ MOZ_ASSERT(!HasProperty(VisibilityStateProperty()),
+ "Shouldn't have a VisibilityStateProperty value "
+ "if NS_FRAME_VISIBILITY_IS_TRACKED is not set");
+
+ // Add the state bit so we know to track visibility for this frame, and
+ // initialize the frame property.
+ AddStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
+ SetProperty(VisibilityStateProperty(), 0);
+
+ mozilla::PresShell* presShell = PresShell();
+ if (!presShell) {
+ return;
+ }
+
+ // Schedule a visibility update. This method will virtually always be called
+ // when layout has changed anyway, so it's very unlikely that any additional
+ // visibility updates will be triggered by this, but this way we guarantee
+ // that if this frame is currently visible we'll eventually find out.
+ presShell->ScheduleApproximateFrameVisibilityUpdateSoon();
+}
+
+void nsIFrame::DisableVisibilityTracking() {
+ if (!HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED)) {
+ return; // Nothing to do.
+ }
+
+ bool isSet = false;
+ uint32_t visibleCount = TakeProperty(VisibilityStateProperty(), &isSet);
+
+ MOZ_ASSERT(isSet,
+ "Should have a VisibilityStateProperty value "
+ "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
+
+ RemoveStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
+
+ if (visibleCount == 0) {
+ return; // We were nonvisible.
+ }
+
+ // We were visible, so send an OnVisibilityChange() notification.
+ OnVisibilityChange(Visibility::ApproximatelyNonVisible);
+}
+
+void nsIFrame::DecApproximateVisibleCount(
+ const Maybe<OnNonvisible>& aNonvisibleAction
+ /* = Nothing() */) {
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED));
+
+ bool isSet = false;
+ uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
+
+ MOZ_ASSERT(isSet,
+ "Should have a VisibilityStateProperty value "
+ "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
+ MOZ_ASSERT(visibleCount > 0,
+ "Frame is already nonvisible and we're "
+ "decrementing its visible count?");
+
+ visibleCount--;
+ SetProperty(VisibilityStateProperty(), visibleCount);
+ if (visibleCount > 0) {
+ return;
+ }
+
+ // We just became nonvisible, so send an OnVisibilityChange() notification.
+ OnVisibilityChange(Visibility::ApproximatelyNonVisible, aNonvisibleAction);
+}
+
+void nsIFrame::IncApproximateVisibleCount() {
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED));
+
+ bool isSet = false;
+ uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);
+
+ MOZ_ASSERT(isSet,
+ "Should have a VisibilityStateProperty value "
+ "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
+
+ visibleCount++;
+ SetProperty(VisibilityStateProperty(), visibleCount);
+ if (visibleCount > 1) {
+ return;
+ }
+
+ // We just became visible, so send an OnVisibilityChange() notification.
+ OnVisibilityChange(Visibility::ApproximatelyVisible);
+}
+
+void nsIFrame::OnVisibilityChange(Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction
+ /* = Nothing() */) {
+ // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS
+ // images here.
+}
+
+static nsIFrame* GetActiveSelectionFrame(nsPresContext* aPresContext,
+ nsIFrame* aFrame) {
+ nsIContent* capturingContent = PresShell::GetCapturingContent();
+ if (capturingContent) {
+ nsIFrame* activeFrame = aPresContext->GetPrimaryFrameFor(capturingContent);
+ return activeFrame ? activeFrame : aFrame;
+ }
+
+ return aFrame;
+}
+
+int16_t nsIFrame::DetermineDisplaySelection() {
+ int16_t selType = nsISelectionController::SELECTION_OFF;
+
+ nsCOMPtr<nsISelectionController> selCon;
+ nsresult result =
+ GetSelectionController(PresContext(), getter_AddRefs(selCon));
+ if (NS_SUCCEEDED(result) && selCon) {
+ result = selCon->GetDisplaySelection(&selType);
+ if (NS_SUCCEEDED(result) &&
+ (selType != nsISelectionController::SELECTION_OFF)) {
+ // Check whether style allows selection.
+ if (!IsSelectable(nullptr)) {
+ selType = nsISelectionController::SELECTION_OFF;
+ }
+ }
+ }
+ return selType;
+}
+
+static Element* FindElementAncestorForMozSelection(nsIContent* aContent) {
+ NS_ENSURE_TRUE(aContent, nullptr);
+ while (aContent && aContent->IsInNativeAnonymousSubtree()) {
+ aContent = aContent->GetClosestNativeAnonymousSubtreeRootParentOrHost();
+ }
+ NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
+ return aContent ? aContent->GetAsElementOrParentElement() : nullptr;
+}
+
+already_AddRefed<ComputedStyle> nsIFrame::ComputeSelectionStyle(
+ int16_t aSelectionStatus) const {
+ // Just bail out if not a selection-status that ::selection applies to.
+ if (aSelectionStatus != nsISelectionController::SELECTION_ON &&
+ aSelectionStatus != nsISelectionController::SELECTION_DISABLED) {
+ return nullptr;
+ }
+ Element* element = FindElementAncestorForMozSelection(GetContent());
+ if (!element) {
+ return nullptr;
+ }
+ RefPtr<ComputedStyle> pseudoStyle =
+ PresContext()->StyleSet()->ProbePseudoElementStyle(
+ *element, PseudoStyleType::selection, nullptr, Style());
+ if (!pseudoStyle) {
+ return nullptr;
+ }
+ // When in high-contrast mode, the style system ends up ignoring the color
+ // declarations, which means that the ::selection style becomes the inherited
+ // color, and default background. That's no good.
+ // When force-color-adjust is set to none allow using the color styles,
+ // as they will not be replaced.
+ if (PresContext()->ForcingColors() &&
+ pseudoStyle->StyleText()->mForcedColorAdjust !=
+ StyleForcedColorAdjust::None) {
+ return nullptr;
+ }
+ return do_AddRef(pseudoStyle);
+}
+
+already_AddRefed<ComputedStyle> nsIFrame::ComputeHighlightSelectionStyle(
+ nsAtom* aHighlightName) {
+ Element* element = FindElementAncestorForMozSelection(GetContent());
+ if (!element) {
+ return nullptr;
+ }
+ return PresContext()->StyleSet()->ProbePseudoElementStyle(
+ *element, PseudoStyleType::highlight, aHighlightName, Style());
+}
+
+template <typename SizeOrMaxSize>
+static inline bool IsIntrinsicKeyword(const SizeOrMaxSize& aSize) {
+ // All keywords other than auto/none/-moz-available depend on intrinsic sizes.
+ return aSize.IsMaxContent() || aSize.IsMinContent() || aSize.IsFitContent() ||
+ aSize.IsFitContentFunction();
+}
+
+bool nsIFrame::CanBeDynamicReflowRoot() const {
+ const auto& display = *StyleDisplay();
+ if (IsLineParticipant() || display.mDisplay.IsRuby() ||
+ display.IsInnerTableStyle() ||
+ display.DisplayInside() == StyleDisplayInside::Table) {
+ // We have a display type where 'width' and 'height' don't actually set the
+ // width or height (i.e., the size depends on content).
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_DYNAMIC_REFLOW_ROOT),
+ "should not have dynamic reflow root bit");
+ return false;
+ }
+
+ // In general, frames that have contain:layout+size can be reflow roots.
+ // (One exception: table-wrapper frames don't work well as reflow roots,
+ // because their inner-table ReflowInput init path tries to reuse & deref
+ // the wrapper's containing block's reflow input, which may be null if we
+ // initiate reflow from the table-wrapper itself.)
+ //
+ // Changes to `contain` force frame reconstructions, so we used to use
+ // NS_FRAME_REFLOW_ROOT, this bit could be set for the whole lifetime of
+ // this frame. But after the support of `content-visibility: auto` which
+ // is with contain layout + size when it's not relevant to user, and only
+ // with contain layout when it is relevant. The frame does not reconstruct
+ // when the relevancy changes. So we use NS_FRAME_DYNAMIC_REFLOW_ROOT instead.
+ //
+ // We place it above the pref check on purpose, to make sure it works for
+ // containment even with the pref disabled.
+ if (display.IsContainLayout() && GetContainSizeAxes().IsBoth()) {
+ return true;
+ }
+
+ if (!StaticPrefs::layout_dynamic_reflow_roots_enabled()) {
+ return false;
+ }
+
+ // We can't serve as a dynamic reflow root if our used 'width' and 'height'
+ // might be influenced by content.
+ //
+ // FIXME: For display:block, we should probably optimize inline-size: auto.
+ // FIXME: Other flex and grid cases?
+ const auto& pos = *StylePosition();
+ const auto& width = pos.mWidth;
+ const auto& height = pos.mHeight;
+ if (!width.IsLengthPercentage() || width.HasPercent() ||
+ !height.IsLengthPercentage() || height.HasPercent() ||
+ IsIntrinsicKeyword(pos.mMinWidth) || IsIntrinsicKeyword(pos.mMaxWidth) ||
+ IsIntrinsicKeyword(pos.mMinHeight) ||
+ IsIntrinsicKeyword(pos.mMaxHeight) ||
+ ((pos.mMinWidth.IsAuto() || pos.mMinHeight.IsAuto()) &&
+ IsFlexOrGridItem())) {
+ return false;
+ }
+
+ // If our flex-basis is 'auto', it'll defer to 'width' (or 'height') which
+ // we've already checked. Otherwise, it preempts them, so we need to
+ // perform the same "could-this-value-be-influenced-by-content" checks that
+ // we performed for 'width' and 'height' above.
+ if (IsFlexItem()) {
+ const auto& flexBasis = pos.mFlexBasis;
+ if (!flexBasis.IsAuto()) {
+ if (!flexBasis.IsSize() || !flexBasis.AsSize().IsLengthPercentage() ||
+ flexBasis.AsSize().HasPercent()) {
+ return false;
+ }
+ }
+ }
+
+ if (!IsFixedPosContainingBlock()) {
+ // We can't treat this frame as a reflow root, since dynamic changes
+ // to absolutely-positioned frames inside of it require that we
+ // reflow the placeholder before we reflow the absolutely positioned
+ // frame.
+ // FIXME: Alternatively, we could sort the reflow roots in
+ // PresShell::ProcessReflowCommands by depth in the tree, from
+ // deepest to least deep. However, for performance (FIXME) we
+ // should really be sorting them in the opposite order!
+ return false;
+ }
+
+ // If we participate in a container's block reflow context, or margins
+ // can collapse through us, we can't be a dynamic reflow root.
+ if (IsBlockFrameOrSubclass() && !HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS)) {
+ return false;
+ }
+
+ // Subgrids are never reflow roots, but 'contain:layout/paint' prevents
+ // creating a subgrid in the first place.
+ if (pos.mGridTemplateColumns.IsSubgrid() ||
+ pos.mGridTemplateRows.IsSubgrid()) {
+ // NOTE: we could check that 'display' of our parent's primary frame is
+ // '[inline-]grid' here but that's probably not worth it in practice.
+ if (!display.IsContainLayout() && !display.IsContainPaint()) {
+ return false;
+ }
+ }
+
+ // If we are split, we can't be a dynamic reflow root. Our reflow status may
+ // change after reflow, and our parent is responsible to create or delete our
+ // next-in-flow.
+ if (GetPrevContinuation() || GetNextContinuation()) {
+ return false;
+ }
+
+ return true;
+}
+
+/********************************************************
+ * Refreshes each content's frame
+ *********************************************************/
+
+void nsIFrame::DisplayOutlineUnconditional(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ // Per https://drafts.csswg.org/css-tables-3/#global-style-overrides:
+ // "All css properties of table-column and table-column-group boxes are
+ // ignored, except when explicitly specified by this specification."
+ // CSS outlines fall into this category, so we skip them on these boxes.
+ MOZ_ASSERT(!IsTableColGroupFrame() && !IsTableColFrame());
+ const auto& outline = *StyleOutline();
+
+ if (!outline.ShouldPaintOutline()) {
+ return;
+ }
+
+ // Outlines are painted by the table wrapper frame.
+ if (IsTableFrame()) {
+ return;
+ }
+
+ if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
+ ScrollableOverflowRect().IsEmpty()) {
+ // Skip parts of IB-splits with an empty overflow rect, see bug 434301.
+ // We may still want to fix some of the overflow area calculations over in
+ // that bug.
+ return;
+ }
+
+ // We don't display outline-style: auto on themed frames that have their own
+ // focus indicators.
+ if (outline.mOutlineStyle.IsAuto()) {
+ auto* disp = StyleDisplay();
+ if (IsThemed(disp) && PresContext()->Theme()->ThemeDrawsFocusForWidget(
+ this, disp->EffectiveAppearance())) {
+ return;
+ }
+ }
+
+ aLists.Outlines()->AppendNewToTop<nsDisplayOutline>(aBuilder, this);
+}
+
+void nsIFrame::DisplayOutline(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) return;
+
+ DisplayOutlineUnconditional(aBuilder, aLists);
+}
+
+void nsIFrame::DisplayInsetBoxShadowUnconditional(
+ nsDisplayListBuilder* aBuilder, nsDisplayList* aList) {
+ // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
+ // just because we're visible? Or should it depend on the cell visibility
+ // when we're not the whole table?
+ const auto* effects = StyleEffects();
+ if (effects->HasBoxShadowWithInset(true)) {
+ aList->AppendNewToTop<nsDisplayBoxShadowInner>(aBuilder, this);
+ }
+}
+
+void nsIFrame::DisplayInsetBoxShadow(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList) {
+ if (!IsVisibleForPainting()) return;
+
+ DisplayInsetBoxShadowUnconditional(aBuilder, aList);
+}
+
+void nsIFrame::DisplayOutsetBoxShadowUnconditional(
+ nsDisplayListBuilder* aBuilder, nsDisplayList* aList) {
+ // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
+ // just because we're visible? Or should it depend on the cell visibility
+ // when we're not the whole table?
+ const auto* effects = StyleEffects();
+ if (effects->HasBoxShadowWithInset(false)) {
+ aList->AppendNewToTop<nsDisplayBoxShadowOuter>(aBuilder, this);
+ }
+}
+
+void nsIFrame::DisplayOutsetBoxShadow(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList) {
+ if (!IsVisibleForPainting()) return;
+
+ DisplayOutsetBoxShadowUnconditional(aBuilder, aList);
+}
+
+void nsIFrame::DisplayCaret(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList) {
+ if (!IsVisibleForPainting()) return;
+
+ aList->AppendNewToTop<nsDisplayCaret>(aBuilder, this);
+}
+
+nscolor nsIFrame::GetCaretColorAt(int32_t aOffset) {
+ return nsLayoutUtils::GetColor(this, &nsStyleUI::mCaretColor);
+}
+
+auto nsIFrame::ComputeShouldPaintBackground() const -> ShouldPaintBackground {
+ nsPresContext* pc = PresContext();
+ ShouldPaintBackground settings{pc->GetBackgroundColorDraw(),
+ pc->GetBackgroundImageDraw()};
+ if (settings.mColor && settings.mImage) {
+ return settings;
+ }
+
+ if (StyleVisibility()->mPrintColorAdjust == StylePrintColorAdjust::Exact) {
+ return {true, true};
+ }
+
+ return settings;
+}
+
+bool nsIFrame::DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (aBuilder->IsForEventDelivery() && !aBuilder->HitTestIsForVisibility()) {
+ // For hit-testing, we generally just need a light-weight data structure
+ // like nsDisplayEventReceiver. But if the hit-testing is for visibility,
+ // then we need to know the opaque region in order to determine whether to
+ // stop or not.
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder,
+ this);
+ return false;
+ }
+
+ const AppendedBackgroundType result =
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, this,
+ GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this),
+ aLists.BorderBackground());
+
+ if (result == AppendedBackgroundType::None) {
+ aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
+ aLists.BorderBackground());
+ }
+
+ return result == AppendedBackgroundType::ThemedBackground;
+}
+
+void nsIFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ // The visibility check belongs here since child elements have the
+ // opportunity to override the visibility property and display even if
+ // their parent is hidden.
+ if (!IsVisibleForPainting()) {
+ return;
+ }
+
+ DisplayOutsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground());
+
+ bool bgIsThemed = DisplayBackgroundUnconditional(aBuilder, aLists);
+ DisplayInsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground());
+
+ // If there's a themed background, we should not create a border item.
+ // It won't be rendered.
+ // Don't paint borders for tables here, since they paint them in a different
+ // order.
+ if (!bgIsThemed && StyleBorder()->HasBorder() && !IsTableFrame()) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, this);
+ }
+
+ DisplayOutlineUnconditional(aBuilder, aLists);
+}
+
+inline static bool IsSVGContentWithCSSClip(const nsIFrame* aFrame) {
+ // The CSS spec says that the 'clip' property only applies to absolutely
+ // positioned elements, whereas the SVG spec says that it applies to SVG
+ // elements regardless of the value of the 'position' property. Here we obey
+ // the CSS spec for outer-<svg> (since that's what we generally do), but
+ // obey the SVG spec for other SVG elements to which 'clip' applies.
+ return aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) &&
+ aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::svg,
+ nsGkAtoms::foreignObject);
+}
+
+Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
+ const nsStyleEffects* aEffects,
+ const nsSize& aSize) const {
+ if (aEffects->mClip.IsAuto() ||
+ !(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) {
+ return Nothing();
+ }
+
+ auto& clipRect = aEffects->mClip.AsRect();
+ nsRect rect = clipRect.ToLayoutRect();
+ if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice)) {
+ // The clip applies to the joined boxes so it's relative the first
+ // continuation.
+ nscoord y = 0;
+ for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
+ y += f->GetRect().height;
+ }
+ rect.MoveBy(nsPoint(0, -y));
+ }
+
+ if (clipRect.right.IsAuto()) {
+ rect.width = aSize.width - rect.x;
+ }
+ if (clipRect.bottom.IsAuto()) {
+ rect.height = aSize.height - rect.y;
+ }
+ return Some(rect);
+}
+
+/**
+ * If the CSS 'overflow' property applies to this frame, and is not
+ * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping
+ * for that overflow in aBuilder->ClipState() to clip all containing-block
+ * descendants.
+ */
+static void ApplyOverflowClipping(
+ nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame,
+ nsIFrame::PhysicalAxes aClipAxes,
+ DisplayListClipState::AutoClipMultiple& aClipState) {
+ // Only 'clip' is handled here (and 'hidden' for table frames, and any
+ // non-'visible' value for blocks in a paginated context).
+ // We allow 'clip' to apply to any kind of frame. This is required by
+ // comboboxes which make their display text (an inline frame) have clipping.
+ MOZ_ASSERT(aClipAxes != nsIFrame::PhysicalAxes::None);
+ MOZ_ASSERT(aFrame->ShouldApplyOverflowClipping(aFrame->StyleDisplay()) ==
+ aClipAxes);
+
+ nsRect clipRect;
+ bool haveRadii = false;
+ nscoord radii[8];
+ auto* disp = aFrame->StyleDisplay();
+ // Only deflate the padding if we clip to the content-box in that axis.
+ auto wm = aFrame->GetWritingMode();
+ bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
+ : disp->mOverflowClipBoxInline) ==
+ StyleOverflowClipBox::ContentBox;
+ bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
+ : disp->mOverflowClipBoxBlock) ==
+ StyleOverflowClipBox::ContentBox;
+
+ nsMargin boxMargin = -aFrame->GetUsedPadding();
+ if (!cbH) {
+ boxMargin.left = boxMargin.right = nscoord(0);
+ }
+ if (!cbV) {
+ boxMargin.top = boxMargin.bottom = nscoord(0);
+ }
+
+ auto clipMargin = aFrame->OverflowClipMargin(aClipAxes);
+
+ boxMargin -= aFrame->GetUsedBorder();
+ boxMargin += nsMargin(clipMargin.height, clipMargin.width, clipMargin.height,
+ clipMargin.width);
+ boxMargin.ApplySkipSides(aFrame->GetSkipSides());
+
+ nsRect rect(nsPoint(0, 0), aFrame->GetSize());
+ rect.Inflate(boxMargin);
+ if (MOZ_UNLIKELY(!(aClipAxes & nsIFrame::PhysicalAxes::Horizontal))) {
+ // NOTE(mats) We shouldn't be clipping at all in this dimension really,
+ // but clipping in just one axis isn't supported by our GFX APIs so we
+ // clip to our visual overflow rect instead.
+ nsRect o = aFrame->InkOverflowRect();
+ rect.x = o.x;
+ rect.width = o.width;
+ }
+ if (MOZ_UNLIKELY(!(aClipAxes & nsIFrame::PhysicalAxes::Vertical))) {
+ // See the note above.
+ nsRect o = aFrame->InkOverflowRect();
+ rect.y = o.y;
+ rect.height = o.height;
+ }
+ clipRect = rect + aBuilder->ToReferenceFrame(aFrame);
+ haveRadii = aFrame->GetBoxBorderRadii(radii, boxMargin);
+ aClipState.ClipContainingBlockDescendantsExtra(clipRect,
+ haveRadii ? radii : nullptr);
+}
+
+nsSize nsIFrame::OverflowClipMargin(PhysicalAxes aClipAxes) const {
+ nsSize result;
+ if (aClipAxes == PhysicalAxes::None) {
+ return result;
+ }
+ const auto& margin = StyleMargin()->mOverflowClipMargin;
+ if (margin.IsZero()) {
+ return result;
+ }
+ nscoord marginAu = margin.ToAppUnits();
+ if (aClipAxes & PhysicalAxes::Horizontal) {
+ result.width = marginAu;
+ }
+ if (aClipAxes & PhysicalAxes::Vertical) {
+ result.height = marginAu;
+ }
+ return result;
+}
+
+/**
+ * Returns whether a display item that gets created with the builder's current
+ * state will have a scrolled clip, i.e. a clip that is scrolled by a scroll
+ * frame which does not move the item itself.
+ */
+static bool BuilderHasScrolledClip(nsDisplayListBuilder* aBuilder) {
+ const DisplayItemClipChain* currentClip =
+ aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
+ if (!currentClip) {
+ return false;
+ }
+
+ const ActiveScrolledRoot* currentClipASR = currentClip->mASR;
+ const ActiveScrolledRoot* currentASR = aBuilder->CurrentActiveScrolledRoot();
+ return ActiveScrolledRoot::PickDescendant(currentClipASR, currentASR) !=
+ currentASR;
+}
+
+class AutoSaveRestoreContainsBlendMode {
+ nsDisplayListBuilder& mBuilder;
+ bool mSavedContainsBlendMode;
+
+ public:
+ explicit AutoSaveRestoreContainsBlendMode(nsDisplayListBuilder& aBuilder)
+ : mBuilder(aBuilder),
+ mSavedContainsBlendMode(aBuilder.ContainsBlendMode()) {}
+
+ ~AutoSaveRestoreContainsBlendMode() {
+ mBuilder.SetContainsBlendMode(mSavedContainsBlendMode);
+ }
+};
+
+static bool IsFrameOrAncestorApzAware(nsIFrame* aFrame) {
+ nsIContent* node = aFrame->GetContent();
+ if (!node) {
+ return false;
+ }
+
+ do {
+ if (node->IsNodeApzAware()) {
+ return true;
+ }
+ nsIContent* shadowRoot = node->GetShadowRoot();
+ if (shadowRoot && shadowRoot->IsNodeApzAware()) {
+ return true;
+ }
+
+ // Even if the node owning aFrame doesn't have apz-aware event listeners
+ // itself, its shadow root or display: contents ancestors (which have no
+ // frames) might, so we need to account for them too.
+ } while ((node = node->GetFlattenedTreeParent()) && node->IsElement() &&
+ node->AsElement()->IsDisplayContents());
+
+ return false;
+}
+
+static void CheckForApzAwareEventHandlers(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ if (aBuilder->GetAncestorHasApzAwareEventHandler()) {
+ return;
+ }
+
+ if (IsFrameOrAncestorApzAware(aFrame)) {
+ aBuilder->SetAncestorHasApzAwareEventHandler(true);
+ }
+}
+
+static void UpdateCurrentHitTestInfo(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ if (!aBuilder->BuildCompositorHitTestInfo()) {
+ // Compositor hit test info is not used.
+ return;
+ }
+
+ CheckForApzAwareEventHandlers(aBuilder, aFrame);
+
+ const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(aBuilder);
+ aBuilder->SetCompositorHitTestInfo(info);
+}
+
+/**
+ * True if aDescendant participates the context aAncestor participating.
+ */
+static bool FrameParticipatesIn3DContext(nsIFrame* aAncestor,
+ nsIFrame* aDescendant) {
+ MOZ_ASSERT(aAncestor != aDescendant);
+ MOZ_ASSERT(aAncestor->GetContent() != aDescendant->GetContent());
+ MOZ_ASSERT(aAncestor->Extend3DContext());
+
+ nsIFrame* ancestor = aAncestor->FirstContinuation();
+ MOZ_ASSERT(ancestor->IsPrimaryFrame());
+
+ nsIFrame* frame;
+ for (frame = aDescendant->GetClosestFlattenedTreeAncestorPrimaryFrame();
+ frame && ancestor != frame;
+ frame = frame->GetClosestFlattenedTreeAncestorPrimaryFrame()) {
+ if (!frame->Extend3DContext()) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(frame == ancestor);
+ return true;
+}
+
+static bool ItemParticipatesIn3DContext(nsIFrame* aAncestor,
+ nsDisplayItem* aItem) {
+ auto type = aItem->GetType();
+ const bool isContainer = type == DisplayItemType::TYPE_WRAP_LIST ||
+ type == DisplayItemType::TYPE_CONTAINER;
+
+ if (isContainer && aItem->GetChildren()->Length() == 1) {
+ // If the wraplist has only one child item, use the type of that item.
+ type = aItem->GetChildren()->GetBottom()->GetType();
+ }
+
+ if (type != DisplayItemType::TYPE_TRANSFORM &&
+ type != DisplayItemType::TYPE_PERSPECTIVE) {
+ return false;
+ }
+ nsIFrame* transformFrame = aItem->Frame();
+ if (aAncestor->GetContent() == transformFrame->GetContent()) {
+ return true;
+ }
+ return FrameParticipatesIn3DContext(aAncestor, transformFrame);
+}
+
+static void WrapSeparatorTransform(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aNonParticipants,
+ nsDisplayList* aParticipants, int aIndex,
+ nsDisplayItem** aSeparator) {
+ if (aNonParticipants->IsEmpty()) {
+ return;
+ }
+
+ nsDisplayTransform* item = MakeDisplayItemWithIndex<nsDisplayTransform>(
+ aBuilder, aFrame, aIndex, aNonParticipants, aBuilder->GetVisibleRect());
+
+ if (*aSeparator == nullptr && item) {
+ *aSeparator = item;
+ }
+
+ aParticipants->AppendToTop(item);
+}
+
+// Try to compute a clip rect to bound the contents of the mask item
+// that will be built for |aMaskedFrame|. If we're not able to compute
+// one, return an empty Maybe.
+// The returned clip rect, if there is one, is relative to |aMaskedFrame|.
+static Maybe<nsRect> ComputeClipForMaskItem(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aMaskedFrame,
+ const SVGUtils::MaskUsage& aMaskUsage) {
+ const nsStyleSVGReset* svgReset = aMaskedFrame->StyleSVGReset();
+
+ nsPoint offsetToUserSpace =
+ nsLayoutUtils::ComputeOffsetToUserSpace(aBuilder, aMaskedFrame);
+ int32_t devPixelRatio = aMaskedFrame->PresContext()->AppUnitsPerDevPixel();
+ gfxPoint devPixelOffsetToUserSpace =
+ nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, devPixelRatio);
+ CSSToLayoutDeviceScale cssToDevScale =
+ aMaskedFrame->PresContext()->CSSToDevPixelScale();
+
+ nsPoint toReferenceFrame;
+ aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame);
+
+ Maybe<gfxRect> combinedClip;
+ if (aMaskUsage.ShouldApplyBasicShapeOrPath()) {
+ Maybe<Rect> result =
+ CSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip(
+ aMaskedFrame, svgReset->mClipPath);
+ if (result) {
+ combinedClip = Some(ThebesRect(*result));
+ }
+ } else if (aMaskUsage.ShouldApplyClipPath()) {
+ gfxRect result = SVGUtils::GetBBox(
+ aMaskedFrame,
+ SVGUtils::eBBoxIncludeClipped | SVGUtils::eBBoxIncludeFill |
+ SVGUtils::eBBoxIncludeMarkers | SVGUtils::eBBoxIncludeStroke |
+ SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath);
+ combinedClip = Some(
+ ThebesRect((CSSRect::FromUnknownRect(ToRect(result)) * cssToDevScale)
+ .ToUnknownRect()));
+ } else {
+ // The code for this case is adapted from ComputeMaskGeometry().
+
+ nsRect borderArea(toReferenceFrame, aMaskedFrame->GetSize());
+ borderArea -= offsetToUserSpace;
+
+ // Use an infinite dirty rect to pass into nsCSSRendering::
+ // GetImageLayerClip() because we don't have an actual dirty rect to
+ // pass in. This is fine because the only time GetImageLayerClip() will
+ // not intersect the incoming dirty rect with something is in the "NoClip"
+ // case, and we handle that specially.
+ nsRect dirtyRect(nscoord_MIN / 2, nscoord_MIN / 2, nscoord_MAX,
+ nscoord_MAX);
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
+ nsTArray<SVGMaskFrame*> maskFrames;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+
+ for (uint32_t i = 0; i < maskFrames.Length(); ++i) {
+ gfxRect clipArea;
+ if (maskFrames[i]) {
+ clipArea = maskFrames[i]->GetMaskArea(aMaskedFrame);
+ clipArea = ThebesRect(
+ (CSSRect::FromUnknownRect(ToRect(clipArea)) * cssToDevScale)
+ .ToUnknownRect());
+ } else {
+ const auto& layer = svgReset->mMask.mLayers[i];
+ if (layer.mClip == StyleGeometryBox::NoClip) {
+ return Nothing();
+ }
+
+ nsCSSRendering::ImageLayerClipState clipState;
+ nsCSSRendering::GetImageLayerClip(
+ layer, aMaskedFrame, *aMaskedFrame->StyleBorder(), borderArea,
+ dirtyRect, false /* aWillPaintBorder */, devPixelRatio, &clipState);
+ clipArea = clipState.mDirtyRectInDevPx;
+ }
+ combinedClip = UnionMaybeRects(combinedClip, Some(clipArea));
+ }
+ }
+ if (combinedClip) {
+ if (combinedClip->IsEmpty()) {
+ // *clipForMask might be empty if all mask references are not resolvable
+ // or the size of them are empty. We still need to create a transparent
+ // mask before bug 1276834 fixed, so don't clip ctx by an empty rectangle
+ // for for now.
+ return Nothing();
+ }
+
+ // Convert to user space.
+ *combinedClip += devPixelOffsetToUserSpace;
+
+ // Round the clip out. In FrameLayerBuilder we round clips to nearest
+ // pixels, and if we have a really thin clip here, that can cause the
+ // clip to become empty if we didn't round out here.
+ // The rounding happens in coordinates that are relative to the reference
+ // frame, which matches what FrameLayerBuilder does.
+ combinedClip->RoundOut();
+
+ // Convert to app units.
+ nsRect result =
+ nsLayoutUtils::RoundGfxRectToAppRect(*combinedClip, devPixelRatio);
+
+ // The resulting clip is relative to the reference frame, but the caller
+ // expects it to be relative to the masked frame, so adjust it.
+ result -= toReferenceFrame;
+ return Some(result);
+ }
+ return Nothing();
+}
+
+struct AutoCheckBuilder {
+ explicit AutoCheckBuilder(nsDisplayListBuilder* aBuilder)
+ : mBuilder(aBuilder) {
+ aBuilder->Check();
+ }
+
+ ~AutoCheckBuilder() { mBuilder->Check(); }
+
+ nsDisplayListBuilder* mBuilder;
+};
+
+/**
+ * Tries to reuse a top-level stacking context item from the previous paint.
+ * Returns true if an item was reused, otherwise false.
+ */
+bool TryToReuseStackingContextItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList, nsIFrame* aFrame) {
+ if (!aBuilder->IsForPainting() || !aBuilder->IsPartialUpdate() ||
+ aBuilder->InInvalidSubtree()) {
+ return false;
+ }
+
+ if (aFrame->IsFrameModified() || aFrame->HasModifiedDescendants()) {
+ return false;
+ }
+
+ auto& items = aFrame->DisplayItems();
+ auto* res = std::find_if(
+ items.begin(), items.end(),
+ [](nsDisplayItem* aItem) { return aItem->IsPreProcessed(); });
+
+ if (res == items.end()) {
+ return false;
+ }
+
+ nsDisplayItem* container = *res;
+ MOZ_ASSERT(container->Frame() == aFrame);
+ DL_LOGD("RDL - Found SC item %p (%s) (frame: %p)", container,
+ container->Name(), container->Frame());
+
+ aList->AppendToTop(container);
+ aBuilder->ReuseDisplayItem(container);
+ return true;
+}
+
+void nsIFrame::BuildDisplayListForStackingContext(
+ nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
+ bool* aCreatedContainerItem) {
+#ifdef DEBUG
+ DL_LOGV("BuildDisplayListForStackingContext (%p) <", this);
+ ScopeExit e(
+ [this]() { DL_LOGV("> BuildDisplayListForStackingContext (%p)", this); });
+#endif
+
+ AutoCheckBuilder check(aBuilder);
+
+ if (aBuilder->IsReusingStackingContextItems() &&
+ TryToReuseStackingContextItem(aBuilder, aList, this)) {
+ if (aCreatedContainerItem) {
+ *aCreatedContainerItem = true;
+ }
+ return;
+ }
+
+ if (HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)) {
+ return;
+ }
+
+ const auto& style = *Style();
+ const nsStyleDisplay* disp = style.StyleDisplay();
+ const nsStyleEffects* effects = style.StyleEffects();
+ EffectSet* effectSetForOpacity =
+ EffectSet::GetForFrame(this, nsCSSPropertyIDSet::OpacityProperties());
+ // We can stop right away if this is a zero-opacity stacking context and
+ // we're painting, and we're not animating opacity.
+ bool needHitTestInfo = aBuilder->BuildCompositorHitTestInfo() &&
+ Style()->PointerEvents() != StylePointerEvents::None;
+ bool opacityItemForEventsOnly = false;
+ if (effects->IsTransparent() && aBuilder->IsForPainting() &&
+ !(disp->mWillChange.bits & StyleWillChangeBits::OPACITY) &&
+ !nsLayoutUtils::HasAnimationOfPropertySet(
+ this, nsCSSPropertyIDSet::OpacityProperties(), effectSetForOpacity)) {
+ if (needHitTestInfo) {
+ opacityItemForEventsOnly = true;
+ } else {
+ return;
+ }
+ }
+
+ if (aBuilder->IsForPainting() && disp->mWillChange.bits) {
+ aBuilder->AddToWillChangeBudget(this, GetSize());
+ }
+
+ // For preserves3d, use the dirty rect already installed on the
+ // builder, since aDirtyRect maybe distorted for transforms along
+ // the chain.
+ nsRect visibleRect = aBuilder->GetVisibleRect();
+ nsRect dirtyRect = aBuilder->GetDirtyRect();
+
+ // We build an opacity item if it's not going to be drawn by SVG content.
+ // We could in principle skip creating an nsDisplayOpacity item if
+ // nsDisplayOpacity::NeedsActiveLayer returns false and usingSVGEffects is
+ // true (the nsDisplayFilter/nsDisplayMasksAndClipPaths could handle the
+ // opacity). Since SVG has perf issues where we sometimes spend a lot of
+ // time creating display list items that might be helpful. We'd need to
+ // restore our mechanism to do that (changed in bug 1482403), and we'd
+ // need to invalidate the frame if the value that would be return from
+ // NeedsActiveLayer was to change, which we don't currently do.
+ const bool useOpacity =
+ HasVisualOpacity(disp, effects, effectSetForOpacity) &&
+ !SVGUtils::CanOptimizeOpacity(this);
+
+ const bool isTransformed = IsTransformed();
+ const bool hasPerspective = isTransformed && HasPerspective();
+ const bool extend3DContext =
+ Extend3DContext(disp, effects, effectSetForOpacity);
+ const bool combines3DTransformWithAncestors =
+ (extend3DContext || isTransformed) && Combines3DTransformWithAncestors();
+
+ Maybe<nsDisplayListBuilder::AutoPreserves3DContext> autoPreserves3DContext;
+ if (extend3DContext && !combines3DTransformWithAncestors) {
+ // Start a new preserves3d context to keep informations on
+ // nsDisplayListBuilder.
+ autoPreserves3DContext.emplace(aBuilder);
+ // Save dirty rect on the builder to avoid being distorted for
+ // multiple transforms along the chain.
+ aBuilder->SavePreserves3DRect();
+
+ // We rebuild everything within preserve-3d and don't try
+ // to retain, so override the dirty rect now.
+ if (aBuilder->IsRetainingDisplayList()) {
+ dirtyRect = visibleRect;
+ aBuilder->SetDisablePartialUpdates(true);
+ }
+ }
+
+ const bool useBlendMode = effects->mMixBlendMode != StyleBlend::Normal;
+ if (useBlendMode) {
+ aBuilder->SetContainsBlendMode(true);
+ }
+
+ // reset blend mode so we can keep track if this stacking context needs have
+ // a nsDisplayBlendContainer. Set the blend mode back when the routine exits
+ // so we keep track if the parent stacking context needs a container too.
+ AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder);
+ aBuilder->SetContainsBlendMode(false);
+
+ // NOTE: When changing this condition make sure to tweak nsGfxScrollFrame as
+ // well.
+ bool usingBackdropFilter = effects->HasBackdropFilters() &&
+ IsVisibleForPainting() &&
+ !style.IsRootElementStyle();
+
+ nsRect visibleRectOutsideTransform = visibleRect;
+ nsDisplayTransform::PrerenderInfo prerenderInfo;
+ bool inTransform = aBuilder->IsInTransform();
+ if (isTransformed) {
+ prerenderInfo = nsDisplayTransform::ShouldPrerenderTransformedContent(
+ aBuilder, this, &visibleRect);
+
+ switch (prerenderInfo.mDecision) {
+ case nsDisplayTransform::PrerenderDecision::Full:
+ case nsDisplayTransform::PrerenderDecision::Partial:
+ dirtyRect = visibleRect;
+ break;
+ case nsDisplayTransform::PrerenderDecision::No: {
+ // If we didn't prerender an animated frame in a preserve-3d context,
+ // then we want disable async animations for the rest of the preserve-3d
+ // (especially ancestors).
+ if ((extend3DContext || combines3DTransformWithAncestors) &&
+ prerenderInfo.mHasAnimations) {
+ aBuilder->SavePreserves3DAllowAsyncAnimation(false);
+ }
+
+ const nsRect overflow = InkOverflowRectRelativeToSelf();
+ if (overflow.IsEmpty() && !extend3DContext) {
+ return;
+ }
+
+ // If we're in preserve-3d then grab the dirty rect that was given to
+ // the root and transform using the combined transform.
+ if (combines3DTransformWithAncestors) {
+ visibleRect = dirtyRect = aBuilder->GetPreserves3DRect();
+ }
+
+ float appPerDev = PresContext()->AppUnitsPerDevPixel();
+ auto transform = nsDisplayTransform::GetResultingTransformMatrix(
+ this, nsPoint(), appPerDev,
+ nsDisplayTransform::kTransformRectFlags);
+ nsRect untransformedDirtyRect;
+ if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, transform,
+ appPerDev,
+ &untransformedDirtyRect)) {
+ dirtyRect = untransformedDirtyRect;
+ nsDisplayTransform::UntransformRect(visibleRect, overflow, transform,
+ appPerDev, &visibleRect);
+ } else {
+ // This should only happen if the transform is singular, in which case
+ // nothing is visible anyway
+ dirtyRect.SetEmpty();
+ visibleRect.SetEmpty();
+ }
+ }
+ }
+ inTransform = true;
+ } else if (IsFixedPosContainingBlock()) {
+ // Restict the building area to the overflow rect for these frames, since
+ // RetainedDisplayListBuilder uses it to know if the size of the stacking
+ // context changed.
+ visibleRect.IntersectRect(visibleRect, InkOverflowRect());
+ dirtyRect.IntersectRect(dirtyRect, InkOverflowRect());
+ }
+
+ bool hasOverrideDirtyRect = false;
+ // If we're doing a partial build, we're not invalid and we're capable
+ // of having an override building rect (stacking context and fixed pos
+ // containing block), then we should assume we have one.
+ // Either we have an explicit one, or nothing in our subtree changed and
+ // we have an implicit empty rect.
+ //
+ // These conditions should match |CanStoreDisplayListBuildingRect()| in
+ // RetainedDisplayListBuilder.cpp
+ if (!aBuilder->IsReusingStackingContextItems() &&
+ aBuilder->IsPartialUpdate() && !aBuilder->InInvalidSubtree() &&
+ !IsFrameModified() && IsFixedPosContainingBlock() &&
+ !GetPrevContinuation() && !GetNextContinuation()) {
+ dirtyRect = nsRect();
+ if (HasOverrideDirtyRegion()) {
+ nsDisplayListBuilder::DisplayListBuildingData* data =
+ GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+ if (data) {
+ dirtyRect = data->mDirtyRect.Intersect(visibleRect);
+ hasOverrideDirtyRect = true;
+ }
+ }
+ }
+
+ bool usingFilter = effects->HasFilters() && !style.IsRootElementStyle();
+ SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(this, false);
+ bool usingMask = maskUsage.UsingMaskOrClipPath();
+ bool usingSVGEffects = usingFilter || usingMask;
+
+ nsRect visibleRectOutsideSVGEffects = visibleRect;
+ nsDisplayList hoistedScrollInfoItemsStorage(aBuilder);
+ if (usingSVGEffects) {
+ dirtyRect =
+ SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, dirtyRect);
+ visibleRect =
+ SVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, visibleRect);
+ aBuilder->EnterSVGEffectsContents(this, &hoistedScrollInfoItemsStorage);
+ }
+
+ bool useStickyPosition = disp->mPosition == StylePositionProperty::Sticky;
+
+ bool useFixedPosition =
+ disp->mPosition == StylePositionProperty::Fixed &&
+ (DisplayPortUtils::IsFixedPosFrameInDisplayPort(this) ||
+ BuilderHasScrolledClip(aBuilder));
+
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
+ aBuilder, this, visibleRect, dirtyRect, isTransformed);
+
+ UpdateCurrentHitTestInfo(aBuilder, this);
+
+ // Depending on the effects that are applied to this frame, we can create
+ // multiple container display items and wrap them around our contents.
+ // This enum lists all the potential container display items, in the order
+ // outside to inside.
+ enum class ContainerItemType : uint8_t {
+ None = 0,
+ OwnLayerIfNeeded,
+ BlendMode,
+ FixedPosition,
+ OwnLayerForTransformWithRoundedClip,
+ Perspective,
+ Transform,
+ SeparatorTransforms,
+ Opacity,
+ Filter,
+ BlendContainer
+ };
+
+ nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
+
+ auto cssClip = GetClipPropClipRect(disp, effects, GetSize());
+ auto ApplyClipProp = [&](DisplayListClipState::AutoSaveRestore& aClipState) {
+ if (!cssClip) {
+ return;
+ }
+ nsPoint offset = aBuilder->GetCurrentFrameOffsetToReferenceFrame();
+ aBuilder->IntersectDirtyRect(*cssClip);
+ aBuilder->IntersectVisibleRect(*cssClip);
+ aClipState.ClipContentDescendants(*cssClip + offset);
+ };
+
+ // The CSS clip property is effectively inside the transform, but outside the
+ // filters. So if we're not transformed we can apply it just here for
+ // simplicity, instead of on each of the places that handle clipCapturedBy.
+ DisplayListClipState::AutoSaveRestore untransformedCssClip(aBuilder);
+ if (!isTransformed) {
+ ApplyClipProp(untransformedCssClip);
+ }
+
+ // If there is a current clip, then depending on the container items we
+ // create, different things can happen to it. Some container items simply
+ // propagate the clip to their children and aren't clipped themselves.
+ // But other container items, especially those that establish a different
+ // geometry for their contents (e.g. transforms), capture the clip on
+ // themselves and unset the clip for their contents. If we create more than
+ // one of those container items, the clip will be captured on the outermost
+ // one and the inner container items will be unclipped.
+ ContainerItemType clipCapturedBy = ContainerItemType::None;
+ if (useFixedPosition) {
+ clipCapturedBy = ContainerItemType::FixedPosition;
+ } else if (isTransformed) {
+ const DisplayItemClipChain* currentClip =
+ aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
+ if ((hasPerspective || extend3DContext) &&
+ (currentClip && currentClip->HasRoundedCorners())) {
+ // If we're creating an nsDisplayTransform item that is going to combine
+ // its transform with its children (preserve-3d or perspective), then we
+ // can't have an intermediate surface. Mask layers force an intermediate
+ // surface, so if we're going to need both then create a separate
+ // wrapping layer for the mask.
+ clipCapturedBy = ContainerItemType::OwnLayerForTransformWithRoundedClip;
+ } else if (hasPerspective) {
+ clipCapturedBy = ContainerItemType::Perspective;
+ } else {
+ clipCapturedBy = ContainerItemType::Transform;
+ }
+ } else if (usingFilter) {
+ clipCapturedBy = ContainerItemType::Filter;
+ }
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ if (clipCapturedBy != ContainerItemType::None) {
+ clipState.Clear();
+ }
+
+ DisplayListClipState::AutoSaveRestore transformedCssClip(aBuilder);
+ if (isTransformed) {
+ // FIXME(emilio, bug 1525159): In the case we have a both a transform _and_
+ // filters, this clips the input to the filters as well, which is not
+ // correct (clipping by the `clip` property is supposed to happen after
+ // applying the filter effects, per [1].
+ //
+ // This is not a regression though, since we used to do that anyway before
+ // bug 1514384, and even without the transform we get it wrong.
+ //
+ // [1]: https://drafts.fxtf.org/css-masking/#placement
+ ApplyClipProp(transformedCssClip);
+ }
+
+ nsDisplayListCollection set(aBuilder);
+ Maybe<nsRect> clipForMask;
+ {
+ DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
+ nsDisplayListBuilder::AutoInTransformSetter inTransformSetter(aBuilder,
+ inTransform);
+ nsDisplayListBuilder::AutoEnterFilter filterASRSetter(aBuilder,
+ usingFilter);
+ nsDisplayListBuilder::AutoInEventsOnly inEventsSetter(
+ aBuilder, opacityItemForEventsOnly);
+
+ // If we have a mask, compute a clip to bound the masked content.
+ // This is necessary in case the content moves with an ancestor
+ // ASR of the mask.
+ // Don't do this if we also have a filter, because then the clip
+ // would be applied before the filter, violating
+ // https://www.w3.org/TR/filter-effects-1/#placement.
+ // Filters are a containing block for fixed and absolute descendants,
+ // so the masked content cannot move with an ancestor ASR.
+ if (usingMask && !usingFilter) {
+ clipForMask = ComputeClipForMaskItem(aBuilder, this, maskUsage);
+ if (clipForMask) {
+ aBuilder->IntersectDirtyRect(*clipForMask);
+ aBuilder->IntersectVisibleRect(*clipForMask);
+ nestedClipState.ClipContentDescendants(
+ *clipForMask + aBuilder->GetCurrentFrameOffsetToReferenceFrame());
+ }
+ }
+
+ // extend3DContext also guarantees that applyAbsPosClipping and
+ // usingSVGEffects are false We only modify the preserve-3d rect if we are
+ // the top of a preserve-3d heirarchy
+ if (extend3DContext) {
+ // Mark these first so MarkAbsoluteFramesForDisplayList knows if we are
+ // going to be forced to descend into frames.
+ aBuilder->MarkPreserve3DFramesForDisplayList(this);
+ }
+
+ aBuilder->AdjustWindowDraggingRegion(this);
+
+ MarkAbsoluteFramesForDisplayList(aBuilder);
+ aBuilder->Check();
+ BuildDisplayList(aBuilder, set);
+ SetBuiltDisplayList(true);
+ aBuilder->Check();
+ aBuilder->DisplayCaret(this, set.Outlines());
+
+ // Blend modes are a real pain for retained display lists. We build a blend
+ // container item if the built list contains any blend mode items within
+ // the current stacking context. This can change without an invalidation
+ // to the stacking context frame, or the blend mode frame (e.g. by moving
+ // an intermediate frame).
+ // When we gain/remove a blend container item, we need to mark this frame
+ // as invalid and have the full display list for merging to track
+ // the change correctly.
+ // It seems really hard to track this in advance, as the bookkeeping
+ // required to note which stacking contexts have blend descendants
+ // is complex and likely to be buggy.
+ // Instead we're doing the sad thing, detecting it afterwards, and just
+ // repeating display list building if it changed.
+ // We have to repeat building for the entire display list (or at least
+ // the outer stacking context), since we need to mark this frame as invalid
+ // to remove any existing content that isn't wrapped in the blend container,
+ // and then we need to build content infront/behind the blend container
+ // to get correct positioning during merging.
+ if (aBuilder->ContainsBlendMode() && aBuilder->IsRetainingDisplayList()) {
+ if (aBuilder->IsPartialUpdate()) {
+ aBuilder->SetPartialBuildFailed(true);
+ } else {
+ aBuilder->SetDisablePartialUpdates(true);
+ }
+ }
+ }
+
+ if (aBuilder->IsBackgroundOnly()) {
+ set.BlockBorderBackgrounds()->DeleteAll(aBuilder);
+ set.Floats()->DeleteAll(aBuilder);
+ set.Content()->DeleteAll(aBuilder);
+ set.PositionedDescendants()->DeleteAll(aBuilder);
+ set.Outlines()->DeleteAll(aBuilder);
+ }
+
+ if (hasOverrideDirtyRect &&
+ StaticPrefs::layout_display_list_show_rebuild_area()) {
+ nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
+ aBuilder, this,
+ dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
+ NS_RGBA(255, 0, 0, 64), false);
+ if (color) {
+ color->SetOverrideZIndex(INT32_MAX);
+ set.PositionedDescendants()->AppendToTop(color);
+ }
+ }
+
+ nsIContent* content = GetContent();
+ if (!content) {
+ content = PresContext()->Document()->GetRootElement();
+ }
+
+ nsDisplayList resultList(aBuilder);
+ set.SerializeWithCorrectZOrder(&resultList, content);
+
+ // Get the ASR to use for the container items that we create here.
+ const ActiveScrolledRoot* containerItemASR = contASRTracker.GetContainerASR();
+
+ bool createdContainer = false;
+
+ // If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the
+ // same list, the nsDisplayBlendContainer should be added first. This only
+ // happens when the element creating this stacking context has mix-blend-mode
+ // and also contains a child which has mix-blend-mode.
+ // The nsDisplayBlendContainer must be added to the list first, so it does not
+ // isolate the containing element blending as well.
+ if (aBuilder->ContainsBlendMode()) {
+ resultList.AppendToTop(nsDisplayBlendContainer::CreateForMixBlendMode(
+ aBuilder, this, &resultList, containerItemASR));
+ createdContainer = true;
+ }
+
+ if (usingBackdropFilter) {
+ nsRect backdropRect =
+ GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
+ resultList.AppendNewToTop<nsDisplayBackdropFilters>(
+ aBuilder, this, &resultList, backdropRect, this);
+ createdContainer = true;
+ }
+
+ // If there are any SVG effects, wrap the list up in an SVG effects item
+ // (which also handles CSS group opacity). Note that we create an SVG effects
+ // item even if resultList is empty, since a filter can produce graphical
+ // output even if the element being filtered wouldn't otherwise do so.
+ if (usingSVGEffects) {
+ MOZ_ASSERT(usingFilter || usingMask,
+ "Beside filter & mask/clip-path, what else effect do we have?");
+
+ if (clipCapturedBy == ContainerItemType::Filter) {
+ clipState.Restore();
+ }
+ // Revert to the post-filter dirty rect.
+ aBuilder->SetVisibleRect(visibleRectOutsideSVGEffects);
+
+ // Skip all filter effects while generating glyph mask.
+ if (usingFilter && !aBuilder->IsForGenerateGlyphMask()) {
+ /* List now emptied, so add the new list to the top. */
+ resultList.AppendNewToTop<nsDisplayFilters>(aBuilder, this, &resultList,
+ this, usingBackdropFilter);
+ createdContainer = true;
+ }
+
+ if (usingMask) {
+ // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so
+ // that's the ASR we prefer to use for the mask item. However, we can
+ // only do this if the mask if clipped with respect to that ASR, because
+ // an item always needs to have finite bounds with respect to its ASR.
+ // If we weren't able to compute a clip for the mask, we fall back to
+ // using containerItemASR, which is the lowest common ancestor clip of
+ // the mask's contents. That's not entirely correct, but it satisfies
+ // the base requirement of the ASR system (that items have finite bounds
+ // wrt. their ASR).
+ const ActiveScrolledRoot* maskASR =
+ clipForMask.isSome() ? aBuilder->CurrentActiveScrolledRoot()
+ : containerItemASR;
+ /* List now emptied, so add the new list to the top. */
+ resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>(
+ aBuilder, this, &resultList, maskASR, usingBackdropFilter);
+ createdContainer = true;
+ }
+
+ // TODO(miko): We could probably create a wraplist here and avoid creating
+ // it later in |BuildDisplayListForChild()|.
+ createdContainer = false;
+
+ // Also add the hoisted scroll info items. We need those for APZ scrolling
+ // because nsDisplayMasksAndClipPaths items can't build active layers.
+ aBuilder->ExitSVGEffectsContents();
+ resultList.AppendToTop(&hoistedScrollInfoItemsStorage);
+ }
+
+ // If the list is non-empty and there is CSS group opacity without SVG
+ // effects, wrap it up in an opacity item.
+ if (useOpacity) {
+ const bool needsActiveOpacityLayer =
+ nsDisplayOpacity::NeedsActiveLayer(aBuilder, this);
+ resultList.AppendNewToTop<nsDisplayOpacity>(
+ aBuilder, this, &resultList, containerItemASR, opacityItemForEventsOnly,
+ needsActiveOpacityLayer, usingBackdropFilter);
+ createdContainer = true;
+ }
+
+ // If we're going to apply a transformation and don't have preserve-3d set,
+ // wrap everything in an nsDisplayTransform. If there's nothing in the list,
+ // don't add anything.
+ //
+ // For the preserve-3d case we want to individually wrap every child in the
+ // list with a separate nsDisplayTransform instead. When the child is already
+ // an nsDisplayTransform, we can skip this step, as the computed transform
+ // will already include our own.
+ //
+ // We also traverse into sublists created by nsDisplayWrapList, so that we
+ // find all the correct children.
+ if (isTransformed && extend3DContext) {
+ // Install dummy nsDisplayTransform as a leaf containing
+ // descendants not participating this 3D rendering context.
+ nsDisplayList nonparticipants(aBuilder);
+ nsDisplayList participants(aBuilder);
+ int index = 1;
+
+ nsDisplayItem* separator = nullptr;
+
+ // TODO: This can be simplified: |participants| is just |resultList|.
+ for (nsDisplayItem* item : resultList.TakeItems()) {
+ if (ItemParticipatesIn3DContext(this, item) &&
+ !item->GetClip().HasClip()) {
+ // The frame of this item participates the same 3D context.
+ WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
+ index++, &separator);
+
+ participants.AppendToTop(item);
+ } else {
+ // The frame of the item doesn't participate the current
+ // context, or has no transform.
+ //
+ // For items participating but not transformed, they are add
+ // to nonparticipants to get a separator layer for handling
+ // clips, if there is, on an intermediate surface.
+ // \see ContainerLayer::DefaultComputeEffectiveTransforms().
+ nonparticipants.AppendToTop(item);
+ }
+ }
+ WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
+ index++, &separator);
+
+ if (separator) {
+ createdContainer = true;
+ }
+
+ resultList.AppendToTop(&participants);
+ }
+
+ if (isTransformed) {
+ transformedCssClip.Restore();
+ if (clipCapturedBy == ContainerItemType::Transform) {
+ // Restore clip state now so nsDisplayTransform is clipped properly.
+ clipState.Restore();
+ }
+ // Revert to the dirtyrect coming in from the parent, without our transform
+ // taken into account.
+ aBuilder->SetVisibleRect(visibleRectOutsideTransform);
+
+ if (this != aBuilder->RootReferenceFrame()) {
+ // Revert to the outer reference frame and offset because all display
+ // items we create from now on are outside the transform.
+ nsPoint toOuterReferenceFrame;
+ const nsIFrame* outerReferenceFrame =
+ aBuilder->FindReferenceFrameFor(GetParent(), &toOuterReferenceFrame);
+ toOuterReferenceFrame += GetPosition();
+
+ buildingDisplayList.SetReferenceFrameAndCurrentOffset(
+ outerReferenceFrame, toOuterReferenceFrame);
+ }
+
+ // We would like to block async animations for ancestors of ones not
+ // prerendered in the preserve-3d tree. Now that we've finished processing
+ // all descendants, update allowAsyncAnimation to take their prerender
+ // state into account
+ // FIXME: We don't block async animations for previous siblings because
+ // their prerender decisions have been made. We may have to figure out a
+ // better way to rollback their prerender decisions.
+ // Alternatively we could not block animations for later siblings, and only
+ // block them for ancestors of a blocked one.
+ if ((extend3DContext || combines3DTransformWithAncestors) &&
+ prerenderInfo.CanUseAsyncAnimations() &&
+ !aBuilder->GetPreserves3DAllowAsyncAnimation()) {
+ // aBuilder->GetPreserves3DAllowAsyncAnimation() means the inner or
+ // previous silbing frames are allowed/disallowed for async animations.
+ prerenderInfo.mDecision = nsDisplayTransform::PrerenderDecision::No;
+ }
+
+ nsDisplayTransform* transformItem = MakeDisplayItem<nsDisplayTransform>(
+ aBuilder, this, &resultList, visibleRect, prerenderInfo.mDecision);
+ if (transformItem) {
+ resultList.AppendToTop(transformItem);
+ createdContainer = true;
+ }
+
+ if (hasPerspective) {
+ transformItem->MarkWithAssociatedPerspective();
+
+ if (clipCapturedBy == ContainerItemType::Perspective) {
+ clipState.Restore();
+ }
+ resultList.AppendNewToTop<nsDisplayPerspective>(aBuilder, this,
+ &resultList);
+ createdContainer = true;
+ }
+ }
+
+ if (clipCapturedBy ==
+ ContainerItemType::OwnLayerForTransformWithRoundedClip) {
+ clipState.Restore();
+ resultList.AppendNewToTopWithIndex<nsDisplayOwnLayer>(
+ aBuilder, this,
+ /* aIndex = */ nsDisplayOwnLayer::OwnLayerForTransformWithRoundedClip,
+ &resultList, aBuilder->CurrentActiveScrolledRoot(),
+ nsDisplayOwnLayerFlags::None, ScrollbarData{},
+ /* aForceActive = */ false, false);
+ createdContainer = true;
+ }
+
+ // If we have sticky positioning, wrap it in a sticky position item.
+ if (useFixedPosition) {
+ if (clipCapturedBy == ContainerItemType::FixedPosition) {
+ clipState.Restore();
+ }
+ // The ASR for the fixed item should be the ASR of our containing block,
+ // which has been set as the builder's current ASR, unless this frame is
+ // invisible and we hadn't saved display item data for it. In that case,
+ // we need to take the containerItemASR since we might have fixed children.
+ // For WebRender, we want to the know what |containerItemASR| is for the
+ // case where the fixed-pos item is not a "real" fixed-pos item (e.g. it's
+ // nested inside a scrolling transform), so we stash that on the display
+ // item as well.
+ const ActiveScrolledRoot* fixedASR = ActiveScrolledRoot::PickAncestor(
+ containerItemASR, aBuilder->CurrentActiveScrolledRoot());
+ resultList.AppendNewToTop<nsDisplayFixedPosition>(
+ aBuilder, this, &resultList, fixedASR, containerItemASR);
+ createdContainer = true;
+ } else if (useStickyPosition) {
+ // For position:sticky, the clip needs to be applied both to the sticky
+ // container item and to the contents. The container item needs the clip
+ // because a scrolled clip needs to move independently from the sticky
+ // contents, and the contents need the clip so that they have finite
+ // clipped bounds with respect to the container item's ASR. The latter is
+ // a little tricky in the case where the sticky item has both fixed and
+ // non-fixed descendants, because that means that the sticky container
+ // item's ASR is the ASR of the fixed descendant.
+ // For WebRender display list building, though, we still want to know the
+ // the ASR that the sticky container item would normally have, so we stash
+ // that on the display item as the "container ASR" (i.e. the normal ASR of
+ // the container item, excluding the special behaviour induced by fixed
+ // descendants).
+ const ActiveScrolledRoot* stickyASR = ActiveScrolledRoot::PickAncestor(
+ containerItemASR, aBuilder->CurrentActiveScrolledRoot());
+
+ auto* stickyItem = MakeDisplayItem<nsDisplayStickyPosition>(
+ aBuilder, this, &resultList, stickyASR,
+ aBuilder->CurrentActiveScrolledRoot(),
+ clipState.IsClippedToDisplayPort());
+
+ bool shouldFlatten = true;
+
+ StickyScrollContainer* stickyScrollContainer =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(this);
+ if (stickyScrollContainer &&
+ stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled()) {
+ shouldFlatten = false;
+ }
+
+ stickyItem->SetShouldFlatten(shouldFlatten);
+
+ resultList.AppendToTop(stickyItem);
+ createdContainer = true;
+
+ // If the sticky element is inside a filter, annotate the scroll frame that
+ // scrolls the filter as having out-of-flow content inside a filter (this
+ // inhibits paint skipping).
+ if (aBuilder->GetFilterASR() && aBuilder->GetFilterASR() == stickyASR) {
+ aBuilder->GetFilterASR()
+ ->mScrollableFrame->SetHasOutOfFlowContentInsideFilter();
+ }
+ }
+
+ // If there's blending, wrap up the list in a blend-mode item. Note that
+ // opacity can be applied before blending as the blend color is not affected
+ // by foreground opacity (only background alpha).
+ if (useBlendMode) {
+ DisplayListClipState::AutoSaveRestore blendModeClipState(aBuilder);
+ resultList.AppendNewToTop<nsDisplayBlendMode>(aBuilder, this, &resultList,
+ effects->mMixBlendMode,
+ containerItemASR, false);
+ createdContainer = true;
+ }
+
+ if (aBuilder->IsReusingStackingContextItems()) {
+ if (resultList.IsEmpty()) {
+ return;
+ }
+
+ nsDisplayItem* container = resultList.GetBottom();
+ if (resultList.Length() > 1 || container->Frame() != this) {
+ container = MakeDisplayItem<nsDisplayContainer>(
+ aBuilder, this, containerItemASR, &resultList);
+ } else {
+ MOZ_ASSERT(resultList.Length() == 1);
+ resultList.Clear();
+ }
+
+ // Mark the outermost display item as reusable. These display items and
+ // their chidren can be reused during the next paint if no ancestor or
+ // descendant frames have been modified.
+ if (!container->IsReusedItem()) {
+ container->SetReusable();
+ }
+ aList->AppendToTop(container);
+ createdContainer = true;
+ } else {
+ aList->AppendToTop(&resultList);
+ }
+
+ if (aCreatedContainerItem) {
+ *aCreatedContainerItem = createdContainer;
+ }
+}
+
+static nsDisplayItem* WrapInWrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList,
+ const ActiveScrolledRoot* aContainerASR,
+ bool aBuiltContainerItem = false) {
+ nsDisplayItem* item = aList->GetBottom();
+ if (!item) {
+ return nullptr;
+ }
+
+ // We need a wrap list if there are multiple items, or if the single
+ // item has a different frame. This can change in a partial build depending
+ // on which items we build, so we need to ensure that we don't transition
+ // to/from a wrap list without invalidating correctly.
+ bool needsWrapList =
+ aList->Length() > 1 || item->Frame() != aFrame || item->GetChildren();
+
+ // If we have an explicit container item (that can't change without an
+ // invalidation) or we're doing a full build and don't need a wrap list, then
+ // we can skip adding one.
+ if (aBuiltContainerItem || (!aBuilder->IsPartialUpdate() && !needsWrapList)) {
+ MOZ_ASSERT(aList->Length() == 1);
+ aList->Clear();
+ return item;
+ }
+
+ // If we're doing a partial build and we didn't need a wrap list
+ // previously then we can try to work from there.
+ if (aBuilder->IsPartialUpdate() &&
+ !aFrame->HasDisplayItem(uint32_t(DisplayItemType::TYPE_CONTAINER))) {
+ // If we now need a wrap list, we must previously have had no display items
+ // or a single one belonging to this frame. Mark the item itself as
+ // discarded so that RetainedDisplayListBuilder uses the ones we just built.
+ // We don't want to mark the frame as modified as that would invalidate
+ // positioned descendants that might be outside of this list, and might not
+ // have been rebuilt this time.
+ if (needsWrapList) {
+ DiscardOldItems(aFrame);
+ } else {
+ MOZ_ASSERT(aList->Length() == 1);
+ aList->Clear();
+ return item;
+ }
+ }
+
+ // The last case we could try to handle is when we previously had a wrap list,
+ // but no longer need it. Unfortunately we can't differentiate this case from
+ // a partial build where other children exist but we just didn't build them
+ // this time.
+ // TODO:RetainedDisplayListBuilder's merge phase has the full list and
+ // could strip them out.
+
+ return MakeDisplayItem<nsDisplayContainer>(aBuilder, aFrame, aContainerASR,
+ aList);
+}
+
+/**
+ * Check if a frame should be visited for building display list.
+ */
+static bool DescendIntoChild(nsDisplayListBuilder* aBuilder,
+ const nsIFrame* aChild, const nsRect& aVisible,
+ const nsRect& aDirty) {
+ if (aChild->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
+ return true;
+ }
+
+ // If the child is a scrollframe that we want to ignore, then we need
+ // to descend into it because its scrolled child may intersect the dirty
+ // area even if the scrollframe itself doesn't.
+ if (aChild == aBuilder->GetIgnoreScrollFrame()) {
+ return true;
+ }
+
+ // There are cases where the "ignore scroll frame" on the builder is not set
+ // correctly, and so we additionally want to catch cases where the child is
+ // a root scrollframe and we are ignoring scrolling on the viewport.
+ if (aChild == aBuilder->GetPresShellIgnoreScrollFrame()) {
+ return true;
+ }
+
+ nsRect overflow = aChild->InkOverflowRect();
+
+ // On mobile, there may be a dynamic toolbar. The root content document's
+ // root scroll frame's ink overflow rect does not include the toolbar
+ // height, but if the toolbar is hidden, we still want to be able to target
+ // content underneath the toolbar, so expand the overflow rect here to
+ // allow display list building to descend into the scroll frame.
+ if (aBuilder->IsForEventDelivery() &&
+ aChild == aChild->PresShell()->GetRootScrollFrame() &&
+ aChild->PresContext()->IsRootContentDocumentCrossProcess() &&
+ aChild->PresContext()->HasDynamicToolbar()) {
+ overflow.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ aChild->PresContext(), overflow.Size()));
+ }
+
+ if (aDirty.Intersects(overflow)) {
+ return true;
+ }
+
+ if (aChild->ForceDescendIntoIfVisible() && aVisible.Intersects(overflow)) {
+ return true;
+ }
+
+ if (aChild->IsTablePart()) {
+ // Relative positioning and transforms can cause table parts to move, but we
+ // will still paint the backgrounds for their ancestor parts under them at
+ // their 'normal' position. That means that we must consider the overflow
+ // rects at both positions.
+
+ // We convert the overflow rect into the nsTableFrame's coordinate
+ // space, applying the normal position offset at each step. Then we
+ // compare that against the builder's cached dirty rect in table
+ // coordinate space.
+ const nsIFrame* f = aChild;
+ nsRect normalPositionOverflowRelativeToTable = overflow;
+
+ while (f->IsTablePart()) {
+ normalPositionOverflowRelativeToTable += f->GetNormalPosition();
+ f = f->GetParent();
+ }
+
+ nsDisplayTableBackgroundSet* tableBGs = aBuilder->GetTableBackgroundSet();
+ if (tableBGs && tableBGs->GetDirtyRect().Intersects(
+ normalPositionOverflowRelativeToTable)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsIFrame::BuildDisplayListForSimpleChild(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aChild,
+ const nsDisplayListSet& aLists) {
+ // This is the shortcut for frames been handled along the common
+ // path, the most common one of THE COMMON CASE mentioned later.
+ MOZ_ASSERT(aChild->Type() != LayoutFrameType::Placeholder);
+ MOZ_ASSERT(!aBuilder->GetSelectedFramesOnly() &&
+ !aBuilder->GetIncludeAllOutOfFlows(),
+ "It should be held for painting to window");
+ MOZ_ASSERT(aChild->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST));
+
+ const nsPoint offset = aChild->GetOffsetTo(this);
+ const nsRect visible = aBuilder->GetVisibleRect() - offset;
+ const nsRect dirty = aBuilder->GetDirtyRect() - offset;
+
+ if (!DescendIntoChild(aBuilder, aChild, visible, dirty)) {
+ DL_LOGV("Skipped frame %p", aChild);
+ return;
+ }
+
+ // Child cannot be transformed since it is not a stacking context.
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, aChild, visible, dirty, false);
+
+ UpdateCurrentHitTestInfo(aBuilder, aChild);
+
+ aChild->MarkAbsoluteFramesForDisplayList(aBuilder);
+ aBuilder->AdjustWindowDraggingRegion(aChild);
+ aBuilder->Check();
+ aChild->BuildDisplayList(aBuilder, aLists);
+ aChild->SetBuiltDisplayList(true);
+ aBuilder->Check();
+ aBuilder->DisplayCaret(aChild, aLists.Outlines());
+}
+
+static bool ShouldSkipFrame(nsDisplayListBuilder* aBuilder,
+ const nsIFrame* aFrame) {
+ // If painting is restricted to just the background of the top level frame,
+ // then we have nothing to do here.
+ if (aBuilder->IsBackgroundOnly()) {
+ return true;
+ }
+ if (aBuilder->IsForGenerateGlyphMask() &&
+ (!aFrame->IsTextFrame() && aFrame->IsLeaf())) {
+ return true;
+ }
+ // The placeholder frame should have the same content as the OOF frame.
+ if (aBuilder->GetSelectedFramesOnly() &&
+ (aFrame->IsLeaf() && !aFrame->IsSelected())) {
+ return true;
+ }
+ static const nsFrameState skipFlags =
+ (NS_FRAME_TOO_DEEP_IN_FRAME_TREE | NS_FRAME_IS_NONDISPLAY);
+ if (aFrame->HasAnyStateBits(skipFlags)) {
+ return true;
+ }
+ return aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually;
+}
+
+void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aChild,
+ const nsDisplayListSet& aLists,
+ DisplayChildFlags aFlags) {
+ AutoCheckBuilder check(aBuilder);
+#ifdef DEBUG
+ DL_LOGV("BuildDisplayListForChild (%p) <", aChild);
+ ScopeExit e(
+ [aChild]() { DL_LOGV("> BuildDisplayListForChild (%p)", aChild); });
+#endif
+
+ if (ShouldSkipFrame(aBuilder, aChild)) {
+ return;
+ }
+
+ if (HidesContent()) {
+ return;
+ }
+
+ // If we're generating a display list for printing, include Link items for
+ // frames that correspond to HTML link elements so that we can have active
+ // links in saved PDF output. Note that the state of "within a link" is
+ // set on the display-list builder, such that all descendants of the link
+ // element will generate display-list links.
+ // TODO: we should be able to optimize this so as to avoid creating links
+ // for the same destination that entirely overlap each other, which adds
+ // nothing useful to the final PDF.
+ Maybe<nsDisplayListBuilder::Linkifier> linkifier;
+ if (StaticPrefs::print_save_as_pdf_links_enabled() &&
+ aBuilder->IsForPrinting()) {
+ linkifier.emplace(aBuilder, aChild, aLists.Content());
+ linkifier->MaybeAppendLink(aBuilder, aChild);
+ }
+
+ nsIFrame* child = aChild;
+ auto* placeholder = child->IsPlaceholderFrame()
+ ? static_cast<nsPlaceholderFrame*>(child)
+ : nullptr;
+ nsIFrame* childOrOutOfFlow =
+ placeholder ? placeholder->GetOutOfFlowFrame() : child;
+
+ nsIFrame* parent = childOrOutOfFlow->GetParent();
+ const auto* parentDisplay = parent->StyleDisplay();
+ const auto overflowClipAxes =
+ parent->ShouldApplyOverflowClipping(parentDisplay);
+
+ const bool isPaintingToWindow = aBuilder->IsPaintingToWindow();
+ const bool doingShortcut =
+ isPaintingToWindow &&
+ child->HasAnyStateBits(NS_FRAME_SIMPLE_DISPLAYLIST) &&
+ // Animations may change the stacking context state.
+ // ShouldApplyOverflowClipping is affected by the parent style, which does
+ // not invalidate the NS_FRAME_SIMPLE_DISPLAYLIST bit.
+ !(overflowClipAxes != PhysicalAxes::None ||
+ child->MayHaveTransformAnimation() || child->MayHaveOpacityAnimation());
+
+ if (aBuilder->IsForPainting()) {
+ aBuilder->ClearWillChangeBudgetStatus(child);
+ }
+
+ if (StaticPrefs::layout_css_scroll_anchoring_highlight()) {
+ if (child->FirstContinuation()->IsScrollAnchor()) {
+ nsRect bounds = child->GetContentRectRelativeToSelf() +
+ aBuilder->ToReferenceFrame(child);
+ nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
+ aBuilder, child, bounds, NS_RGBA(255, 0, 255, 64));
+ if (color) {
+ color->SetOverrideZIndex(INT32_MAX);
+ aLists.PositionedDescendants()->AppendToTop(color);
+ }
+ }
+ }
+
+ if (doingShortcut) {
+ BuildDisplayListForSimpleChild(aBuilder, child, aLists);
+ return;
+ }
+
+ // dirty rect in child-relative coordinates
+ NS_ASSERTION(aBuilder->GetCurrentFrame() == this, "Wrong coord space!");
+ const nsPoint offset = child->GetOffsetTo(this);
+ nsRect visible = aBuilder->GetVisibleRect() - offset;
+ nsRect dirty = aBuilder->GetDirtyRect() - offset;
+
+ nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr;
+ if (placeholder) {
+ if (placeholder->HasAnyStateBits(PLACEHOLDER_FOR_TOPLAYER)) {
+ // If the out-of-flow frame is in the top layer, the viewport frame
+ // will paint it. Skip it here. Note that, only out-of-flow frames
+ // with this property should be skipped, because non-HTML elements
+ // may stop their children from being out-of-flow. Those frames
+ // should still be handled in the normal in-flow path.
+ return;
+ }
+
+ child = childOrOutOfFlow;
+ if (aBuilder->IsForPainting()) {
+ aBuilder->ClearWillChangeBudgetStatus(child);
+ }
+
+ // If 'child' is a pushed float then it's owned by a block that's not an
+ // ancestor of the placeholder, and it will be painted by that block and
+ // should not be painted through the placeholder. Also recheck
+ // NS_FRAME_TOO_DEEP_IN_FRAME_TREE and NS_FRAME_IS_NONDISPLAY.
+ static const nsFrameState skipFlags =
+ (NS_FRAME_IS_PUSHED_FLOAT | NS_FRAME_TOO_DEEP_IN_FRAME_TREE |
+ NS_FRAME_IS_NONDISPLAY);
+ if (child->HasAnyStateBits(skipFlags) || nsLayoutUtils::IsPopup(child)) {
+ return;
+ }
+
+ MOZ_ASSERT(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+ savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(child);
+
+ if (aBuilder->GetIncludeAllOutOfFlows()) {
+ visible = child->InkOverflowRect();
+ dirty = child->InkOverflowRect();
+ } else if (savedOutOfFlowData) {
+ visible =
+ savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, child, &dirty);
+ } else {
+ // The out-of-flow frame did not intersect the dirty area. We may still
+ // need to traverse into it, since it may contain placeholders we need
+ // to enter to reach other out-of-flow frames that are visible.
+ visible.SetEmpty();
+ dirty.SetEmpty();
+ }
+ }
+
+ NS_ASSERTION(!child->IsPlaceholderFrame(),
+ "Should have dealt with placeholders already");
+
+ if (!DescendIntoChild(aBuilder, child, visible, dirty)) {
+ DL_LOGV("Skipped frame %p", child);
+ return;
+ }
+
+ const bool isSVG = child->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
+
+ // This flag is raised if the control flow strays off the common path.
+ // The common path is the most common one of THE COMMON CASE mentioned later.
+ bool awayFromCommonPath = !isPaintingToWindow;
+
+ // true if this is a real or pseudo stacking context
+ bool pseudoStackingContext =
+ aFlags.contains(DisplayChildFlag::ForcePseudoStackingContext);
+
+ if (!pseudoStackingContext && !isSVG &&
+ aFlags.contains(DisplayChildFlag::Inline) &&
+ !child->IsLineParticipant()) {
+ // child is a non-inline frame in an inline context, i.e.,
+ // it acts like inline-block or inline-table. Therefore it is a
+ // pseudo-stacking-context.
+ pseudoStackingContext = true;
+ }
+
+ const nsStyleDisplay* ourDisp = StyleDisplay();
+ // Don't paint our children if the theme object is a leaf.
+ if (IsThemed(ourDisp) && !PresContext()->Theme()->WidgetIsContainer(
+ ourDisp->EffectiveAppearance())) {
+ return;
+ }
+
+ // Since we're now sure that we're adding this frame to the display list
+ // (which means we're painting it, modulo occlusion), mark it as visible
+ // within the displayport.
+ if (isPaintingToWindow && child->TrackingVisibility() &&
+ child->IsVisibleForPainting()) {
+ child->PresShell()->EnsureFrameInApproximatelyVisibleList(child);
+ awayFromCommonPath = true;
+ }
+
+ // Child is composited if it's transformed, partially transparent, or has
+ // SVG effects or a blend mode..
+ const nsStyleDisplay* disp = child->StyleDisplay();
+ const nsStyleEffects* effects = child->StyleEffects();
+
+ const bool isPositioned = disp->IsPositionedStyle();
+ const bool isStackingContext =
+ aFlags.contains(DisplayChildFlag::ForceStackingContext) ||
+ child->IsStackingContext(disp, effects);
+
+ if (pseudoStackingContext || isStackingContext || isPositioned ||
+ placeholder || (!isSVG && disp->IsFloating(child)) ||
+ (isSVG && effects->mClip.IsRect() && IsSVGContentWithCSSClip(child))) {
+ pseudoStackingContext = true;
+ awayFromCommonPath = true;
+ }
+
+ NS_ASSERTION(!isStackingContext || pseudoStackingContext,
+ "Stacking contexts must also be pseudo-stacking-contexts");
+
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, child, visible, dirty);
+
+ UpdateCurrentHitTestInfo(aBuilder, child);
+
+ DisplayListClipState::AutoClipMultiple clipState(aBuilder);
+ nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
+
+ if (savedOutOfFlowData) {
+ aBuilder->SetBuildingInvisibleItems(false);
+
+ clipState.SetClipChainForContainingBlockDescendants(
+ savedOutOfFlowData->mContainingBlockClipChain);
+ asrSetter.SetCurrentActiveScrolledRoot(
+ savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
+ asrSetter.SetCurrentScrollParentId(savedOutOfFlowData->mScrollParentId);
+ MOZ_ASSERT(awayFromCommonPath,
+ "It is impossible when savedOutOfFlowData is true");
+ } else if (HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) &&
+ placeholder) {
+ NS_ASSERTION(visible.IsEmpty(), "should have empty visible rect");
+ // Every item we build from now until we descent into an out of flow that
+ // does have saved out of flow data should be invisible. This state gets
+ // restored when AutoBuildingDisplayList gets out of scope.
+ aBuilder->SetBuildingInvisibleItems(true);
+
+ // If we have nested out-of-flow frames and the outer one isn't visible
+ // then we won't have stored clip data for it. We can just clear the clip
+ // instead since we know we won't render anything, and the inner out-of-flow
+ // frame will setup the correct clip for itself.
+ clipState.SetClipChainForContainingBlockDescendants(nullptr);
+ }
+
+ // Setup clipping for the parent's overflow:clip,
+ // or overflow:hidden on elements that don't support scrolling (and therefore
+ // don't create nsHTML/XULScrollFrame). This clipping needs to not clip
+ // anything directly rendered by the parent, only the rendering of its
+ // children.
+ // Don't use overflowClip to restrict the dirty rect, since some of the
+ // descendants may not be clipped by it. Even if we end up with unnecessary
+ // display items, they'll be pruned during ComputeVisibility.
+ //
+ // FIXME(emilio): Why can't we handle this more similarly to `clip` (on the
+ // parent, rather than on the children)? Would ClipContentDescendants do what
+ // we want?
+ if (overflowClipAxes != PhysicalAxes::None) {
+ ApplyOverflowClipping(aBuilder, parent, overflowClipAxes, clipState);
+ awayFromCommonPath = true;
+ }
+
+ nsDisplayList list(aBuilder);
+ nsDisplayList extraPositionedDescendants(aBuilder);
+ const ActiveScrolledRoot* wrapListASR;
+ bool builtContainerItem = false;
+ if (isStackingContext) {
+ // True stacking context.
+ // For stacking contexts, BuildDisplayListForStackingContext handles
+ // clipping and MarkAbsoluteFramesForDisplayList.
+ nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
+ child->BuildDisplayListForStackingContext(aBuilder, &list,
+ &builtContainerItem);
+ wrapListASR = contASRTracker.GetContainerASR();
+ if (!aBuilder->IsReusingStackingContextItems() &&
+ aBuilder->GetCaretFrame() == child) {
+ builtContainerItem = false;
+ }
+ } else {
+ Maybe<nsRect> clipPropClip =
+ child->GetClipPropClipRect(disp, effects, child->GetSize());
+ if (clipPropClip) {
+ aBuilder->IntersectVisibleRect(*clipPropClip);
+ aBuilder->IntersectDirtyRect(*clipPropClip);
+ clipState.ClipContentDescendants(*clipPropClip +
+ aBuilder->ToReferenceFrame(child));
+ awayFromCommonPath = true;
+ }
+
+ child->MarkAbsoluteFramesForDisplayList(aBuilder);
+ child->SetBuiltDisplayList(true);
+
+ // Some SVG frames might change opacity without invalidating the frame, so
+ // exclude them from the fast-path.
+ if (!awayFromCommonPath && !child->IsSVGFrame()) {
+ // The shortcut is available for the child for next time.
+ child->AddStateBits(NS_FRAME_SIMPLE_DISPLAYLIST);
+ }
+
+ if (!pseudoStackingContext) {
+ // THIS IS THE COMMON CASE.
+ // Not a pseudo or real stacking context. Do the simple thing and
+ // return early.
+ aBuilder->AdjustWindowDraggingRegion(child);
+ aBuilder->Check();
+ child->BuildDisplayList(aBuilder, aLists);
+ aBuilder->Check();
+ aBuilder->DisplayCaret(child, aLists.Outlines());
+ return;
+ }
+
+ // A pseudo-stacking context (e.g., a positioned element with z-index auto).
+ // We allow positioned descendants of the child to escape to our parent
+ // stacking context's positioned descendant list, because they might be
+ // z-index:non-auto
+ nsDisplayListCollection pseudoStack(aBuilder);
+
+ aBuilder->AdjustWindowDraggingRegion(child);
+ nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
+ aBuilder->Check();
+ child->BuildDisplayList(aBuilder, pseudoStack);
+ aBuilder->Check();
+ if (aBuilder->DisplayCaret(child, pseudoStack.Outlines())) {
+ builtContainerItem = false;
+ }
+ wrapListASR = contASRTracker.GetContainerASR();
+
+ list.AppendToTop(pseudoStack.BorderBackground());
+ list.AppendToTop(pseudoStack.BlockBorderBackgrounds());
+ list.AppendToTop(pseudoStack.Floats());
+ list.AppendToTop(pseudoStack.Content());
+ list.AppendToTop(pseudoStack.Outlines());
+ extraPositionedDescendants.AppendToTop(pseudoStack.PositionedDescendants());
+ }
+
+ buildingForChild.RestoreBuildingInvisibleItemsValue();
+
+ if (!list.IsEmpty()) {
+ if (isPositioned || isStackingContext) {
+ // Genuine stacking contexts, and positioned pseudo-stacking-contexts,
+ // go in this level.
+ nsDisplayItem* item = WrapInWrapList(aBuilder, child, &list, wrapListASR,
+ builtContainerItem);
+ if (isSVG) {
+ aLists.Content()->AppendToTop(item);
+ } else {
+ aLists.PositionedDescendants()->AppendToTop(item);
+ }
+ } else if (!isSVG && disp->IsFloating(child)) {
+ aLists.Floats()->AppendToTop(
+ WrapInWrapList(aBuilder, child, &list, wrapListASR));
+ } else {
+ aLists.Content()->AppendToTop(&list);
+ }
+ }
+ // We delay placing the positioned descendants of positioned frames to here,
+ // because in the absence of z-index this is the correct order for them.
+ // This doesn't affect correctness because the positioned descendants list
+ // is sorted by z-order and content in BuildDisplayListForStackingContext,
+ // but it means that sort routine needs to do less work.
+ aLists.PositionedDescendants()->AppendToTop(&extraPositionedDescendants);
+}
+
+void nsIFrame::MarkAbsoluteFramesForDisplayList(
+ nsDisplayListBuilder* aBuilder) {
+ if (IsAbsoluteContainer()) {
+ aBuilder->MarkFramesForDisplayList(
+ this, GetAbsoluteContainingBlock()->GetChildList());
+ }
+}
+
+nsresult nsIFrame::GetContentForEvent(const WidgetEvent* aEvent,
+ nsIContent** aContent) {
+ nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
+ *aContent = f->GetContent();
+ NS_IF_ADDREF(*aContent);
+ return NS_OK;
+}
+
+void nsIFrame::FireDOMEvent(const nsAString& aDOMEventName,
+ nsIContent* aContent) {
+ nsIContent* target = aContent ? aContent : GetContent();
+
+ if (target) {
+ RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
+ target, aDOMEventName, CanBubble::eYes, ChromeOnlyDispatch::eNo);
+ DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
+ }
+}
+
+nsresult nsIFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ if (aEvent->mMessage == eMouseMove) {
+ // XXX If the second argument of HandleDrag() is WidgetMouseEvent,
+ // the implementation becomes simpler.
+ return HandleDrag(aPresContext, aEvent, aEventStatus);
+ }
+
+ if ((aEvent->mClass == eMouseEventClass &&
+ aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) ||
+ aEvent->mClass == eTouchEventClass) {
+ if (aEvent->mMessage == eMouseDown || aEvent->mMessage == eTouchStart) {
+ HandlePress(aPresContext, aEvent, aEventStatus);
+ } else if (aEvent->mMessage == eMouseUp || aEvent->mMessage == eTouchEnd) {
+ HandleRelease(aPresContext, aEvent, aEventStatus);
+ }
+ return NS_OK;
+ }
+
+ // When secondary buttion is down, we need to move selection to make users
+ // possible to paste something at click point quickly.
+ // When middle button is down, we need to just move selection and focus at
+ // the clicked point. Note that even if middle click paste is not enabled,
+ // Chrome moves selection at middle mouse button down. So, we should follow
+ // the behavior for the compatibility.
+ if (aEvent->mMessage == eMouseDown) {
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent && (mouseEvent->mButton == MouseButton::eSecondary ||
+ mouseEvent->mButton == MouseButton::eMiddle)) {
+ if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
+ return NS_OK;
+ }
+ return MoveCaretToEventPoint(aPresContext, mouseEvent, aEventStatus);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsIFrame::GetDataForTableSelection(
+ const nsFrameSelection* aFrameSelection, mozilla::PresShell* aPresShell,
+ WidgetMouseEvent* aMouseEvent, nsIContent** aParentContent,
+ int32_t* aContentOffset, TableSelectionMode* aTarget) {
+ if (!aFrameSelection || !aPresShell || !aMouseEvent || !aParentContent ||
+ !aContentOffset || !aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ *aParentContent = nullptr;
+ *aContentOffset = 0;
+ *aTarget = TableSelectionMode::None;
+
+ int16_t displaySelection = aPresShell->GetSelectionFlags();
+
+ bool selectingTableCells = aFrameSelection->IsInTableSelectionMode();
+
+ // DISPLAY_ALL means we're in an editor.
+ // If already in cell selection mode,
+ // continue selecting with mouse drag or end on mouse up,
+ // or when using shift key to extend block of cells
+ // (Mouse down does normal selection unless Ctrl/Cmd is pressed)
+ bool doTableSelection =
+ displaySelection == nsISelectionDisplay::DISPLAY_ALL &&
+ selectingTableCells &&
+ (aMouseEvent->mMessage == eMouseMove ||
+ (aMouseEvent->mMessage == eMouseUp &&
+ aMouseEvent->mButton == MouseButton::ePrimary) ||
+ aMouseEvent->IsShift());
+
+ if (!doTableSelection) {
+ // In Browser, special 'table selection' key must be pressed for table
+ // selection or when just Shift is pressed and we're already in table/cell
+ // selection mode
+#ifdef XP_MACOSX
+ doTableSelection = aMouseEvent->IsMeta() ||
+ (aMouseEvent->IsShift() && selectingTableCells);
+#else
+ doTableSelection = aMouseEvent->IsControl() ||
+ (aMouseEvent->IsShift() && selectingTableCells);
+#endif
+ }
+ if (!doTableSelection) return NS_OK;
+
+ // Get the cell frame or table frame (or parent) of the current content node
+ nsIFrame* frame = this;
+ bool foundCell = false;
+ bool foundTable = false;
+
+ // Get the limiting node to stop parent frame search
+ nsIContent* limiter = aFrameSelection->GetLimiter();
+
+ // If our content node is an ancestor of the limiting node,
+ // we should stop the search right now.
+ if (limiter && limiter->IsInclusiveDescendantOf(GetContent())) return NS_OK;
+
+ // We don't initiate row/col selection from here now,
+ // but we may in future
+ // bool selectColumn = false;
+ // bool selectRow = false;
+
+ while (frame) {
+ // Check for a table cell by querying to a known CellFrame interface
+ nsITableCellLayout* cellElement = do_QueryFrame(frame);
+ if (cellElement) {
+ foundCell = true;
+ // TODO: If we want to use proximity to top or left border
+ // for row and column selection, this is the place to do it
+ break;
+ } else {
+ // If not a cell, check for table
+ // This will happen when starting frame is the table or child of a table,
+ // such as a row (we were inbetween cells or in table border)
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(frame);
+ if (tableFrame) {
+ foundTable = true;
+ // TODO: How can we select row when along left table edge
+ // or select column when along top edge?
+ break;
+ } else {
+ frame = frame->GetParent();
+ // Stop if we have hit the selection's limiting content node
+ if (frame && frame->GetContent() == limiter) break;
+ }
+ }
+ }
+ // We aren't in a cell or table
+ if (!foundCell && !foundTable) return NS_OK;
+
+ nsIContent* tableOrCellContent = frame->GetContent();
+ if (!tableOrCellContent) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIContent> parentContent = tableOrCellContent->GetParent();
+ if (!parentContent) return NS_ERROR_FAILURE;
+
+ const int32_t offset =
+ parentContent->ComputeIndexOf_Deprecated(tableOrCellContent);
+ // Not likely?
+ if (offset < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Everything is OK -- set the return values
+ parentContent.forget(aParentContent);
+
+ *aContentOffset = offset;
+
+#if 0
+ if (selectRow)
+ *aTarget = TableSelectionMode::Row;
+ else if (selectColumn)
+ *aTarget = TableSelectionMode::Column;
+ else
+#endif
+ if (foundCell) {
+ *aTarget = TableSelectionMode::Cell;
+ } else if (foundTable) {
+ *aTarget = TableSelectionMode::Table;
+ }
+
+ return NS_OK;
+}
+
+static bool IsEditingHost(const nsIFrame* aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ return content && content->IsEditingHost();
+}
+
+static StyleUserSelect UsedUserSelect(const nsIFrame* aFrame) {
+ if (aFrame->IsGeneratedContentFrame()) {
+ return StyleUserSelect::None;
+ }
+
+ // Per https://drafts.csswg.org/css-ui-4/#content-selection:
+ //
+ // The used value is the same as the computed value, except:
+ //
+ // 1 - on editable elements where the used value is always 'contain'
+ // regardless of the computed value
+ // 2 - when the computed value is auto, in which case the used value is one
+ // of the other values...
+ //
+ // See https://github.com/w3c/csswg-drafts/issues/3344 to see why we do this
+ // at used-value time instead of at computed-value time.
+
+ if (aFrame->IsTextInputFrame() || IsEditingHost(aFrame)) {
+ // We don't implement 'contain' itself, but we make 'text' behave as
+ // 'contain' for contenteditable and <input> / <textarea> elements anyway so
+ // this is ok.
+ return StyleUserSelect::Text;
+ }
+
+ auto style = aFrame->Style()->UserSelect();
+ if (style != StyleUserSelect::Auto) {
+ return style;
+ }
+
+ auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
+ return parent ? UsedUserSelect(parent) : StyleUserSelect::Text;
+}
+
+bool nsIFrame::IsSelectable(StyleUserSelect* aSelectStyle) const {
+ auto style = UsedUserSelect(this);
+ if (aSelectStyle) {
+ *aSelectStyle = style;
+ }
+ return style != StyleUserSelect::None;
+}
+
+bool nsIFrame::ShouldHaveLineIfEmpty() const {
+ if (Style()->IsPseudoOrAnonBox() &&
+ Style()->GetPseudoType() != PseudoStyleType::scrolledContent) {
+ return false;
+ }
+ return IsEditingHost(this);
+}
+
+/**
+ * Handles the Mouse Press Event for the frame
+ */
+NS_IMETHODIMP
+nsIFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_ARG_POINTER(aEvent);
+ if (aEvent->mClass == eTouchEventClass) {
+ return NS_OK;
+ }
+
+ return MoveCaretToEventPoint(aPresContext, aEvent->AsMouseEvent(),
+ aEventStatus);
+}
+
+nsresult nsIFrame::MoveCaretToEventPoint(nsPresContext* aPresContext,
+ WidgetMouseEvent* aMouseEvent,
+ nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aPresContext);
+ MOZ_ASSERT(aMouseEvent);
+ MOZ_ASSERT(aMouseEvent->mMessage == eMouseDown);
+ MOZ_ASSERT(aEventStatus);
+ MOZ_ASSERT(nsEventStatus_eConsumeNoDefault != *aEventStatus);
+
+ mozilla::PresShell* presShell = aPresContext->GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We often get out of sync state issues with mousedown events that
+ // get interrupted by alerts/dialogs.
+ // Check with the ESM to see if we should process this one
+ if (!aPresContext->EventStateManager()->EventStatusOK(aMouseEvent)) {
+ return NS_OK;
+ }
+
+ const nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aMouseEvent, RelativeTo{this});
+
+ // When not using `alt`, and clicking on a draggable, but non-editable
+ // element, don't do anything, and let d&d handle the event.
+ //
+ // See bug 48876, bug 388659 and bug 55921 for context here.
+ //
+ // FIXME(emilio): The .Contains(pt) check looks a bit fishy. When would it be
+ // false given we're the event target? If it is needed, why not checking the
+ // actual draggable node rect instead?
+ if (!aMouseEvent->IsAlt() && GetRectRelativeToSelf().Contains(pt)) {
+ for (nsIContent* content = mContent; content;
+ content = content->GetFlattenedTreeParent()) {
+ if (nsContentUtils::ContentIsDraggable(content) &&
+ !content->IsEditable()) {
+ return NS_OK;
+ }
+ }
+ }
+
+ // If we are in Navigator and the click is in a draggable node, we don't want
+ // to start selection because we don't want to interfere with a potential
+ // drag of said node and steal all its glory.
+ const bool isEditor =
+ presShell->GetSelectionFlags() == nsISelectionDisplay::DISPLAY_ALL;
+
+ // Don't do something if it's middle button down event.
+ const bool isPrimaryButtonDown =
+ aMouseEvent->mButton == MouseButton::ePrimary;
+
+ // check whether style allows selection
+ // if not, don't tell selection the mouse event even occurred.
+ StyleUserSelect selectStyle;
+ // check for select: none
+ if (!IsSelectable(&selectStyle)) {
+ return NS_OK;
+ }
+
+ if (isPrimaryButtonDown) {
+ // If the mouse is dragged outside the nearest enclosing scrollable area
+ // while making a selection, the area will be scrolled. To do this, capture
+ // the mouse on the nearest scrollable frame. If there isn't a scrollable
+ // frame, or something else is already capturing the mouse, there's no
+ // reason to capture.
+ if (!PresShell::GetCapturingContent()) {
+ nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(
+ this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+ if (scrollFrame) {
+ nsIFrame* capturingFrame = do_QueryFrame(scrollFrame);
+ PresShell::SetCapturingContent(capturingFrame->GetContent(),
+ CaptureFlags::IgnoreAllowedState);
+ }
+ }
+ }
+
+ // XXX This is screwy; it really should use the selection frame, not the
+ // event frame
+ const nsFrameSelection* frameselection =
+ selectStyle == StyleUserSelect::Text ? GetConstFrameSelection()
+ : presShell->ConstFrameSelection();
+
+ if (!frameselection || frameselection->GetDisplaySelection() ==
+ nsISelectionController::SELECTION_OFF) {
+ return NS_OK; // nothing to do we cannot affect selection from here
+ }
+
+#ifdef XP_MACOSX
+ // If Control key is pressed on macOS, it should be treated as right click.
+ // So, don't change selection.
+ if (aMouseEvent->IsControl()) {
+ return NS_OK;
+ }
+ const bool control = aMouseEvent->IsMeta();
+#else
+ const bool control = aMouseEvent->IsControl();
+#endif
+
+ RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection);
+ if (isPrimaryButtonDown && aMouseEvent->mClickCount > 1) {
+ // These methods aren't const but can't actually delete anything,
+ // so no need for AutoWeakFrame.
+ fc->SetDragState(true);
+ return HandleMultiplePress(aPresContext, aMouseEvent, aEventStatus,
+ control);
+ }
+
+ ContentOffsets offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
+
+ if (!offsets.content) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const bool isSecondaryButton =
+ aMouseEvent->mButton == MouseButton::eSecondary;
+ if (isSecondaryButton &&
+ !MovingCaretToEventPointAllowedIfSecondaryButtonEvent(
+ *frameselection, *aMouseEvent, *offsets.content,
+ // When we collapse selection in nsFrameSelection::TakeFocus,
+ // we always collapse selection to the start offset. Therefore,
+ // we can ignore the end offset here. E.g., when an <img> is clicked,
+ // set the primary offset to after it, but the the secondary offset
+ // may be before it, see OffsetsForSingleFrame for the detail.
+ offsets.StartOffset())) {
+ return NS_OK;
+ }
+
+ if (aMouseEvent->mMessage == eMouseDown &&
+ aMouseEvent->mButton == MouseButton::eMiddle &&
+ !offsets.content->IsEditable()) {
+ // However, some users don't like the Chrome compatible behavior of
+ // middle mouse click. They want to keep selection after starting
+ // autoscroll. However, the selection change is important for middle
+ // mouse past. Therefore, we should allow users to take the traditional
+ // behavior back by themselves unless middle click paste is enabled or
+ // autoscrolling is disabled.
+ if (!Preferences::GetBool("middlemouse.paste", false) &&
+ Preferences::GetBool("general.autoScroll", false) &&
+ Preferences::GetBool("general.autoscroll.prevent_to_collapse_selection_"
+ "by_middle_mouse_down",
+ false)) {
+ return NS_OK;
+ }
+ }
+
+ if (isPrimaryButtonDown) {
+ // Let Ctrl/Cmd + left mouse down do table selection instead of drag
+ // initiation.
+ nsCOMPtr<nsIContent> parentContent;
+ int32_t contentOffset;
+ TableSelectionMode target;
+ nsresult rv = GetDataForTableSelection(
+ frameselection, presShell, aMouseEvent, getter_AddRefs(parentContent),
+ &contentOffset, &target);
+ if (NS_SUCCEEDED(rv) && parentContent) {
+ fc->SetDragState(true);
+ return fc->HandleTableSelection(parentContent, contentOffset, target,
+ aMouseEvent);
+ }
+ }
+
+ fc->SetDelayedCaretData(0);
+
+ if (isPrimaryButtonDown) {
+ // Check if any part of this frame is selected, and if the user clicked
+ // inside the selected region, and if it's the left button. If so, we delay
+ // starting a new selection since the user may be trying to drag the
+ // selected region to some other app.
+
+ if (GetContent() && GetContent()->IsMaybeSelected()) {
+ bool inSelection = false;
+ UniquePtr<SelectionDetails> details = frameselection->LookUpSelection(
+ offsets.content, 0, offsets.EndOffset(), false);
+
+ //
+ // If there are any details, check to see if the user clicked
+ // within any selected region of the frame.
+ //
+
+ for (SelectionDetails* curDetail = details.get(); curDetail;
+ curDetail = curDetail->mNext.get()) {
+ //
+ // If the user clicked inside a selection, then just
+ // return without doing anything. We will handle placing
+ // the caret later on when the mouse is released. We ignore
+ // the spellcheck, find and url formatting selections.
+ //
+ if (curDetail->mSelectionType != SelectionType::eSpellCheck &&
+ curDetail->mSelectionType != SelectionType::eFind &&
+ curDetail->mSelectionType != SelectionType::eURLSecondary &&
+ curDetail->mSelectionType != SelectionType::eURLStrikeout &&
+ curDetail->mSelectionType != SelectionType::eHighlight &&
+ curDetail->mStart <= offsets.StartOffset() &&
+ offsets.EndOffset() <= curDetail->mEnd) {
+ inSelection = true;
+ }
+ }
+
+ if (inSelection) {
+ fc->SetDragState(false);
+ fc->SetDelayedCaretData(aMouseEvent);
+ return NS_OK;
+ }
+ }
+
+ fc->SetDragState(true);
+ }
+
+ // Do not touch any nsFrame members after this point without adding
+ // weakFrame checks.
+ const nsFrameSelection::FocusMode focusMode = [&]() {
+ // If "Shift" and "Ctrl" are both pressed, "Shift" is given precedence. This
+ // mimics the old behaviour.
+ const bool isShift =
+ aMouseEvent->IsShift() &&
+ // If Shift + secondary button press shoud open context menu without a
+ // contextmenu event, user wants to open context menu like as a
+ // secondary button press without Shift key.
+ !(isSecondaryButton &&
+ StaticPrefs::dom_event_contextmenu_shift_suppresses_event());
+ if (isShift) {
+ // If clicked in a link when focused content is editable, we should
+ // collapse selection in the link for compatibility with Blink.
+ if (isEditor) {
+ for (Element* element : mContent->InclusiveAncestorsOfType<Element>()) {
+ if (element->IsLink()) {
+ return nsFrameSelection::FocusMode::kCollapseToNewPoint;
+ }
+ }
+ }
+ return nsFrameSelection::FocusMode::kExtendSelection;
+ }
+
+ if (isPrimaryButtonDown && control) {
+ return nsFrameSelection::FocusMode::kMultiRangeSelection;
+ }
+
+ return nsFrameSelection::FocusMode::kCollapseToNewPoint;
+ }();
+
+ nsresult rv = fc->HandleClick(
+ MOZ_KnownLive(offsets.content) /* bug 1636889 */, offsets.StartOffset(),
+ offsets.EndOffset(), focusMode, offsets.associate);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We don't handle mouse button up if it's middle button.
+ if (isPrimaryButtonDown && offsets.offset != offsets.secondaryOffset) {
+ fc->MaintainSelection();
+ }
+
+ if (isPrimaryButtonDown && isEditor && !aMouseEvent->IsShift() &&
+ (offsets.EndOffset() - offsets.StartOffset()) == 1) {
+ // A single node is selected and we aren't extending an existing selection,
+ // which means the user clicked directly on an object (either
+ // `user-select: all` or a non-text node without children). Therefore,
+ // disable selection extension during mouse moves.
+ // XXX This is a bit hacky; shouldn't editor be able to deal with this?
+ fc->SetDragState(false);
+ }
+
+ return NS_OK;
+}
+
+bool nsIFrame::MovingCaretToEventPointAllowedIfSecondaryButtonEvent(
+ const nsFrameSelection& aFrameSelection,
+ WidgetMouseEvent& aSecondaryButtonEvent,
+ const nsIContent& aContentAtEventPoint, int32_t aOffsetAtEventPoint) const {
+ MOZ_ASSERT(aSecondaryButtonEvent.mButton == MouseButton::eSecondary);
+
+ if (NS_WARN_IF(aOffsetAtEventPoint < 0)) {
+ return false;
+ }
+
+ const bool contentIsEditable = aContentAtEventPoint.IsEditable();
+ const TextControlElement* const contentAsTextControl =
+ TextControlElement::FromNodeOrNull(
+ aContentAtEventPoint.IsTextControlElement()
+ ? &aContentAtEventPoint
+ : aContentAtEventPoint.GetClosestNativeAnonymousSubtreeRoot());
+ if (Selection* selection =
+ aFrameSelection.GetSelection(SelectionType::eNormal)) {
+ const bool selectionIsCollapsed = selection->IsCollapsed();
+ // If right click in a selection range, we should not collapse selection.
+ if (!selectionIsCollapsed &&
+ nsContentUtils::IsPointInSelection(
+ *selection, aContentAtEventPoint,
+ static_cast<uint32_t>(aOffsetAtEventPoint))) {
+ return false;
+ }
+ const bool wantToPreventMoveCaret =
+ StaticPrefs::
+ ui_mouse_right_click_move_caret_stop_if_in_focused_editable_node() &&
+ selectionIsCollapsed && (contentIsEditable || contentAsTextControl);
+ const bool wantToPreventCollapseSelection =
+ StaticPrefs::
+ ui_mouse_right_click_collapse_selection_stop_if_non_collapsed_selection() &&
+ !selectionIsCollapsed;
+ if (wantToPreventMoveCaret || wantToPreventCollapseSelection) {
+ // If currently selection is limited in an editing host, we should not
+ // collapse selection nor move caret if the clicked point is in the
+ // ancestor limiter. Otherwise, this mouse click moves focus from the
+ // editing host to different one or blur the editing host. In this case,
+ // we need to update selection because keeping current selection in the
+ // editing host looks like it's not blurred.
+ // FIXME: If the active editing host is the document element, editor
+ // does not set ancestor limiter properly. Fix it in the editor side.
+ if (nsIContent* ancestorLimiter = selection->GetAncestorLimiter()) {
+ MOZ_ASSERT(ancestorLimiter->IsEditable());
+ return !aContentAtEventPoint.IsInclusiveDescendantOf(ancestorLimiter);
+ }
+ }
+ // If selection is editable and `stop_if_in_focused_editable_node` pref is
+ // set to true, user does not want to move caret to right click place if
+ // clicked in the focused text control element.
+ if (wantToPreventMoveCaret && contentAsTextControl &&
+ contentAsTextControl == nsFocusManager::GetFocusedElementStatic()) {
+ return false;
+ }
+ // If currently selection is not limited in an editing host, we should
+ // collapse selection only when this click moves focus to an editing
+ // host because we need to update selection in this case.
+ if (wantToPreventCollapseSelection && !contentIsEditable) {
+ return false;
+ }
+ }
+
+ return !StaticPrefs::
+ ui_mouse_right_click_collapse_selection_stop_if_non_editable_node() ||
+ // The user does not want to collapse selection into non-editable
+ // content by a right button click.
+ contentIsEditable ||
+ // Treat clicking in a text control as always clicked on editable
+ // content because we want a hack only for clicking in normal text
+ // nodes which is outside any editing hosts.
+ contentAsTextControl;
+}
+
+nsresult nsIFrame::SelectByTypeAtPoint(nsPresContext* aPresContext,
+ const nsPoint& aPoint,
+ nsSelectionAmount aBeginAmountType,
+ nsSelectionAmount aEndAmountType,
+ uint32_t aSelectFlags) {
+ NS_ENSURE_ARG_POINTER(aPresContext);
+
+ // No point in selecting if selection is turned off
+ if (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
+ return NS_OK;
+ }
+
+ ContentOffsets offsets = GetContentOffsetsFromPoint(
+ aPoint, SKIP_HIDDEN | IGNORE_NATIVE_ANONYMOUS_SUBTREE);
+ if (!offsets.content) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t offset;
+ nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset(
+ offsets.content, offsets.offset, offsets.associate, &offset);
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+ return frame->PeekBackwardAndForward(
+ aBeginAmountType, aEndAmountType, static_cast<int32_t>(offset),
+ aBeginAmountType != eSelectWord, aSelectFlags);
+}
+
+/**
+ * Multiple Mouse Press -- line or paragraph selection -- for the frame.
+ * Wouldn't it be nice if this didn't have to be hardwired into Frame code?
+ */
+NS_IMETHODIMP
+nsIFrame::HandleMultiplePress(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus, bool aControlHeld) {
+ NS_ENSURE_ARG_POINTER(aEvent);
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
+ DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
+ return NS_OK;
+ }
+
+ // Find out whether we're doing line or paragraph selection.
+ // If browser.triple_click_selects_paragraph is true, triple-click selects
+ // paragraph. Otherwise, triple-click selects line, and quadruple-click
+ // selects paragraph (on platforms that support quadruple-click).
+ nsSelectionAmount beginAmount, endAmount;
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (!mouseEvent) {
+ return NS_OK;
+ }
+
+ if (mouseEvent->mClickCount == 4) {
+ beginAmount = endAmount = eSelectParagraph;
+ } else if (mouseEvent->mClickCount == 3) {
+ if (Preferences::GetBool("browser.triple_click_selects_paragraph")) {
+ beginAmount = endAmount = eSelectParagraph;
+ } else {
+ beginAmount = eSelectBeginLine;
+ endAmount = eSelectEndLine;
+ }
+ } else if (mouseEvent->mClickCount == 2) {
+ // We only want inline frames; PeekBackwardAndForward dislikes blocks
+ beginAmount = endAmount = eSelectWord;
+ } else {
+ return NS_OK;
+ }
+
+ nsPoint relPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ mouseEvent, RelativeTo{this});
+ return SelectByTypeAtPoint(aPresContext, relPoint, beginAmount, endAmount,
+ (aControlHeld ? SELECT_ACCUMULATE : 0));
+}
+
+nsresult nsIFrame::PeekBackwardAndForward(nsSelectionAmount aAmountBack,
+ nsSelectionAmount aAmountForward,
+ int32_t aStartPos, bool aJumpLines,
+ uint32_t aSelectFlags) {
+ nsIFrame* baseFrame = this;
+ int32_t baseOffset = aStartPos;
+ nsresult rv;
+
+ PeekOffsetOptions peekOffsetOptions{PeekOffsetOption::StopAtScroller};
+ if (aJumpLines) {
+ peekOffsetOptions += PeekOffsetOption::JumpLines;
+ }
+
+ if (aAmountBack == eSelectWord) {
+ // To avoid selecting the previous word when at start of word,
+ // first move one character forward.
+ PeekOffsetStruct pos(eSelectCharacter, eDirNext, aStartPos, nsPoint(0, 0),
+ peekOffsetOptions);
+ rv = PeekOffset(&pos);
+ if (NS_SUCCEEDED(rv)) {
+ baseFrame = pos.mResultFrame;
+ baseOffset = pos.mContentOffset;
+ }
+ }
+
+ // Search backward for a boundary.
+ PeekOffsetStruct startpos(aAmountBack, eDirPrevious, baseOffset,
+ nsPoint(0, 0), peekOffsetOptions);
+ rv = baseFrame->PeekOffset(&startpos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the backward search stayed within the same frame, search forward from
+ // that position for the end boundary; but if it crossed out to a sibling or
+ // ancestor, start from the original position.
+ if (startpos.mResultFrame == baseFrame) {
+ baseOffset = startpos.mContentOffset;
+ } else {
+ baseFrame = this;
+ baseOffset = aStartPos;
+ }
+
+ PeekOffsetStruct endpos(aAmountForward, eDirNext, baseOffset, nsPoint(0, 0),
+ peekOffsetOptions);
+ rv = baseFrame->PeekOffset(&endpos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Keep frameSelection alive.
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+
+ const nsFrameSelection::FocusMode focusMode =
+ (aSelectFlags & SELECT_ACCUMULATE)
+ ? nsFrameSelection::FocusMode::kMultiRangeSelection
+ : nsFrameSelection::FocusMode::kCollapseToNewPoint;
+ rv = frameSelection->HandleClick(
+ MOZ_KnownLive(startpos.mResultContent) /* bug 1636889 */,
+ startpos.mContentOffset, startpos.mContentOffset, focusMode,
+ CaretAssociationHint::After);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = frameSelection->HandleClick(
+ MOZ_KnownLive(endpos.mResultContent) /* bug 1636889 */,
+ endpos.mContentOffset, endpos.mContentOffset,
+ nsFrameSelection::FocusMode::kExtendSelection,
+ CaretAssociationHint::Before);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (aAmountBack == eSelectWord) {
+ frameSelection->SetIsDoubleClickSelection(true);
+ }
+
+ // maintain selection
+ return frameSelection->MaintainSelection(aAmountBack);
+}
+
+NS_IMETHODIMP nsIFrame::HandleDrag(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aEvent->mClass == eMouseEventClass,
+ "HandleDrag can only handle mouse event");
+
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ RefPtr<nsFrameSelection> frameselection = GetFrameSelection();
+ if (!frameselection) {
+ return NS_OK;
+ }
+
+ bool mouseDown = frameselection->GetDragState();
+ if (!mouseDown) {
+ return NS_OK;
+ }
+
+ nsIFrame* scrollbar =
+ nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::Scrollbar);
+ if (!scrollbar) {
+ // XXX Do we really need to exclude non-selectable content here?
+ // GetContentOffsetsFromPoint can handle it just fine, although some
+ // other stuff might not like it.
+ // NOTE: DetermineDisplaySelection() returns SELECTION_OFF for
+ // non-selectable frames.
+ if (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF) {
+ return NS_OK;
+ }
+ }
+
+ frameselection->StopAutoScrollTimer();
+
+ // Check if we are dragging in a table cell
+ nsCOMPtr<nsIContent> parentContent;
+ int32_t contentOffset;
+ TableSelectionMode target;
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ mozilla::PresShell* presShell = aPresContext->PresShell();
+ nsresult result;
+ result = GetDataForTableSelection(frameselection, presShell, mouseEvent,
+ getter_AddRefs(parentContent),
+ &contentOffset, &target);
+
+ AutoWeakFrame weakThis = this;
+ if (NS_SUCCEEDED(result) && parentContent) {
+ result = frameselection->HandleTableSelection(parentContent, contentOffset,
+ target, mouseEvent);
+ if (NS_WARN_IF(NS_FAILED(result))) {
+ return result;
+ }
+ } else {
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent,
+ RelativeTo{this});
+ frameselection->HandleDrag(this, pt);
+ }
+
+ // The frameselection object notifies selection listeners synchronously above
+ // which might have killed us.
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+
+ // get the nearest scrollframe
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
+ this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+
+ if (scrollFrame) {
+ nsIFrame* capturingFrame = scrollFrame->GetScrolledFrame();
+ if (capturingFrame) {
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ mouseEvent, RelativeTo{capturingFrame});
+ frameselection->StartAutoScrollTimer(capturingFrame, pt, 30);
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * This static method handles part of the nsIFrame::HandleRelease in a way
+ * which doesn't rely on the nsFrame object to stay alive.
+ */
+MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult HandleFrameSelection(
+ nsFrameSelection* aFrameSelection, nsIFrame::ContentOffsets& aOffsets,
+ bool aHandleTableSel, int32_t aContentOffsetForTableSel,
+ TableSelectionMode aTargetForTableSel,
+ nsIContent* aParentContentForTableSel, WidgetGUIEvent* aEvent,
+ const nsEventStatus* aEventStatus) {
+ if (!aFrameSelection) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ if (nsEventStatus_eConsumeNoDefault != *aEventStatus) {
+ if (!aHandleTableSel) {
+ if (!aOffsets.content || !aFrameSelection->HasDelayedCaretData()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We are doing this to simulate what we would have done on HandlePress.
+ // We didn't do it there to give the user an opportunity to drag
+ // the text, but since they didn't drag, we want to place the
+ // caret.
+ // However, we'll use the mouse position from the release, since:
+ // * it's easier
+ // * that's the normal click position to use (although really, in
+ // the normal case, small movements that don't count as a drag
+ // can do selection)
+ aFrameSelection->SetDragState(true);
+
+ const nsFrameSelection::FocusMode focusMode =
+ aFrameSelection->IsShiftDownInDelayedCaretData()
+ ? nsFrameSelection::FocusMode::kExtendSelection
+ : nsFrameSelection::FocusMode::kCollapseToNewPoint;
+ rv = aFrameSelection->HandleClick(
+ MOZ_KnownLive(aOffsets.content) /* bug 1636889 */,
+ aOffsets.StartOffset(), aOffsets.EndOffset(), focusMode,
+ aOffsets.associate);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else if (aParentContentForTableSel) {
+ aFrameSelection->SetDragState(false);
+ rv = aFrameSelection->HandleTableSelection(
+ aParentContentForTableSel, aContentOffsetForTableSel,
+ aTargetForTableSel, aEvent->AsMouseEvent());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ aFrameSelection->SetDelayedCaretData(0);
+ }
+
+ aFrameSelection->SetDragState(false);
+ aFrameSelection->StopAutoScrollTimer();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIFrame::HandleRelease(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ if (aEvent->mClass != eMouseEventClass) {
+ return NS_OK;
+ }
+
+ nsIFrame* activeFrame = GetActiveSelectionFrame(aPresContext, this);
+
+ nsCOMPtr<nsIContent> captureContent = PresShell::GetCapturingContent();
+
+ bool selectionOff =
+ (DetermineDisplaySelection() == nsISelectionController::SELECTION_OFF);
+
+ RefPtr<nsFrameSelection> frameselection;
+ ContentOffsets offsets;
+ nsCOMPtr<nsIContent> parentContent;
+ int32_t contentOffsetForTableSel = 0;
+ TableSelectionMode targetForTableSel = TableSelectionMode::None;
+ bool handleTableSelection = true;
+
+ if (!selectionOff) {
+ frameselection = GetFrameSelection();
+ if (nsEventStatus_eConsumeNoDefault != *aEventStatus && frameselection) {
+ // Check if the frameselection recorded the mouse going down.
+ // If not, the user must have clicked in a part of the selection.
+ // Place the caret before continuing!
+
+ if (frameselection->MouseDownRecorded()) {
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aEvent, RelativeTo{this});
+ offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
+ handleTableSelection = false;
+ } else {
+ GetDataForTableSelection(frameselection, PresShell(),
+ aEvent->AsMouseEvent(),
+ getter_AddRefs(parentContent),
+ &contentOffsetForTableSel, &targetForTableSel);
+ }
+ }
+ }
+
+ // We might be capturing in some other document and the event just happened to
+ // trickle down here. Make sure that document's frame selection is notified.
+ // Note, this may cause the current nsFrame object to be deleted, bug 336592.
+ RefPtr<nsFrameSelection> frameSelection;
+ if (activeFrame != this && activeFrame->DetermineDisplaySelection() !=
+ nsISelectionController::SELECTION_OFF) {
+ frameSelection = activeFrame->GetFrameSelection();
+ }
+
+ // Also check the selection of the capturing content which might be in a
+ // different document.
+ if (!frameSelection && captureContent) {
+ if (Document* doc = captureContent->GetComposedDoc()) {
+ mozilla::PresShell* capturingPresShell = doc->GetPresShell();
+ if (capturingPresShell &&
+ capturingPresShell != PresContext()->GetPresShell()) {
+ frameSelection = capturingPresShell->FrameSelection();
+ }
+ }
+ }
+
+ if (frameSelection) {
+ AutoWeakFrame wf(this);
+ frameSelection->SetDragState(false);
+ frameSelection->StopAutoScrollTimer();
+ if (wf.IsAlive()) {
+ nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(
+ this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+ if (scrollFrame) {
+ // Perform any additional scrolling needed to maintain CSS snap point
+ // requirements when autoscrolling is over.
+ scrollFrame->ScrollSnap();
+ }
+ }
+ }
+
+ // Do not call any methods of the current object after this point!!!
+ // The object is perhaps dead!
+
+ return selectionOff ? NS_OK
+ : HandleFrameSelection(
+ frameselection, offsets, handleTableSelection,
+ contentOffsetForTableSel, targetForTableSel,
+ parentContent, aEvent, aEventStatus);
+}
+
+struct MOZ_STACK_CLASS FrameContentRange {
+ FrameContentRange(nsIContent* aContent, int32_t aStart, int32_t aEnd)
+ : content(aContent), start(aStart), end(aEnd) {}
+ nsCOMPtr<nsIContent> content;
+ int32_t start;
+ int32_t end;
+};
+
+// Retrieve the content offsets of a frame
+static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ if (!content) {
+ NS_WARNING("Frame has no content");
+ return FrameContentRange(nullptr, -1, -1);
+ }
+
+ LayoutFrameType type = aFrame->Type();
+ if (type == LayoutFrameType::Text) {
+ auto [offset, offsetEnd] = aFrame->GetOffsets();
+ return FrameContentRange(content, offset, offsetEnd);
+ }
+
+ if (type == LayoutFrameType::Br) {
+ nsIContent* parent = content->GetParent();
+ const int32_t beginOffset = parent->ComputeIndexOf_Deprecated(content);
+ return FrameContentRange(parent, beginOffset, beginOffset);
+ }
+
+ while (content->IsRootOfNativeAnonymousSubtree()) {
+ content = content->GetParent();
+ }
+
+ nsIContent* parent = content->GetParent();
+ if (aFrame->IsBlockOutside() || !parent) {
+ return FrameContentRange(content, 0, content->GetChildCount());
+ }
+
+ // TODO(emilio): Revise this in presence of Shadow DOM / display: contents,
+ // it's likely that we don't want to just walk the light tree, and we need to
+ // change the representation of FrameContentRange.
+ const int32_t index = parent->ComputeIndexOf_Deprecated(content);
+ MOZ_ASSERT(index >= 0);
+ return FrameContentRange(parent, index, index + 1);
+}
+
+// The FrameTarget represents the closest frame to a point that can be selected
+// The frame is the frame represented, frameEdge says whether one end of the
+// frame is the result (in which case different handling is needed), and
+// afterFrame says which end is represented if frameEdge is true
+struct FrameTarget {
+ explicit operator bool() const { return !!frame; }
+
+ nsIFrame* frame = nullptr;
+ bool frameEdge = false;
+ bool afterFrame = false;
+};
+
+// See function implementation for information
+static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
+ const nsPoint& aPoint,
+ uint32_t aFlags);
+
+static bool SelfIsSelectable(nsIFrame* aFrame, uint32_t aFlags) {
+ if ((aFlags & nsIFrame::SKIP_HIDDEN) &&
+ !aFrame->StyleVisibility()->IsVisible()) {
+ return false;
+ }
+ return !aFrame->IsGeneratedContentFrame() &&
+ aFrame->Style()->UserSelect() != StyleUserSelect::None;
+}
+
+static bool FrameContentCanHaveParentSelectionRange(nsIFrame* aFrame) {
+ // If we are only near (not directly over) then don't traverse
+ // frames with independent selection (e.g. text and list controls, see bug
+ // 268497). Note that this prevents any of the users of this method from
+ // entering form controls.
+ // XXX We might want some way to allow using the up-arrow to go into a form
+ // control, but the focus didn't work right anyway; it'd probably be enough
+ // if the left and right arrows could enter textboxes (which I don't believe
+ // they can at the moment)
+ if (aFrame->IsTextInputFrame() || aFrame->IsListControlFrame()) {
+ MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
+ return false;
+ }
+
+ // Failure in this assertion means a new type of frame forms the root of an
+ // NS_FRAME_INDEPENDENT_SELECTION subtree. In such case, the condition above
+ // should be changed to handle it.
+ MOZ_ASSERT_IF(
+ aFrame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION),
+ aFrame->GetParent()->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION));
+
+ return !aFrame->IsGeneratedContentFrame();
+}
+
+static bool SelectionDescendToKids(nsIFrame* aFrame) {
+ if (!FrameContentCanHaveParentSelectionRange(aFrame)) {
+ return false;
+ }
+ auto style = aFrame->Style()->UserSelect();
+ return style != StyleUserSelect::All && style != StyleUserSelect::None;
+}
+
+static FrameTarget GetSelectionClosestFrameForChild(nsIFrame* aChild,
+ const nsPoint& aPoint,
+ uint32_t aFlags) {
+ nsIFrame* parent = aChild->GetParent();
+ if (SelectionDescendToKids(aChild)) {
+ nsPoint pt = aPoint - aChild->GetOffsetTo(parent);
+ return GetSelectionClosestFrame(aChild, pt, aFlags);
+ }
+ return FrameTarget{aChild, false, false};
+}
+
+// When the cursor needs to be at the beginning of a block, it shouldn't be
+// before the first child. A click on a block whose first child is a block
+// should put the cursor in the child. The cursor shouldn't be between the
+// blocks, because that's not where it's expected.
+// Note that this method is guaranteed to succeed.
+static FrameTarget DrillDownToSelectionFrame(nsIFrame* aFrame, bool aEndFrame,
+ uint32_t aFlags) {
+ if (SelectionDescendToKids(aFrame)) {
+ nsIFrame* result = nullptr;
+ nsIFrame* frame = aFrame->PrincipalChildList().FirstChild();
+ if (!aEndFrame) {
+ while (frame && (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty()))
+ frame = frame->GetNextSibling();
+ if (frame) result = frame;
+ } else {
+ // Because the frame tree is singly linked, to find the last frame,
+ // we have to iterate through all the frames
+ // XXX I have a feeling this could be slow for long blocks, although
+ // I can't find any slowdowns
+ while (frame) {
+ if (!frame->IsEmpty() && SelfIsSelectable(frame, aFlags))
+ result = frame;
+ frame = frame->GetNextSibling();
+ }
+ }
+ if (result) return DrillDownToSelectionFrame(result, aEndFrame, aFlags);
+ }
+ // If the current frame has no targetable children, target the current frame
+ return FrameTarget{aFrame, true, aEndFrame};
+}
+
+// This method finds the closest valid FrameTarget on a given line; if there is
+// no valid FrameTarget on the line, it returns a null FrameTarget
+static FrameTarget GetSelectionClosestFrameForLine(
+ nsBlockFrame* aParent, nsBlockFrame::LineIterator aLine,
+ const nsPoint& aPoint, uint32_t aFlags) {
+ // Account for end of lines (any iterator from the block is valid)
+ if (aLine == aParent->LinesEnd())
+ return DrillDownToSelectionFrame(aParent, true, aFlags);
+ nsIFrame* frame = aLine->mFirstChild;
+ nsIFrame* closestFromIStart = nullptr;
+ nsIFrame* closestFromIEnd = nullptr;
+ nscoord closestIStart = aLine->IStart(), closestIEnd = aLine->IEnd();
+ WritingMode wm = aLine->mWritingMode;
+ LogicalPoint pt(wm, aPoint, aLine->mContainerSize);
+ bool canSkipBr = false;
+ bool lastFrameWasEditable = false;
+ for (int32_t n = aLine->GetChildCount(); n;
+ --n, frame = frame->GetNextSibling()) {
+ // Skip brFrames. Can only skip if the line contains at least
+ // one selectable and non-empty frame before. Also, avoid skipping brs if
+ // the previous thing had a different editableness than us, since then we
+ // may end up not being able to select after it if the br is the last thing
+ // on the line.
+ if (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty() ||
+ (canSkipBr && frame->IsBrFrame() &&
+ lastFrameWasEditable == frame->GetContent()->IsEditable())) {
+ continue;
+ }
+ canSkipBr = true;
+ lastFrameWasEditable =
+ frame->GetContent() && frame->GetContent()->IsEditable();
+ LogicalRect frameRect =
+ LogicalRect(wm, frame->GetRect(), aLine->mContainerSize);
+ if (pt.I(wm) >= frameRect.IStart(wm)) {
+ if (pt.I(wm) < frameRect.IEnd(wm)) {
+ return GetSelectionClosestFrameForChild(frame, aPoint, aFlags);
+ }
+ if (frameRect.IEnd(wm) >= closestIStart) {
+ closestFromIStart = frame;
+ closestIStart = frameRect.IEnd(wm);
+ }
+ } else {
+ if (frameRect.IStart(wm) <= closestIEnd) {
+ closestFromIEnd = frame;
+ closestIEnd = frameRect.IStart(wm);
+ }
+ }
+ }
+ if (!closestFromIStart && !closestFromIEnd) {
+ // We should only get here if there are no selectable frames on a line
+ // XXX Do we need more elaborate handling here?
+ return FrameTarget();
+ }
+ if (closestFromIStart &&
+ (!closestFromIEnd ||
+ (abs(pt.I(wm) - closestIStart) <= abs(pt.I(wm) - closestIEnd)))) {
+ return GetSelectionClosestFrameForChild(closestFromIStart, aPoint, aFlags);
+ }
+ return GetSelectionClosestFrameForChild(closestFromIEnd, aPoint, aFlags);
+}
+
+// This method is for the special handling we do for block frames; they're
+// special because they represent paragraphs and because they are organized
+// into lines, which have bounds that are not stored elsewhere in the
+// frame tree. Returns a null FrameTarget for frames which are not
+// blocks or blocks with no lines except editable one.
+static FrameTarget GetSelectionClosestFrameForBlock(nsIFrame* aFrame,
+ const nsPoint& aPoint,
+ uint32_t aFlags) {
+ nsBlockFrame* bf = do_QueryFrame(aFrame);
+ if (!bf) {
+ return FrameTarget();
+ }
+
+ // This code searches for the correct line
+ nsBlockFrame::LineIterator end = bf->LinesEnd();
+ nsBlockFrame::LineIterator curLine = bf->LinesBegin();
+ nsBlockFrame::LineIterator closestLine = end;
+
+ if (curLine != end) {
+ // Convert aPoint into a LogicalPoint in the writing-mode of this block
+ WritingMode wm = curLine->mWritingMode;
+ LogicalPoint pt(wm, aPoint, curLine->mContainerSize);
+ do {
+ // Check to see if our point lies within the line's block-direction bounds
+ nscoord BCoord = pt.B(wm) - curLine->BStart();
+ nscoord BSize = curLine->BSize();
+ if (BCoord >= 0 && BCoord < BSize) {
+ closestLine = curLine;
+ break; // We found the line; stop looking
+ }
+ if (BCoord < 0) break;
+ ++curLine;
+ } while (curLine != end);
+
+ if (closestLine == end) {
+ nsBlockFrame::LineIterator prevLine = curLine.prev();
+ nsBlockFrame::LineIterator nextLine = curLine;
+ // Avoid empty lines
+ while (nextLine != end && nextLine->IsEmpty()) ++nextLine;
+ while (prevLine != end && prevLine->IsEmpty()) --prevLine;
+
+ // This hidden pref dictates whether a point above or below all lines
+ // comes up with a line or the beginning or end of the frame; 0 on
+ // Windows, 1 on other platforms by default at the writing of this code
+ int32_t dragOutOfFrame =
+ Preferences::GetInt("browser.drag_out_of_frame_style");
+
+ if (prevLine == end) {
+ if (dragOutOfFrame == 1 || nextLine == end)
+ return DrillDownToSelectionFrame(aFrame, false, aFlags);
+ closestLine = nextLine;
+ } else if (nextLine == end) {
+ if (dragOutOfFrame == 1)
+ return DrillDownToSelectionFrame(aFrame, true, aFlags);
+ closestLine = prevLine;
+ } else { // Figure out which line is closer
+ if (pt.B(wm) - prevLine->BEnd() < nextLine->BStart() - pt.B(wm))
+ closestLine = prevLine;
+ else
+ closestLine = nextLine;
+ }
+ }
+ }
+
+ do {
+ if (auto target =
+ GetSelectionClosestFrameForLine(bf, closestLine, aPoint, aFlags)) {
+ return target;
+ }
+ ++closestLine;
+ } while (closestLine != end);
+
+ // Fall back to just targeting the last targetable place
+ return DrillDownToSelectionFrame(aFrame, true, aFlags);
+}
+
+// Use frame edge for grid, flex, table, and non-editable images. Choose the
+// edge based on the point position past the frame rect. If past the middle,
+// caret should be at end, otherwise at start. This behavior matches Blink.
+//
+// TODO(emilio): Can we use this code path for other replaced elements other
+// than images? Or even all other frames? We only get there when we didn't find
+// selectable children... At least one XUL test fails if we make this apply to
+// XUL labels. Also, editable images need _not_ to use the frame edge, see
+// below.
+static bool UseFrameEdge(nsIFrame* aFrame) {
+ if (aFrame->IsFlexOrGridContainer() || aFrame->IsTableFrame()) {
+ return true;
+ }
+ const nsImageFrame* image = do_QueryFrame(aFrame);
+ if (image && !aFrame->GetContent()->IsEditable()) {
+ // Editable images are a special-case because editing relies on clicking on
+ // an editable image selecting it, for it to show resizers.
+ return true;
+ }
+ return false;
+}
+
+static FrameTarget LastResortFrameTargetForFrame(nsIFrame* aFrame,
+ const nsPoint& aPoint) {
+ if (!UseFrameEdge(aFrame)) {
+ return {aFrame, false, false};
+ }
+ const auto& rect = aFrame->GetRectRelativeToSelf();
+ nscoord reference;
+ nscoord middle;
+ if (aFrame->GetWritingMode().IsVertical()) {
+ reference = aPoint.y;
+ middle = rect.Height() / 2;
+ } else {
+ reference = aPoint.x;
+ middle = rect.Width() / 2;
+ }
+ const bool afterFrame = reference > middle;
+ return {aFrame, true, afterFrame};
+}
+
+// GetSelectionClosestFrame is the helper function that calculates the closest
+// frame to the given point.
+// It doesn't completely account for offset styles, so needs to be used in
+// restricted environments.
+// Cannot handle overlapping frames correctly, so it should receive the output
+// of GetFrameForPoint
+// Guaranteed to return a valid FrameTarget.
+// aPoint is relative to aFrame.
+static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
+ const nsPoint& aPoint,
+ uint32_t aFlags) {
+ // Handle blocks; if the frame isn't a block, the method fails
+ if (auto target = GetSelectionClosestFrameForBlock(aFrame, aPoint, aFlags)) {
+ return target;
+ }
+
+ if (aFlags & nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE &&
+ !FrameContentCanHaveParentSelectionRange(aFrame)) {
+ return LastResortFrameTargetForFrame(aFrame, aPoint);
+ }
+
+ if (nsIFrame* kid = aFrame->PrincipalChildList().FirstChild()) {
+ // Go through all the child frames to find the closest one
+ nsIFrame::FrameWithDistance closest = {nullptr, nscoord_MAX, nscoord_MAX};
+ for (; kid; kid = kid->GetNextSibling()) {
+ if (!SelfIsSelectable(kid, aFlags) || kid->IsEmpty()) {
+ continue;
+ }
+
+ kid->FindCloserFrameForSelection(aPoint, &closest);
+ }
+ if (closest.mFrame) {
+ if (closest.mFrame->IsInSVGTextSubtree())
+ return FrameTarget{closest.mFrame, false, false};
+ return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags);
+ }
+ }
+
+ return LastResortFrameTargetForFrame(aFrame, aPoint);
+}
+
+static nsIFrame::ContentOffsets OffsetsForSingleFrame(nsIFrame* aFrame,
+ const nsPoint& aPoint) {
+ nsIFrame::ContentOffsets offsets;
+ FrameContentRange range = GetRangeForFrame(aFrame);
+ offsets.content = range.content;
+ // If there are continuations (meaning it's not one rectangle), this is the
+ // best this function can do
+ if (aFrame->GetNextContinuation() || aFrame->GetPrevContinuation()) {
+ offsets.offset = range.start;
+ offsets.secondaryOffset = range.end;
+ offsets.associate = CaretAssociationHint::After;
+ return offsets;
+ }
+
+ // Figure out whether the offsets should be over, after, or before the frame
+ nsRect rect(nsPoint(0, 0), aFrame->GetSize());
+
+ bool isBlock = !aFrame->StyleDisplay()->IsInlineFlow();
+ bool isRtl = (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl);
+ if ((isBlock && rect.y < aPoint.y) ||
+ (!isBlock && ((isRtl && rect.x + rect.width / 2 > aPoint.x) ||
+ (!isRtl && rect.x + rect.width / 2 < aPoint.x)))) {
+ offsets.offset = range.end;
+ if (rect.Contains(aPoint))
+ offsets.secondaryOffset = range.start;
+ else
+ offsets.secondaryOffset = range.end;
+ } else {
+ offsets.offset = range.start;
+ if (rect.Contains(aPoint))
+ offsets.secondaryOffset = range.end;
+ else
+ offsets.secondaryOffset = range.start;
+ }
+ offsets.associate = offsets.offset == range.start
+ ? CaretAssociationHint::After
+ : CaretAssociationHint::Before;
+ return offsets;
+}
+
+static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) {
+ nsIFrame* adjustedFrame = aFrame;
+ for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
+ // These are the conditions that make all children not able to handle
+ // a cursor.
+ auto userSelect = frame->Style()->UserSelect();
+ if (userSelect != StyleUserSelect::Auto &&
+ userSelect != StyleUserSelect::All) {
+ break;
+ }
+ if (userSelect == StyleUserSelect::All ||
+ frame->IsGeneratedContentFrame()) {
+ adjustedFrame = frame;
+ }
+ }
+ return adjustedFrame;
+}
+
+nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(
+ const nsPoint& aPoint, uint32_t aFlags) {
+ nsIFrame* adjustedFrame;
+ if (aFlags & IGNORE_SELECTION_STYLE) {
+ adjustedFrame = this;
+ } else {
+ // This section of code deals with special selection styles. Note that
+ // -moz-all exists, even though it doesn't need to be explicitly handled.
+ //
+ // The offset is forced not to end up in generated content; content offsets
+ // cannot represent content outside of the document's content tree.
+
+ adjustedFrame = AdjustFrameForSelectionStyles(this);
+
+ // `user-select: all` needs special handling, because clicking on it should
+ // lead to the whole frame being selected.
+ if (adjustedFrame->Style()->UserSelect() == StyleUserSelect::All) {
+ nsPoint adjustedPoint = aPoint + GetOffsetTo(adjustedFrame);
+ return OffsetsForSingleFrame(adjustedFrame, adjustedPoint);
+ }
+
+ // For other cases, try to find a closest frame starting from the parent of
+ // the unselectable frame
+ if (adjustedFrame != this) {
+ adjustedFrame = adjustedFrame->GetParent();
+ }
+ }
+
+ nsPoint adjustedPoint = aPoint + GetOffsetTo(adjustedFrame);
+
+ FrameTarget closest =
+ GetSelectionClosestFrame(adjustedFrame, adjustedPoint, aFlags);
+
+ // If the correct offset is at one end of a frame, use offset-based
+ // calculation method
+ if (closest.frameEdge) {
+ ContentOffsets offsets;
+ FrameContentRange range = GetRangeForFrame(closest.frame);
+ offsets.content = range.content;
+ if (closest.afterFrame)
+ offsets.offset = range.end;
+ else
+ offsets.offset = range.start;
+ offsets.secondaryOffset = offsets.offset;
+ offsets.associate = offsets.offset == range.start
+ ? CaretAssociationHint::After
+ : CaretAssociationHint::Before;
+ return offsets;
+ }
+
+ nsPoint pt;
+ if (closest.frame != this) {
+ if (closest.frame->IsInSVGTextSubtree()) {
+ pt = nsLayoutUtils::TransformAncestorPointToFrame(
+ RelativeTo{closest.frame}, aPoint, RelativeTo{this});
+ } else {
+ pt = aPoint - closest.frame->GetOffsetTo(this);
+ }
+ } else {
+ pt = aPoint;
+ }
+ return closest.frame->CalcContentOffsetsFromFramePoint(pt);
+
+ // XXX should I add some kind of offset standardization?
+ // consider <b>xxxxx</b><i>zzzzz</i>; should any click between the last
+ // x and first z put the cursor in the same logical position in addition
+ // to the same visual position?
+}
+
+nsIFrame::ContentOffsets nsIFrame::CalcContentOffsetsFromFramePoint(
+ const nsPoint& aPoint) {
+ return OffsetsForSingleFrame(this, aPoint);
+}
+
+bool nsIFrame::AssociateImage(const StyleImage& aImage) {
+ imgRequestProxy* req = aImage.GetImageRequest();
+ if (!req) {
+ return false;
+ }
+
+ mozilla::css::ImageLoader* loader =
+ PresContext()->Document()->StyleImageLoader();
+
+ loader->AssociateRequestToFrame(req, this);
+ return true;
+}
+
+void nsIFrame::DisassociateImage(const StyleImage& aImage) {
+ imgRequestProxy* req = aImage.GetImageRequest();
+ if (!req) {
+ return;
+ }
+
+ mozilla::css::ImageLoader* loader =
+ PresContext()->Document()->StyleImageLoader();
+
+ loader->DisassociateRequestFromFrame(req, this);
+}
+
+StyleImageRendering nsIFrame::UsedImageRendering() const {
+ ComputedStyle* style;
+ if (IsCanvasFrame()) {
+ // XXXdholbert Maybe we should use FindCanvasBackground here (instead of
+ // FindBackground), since we're inside an IsCanvasFrame check? Though then
+ // we'd also have to copypaste or abstract-away the multi-part root-frame
+ // lookup that the canvas-flavored API requires.
+ style = nsCSSRendering::FindBackground(this);
+ } else {
+ style = Style();
+ }
+ return style->StyleVisibility()->mImageRendering;
+}
+
+// The touch-action CSS property applies to: all elements except: non-replaced
+// inline elements, table rows, row groups, table columns, and column groups.
+StyleTouchAction nsIFrame::UsedTouchAction() const {
+ if (IsLineParticipant()) {
+ return StyleTouchAction::AUTO;
+ }
+ auto& disp = *StyleDisplay();
+ if (disp.IsInternalTableStyleExceptCell()) {
+ return StyleTouchAction::AUTO;
+ }
+ return disp.mTouchAction;
+}
+
+nsIFrame::Cursor nsIFrame::GetCursor(const nsPoint&) {
+ StyleCursorKind kind = StyleUI()->Cursor().keyword;
+ if (kind == StyleCursorKind::Auto) {
+ // If this is editable, I-beam cursor is better for most elements.
+ kind = (mContent && mContent->IsEditable()) ? StyleCursorKind::Text
+ : StyleCursorKind::Default;
+ }
+ if (kind == StyleCursorKind::Text && GetWritingMode().IsVertical()) {
+ // Per CSS UI spec, UA may treat value 'text' as
+ // 'vertical-text' for vertical text.
+ kind = StyleCursorKind::VerticalText;
+ }
+
+ return Cursor{kind, AllowCustomCursorImage::Yes};
+}
+
+// Resize and incremental reflow
+
+/* virtual */
+void nsIFrame::MarkIntrinsicISizesDirty() {
+ // If we're a flex item, clear our flex-item-specific cached measurements
+ // (which likely depended on our now-stale intrinsic isize).
+ if (IsFlexItem()) {
+ nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(this);
+ }
+
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
+ nsFontInflationData::MarkFontInflationDataTextDirty(this);
+ }
+
+ RemoveProperty(nsGridContainerFrame::CachedBAxisMeasurement::Prop());
+}
+
+void nsIFrame::MarkSubtreeDirty() {
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ return;
+ }
+ // Unconditionally mark given frame dirty.
+ AddStateBits(NS_FRAME_IS_DIRTY);
+
+ // Mark all descendants dirty, unless:
+ // - Already dirty.
+ // - TableColGroup
+ AutoTArray<nsIFrame*, 32> stack;
+ for (const auto& childLists : ChildLists()) {
+ for (nsIFrame* kid : childLists.mList) {
+ stack.AppendElement(kid);
+ }
+ }
+ while (!stack.IsEmpty()) {
+ nsIFrame* f = stack.PopLastElement();
+ if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY) || f->IsTableColGroupFrame()) {
+ continue;
+ }
+
+ f->AddStateBits(NS_FRAME_IS_DIRTY);
+
+ for (const auto& childLists : f->ChildLists()) {
+ for (nsIFrame* kid : childLists.mList) {
+ stack.AppendElement(kid);
+ }
+ }
+ }
+}
+
+/* virtual */
+nscoord nsIFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result = 0;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ return result;
+}
+
+/* virtual */
+nscoord nsIFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result = 0;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ return result;
+}
+
+/* virtual */
+void nsIFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ nscoord isize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, this, IntrinsicISizeType::MinISize);
+ aData->DefaultAddInlineMinISize(this, isize);
+}
+
+/* virtual */
+void nsIFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
+ nsIFrame::InlinePrefISizeData* aData) {
+ nscoord isize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, this, IntrinsicISizeType::PrefISize);
+ aData->DefaultAddInlinePrefISize(isize);
+}
+
+void nsIFrame::InlineMinISizeData::DefaultAddInlineMinISize(nsIFrame* aFrame,
+ nscoord aISize,
+ bool aAllowBreak) {
+ auto parent = aFrame->GetParent();
+ MOZ_ASSERT(parent, "Must have a parent if we get here!");
+ const bool mayBreak = aAllowBreak && !aFrame->CanContinueTextRun() &&
+ !parent->Style()->ShouldSuppressLineBreak() &&
+ parent->StyleText()->WhiteSpaceCanWrap(parent);
+ if (mayBreak) {
+ OptionallyBreak();
+ }
+ mTrailingWhitespace = 0;
+ mSkipWhitespace = false;
+ mCurrentLine += aISize;
+ mAtStartOfLine = false;
+ if (mayBreak) {
+ OptionallyBreak();
+ }
+}
+
+void nsIFrame::InlinePrefISizeData::DefaultAddInlinePrefISize(nscoord aISize) {
+ mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize);
+ mTrailingWhitespace = 0;
+ mSkipWhitespace = false;
+ mLineIsEmpty = false;
+}
+
+void nsIFrame::InlineMinISizeData::ForceBreak() {
+ mCurrentLine -= mTrailingWhitespace;
+ mPrevLines = std::max(mPrevLines, mCurrentLine);
+ mCurrentLine = mTrailingWhitespace = 0;
+
+ for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
+ nscoord float_min = mFloats[i].Width();
+ if (float_min > mPrevLines) mPrevLines = float_min;
+ }
+ mFloats.Clear();
+ mSkipWhitespace = true;
+}
+
+void nsIFrame::InlineMinISizeData::OptionallyBreak(nscoord aHyphenWidth) {
+ // If we can fit more content into a smaller width by staying on this
+ // line (because we're still at a negative offset due to negative
+ // text-indent or negative margin), don't break. Otherwise, do the
+ // same as ForceBreak. it doesn't really matter when we accumulate
+ // floats.
+ if (mCurrentLine + aHyphenWidth < 0 || mAtStartOfLine) return;
+ mCurrentLine += aHyphenWidth;
+ ForceBreak();
+}
+
+void nsIFrame::InlinePrefISizeData::ForceBreak(StyleClear aClearType) {
+ // If this force break is not clearing any float, we can leave all the
+ // floats to the next force break.
+ if (!mFloats.IsEmpty() && aClearType != StyleClear::None) {
+ // preferred widths accumulated for floats that have already
+ // been cleared past
+ nscoord floats_done = 0,
+ // preferred widths accumulated for floats that have not yet
+ // been cleared past
+ floats_cur_left = 0, floats_cur_right = 0;
+
+ for (const FloatInfo& floatInfo : mFloats) {
+ const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
+ StyleClear clearType = floatDisp->mClear;
+ if (clearType == StyleClear::Left || clearType == StyleClear::Right ||
+ clearType == StyleClear::Both) {
+ nscoord floats_cur =
+ NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
+ if (floats_cur > floats_done) {
+ floats_done = floats_cur;
+ }
+ if (clearType != StyleClear::Right) {
+ floats_cur_left = 0;
+ }
+ if (clearType != StyleClear::Left) {
+ floats_cur_right = 0;
+ }
+ }
+
+ StyleFloat floatStyle = floatDisp->mFloat;
+ nscoord& floats_cur =
+ floatStyle == StyleFloat::Left ? floats_cur_left : floats_cur_right;
+ nscoord floatWidth = floatInfo.Width();
+ // Negative-width floats don't change the available space so they
+ // shouldn't change our intrinsic line width either.
+ floats_cur = NSCoordSaturatingAdd(floats_cur, std::max(0, floatWidth));
+ }
+
+ nscoord floats_cur =
+ NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
+ if (floats_cur > floats_done) floats_done = floats_cur;
+
+ mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, floats_done);
+
+ if (aClearType == StyleClear::Both) {
+ mFloats.Clear();
+ } else {
+ // If the break type does not clear all floats, it means there may
+ // be some floats whose isize should contribute to the intrinsic
+ // isize of the next line. The code here scans the current mFloats
+ // and keeps floats which are not cleared by this break. Note that
+ // floats may be cleared directly or indirectly. See below.
+ nsTArray<FloatInfo> newFloats;
+ MOZ_ASSERT(
+ aClearType == StyleClear::Left || aClearType == StyleClear::Right,
+ "Other values should have been handled in other branches");
+ StyleFloat clearFloatType =
+ aClearType == StyleClear::Left ? StyleFloat::Left : StyleFloat::Right;
+ // Iterate the array in reverse so that we can stop when there are
+ // no longer any floats we need to keep. See below.
+ for (FloatInfo& floatInfo : Reversed(mFloats)) {
+ const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
+ if (floatDisp->mFloat != clearFloatType) {
+ newFloats.AppendElement(floatInfo);
+ } else {
+ // This is a float on the side that this break directly clears
+ // which means we're not keeping it in mFloats. However, if
+ // this float clears floats on the opposite side (via a value
+ // of either 'both' or one of 'left'/'right'), any remaining
+ // (earlier) floats on that side would be indirectly cleared
+ // as well. Thus, we should break out of this loop and stop
+ // considering earlier floats to be kept in mFloats.
+ StyleClear clearType = floatDisp->mClear;
+ if (clearType != aClearType && clearType != StyleClear::None) {
+ break;
+ }
+ }
+ }
+ newFloats.Reverse();
+ mFloats = std::move(newFloats);
+ }
+ }
+
+ mCurrentLine =
+ NSCoordSaturatingSubtract(mCurrentLine, mTrailingWhitespace, nscoord_MAX);
+ mPrevLines = std::max(mPrevLines, mCurrentLine);
+ mCurrentLine = mTrailingWhitespace = 0;
+ mSkipWhitespace = true;
+ mLineIsEmpty = true;
+}
+
+static nscoord ResolveMargin(const LengthPercentageOrAuto& aStyle,
+ nscoord aPercentageBasis) {
+ if (aStyle.IsAuto()) {
+ return nscoord(0);
+ }
+ return nsLayoutUtils::ResolveToLength<false>(aStyle.AsLengthPercentage(),
+ aPercentageBasis);
+}
+
+static nscoord ResolvePadding(const LengthPercentage& aStyle,
+ nscoord aPercentageBasis) {
+ return nsLayoutUtils::ResolveToLength<true>(aStyle, aPercentageBasis);
+}
+
+static nsIFrame::IntrinsicSizeOffsetData IntrinsicSizeOffsets(
+ nsIFrame* aFrame, nscoord aPercentageBasis, bool aForISize) {
+ nsIFrame::IntrinsicSizeOffsetData result;
+ WritingMode wm = aFrame->GetWritingMode();
+ const auto& margin = aFrame->StyleMargin()->mMargin;
+ bool verticalAxis = aForISize == wm.IsVertical();
+ if (verticalAxis) {
+ result.margin += ResolveMargin(margin.Get(eSideTop), aPercentageBasis);
+ result.margin += ResolveMargin(margin.Get(eSideBottom), aPercentageBasis);
+ } else {
+ result.margin += ResolveMargin(margin.Get(eSideLeft), aPercentageBasis);
+ result.margin += ResolveMargin(margin.Get(eSideRight), aPercentageBasis);
+ }
+
+ const auto& padding = aFrame->StylePadding()->mPadding;
+ if (verticalAxis) {
+ result.padding += ResolvePadding(padding.Get(eSideTop), aPercentageBasis);
+ result.padding +=
+ ResolvePadding(padding.Get(eSideBottom), aPercentageBasis);
+ } else {
+ result.padding += ResolvePadding(padding.Get(eSideLeft), aPercentageBasis);
+ result.padding += ResolvePadding(padding.Get(eSideRight), aPercentageBasis);
+ }
+
+ const nsStyleBorder* styleBorder = aFrame->StyleBorder();
+ if (verticalAxis) {
+ result.border += styleBorder->GetComputedBorderWidth(eSideTop);
+ result.border += styleBorder->GetComputedBorderWidth(eSideBottom);
+ } else {
+ result.border += styleBorder->GetComputedBorderWidth(eSideLeft);
+ result.border += styleBorder->GetComputedBorderWidth(eSideRight);
+ }
+
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ if (aFrame->IsThemed(disp)) {
+ nsPresContext* presContext = aFrame->PresContext();
+
+ LayoutDeviceIntMargin border = presContext->Theme()->GetWidgetBorder(
+ presContext->DeviceContext(), aFrame, disp->EffectiveAppearance());
+ result.border = presContext->DevPixelsToAppUnits(
+ verticalAxis ? border.TopBottom() : border.LeftRight());
+
+ LayoutDeviceIntMargin padding;
+ if (presContext->Theme()->GetWidgetPadding(
+ presContext->DeviceContext(), aFrame, disp->EffectiveAppearance(),
+ &padding)) {
+ result.padding = presContext->DevPixelsToAppUnits(
+ verticalAxis ? padding.TopBottom() : padding.LeftRight());
+ }
+ }
+ return result;
+}
+
+/* virtual */ nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicISizeOffsets(
+ nscoord aPercentageBasis) {
+ return IntrinsicSizeOffsets(this, aPercentageBasis, true);
+}
+
+nsIFrame::IntrinsicSizeOffsetData nsIFrame::IntrinsicBSizeOffsets(
+ nscoord aPercentageBasis) {
+ return IntrinsicSizeOffsets(this, aPercentageBasis, false);
+}
+
+/* virtual */
+IntrinsicSize nsIFrame::GetIntrinsicSize() {
+ return IntrinsicSize(); // default is width/height set to eStyleUnit_None
+}
+
+AspectRatio nsIFrame::GetAspectRatio() const {
+ // Per spec, 'aspect-ratio' property applies to all elements except inline
+ // boxes and internal ruby or table boxes.
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio
+ // For those frame types that don't support aspect-ratio, they must not have
+ // the natural ratio, so this early return is fine.
+ if (!SupportsAspectRatio()) {
+ return AspectRatio();
+ }
+
+ const StyleAspectRatio& aspectRatio = StylePosition()->mAspectRatio;
+ // If aspect-ratio is zero or infinite, it's a degenerate ratio and behaves
+ // as auto.
+ // https://drafts.csswg.org/css-sizing-4/#valdef-aspect-ratio-ratio
+ if (!aspectRatio.BehavesAsAuto()) {
+ // Non-auto. Return the preferred aspect ratio from the aspect-ratio style.
+ return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes);
+ }
+
+ // The rest of the cases are when aspect-ratio has 'auto'.
+ if (auto intrinsicRatio = GetIntrinsicRatio()) {
+ return intrinsicRatio;
+ }
+
+ if (aspectRatio.HasRatio()) {
+ // If it's a degenerate ratio, this returns 0. Just the same as the auto
+ // case.
+ return aspectRatio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::No);
+ }
+
+ return AspectRatio();
+}
+
+/* virtual */
+AspectRatio nsIFrame::GetIntrinsicRatio() const { return AspectRatio(); }
+
+static bool ShouldApplyAutomaticMinimumOnInlineAxis(
+ WritingMode aWM, const nsStyleDisplay* aDisplay,
+ const nsStylePosition* aPosition) {
+ // Apply the automatic minimum size for aspect ratio:
+ // Note: The replaced elements shouldn't be here, so we only check the scroll
+ // container.
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ return !aDisplay->IsScrollableOverflow() && aPosition->MinISize(aWM).IsAuto();
+}
+
+struct MinMaxSize {
+ nscoord mMinSize = 0;
+ nscoord mMaxSize = NS_UNCONSTRAINEDSIZE;
+
+ nscoord ClampSizeToMinAndMax(nscoord aSize) const {
+ return NS_CSS_MINMAX(aSize, mMinSize, mMaxSize);
+ }
+};
+static MinMaxSize ComputeTransferredMinMaxInlineSize(
+ const WritingMode aWM, const AspectRatio& aAspectRatio,
+ const MinMaxSize& aMinMaxBSize, const LogicalSize& aBoxSizingAdjustment) {
+ // Note: the spec mentions that
+ // 1. This transferred minimum is capped by any definite preferred or maximum
+ // size in the destination axis.
+ // 2. This transferred maximum is floored by any definite preferred or minimum
+ // size in the destination axis
+ //
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio
+ //
+ // The spec requires us to clamp these by the specified size (it calls it the
+ // preferred size). However, we actually don't need to worry about that,
+ // because we only use this if the inline size is indefinite.
+ //
+ // We do not need to clamp the transferred minimum and maximum as long as we
+ // always apply the transferred min/max size before the explicit min/max size,
+ // the result will be identical.
+
+ MinMaxSize transferredISize;
+
+ if (aMinMaxBSize.mMinSize > 0) {
+ transferredISize.mMinSize = aAspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, aMinMaxBSize.mMinSize,
+ aBoxSizingAdjustment);
+ }
+
+ if (aMinMaxBSize.mMaxSize != NS_UNCONSTRAINEDSIZE) {
+ transferredISize.mMaxSize = aAspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, aMinMaxBSize.mMaxSize,
+ aBoxSizingAdjustment);
+ }
+
+ // Minimum size wins over maximum size.
+ transferredISize.mMaxSize =
+ std::max(transferredISize.mMinSize, transferredISize.mMaxSize);
+ return transferredISize;
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult nsIFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ MOZ_ASSERT(!GetIntrinsicRatio(),
+ "Please override this method and call "
+ "nsContainerFrame::ComputeSizeWithIntrinsicDimensions instead.");
+ LogicalSize result =
+ ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
+ aBorderPadding, aSizeOverrides, aFlags);
+ const nsStylePosition* stylePos = StylePosition();
+ const nsStyleDisplay* disp = StyleDisplay();
+ auto aspectRatioUsage = AspectRatioUsage::None;
+
+ const auto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border
+ ? aBorderPadding
+ : LogicalSize(aWM);
+ nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) +
+ aBorderPadding.ISize(aWM) -
+ boxSizingAdjust.ISize(aWM);
+
+ const auto& styleISize = aSizeOverrides.mStyleISize
+ ? *aSizeOverrides.mStyleISize
+ : stylePos->ISize(aWM);
+ const auto& styleBSize = aSizeOverrides.mStyleBSize
+ ? *aSizeOverrides.mStyleBSize
+ : stylePos->BSize(aWM);
+ const auto& aspectRatio = aSizeOverrides.mAspectRatio
+ ? *aSizeOverrides.mAspectRatio
+ : GetAspectRatio();
+
+ auto parentFrame = GetParent();
+ auto alignCB = parentFrame;
+ bool isGridItem = IsGridItem();
+ const bool isSubgrid = IsSubgrid();
+ if (parentFrame && parentFrame->IsTableWrapperFrame() && IsTableFrame()) {
+ // An inner table frame is sized as a grid item if its table wrapper is,
+ // because they actually have the same CB (the wrapper's CB).
+ // @see ReflowInput::InitCBReflowInput
+ auto tableWrapper = GetParent();
+ auto grandParent = tableWrapper->GetParent();
+ isGridItem = grandParent->IsGridContainerFrame() &&
+ !tableWrapper->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+ if (isGridItem) {
+ // When resolving justify/align-self below, we want to use the grid
+ // container's justify/align-items value and WritingMode.
+ alignCB = grandParent;
+ }
+ }
+ const bool isFlexItem =
+ IsFlexItem() && !parentFrame->HasAnyStateBits(
+ NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
+ // This variable only gets set (and used) if isFlexItem is true. It
+ // indicates which axis (in this frame's own WM) corresponds to its
+ // flex container's main axis.
+ LogicalAxis flexMainAxis =
+ eLogicalAxisInline; // (init to make valgrind happy)
+ if (isFlexItem) {
+ flexMainAxis = nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)
+ ? eLogicalAxisInline
+ : eLogicalAxisBlock;
+ }
+
+ const bool isOrthogonal = aWM.IsOrthogonalTo(alignCB->GetWritingMode());
+ const bool isAutoISize = styleISize.IsAuto();
+ const bool isAutoBSize =
+ nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM));
+
+ // Compute inline-axis size
+ const bool isSubgriddedInInlineAxis =
+ isSubgrid && static_cast<nsGridContainerFrame*>(this)->IsColSubgrid();
+
+ // Per https://drafts.csswg.org/css-grid/#subgrid-box-alignment, if we are
+ // subgridded in the inline-axis, ignore our style inline-size, and stretch to
+ // fill the CB.
+ const bool shouldComputeISize = !isAutoISize && !isSubgriddedInInlineAxis;
+ if (shouldComputeISize) {
+ auto iSizeResult = ComputeISizeValue(
+ aRenderingContext, aWM, aCBSize, boxSizingAdjust,
+ boxSizingToMarginEdgeISize, styleISize, aSizeOverrides, aFlags);
+ result.ISize(aWM) = iSizeResult.mISize;
+ aspectRatioUsage = iSizeResult.mAspectRatioUsage;
+ } else if (MOZ_UNLIKELY(isGridItem) && !IsTrueOverflowContainer()) {
+ // 'auto' inline-size for grid-level box - fill the CB for 'stretch' /
+ // 'normal' and clamp it to the CB if requested:
+ bool stretch = false;
+ bool mayUseAspectRatio = aspectRatio && !isAutoBSize;
+ if (!aFlags.contains(ComputeSizeFlag::ShrinkWrap) &&
+ !StyleMargin()->HasInlineAxisAuto(aWM) &&
+ !alignCB->IsMasonry(isOrthogonal ? eLogicalAxisBlock
+ : eLogicalAxisInline)) {
+ auto inlineAxisAlignment =
+ isOrthogonal ? StylePosition()->UsedAlignSelf(alignCB->Style())._0
+ : StylePosition()->UsedJustifySelf(alignCB->Style())._0;
+ stretch = inlineAxisAlignment == StyleAlignFlags::STRETCH ||
+ (inlineAxisAlignment == StyleAlignFlags::NORMAL &&
+ !mayUseAspectRatio);
+ }
+
+ // Apply the preferred aspect ratio for alignments other than *stretch* and
+ // *normal without aspect ratio*.
+ // The spec says all other values should size the items as fit-content, and
+ // the intrinsic size should respect the preferred aspect ratio, so we also
+ // apply aspect ratio for all other values.
+ // https://drafts.csswg.org/css-grid/#grid-item-sizing
+ if (!stretch && mayUseAspectRatio) {
+ // Note: we don't need to handle aspect ratio for inline axis if both
+ // width/height are auto. The default ratio-dependent axis is block axis
+ // in this case, so we can simply get the block size from the non-auto
+ // |styleBSize|.
+ auto bSize = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ styleBSize.AsLengthPercentage());
+ result.ISize(aWM) = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
+ aspectRatioUsage = AspectRatioUsage::ToComputeISize;
+ }
+
+ if (stretch || aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
+ auto iSizeToFillCB =
+ std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
+ aMargin.ISize(aWM));
+ if (stretch || result.ISize(aWM) > iSizeToFillCB) {
+ result.ISize(aWM) = iSizeToFillCB;
+ }
+ }
+ } else if (aspectRatio && !isAutoBSize) {
+ auto bSize = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ styleBSize.AsLengthPercentage());
+ result.ISize(aWM) = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust);
+ aspectRatioUsage = AspectRatioUsage::ToComputeISize;
+ }
+
+ // Calculate and apply transferred min & max size contraints.
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers
+ //
+ // Note: The basic principle is that sizing constraints transfer through the
+ // aspect-ratio to the other side to preserve the aspect ratio to the extent
+ // that they can without violating any sizes specified explicitly on that
+ // affected axis.
+ //
+ // FIXME: The spec words may not be correct, so we may have to update this
+ // tentative solution once this spec issue gets resolved. Here, we clamp the
+ // flex base size by the transferred min and max sizes, and don't include
+ // the transferred min & max sizes into its used min & max sizes. So this
+ // lets us match other browsers' current behaviors.
+ // https://github.com/w3c/csswg-drafts/issues/6071
+ //
+ // Note: This may make more sense if we clamp the flex base size in
+ // FlexItem::ResolveFlexBaseSizeFromAspectRatio(). However, the result should
+ // be identical. FlexItem::ResolveFlexBaseSizeFromAspectRatio() only handles
+ // the case of the definite cross size, and the definite cross size is clamped
+ // by the min & max cross sizes below in this function. This means its flex
+ // base size has been clamped by the transferred min & max size already after
+ // generating the flex items. So here we make the code more general for both
+ // definite cross size and indefinite cross size.
+ const bool isDefiniteISize = styleISize.IsLengthPercentage();
+ const auto& minBSizeCoord = stylePos->MinBSize(aWM);
+ const auto& maxBSizeCoord = stylePos->MaxBSize(aWM);
+ const bool isAutoMinBSize =
+ nsLayoutUtils::IsAutoBSize(minBSizeCoord, aCBSize.BSize(aWM));
+ const bool isAutoMaxBSize =
+ nsLayoutUtils::IsAutoBSize(maxBSizeCoord, aCBSize.BSize(aWM));
+ if (aspectRatio && !isDefiniteISize) {
+ const MinMaxSize minMaxBSize{
+ isAutoMinBSize ? 0
+ : nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ minBSizeCoord.AsLengthPercentage()),
+ isAutoMaxBSize ? NS_UNCONSTRAINEDSIZE
+ : nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ maxBSizeCoord.AsLengthPercentage())};
+ MinMaxSize transferredMinMaxISize = ComputeTransferredMinMaxInlineSize(
+ aWM, aspectRatio, minMaxBSize, boxSizingAdjust);
+
+ result.ISize(aWM) =
+ transferredMinMaxISize.ClampSizeToMinAndMax(result.ISize(aWM));
+ }
+
+ // Flex items ignore their min & max sizing properties in their
+ // flex container's main-axis. (Those properties get applied later in
+ // the flexbox algorithm.)
+ const bool isFlexItemInlineAxisMainAxis =
+ isFlexItem && flexMainAxis == eLogicalAxisInline;
+ // Grid items that are subgridded in inline-axis also ignore their min & max
+ // sizing properties in that axis.
+ const bool shouldIgnoreMinMaxISize =
+ isFlexItemInlineAxisMainAxis || isSubgriddedInInlineAxis;
+ const auto& maxISizeCoord = stylePos->MaxISize(aWM);
+ nscoord maxISize = NS_UNCONSTRAINEDSIZE;
+ if (!maxISizeCoord.IsNone() && !shouldIgnoreMinMaxISize) {
+ maxISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
+ boxSizingAdjust, boxSizingToMarginEdgeISize,
+ maxISizeCoord, aSizeOverrides, aFlags)
+ .mISize;
+ result.ISize(aWM) = std::min(maxISize, result.ISize(aWM));
+ }
+
+ const auto& minISizeCoord = stylePos->MinISize(aWM);
+ nscoord minISize;
+ if (!minISizeCoord.IsAuto() && !shouldIgnoreMinMaxISize) {
+ minISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
+ boxSizingAdjust, boxSizingToMarginEdgeISize,
+ minISizeCoord, aSizeOverrides, aFlags)
+ .mISize;
+ } else if (MOZ_UNLIKELY(
+ aFlags.contains(ComputeSizeFlag::IApplyAutoMinSize))) {
+ // This implements "Implied Minimum Size of Grid Items".
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ minISize = std::min(maxISize, GetMinISize(aRenderingContext));
+ if (styleISize.IsLengthPercentage()) {
+ minISize = std::min(minISize, result.ISize(aWM));
+ } else if (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
+ // "if the grid item spans only grid tracks that have a fixed max track
+ // sizing function, its automatic minimum size in that dimension is
+ // further clamped to less than or equal to the size necessary to fit
+ // its margin box within the resulting grid area (flooring at zero)"
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ auto maxMinISize =
+ std::max(nscoord(0), aCBSize.ISize(aWM) - aBorderPadding.ISize(aWM) -
+ aMargin.ISize(aWM));
+ minISize = std::min(minISize, maxMinISize);
+ }
+ } else if (aspectRatioUsage == AspectRatioUsage::ToComputeISize &&
+ ShouldApplyAutomaticMinimumOnInlineAxis(aWM, disp, stylePos)) {
+ // This means we successfully applied aspect-ratio and now need to check
+ // if we need to apply the implied minimum size:
+ // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ MOZ_ASSERT(!HasReplacedSizing(),
+ "aspect-ratio minimums should not apply to replaced elements");
+ // The inline size computed by aspect-ratio shouldn't less than the content
+ // size.
+ minISize = GetMinISize(aRenderingContext);
+ } else {
+ // Treat "min-width: auto" as 0.
+ // NOTE: Technically, "auto" is supposed to behave like "min-content" on
+ // flex items. However, we don't need to worry about that here, because
+ // flex items' min-sizes are intentionally ignored until the flex
+ // container explicitly considers them during space distribution.
+ minISize = 0;
+ }
+ result.ISize(aWM) = std::max(minISize, result.ISize(aWM));
+
+ // Compute block-axis size
+ // (but not if we have auto bsize -- then, we'll just stick with the bsize
+ // that we already calculated in the initial ComputeAutoSize() call. However,
+ // if we have a valid preferred aspect ratio, we still have to compute the
+ // block size because aspect ratio affects the intrinsic content size.)
+ const bool isSubgriddedInBlockAxis =
+ isSubgrid && static_cast<nsGridContainerFrame*>(this)->IsRowSubgrid();
+
+ // Per https://drafts.csswg.org/css-grid/#subgrid-box-alignment, if we are
+ // subgridded in the block-axis, ignore our style block-size, and stretch to
+ // fill the CB.
+ const bool shouldComputeBSize = !isAutoBSize && !isSubgriddedInBlockAxis;
+ if (shouldComputeBSize) {
+ result.BSize(aWM) = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ styleBSize.AsLengthPercentage());
+ } else if (MOZ_UNLIKELY(isGridItem) && styleBSize.IsAuto() &&
+ !aFlags.contains(ComputeSizeFlag::IsGridMeasuringReflow) &&
+ !IsTrueOverflowContainer() &&
+ !alignCB->IsMasonry(isOrthogonal ? eLogicalAxisInline
+ : eLogicalAxisBlock)) {
+ auto cbSize = aCBSize.BSize(aWM);
+ if (cbSize != NS_UNCONSTRAINEDSIZE) {
+ // 'auto' block-size for grid-level box - fill the CB for 'stretch' /
+ // 'normal' and clamp it to the CB if requested:
+ bool stretch = false;
+ bool mayUseAspectRatio =
+ aspectRatio && result.ISize(aWM) != NS_UNCONSTRAINEDSIZE;
+ if (!StyleMargin()->HasBlockAxisAuto(aWM)) {
+ auto blockAxisAlignment =
+ isOrthogonal ? StylePosition()->UsedJustifySelf(alignCB->Style())._0
+ : StylePosition()->UsedAlignSelf(alignCB->Style())._0;
+ stretch = blockAxisAlignment == StyleAlignFlags::STRETCH ||
+ (blockAxisAlignment == StyleAlignFlags::NORMAL &&
+ !mayUseAspectRatio);
+ }
+
+ // Apply the preferred aspect ratio for alignments other than *stretch*
+ // and *normal without aspect ratio*.
+ // The spec says all other values should size the items as fit-content,
+ // and the intrinsic size should respect the preferred aspect ratio, so
+ // we also apply aspect ratio for all other values.
+ // https://drafts.csswg.org/css-grid/#grid-item-sizing
+ if (!stretch && mayUseAspectRatio) {
+ result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisBlock, aWM, result.ISize(aWM),
+ boxSizingAdjust);
+ MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None);
+ aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
+ }
+
+ if (stretch || aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) {
+ auto bSizeToFillCB =
+ std::max(nscoord(0),
+ cbSize - aBorderPadding.BSize(aWM) - aMargin.BSize(aWM));
+ if (stretch || (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE &&
+ result.BSize(aWM) > bSizeToFillCB)) {
+ result.BSize(aWM) = bSizeToFillCB;
+ }
+ }
+ }
+ } else if (aspectRatio) {
+ // If both inline and block dimensions are auto, the block axis is the
+ // ratio-dependent axis by default.
+ // If we have a super large inline size, aspect-ratio should still be
+ // applied (so aspectRatioUsage flag is set as expected). That's why we
+ // apply aspect-ratio unconditionally for auto block size here.
+ result.BSize(aWM) = aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisBlock, aWM, result.ISize(aWM),
+ boxSizingAdjust);
+ MOZ_ASSERT(aspectRatioUsage == AspectRatioUsage::None);
+ aspectRatioUsage = AspectRatioUsage::ToComputeBSize;
+ }
+
+ if (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
+ // Flex items ignore their min & max sizing properties in their flex
+ // container's main-axis. (Those properties get applied later in the flexbox
+ // algorithm.)
+ const bool isFlexItemBlockAxisMainAxis =
+ isFlexItem && flexMainAxis == eLogicalAxisBlock;
+ // Grid items that are subgridded in block-axis also ignore their min & max
+ // sizing properties in that axis.
+ const bool shouldIgnoreMinMaxBSize =
+ isFlexItemBlockAxisMainAxis || isSubgriddedInBlockAxis;
+ if (!isAutoMaxBSize && !shouldIgnoreMinMaxBSize) {
+ nscoord maxBSize = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ maxBSizeCoord.AsLengthPercentage());
+ result.BSize(aWM) = std::min(maxBSize, result.BSize(aWM));
+ }
+
+ if (!isAutoMinBSize && !shouldIgnoreMinMaxBSize) {
+ nscoord minBSize = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM),
+ minBSizeCoord.AsLengthPercentage());
+ result.BSize(aWM) = std::max(minBSize, result.BSize(aWM));
+ }
+ }
+
+ if (IsThemed(disp)) {
+ nsPresContext* pc = PresContext();
+ const LayoutDeviceIntSize widget = pc->Theme()->GetMinimumWidgetSize(
+ pc, this, disp->EffectiveAppearance());
+
+ // Convert themed widget's physical dimensions to logical coords
+ LogicalSize size(aWM, LayoutDeviceIntSize::ToAppUnits(
+ widget, pc->AppUnitsPerDevPixel()));
+
+ // GetMinimumWidgetSize() returns border-box; we need content-box.
+ size -= aBorderPadding;
+
+ if (size.BSize(aWM) > result.BSize(aWM)) {
+ result.BSize(aWM) = size.BSize(aWM);
+ }
+ if (size.ISize(aWM) > result.ISize(aWM)) {
+ result.ISize(aWM) = size.ISize(aWM);
+ }
+ }
+
+ result.ISize(aWM) = std::max(0, result.ISize(aWM));
+ result.BSize(aWM) = std::max(0, result.BSize(aWM));
+
+ return {result, aspectRatioUsage};
+}
+
+nsRect nsIFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
+ return InkOverflowRect();
+}
+
+/* virtual */
+nsresult nsIFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
+ nscoord* aXMost) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* virtual */
+LogicalSize nsIFrame::ComputeAutoSize(
+ gfxContext* aRenderingContext, WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
+ // Use basic shrink-wrapping as a default implementation.
+ LogicalSize result(aWM, 0xdeadbeef, NS_UNCONSTRAINEDSIZE);
+
+ // don't bother setting it if the result won't be used
+ const auto& styleISize = aSizeOverrides.mStyleISize
+ ? *aSizeOverrides.mStyleISize
+ : StylePosition()->ISize(aWM);
+ if (styleISize.IsAuto()) {
+ nscoord availBased =
+ aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
+ result.ISize(aWM) = ShrinkISizeToFit(aRenderingContext, availBased, aFlags);
+ }
+ return result;
+}
+
+nscoord nsIFrame::ShrinkISizeToFit(gfxContext* aRenderingContext,
+ nscoord aISizeInCB,
+ ComputeSizeFlags aFlags) {
+ // If we're a container for font size inflation, then shrink
+ // wrapping inside of us should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(this);
+
+ nscoord result;
+ nscoord minISize = GetMinISize(aRenderingContext);
+ if (minISize > aISizeInCB) {
+ const bool clamp = aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize);
+ result = MOZ_UNLIKELY(clamp) ? aISizeInCB : minISize;
+ } else {
+ nscoord prefISize = GetPrefISize(aRenderingContext);
+ if (prefISize > aISizeInCB) {
+ result = aISizeInCB;
+ } else {
+ result = prefISize;
+ }
+ }
+ return result;
+}
+
+Maybe<nscoord> nsIFrame::ComputeInlineSizeFromAspectRatio(
+ WritingMode aWM, const LogicalSize& aCBSize,
+ const LogicalSize& aContentEdgeToBoxSizing,
+ const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) const {
+ // FIXME: Bug 1670151: Use GetAspectRatio() to cover replaced elements (and
+ // then we can drop the check of eSupportsAspectRatio).
+ const AspectRatio aspectRatio =
+ aSizeOverrides.mAspectRatio
+ ? *aSizeOverrides.mAspectRatio
+ : StylePosition()->mAspectRatio.ToLayoutRatio();
+ if (!SupportsAspectRatio() || !aspectRatio) {
+ return Nothing();
+ }
+
+ const StyleSize& styleBSize = aSizeOverrides.mStyleBSize
+ ? *aSizeOverrides.mStyleBSize
+ : StylePosition()->BSize(aWM);
+ if (nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM))) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(styleBSize.IsLengthPercentage());
+ nscoord bSize = nsLayoutUtils::ComputeBSizeValue(
+ aCBSize.BSize(aWM), aContentEdgeToBoxSizing.BSize(aWM),
+ styleBSize.AsLengthPercentage());
+ return Some(aspectRatio.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, aWM, bSize, aContentEdgeToBoxSizing));
+}
+
+nsIFrame::ISizeComputationResult nsIFrame::ComputeISizeValue(
+ gfxContext* aRenderingContext, const WritingMode aWM,
+ const LogicalSize& aContainingBlockSize,
+ const LogicalSize& aContentEdgeToBoxSizing, nscoord aBoxSizingToMarginEdge,
+ ExtremumLength aSize, Maybe<nscoord> aAvailableISizeOverride,
+ const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
+ // If 'this' is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(this);
+ // If we have an aspect-ratio and a definite block size, we resolve the
+ // min-content and max-content size by the aspect-ratio and the block size.
+ // https://github.com/w3c/csswg-drafts/issues/5032
+ Maybe<nscoord> intrinsicSizeFromAspectRatio =
+ aSize == ExtremumLength::MozAvailable
+ ? Nothing()
+ : ComputeInlineSizeFromAspectRatio(aWM, aContainingBlockSize,
+ aContentEdgeToBoxSizing,
+ aSizeOverrides, aFlags);
+ nscoord result;
+ switch (aSize) {
+ case ExtremumLength::MaxContent:
+ result = intrinsicSizeFromAspectRatio ? *intrinsicSizeFromAspectRatio
+ : GetPrefISize(aRenderingContext);
+ NS_ASSERTION(result >= 0, "inline-size less than zero");
+ return {result, intrinsicSizeFromAspectRatio
+ ? AspectRatioUsage::ToComputeISize
+ : AspectRatioUsage::None};
+ case ExtremumLength::MinContent:
+ result = intrinsicSizeFromAspectRatio ? *intrinsicSizeFromAspectRatio
+ : GetMinISize(aRenderingContext);
+ NS_ASSERTION(result >= 0, "inline-size less than zero");
+ if (MOZ_UNLIKELY(
+ aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
+ auto available =
+ aContainingBlockSize.ISize(aWM) -
+ (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing.ISize(aWM));
+ result = std::min(available, result);
+ }
+ return {result, intrinsicSizeFromAspectRatio
+ ? AspectRatioUsage::ToComputeISize
+ : AspectRatioUsage::None};
+ case ExtremumLength::FitContentFunction:
+ case ExtremumLength::FitContent: {
+ nscoord pref = NS_UNCONSTRAINEDSIZE;
+ nscoord min = 0;
+ if (intrinsicSizeFromAspectRatio) {
+ // The min-content and max-content size are identical and equal to the
+ // size computed from the block size and the aspect ratio.
+ pref = min = *intrinsicSizeFromAspectRatio;
+ } else {
+ pref = GetPrefISize(aRenderingContext);
+ min = GetMinISize(aRenderingContext);
+ }
+
+ nscoord fill = aAvailableISizeOverride
+ ? *aAvailableISizeOverride
+ : aContainingBlockSize.ISize(aWM) -
+ (aBoxSizingToMarginEdge +
+ aContentEdgeToBoxSizing.ISize(aWM));
+
+ if (MOZ_UNLIKELY(
+ aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize))) {
+ min = std::min(min, fill);
+ }
+ result = std::max(min, std::min(pref, fill));
+ NS_ASSERTION(result >= 0, "inline-size less than zero");
+ return {result};
+ }
+ case ExtremumLength::MozAvailable:
+ return {aContainingBlockSize.ISize(aWM) -
+ (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing.ISize(aWM))};
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown extremum length?");
+ return {};
+}
+
+nscoord nsIFrame::ComputeISizeValue(const WritingMode aWM,
+ const LogicalSize& aContainingBlockSize,
+ const LogicalSize& aContentEdgeToBoxSizing,
+ const LengthPercentage& aSize) {
+ LAYOUT_WARN_IF_FALSE(
+ aContainingBlockSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained inline-size; this should only result from "
+ "very large sizes, not attempts at intrinsic inline-size "
+ "calculation");
+ NS_ASSERTION(aContainingBlockSize.ISize(aWM) >= 0,
+ "inline-size less than zero");
+
+ nscoord result = aSize.Resolve(aContainingBlockSize.ISize(aWM));
+ // The result of a calc() expression might be less than 0; we
+ // should clamp at runtime (below). (Percentages and coords that
+ // are less than 0 have already been dropped by the parser.)
+ result -= aContentEdgeToBoxSizing.ISize(aWM);
+ return std::max(0, result);
+}
+
+void nsIFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) {
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsIFrame::DidReflow"));
+
+ if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ RemoveStateBits(NS_FRAME_IN_REFLOW);
+ return;
+ }
+
+ SVGObserverUtils::InvalidateDirectRenderingObservers(
+ this, SVGObserverUtils::INVALIDATE_REFLOW);
+
+ RemoveStateBits(NS_FRAME_IN_REFLOW | NS_FRAME_FIRST_REFLOW |
+ NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Clear bits that were used in ReflowInput::InitResizeFlags (see
+ // comment there for why we can't clear it there).
+ SetHasBSizeChange(false);
+ SetHasPaddingChange(false);
+
+ // Notify the percent bsize observer if there is a percent bsize.
+ // The observer may be able to initiate another reflow with a computed
+ // bsize. This happens in the case where a table cell has no computed
+ // bsize but can fabricate one when the cell bsize is known.
+ if (aReflowInput && aReflowInput->mPercentBSizeObserver && !GetPrevInFlow()) {
+ const auto& bsize =
+ aReflowInput->mStylePosition->BSize(aReflowInput->GetWritingMode());
+ if (bsize.HasPercent()) {
+ aReflowInput->mPercentBSizeObserver->NotifyPercentBSize(*aReflowInput);
+ }
+ }
+
+ aPresContext->ReflowedFrame();
+}
+
+void nsIFrame::FinishReflowWithAbsoluteFrames(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ bool aConstrainBSize) {
+ ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus,
+ aConstrainBSize);
+
+ FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
+}
+
+void nsIFrame::ReflowAbsoluteFrames(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ bool aConstrainBSize) {
+ if (HasAbsolutelyPositionedChildren()) {
+ nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
+
+ // Let the absolutely positioned container reflow any absolutely positioned
+ // child frames that need to be reflowed
+
+ // The containing block for the abs pos kids is formed by our padding edge.
+ nsMargin usedBorder = GetUsedBorder();
+ nscoord containingBlockWidth =
+ std::max(0, aDesiredSize.Width() - usedBorder.LeftRight());
+ nscoord containingBlockHeight =
+ std::max(0, aDesiredSize.Height() - usedBorder.TopBottom());
+ nsContainerFrame* container = do_QueryFrame(this);
+ NS_ASSERTION(container,
+ "Abs-pos children only supported on container frames for now");
+
+ nsRect containingBlock(0, 0, containingBlockWidth, containingBlockHeight);
+ AbsPosReflowFlags flags =
+ AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized
+ if (aConstrainBSize) {
+ flags |= AbsPosReflowFlags::ConstrainHeight;
+ }
+ absoluteContainer->Reflow(container, aPresContext, aReflowInput, aStatus,
+ containingBlock, flags,
+ &aDesiredSize.mOverflowAreas);
+ }
+}
+
+/* virtual */
+bool nsIFrame::CanContinueTextRun() const {
+ // By default, a frame will *not* allow a text run to be continued
+ // through it.
+ return false;
+}
+
+void nsIFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsFrame");
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ aDesiredSize.ClearSize();
+}
+
+bool nsIFrame::IsContentDisabled() const {
+ // FIXME(emilio): Doing this via CSS means callers must ensure the style is up
+ // to date, and they don't!
+ if (StyleUI()->UserInput() == StyleUserInput::None) {
+ return true;
+ }
+
+ auto* element = nsGenericHTMLElement::FromNodeOrNull(GetContent());
+ return element && element->IsDisabled();
+}
+
+bool nsIFrame::IsContentRelevant() const {
+ MOZ_ASSERT(StyleDisplay()->ContentVisibility(*this) ==
+ StyleContentVisibility::Auto);
+
+ auto* element = Element::FromNodeOrNull(GetContent());
+ MOZ_ASSERT(element);
+
+ Maybe<ContentRelevancy> relevancy = element->GetContentRelevancy();
+ return relevancy.isSome() && !relevancy->isEmpty();
+}
+
+bool nsIFrame::HidesContent(
+ const EnumSet<IncludeContentVisibility>& aInclude) const {
+ auto effectiveContentVisibility = StyleDisplay()->ContentVisibility(*this);
+ if (aInclude.contains(IncludeContentVisibility::Hidden) &&
+ effectiveContentVisibility == StyleContentVisibility::Hidden) {
+ return true;
+ }
+
+ if (aInclude.contains(IncludeContentVisibility::Auto) &&
+ effectiveContentVisibility == StyleContentVisibility::Auto) {
+ return !IsContentRelevant();
+ }
+
+ return false;
+}
+
+bool nsIFrame::HidesContentForLayout() const {
+ return HidesContent() && !PresShell()->IsForcingLayoutForHiddenContent(this);
+}
+
+bool nsIFrame::IsHiddenByContentVisibilityOfInFlowParentForLayout() const {
+ const auto* parent = GetInFlowParent();
+ // The anonymous children owned by parent are important for properly sizing
+ // their parents.
+ return parent && parent->HidesContentForLayout() &&
+ !(parent->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES) &&
+ Style()->IsAnonBox());
+}
+
+nsIFrame* nsIFrame::GetClosestContentVisibilityAncestor(
+ const EnumSet<IncludeContentVisibility>& aInclude) const {
+ if (!StaticPrefs::layout_css_content_visibility_enabled()) {
+ return nullptr;
+ }
+
+ auto* parent = GetInFlowParent();
+ bool isAnonymousBlock = Style()->IsAnonBox() && parent &&
+ parent->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES);
+ for (nsIFrame* cur = parent; cur; cur = cur->GetInFlowParent()) {
+ if (!isAnonymousBlock && cur->HidesContent(aInclude)) {
+ return cur;
+ }
+
+ // Anonymous boxes are not hidden by the content-visibility of their first
+ // non-anonymous ancestor, but can be hidden by ancestors further up the
+ // tree.
+ isAnonymousBlock = false;
+ }
+
+ return nullptr;
+}
+
+bool nsIFrame::IsHiddenByContentVisibilityOnAnyAncestor(
+ const EnumSet<IncludeContentVisibility>& aInclude) const {
+ return !!GetClosestContentVisibilityAncestor(aInclude);
+}
+
+bool nsIFrame::HasSelectionInSubtree() {
+ if (IsSelected()) {
+ return true;
+ }
+
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+ if (!frameSelection) {
+ return false;
+ }
+
+ const Selection* selection =
+ frameSelection->GetSelection(SelectionType::eNormal);
+ if (!selection) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < selection->RangeCount(); i++) {
+ auto* range = selection->GetRangeAt(i);
+ MOZ_ASSERT(range);
+
+ const auto* commonAncestorNode =
+ range->GetRegisteredClosestCommonInclusiveAncestor();
+ if (commonAncestorNode &&
+ commonAncestorNode->IsInclusiveDescendantOf(GetContent())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsIFrame::UpdateIsRelevantContent(
+ const ContentRelevancy& aRelevancyToUpdate) {
+ MOZ_ASSERT(StyleDisplay()->ContentVisibility(*this) ==
+ StyleContentVisibility::Auto);
+
+ auto* element = Element::FromNodeOrNull(GetContent());
+ MOZ_ASSERT(element);
+
+ ContentRelevancy newRelevancy;
+ Maybe<ContentRelevancy> oldRelevancy = element->GetContentRelevancy();
+ if (oldRelevancy.isSome()) {
+ newRelevancy = *oldRelevancy;
+ }
+
+ auto setRelevancyValue = [&](ContentRelevancyReason reason, bool value) {
+ if (value) {
+ newRelevancy += reason;
+ } else {
+ newRelevancy -= reason;
+ }
+ };
+
+ if (!oldRelevancy ||
+ aRelevancyToUpdate.contains(ContentRelevancyReason::Visible)) {
+ Maybe<bool> visible = element->GetVisibleForContentVisibility();
+ if (visible.isSome()) {
+ setRelevancyValue(ContentRelevancyReason::Visible, *visible);
+ }
+ }
+
+ if (!oldRelevancy ||
+ aRelevancyToUpdate.contains(ContentRelevancyReason::FocusInSubtree)) {
+ setRelevancyValue(ContentRelevancyReason::FocusInSubtree,
+ element->State().HasAtLeastOneOfStates(
+ ElementState::FOCUS_WITHIN | ElementState::FOCUS));
+ }
+
+ if (!oldRelevancy ||
+ aRelevancyToUpdate.contains(ContentRelevancyReason::Selected)) {
+ setRelevancyValue(ContentRelevancyReason::Selected,
+ HasSelectionInSubtree());
+ }
+
+ // If the proximity to the viewport has not been determined yet,
+ // and neither the element nor its contents are focused or selected,
+ // we should wait for the determination of the proximity. Otherwise,
+ // there might be a redundant contentvisibilityautostatechange event.
+ // See https://github.com/w3c/csswg-drafts/issues/9803
+ bool isProximityToViewportDetermined =
+ oldRelevancy ? true : element->GetVisibleForContentVisibility().isSome();
+ if (!isProximityToViewportDetermined && newRelevancy.isEmpty()) {
+ return false;
+ }
+
+ bool overallRelevancyChanged =
+ !oldRelevancy || oldRelevancy->isEmpty() != newRelevancy.isEmpty();
+ if (!oldRelevancy || *oldRelevancy != newRelevancy) {
+ element->SetContentRelevancy(newRelevancy);
+ }
+
+ if (!overallRelevancyChanged) {
+ return false;
+ }
+
+ HandleLastRememberedSize();
+ PresContext()->SetNeedsToUpdateHiddenByContentVisibilityForAnimations();
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ InvalidateFrame();
+
+ ContentVisibilityAutoStateChangeEventInit init;
+ init.mSkipped = newRelevancy.isEmpty();
+ RefPtr<ContentVisibilityAutoStateChangeEvent> event =
+ ContentVisibilityAutoStateChangeEvent::Constructor(
+ element, u"contentvisibilityautostatechange"_ns, init);
+
+ // Per
+ // https://drafts.csswg.org/css-contain/#content-visibility-auto-state-changed
+ // "This event is dispatched by posting a task at the time when the state
+ // change occurs."
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(element, event.forget());
+ DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
+ return true;
+}
+
+nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) {
+ MOZ_ASSERT_UNREACHABLE("should only be called for text frames");
+ return NS_OK;
+}
+
+nsresult nsIFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) {
+ return NS_OK;
+}
+
+// Flow member functions
+
+nsIFrame* nsIFrame::GetPrevContinuation() const { return nullptr; }
+
+void nsIFrame::SetPrevContinuation(nsIFrame* aPrevContinuation) {
+ MOZ_ASSERT(false, "not splittable");
+}
+
+nsIFrame* nsIFrame::GetNextContinuation() const { return nullptr; }
+
+void nsIFrame::SetNextContinuation(nsIFrame*) {
+ MOZ_ASSERT(false, "not splittable");
+}
+
+nsIFrame* nsIFrame::GetPrevInFlow() const { return nullptr; }
+
+void nsIFrame::SetPrevInFlow(nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT(false, "not splittable");
+}
+
+nsIFrame* nsIFrame::GetNextInFlow() const { return nullptr; }
+
+void nsIFrame::SetNextInFlow(nsIFrame*) { MOZ_ASSERT(false, "not splittable"); }
+
+nsIFrame* nsIFrame::GetTailContinuation() {
+ nsIFrame* frame = this;
+ while (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ frame = frame->GetPrevContinuation();
+ NS_ASSERTION(frame, "first continuation can't be overflow container");
+ }
+ for (nsIFrame* next = frame->GetNextContinuation();
+ next && !next->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
+ next = frame->GetNextContinuation()) {
+ frame = next;
+ }
+
+ MOZ_ASSERT(frame, "illegal state in continuation chain.");
+ return frame;
+}
+
+// Associated view object
+void nsIFrame::SetView(nsView* aView) {
+ if (aView) {
+ aView->SetFrame(this);
+
+#ifdef DEBUG
+ LayoutFrameType frameType = Type();
+ NS_ASSERTION(frameType == LayoutFrameType::SubDocument ||
+ frameType == LayoutFrameType::ListControl ||
+ frameType == LayoutFrameType::Viewport ||
+ frameType == LayoutFrameType::MenuPopup,
+ "Only specific frame types can have an nsView");
+#endif
+
+ // Store the view on the frame.
+ SetViewInternal(aView);
+
+ // Set the frame state bit that says the frame has a view
+ AddStateBits(NS_FRAME_HAS_VIEW);
+
+ // Let all of the ancestors know they have a descendant with a view.
+ for (nsIFrame* f = GetParent();
+ f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
+ f = f->GetParent())
+ f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Destroying a view while the frame is alive?");
+ RemoveStateBits(NS_FRAME_HAS_VIEW);
+ SetViewInternal(nullptr);
+ }
+}
+
+// Find the first geometric parent that has a view
+nsIFrame* nsIFrame::GetAncestorWithView() const {
+ for (nsIFrame* f = GetParent(); nullptr != f; f = f->GetParent()) {
+ if (f->HasView()) {
+ return f;
+ }
+ }
+ return nullptr;
+}
+
+template <nsPoint (nsIFrame::*PositionGetter)() const>
+static nsPoint OffsetCalculator(const nsIFrame* aThis, const nsIFrame* aOther) {
+ MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!");
+
+ NS_ASSERTION(aThis->PresContext() == aOther->PresContext(),
+ "GetOffsetTo called on frames in different documents");
+
+ nsPoint offset(0, 0);
+ const nsIFrame* f;
+ for (f = aThis; f != aOther && f; f = f->GetParent()) {
+ offset += (f->*PositionGetter)();
+ }
+
+ if (f != aOther) {
+ // Looks like aOther wasn't an ancestor of |this|. So now we have
+ // the root-frame-relative position of |this| in |offset|. Convert back
+ // to the coordinates of aOther
+ while (aOther) {
+ offset -= (aOther->*PositionGetter)();
+ aOther = aOther->GetParent();
+ }
+ }
+
+ return offset;
+}
+
+nsPoint nsIFrame::GetOffsetTo(const nsIFrame* aOther) const {
+ return OffsetCalculator<&nsIFrame::GetPosition>(this, aOther);
+}
+
+nsPoint nsIFrame::GetOffsetToIgnoringScrolling(const nsIFrame* aOther) const {
+ return OffsetCalculator<&nsIFrame::GetPositionIgnoringScrolling>(this,
+ aOther);
+}
+
+nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther) const {
+ return GetOffsetToCrossDoc(aOther, PresContext()->AppUnitsPerDevPixel());
+}
+
+nsPoint nsIFrame::GetOffsetToCrossDoc(const nsIFrame* aOther,
+ const int32_t aAPD) const {
+ MOZ_ASSERT(aOther, "Must have frame for destination coordinate system!");
+ NS_ASSERTION(PresContext()->GetRootPresContext() ==
+ aOther->PresContext()->GetRootPresContext(),
+ "trying to get the offset between frames in different document "
+ "hierarchies?");
+ if (PresContext()->GetRootPresContext() !=
+ aOther->PresContext()->GetRootPresContext()) {
+ // crash right away, we are almost certainly going to crash anyway.
+ MOZ_CRASH(
+ "trying to get the offset between frames in different "
+ "document hierarchies?");
+ }
+
+ const nsIFrame* root = nullptr;
+ // offset will hold the final offset
+ // docOffset holds the currently accumulated offset at the current APD, it
+ // will be converted and added to offset when the current APD changes.
+ nsPoint offset(0, 0), docOffset(0, 0);
+ const nsIFrame* f = this;
+ int32_t currAPD = PresContext()->AppUnitsPerDevPixel();
+ while (f && f != aOther) {
+ docOffset += f->GetPosition();
+ nsIFrame* parent = f->GetParent();
+ if (parent) {
+ f = parent;
+ } else {
+ nsPoint newOffset(0, 0);
+ root = f;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f, &newOffset);
+ int32_t newAPD = f ? f->PresContext()->AppUnitsPerDevPixel() : 0;
+ if (!f || newAPD != currAPD) {
+ // Convert docOffset to the right APD and add it to offset.
+ offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
+ docOffset.x = docOffset.y = 0;
+ }
+ currAPD = newAPD;
+ docOffset += newOffset;
+ }
+ }
+ if (f == aOther) {
+ offset += docOffset.ScaleToOtherAppUnits(currAPD, aAPD);
+ } else {
+ // Looks like aOther wasn't an ancestor of |this|. So now we have
+ // the root-document-relative position of |this| in |offset|. Subtract the
+ // root-document-relative position of |aOther| from |offset|.
+ // This call won't try to recurse again because root is an ancestor of
+ // aOther.
+ nsPoint negOffset = aOther->GetOffsetToCrossDoc(root, aAPD);
+ offset -= negOffset;
+ }
+
+ return offset;
+}
+
+CSSIntRect nsIFrame::GetScreenRect() const {
+ return CSSIntRect::FromAppUnitsToNearest(GetScreenRectInAppUnits());
+}
+
+nsRect nsIFrame::GetScreenRectInAppUnits() const {
+ nsPresContext* presContext = PresContext();
+ nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
+ nsPoint rootScreenPos(0, 0);
+ nsPoint rootFrameOffsetInParent(0, 0);
+ nsIFrame* rootFrameParent = nsLayoutUtils::GetCrossDocParentFrameInProcess(
+ rootFrame, &rootFrameOffsetInParent);
+ if (rootFrameParent) {
+ nsRect parentScreenRectAppUnits =
+ rootFrameParent->GetScreenRectInAppUnits();
+ nsPresContext* parentPresContext = rootFrameParent->PresContext();
+ double parentScale = double(presContext->AppUnitsPerDevPixel()) /
+ parentPresContext->AppUnitsPerDevPixel();
+ nsPoint rootPt =
+ parentScreenRectAppUnits.TopLeft() + rootFrameOffsetInParent;
+ rootScreenPos.x = NS_round(parentScale * rootPt.x);
+ rootScreenPos.y = NS_round(parentScale * rootPt.y);
+ } else {
+ nsCOMPtr<nsIWidget> rootWidget =
+ presContext->PresShell()->GetViewManager()->GetRootWidget();
+ if (rootWidget) {
+ LayoutDeviceIntPoint rootDevPx = rootWidget->WidgetToScreenOffset();
+ rootScreenPos.x = presContext->DevPixelsToAppUnits(rootDevPx.x);
+ rootScreenPos.y = presContext->DevPixelsToAppUnits(rootDevPx.y);
+ }
+ }
+
+ return nsRect(rootScreenPos + GetOffsetTo(rootFrame), GetSize());
+}
+
+// Returns the offset from this frame to the closest geometric parent that
+// has a view. Also returns the containing view or null in case of error
+void nsIFrame::GetOffsetFromView(nsPoint& aOffset, nsView** aView) const {
+ MOZ_ASSERT(nullptr != aView, "null OUT parameter pointer");
+ nsIFrame* frame = const_cast<nsIFrame*>(this);
+
+ *aView = nullptr;
+ aOffset.MoveTo(0, 0);
+ do {
+ aOffset += frame->GetPosition();
+ frame = frame->GetParent();
+ } while (frame && !frame->HasView());
+
+ if (frame) {
+ *aView = frame->GetView();
+ }
+}
+
+nsIWidget* nsIFrame::GetNearestWidget() const {
+ return GetClosestView()->GetNearestWidget(nullptr);
+}
+
+nsIWidget* nsIFrame::GetNearestWidget(nsPoint& aOffset) const {
+ nsPoint offsetToView;
+ nsPoint offsetToWidget;
+ nsIWidget* widget =
+ GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
+ aOffset = offsetToView + offsetToWidget;
+ return widget;
+}
+
+Matrix4x4Flagged nsIFrame::GetTransformMatrix(ViewportType aViewportType,
+ RelativeTo aStopAtAncestor,
+ nsIFrame** aOutAncestor,
+ uint32_t aFlags) const {
+ MOZ_ASSERT(aOutAncestor, "Need a place to put the ancestor!");
+
+ /* If we're transformed, we want to hand back the combination
+ * transform/translate matrix that will apply our current transform, then
+ * shift us to our parent.
+ */
+ const bool isTransformed = IsTransformed();
+ const nsIFrame* zoomedContentRoot = nullptr;
+ if (aStopAtAncestor.mViewportType == ViewportType::Visual) {
+ zoomedContentRoot = ViewportUtils::IsZoomedContentRoot(this);
+ if (zoomedContentRoot) {
+ MOZ_ASSERT(aViewportType != ViewportType::Visual);
+ }
+ }
+
+ if (isTransformed || zoomedContentRoot) {
+ Matrix4x4 result;
+ int32_t scaleFactor =
+ ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel()
+ : PresContext()->AppUnitsPerDevPixel());
+
+ /* Compute the delta to the parent, which we need because we are converting
+ * coordinates to our parent.
+ */
+ if (isTransformed) {
+ NS_ASSERTION(nsLayoutUtils::GetCrossDocParentFrameInProcess(this),
+ "Cannot transform the viewport frame!");
+
+ result = result * nsDisplayTransform::GetResultingTransformMatrix(
+ this, nsPoint(0, 0), scaleFactor,
+ nsDisplayTransform::INCLUDE_PERSPECTIVE |
+ nsDisplayTransform::OFFSET_BY_ORIGIN);
+ }
+
+ // The offset from a zoomed content root to its parent (e.g. from
+ // a canvas frame to a scroll frame) is in layout coordinates, so
+ // apply it before applying any layout-to-visual transform.
+ *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrameInProcess(this);
+ nsPoint delta = GetOffsetToCrossDoc(*aOutAncestor);
+ /* Combine the raw transform with a translation to our parent. */
+ result.PostTranslate(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
+ NSAppUnitsToFloatPixels(delta.y, scaleFactor), 0.0f);
+
+ if (zoomedContentRoot) {
+ Matrix4x4 layoutToVisual;
+ ScrollableLayerGuid::ViewID targetScrollId =
+ nsLayoutUtils::FindOrCreateIDFor(zoomedContentRoot->GetContent());
+ if (aFlags & nsIFrame::IN_CSS_UNITS) {
+ layoutToVisual =
+ ViewportUtils::GetVisualToLayoutTransform(targetScrollId)
+ .Inverse()
+ .ToUnknownMatrix();
+ } else {
+ layoutToVisual =
+ ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
+ targetScrollId)
+ .Inverse()
+ .ToUnknownMatrix();
+ }
+ result = result * layoutToVisual;
+ }
+
+ return result;
+ }
+
+ *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrameInProcess(this);
+
+ /* Otherwise, we're not transformed. In that case, we'll walk up the frame
+ * tree until we either hit the root frame or something that may be
+ * transformed. We'll then change coordinates into that frame, since we're
+ * guaranteed that nothing in-between can be transformed. First, however,
+ * we have to check to see if we have a parent. If not, we'll set the
+ * outparam to null (indicating that there's nothing left) and will hand back
+ * the identity matrix.
+ */
+ if (!*aOutAncestor) return Matrix4x4();
+
+ /* Keep iterating while the frame can't possibly be transformed. */
+ const nsIFrame* current = this;
+ auto shouldStopAt = [](const nsIFrame* aCurrent, nsIFrame* aAncestor,
+ uint32_t aFlags) {
+ return aAncestor->IsTransformed() || nsLayoutUtils::IsPopup(aAncestor) ||
+ ViewportUtils::IsZoomedContentRoot(aAncestor) ||
+ ((aFlags & STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) &&
+ (aAncestor->IsStackingContext() ||
+ DisplayPortUtils::FrameHasDisplayPort(aAncestor, aCurrent)));
+ };
+ while (*aOutAncestor != aStopAtAncestor.mFrame &&
+ !shouldStopAt(current, *aOutAncestor, aFlags)) {
+ /* If no parent, stop iterating. Otherwise, update the ancestor. */
+ nsIFrame* parent =
+ nsLayoutUtils::GetCrossDocParentFrameInProcess(*aOutAncestor);
+ if (!parent) break;
+
+ current = *aOutAncestor;
+ *aOutAncestor = parent;
+ }
+
+ NS_ASSERTION(*aOutAncestor, "Somehow ended up with a null ancestor...?");
+
+ /* Translate from this frame to our ancestor, if it exists. That's the
+ * entire transform, so we're done.
+ */
+ nsPoint delta = GetOffsetToCrossDoc(*aOutAncestor);
+ int32_t scaleFactor =
+ ((aFlags & IN_CSS_UNITS) ? AppUnitsPerCSSPixel()
+ : PresContext()->AppUnitsPerDevPixel());
+ return Matrix4x4::Translation(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
+ NSAppUnitsToFloatPixels(delta.y, scaleFactor),
+ 0.0f);
+}
+
+static void InvalidateRenderingObservers(nsIFrame* aDisplayRoot,
+ nsIFrame* aFrame,
+ bool aFrameChanged = true) {
+ MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame));
+ SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame);
+ nsIFrame* parent = aFrame;
+ while (parent != aDisplayRoot &&
+ (parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent)) &&
+ !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(parent);
+ }
+
+ if (!aFrameChanged) {
+ return;
+ }
+
+ aFrame->MarkNeedsDisplayItemRebuild();
+}
+
+static void SchedulePaintInternal(
+ nsIFrame* aDisplayRoot, nsIFrame* aFrame,
+ nsIFrame::PaintType aType = nsIFrame::PAINT_DEFAULT) {
+ MOZ_ASSERT(aDisplayRoot == nsLayoutUtils::GetDisplayRootFrame(aFrame));
+ nsPresContext* pres = aDisplayRoot->PresContext()->GetRootPresContext();
+
+ // No need to schedule a paint for an external document since they aren't
+ // painted directly.
+ if (!pres || (pres->Document() && pres->Document()->IsResourceDoc())) {
+ return;
+ }
+ if (!pres->GetContainerWeak()) {
+ NS_WARNING("Shouldn't call SchedulePaint in a detached pres context");
+ return;
+ }
+
+ pres->PresShell()->ScheduleViewManagerFlush();
+
+ if (aType == nsIFrame::PAINT_DEFAULT) {
+ aDisplayRoot->AddStateBits(NS_FRAME_UPDATE_LAYER_TREE);
+ }
+}
+
+static void InvalidateFrameInternal(nsIFrame* aFrame, bool aHasDisplayItem,
+ bool aRebuildDisplayItems) {
+ if (aHasDisplayItem) {
+ aFrame->AddStateBits(NS_FRAME_NEEDS_PAINT);
+ }
+
+ if (aRebuildDisplayItems) {
+ aFrame->MarkNeedsDisplayItemRebuild();
+ }
+ SVGObserverUtils::InvalidateDirectRenderingObservers(aFrame);
+ bool needsSchedulePaint = false;
+ if (nsLayoutUtils::IsPopup(aFrame)) {
+ needsSchedulePaint = true;
+ } else {
+ nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
+ while (parent &&
+ !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
+ if (aHasDisplayItem && !parent->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
+ }
+ SVGObserverUtils::InvalidateDirectRenderingObservers(parent);
+
+ // If we're inside a popup, then we need to make sure that we
+ // call schedule paint so that the NS_FRAME_UPDATE_LAYER_TREE
+ // flag gets added to the popup display root frame.
+ if (nsLayoutUtils::IsPopup(parent)) {
+ needsSchedulePaint = true;
+ break;
+ }
+ parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent);
+ }
+ if (!parent) {
+ needsSchedulePaint = true;
+ }
+ }
+ if (!aHasDisplayItem) {
+ return;
+ }
+ if (needsSchedulePaint) {
+ nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
+ SchedulePaintInternal(displayRoot, aFrame);
+ }
+ if (aFrame->HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
+ aFrame->RemoveProperty(nsIFrame::InvalidationRect());
+ aFrame->RemoveStateBits(NS_FRAME_HAS_INVALID_RECT);
+ }
+}
+
+void nsIFrame::InvalidateFrameSubtree(bool aRebuildDisplayItems /* = true */) {
+ InvalidateFrame(0, aRebuildDisplayItems);
+
+ if (HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) {
+ return;
+ }
+
+ AddStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT);
+
+ for (const auto& childList : CrossDocChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ // Don't explicitly rebuild display items for our descendants,
+ // since we should be marked and it implicitly includes all
+ // descendants.
+ child->InvalidateFrameSubtree(false);
+ }
+ }
+}
+
+void nsIFrame::ClearInvalidationStateBits() {
+ if (HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
+ for (const auto& childList : CrossDocChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ child->ClearInvalidationStateBits();
+ }
+ }
+ }
+
+ RemoveStateBits(NS_FRAME_NEEDS_PAINT | NS_FRAME_DESCENDANT_NEEDS_PAINT |
+ NS_FRAME_ALL_DESCENDANTS_NEED_PAINT);
+}
+
+bool HasRetainedDataFor(const nsIFrame* aFrame, uint32_t aDisplayItemKey) {
+ if (RefPtr<WebRenderUserData> data =
+ GetWebRenderUserData<WebRenderFallbackData>(aFrame,
+ aDisplayItemKey)) {
+ return true;
+ }
+
+ return false;
+}
+
+void nsIFrame::InvalidateFrame(uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems /* = true */) {
+ bool hasDisplayItem =
+ !aDisplayItemKey || HasRetainedDataFor(this, aDisplayItemKey);
+ InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems);
+}
+
+void nsIFrame::InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems /* = true */) {
+ if (aRect.IsEmpty()) {
+ return;
+ }
+ bool hasDisplayItem =
+ !aDisplayItemKey || HasRetainedDataFor(this, aDisplayItemKey);
+ bool alreadyInvalid = false;
+ if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
+ InvalidateFrameInternal(this, hasDisplayItem, aRebuildDisplayItems);
+ } else {
+ alreadyInvalid = true;
+ }
+
+ if (!hasDisplayItem) {
+ return;
+ }
+
+ nsRect* rect;
+ if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
+ rect = GetProperty(InvalidationRect());
+ MOZ_ASSERT(rect);
+ } else {
+ if (alreadyInvalid) {
+ return;
+ }
+ rect = new nsRect();
+ AddProperty(InvalidationRect(), rect);
+ AddStateBits(NS_FRAME_HAS_INVALID_RECT);
+ }
+
+ *rect = rect->Union(aRect);
+}
+
+/*static*/
+uint8_t nsIFrame::sLayerIsPrerenderedDataKey;
+
+bool nsIFrame::IsInvalid(nsRect& aRect) {
+ if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
+ return false;
+ }
+
+ if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
+ nsRect* rect = GetProperty(InvalidationRect());
+ NS_ASSERTION(
+ rect, "Must have an invalid rect if NS_FRAME_HAS_INVALID_RECT is set!");
+ aRect = *rect;
+ } else {
+ aRect.SetEmpty();
+ }
+ return true;
+}
+
+void nsIFrame::SchedulePaint(PaintType aType, bool aFrameChanged) {
+ if (PresShell()->IsPaintingSuppressed()) {
+ // We can't have any display items yet, and when we unsuppress we will
+ // invalidate the root frame.
+ return;
+ }
+ nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
+ InvalidateRenderingObservers(displayRoot, this, aFrameChanged);
+ SchedulePaintInternal(displayRoot, this, aType);
+}
+
+void nsIFrame::SchedulePaintWithoutInvalidatingObservers(PaintType aType) {
+ nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
+ SchedulePaintInternal(displayRoot, this, aType);
+}
+
+void nsIFrame::InvalidateLayer(DisplayItemType aDisplayItemKey,
+ const nsIntRect* aDamageRect,
+ const nsRect* aFrameDamageRect,
+ uint32_t aFlags /* = 0 */) {
+ NS_ASSERTION(aDisplayItemKey > DisplayItemType::TYPE_ZERO, "Need a key");
+
+ nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
+ InvalidateRenderingObservers(displayRoot, this, false);
+
+ // Check if frame supports WebRender's async update
+ if ((aFlags & UPDATE_IS_ASYNC) &&
+ WebRenderUserData::SupportsAsyncUpdate(this)) {
+ // WebRender does not use layer, then return nullptr.
+ return;
+ }
+
+ if (aFrameDamageRect && aFrameDamageRect->IsEmpty()) {
+ return;
+ }
+
+ // In the bug 930056, dialer app startup but not shown on the
+ // screen because sometimes we don't have any retainned data
+ // for remote type displayitem and thus Repaint event is not
+ // triggered. So, always invalidate in this case.
+ DisplayItemType displayItemKey = aDisplayItemKey;
+ if (aDisplayItemKey == DisplayItemType::TYPE_REMOTE) {
+ displayItemKey = DisplayItemType::TYPE_ZERO;
+ }
+
+ if (aFrameDamageRect) {
+ InvalidateFrameWithRect(*aFrameDamageRect,
+ static_cast<uint32_t>(displayItemKey));
+ } else {
+ InvalidateFrame(static_cast<uint32_t>(displayItemKey));
+ }
+}
+
+static nsRect ComputeEffectsRect(nsIFrame* aFrame, const nsRect& aOverflowRect,
+ const nsSize& aNewSize) {
+ nsRect r = aOverflowRect;
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // For SVG frames, we only need to account for filters.
+ // TODO: We could also take account of clipPath and mask to reduce the
+ // ink overflow, but that's not essential.
+ if (aFrame->StyleEffects()->HasFilters()) {
+ SetOrUpdateRectValuedProperty(aFrame, nsIFrame::PreEffectsBBoxProperty(),
+ r);
+ r = SVGUtils::GetPostFilterInkOverflowRect(aFrame, aOverflowRect);
+ }
+ return r;
+ }
+
+ // box-shadow
+ r.UnionRect(r, nsLayoutUtils::GetBoxShadowRectForFrame(aFrame, aNewSize));
+
+ // border-image-outset.
+ // We need to include border-image-outset because it can cause the
+ // border image to be drawn beyond the border box.
+
+ // (1) It's important we not check whether there's a border-image
+ // since the style hint for a change in border image doesn't cause
+ // reflow, and that's probably more important than optimizing the
+ // overflow areas for the silly case of border-image-outset without
+ // border-image
+ // (2) It's important that we not check whether the border-image
+ // is actually loaded, since that would require us to reflow when
+ // the image loads.
+ const nsStyleBorder* styleBorder = aFrame->StyleBorder();
+ nsMargin outsetMargin = styleBorder->GetImageOutset();
+
+ if (outsetMargin != nsMargin(0, 0, 0, 0)) {
+ nsRect outsetRect(nsPoint(0, 0), aNewSize);
+ outsetRect.Inflate(outsetMargin);
+ r.UnionRect(r, outsetRect);
+ }
+
+ // Note that we don't remove the outlineInnerRect if a frame loses outline
+ // style. That would require an extra property lookup for every frame,
+ // or a new frame state bit to track whether a property had been stored,
+ // or something like that. It's not worth doing that here. At most it's
+ // only one heap-allocated rect per frame and it will be cleaned up when
+ // the frame dies.
+
+ if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame)) {
+ SetOrUpdateRectValuedProperty(aFrame, nsIFrame::PreEffectsBBoxProperty(),
+ r);
+ r = SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(aFrame, r);
+ }
+
+ return r;
+}
+
+void nsIFrame::SetPosition(const nsPoint& aPt) {
+ if (mRect.TopLeft() == aPt) {
+ return;
+ }
+ mRect.MoveTo(aPt);
+ MarkNeedsDisplayItemRebuild();
+}
+
+void nsIFrame::MovePositionBy(const nsPoint& aTranslation) {
+ nsPoint position = GetNormalPosition() + aTranslation;
+
+ const nsMargin* computedOffsets = nullptr;
+ if (IsRelativelyOrStickyPositioned()) {
+ computedOffsets = GetProperty(nsIFrame::ComputedOffsetProperty());
+ }
+ ReflowInput::ApplyRelativePositioning(
+ this, computedOffsets ? *computedOffsets : nsMargin(), &position);
+ SetPosition(position);
+}
+
+nsRect nsIFrame::GetNormalRect() const {
+ // It might be faster to first check
+ // StyleDisplay()->IsRelativelyPositionedStyle().
+ bool hasProperty;
+ nsPoint normalPosition = GetProperty(NormalPositionProperty(), &hasProperty);
+ if (hasProperty) {
+ return nsRect(normalPosition, GetSize());
+ }
+ return GetRect();
+}
+
+nsRect nsIFrame::GetBoundingClientRect() {
+ return nsLayoutUtils::GetAllInFlowRectsUnion(
+ this, nsLayoutUtils::GetContainingBlockForClientRect(this),
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+}
+
+nsPoint nsIFrame::GetPositionIgnoringScrolling() const {
+ return GetParent() ? GetParent()->GetPositionOfChildIgnoringScrolling(this)
+ : GetPosition();
+}
+
+nsRect nsIFrame::GetOverflowRect(OverflowType aType) const {
+ // Note that in some cases the overflow area might not have been
+ // updated (yet) to reflect any outline set on the frame or the area
+ // of child frames. That's OK because any reflow that updates these
+ // areas will invalidate the appropriate area, so any (mis)uses of
+ // this method will be fixed up.
+
+ if (mOverflow.mType == OverflowStorageType::Large) {
+ // there is an overflow rect, and it's not stored as deltas but as
+ // a separately-allocated rect
+ return GetOverflowAreasProperty()->Overflow(aType);
+ }
+
+ if (aType == OverflowType::Ink &&
+ mOverflow.mType != OverflowStorageType::None) {
+ return InkOverflowFromDeltas();
+ }
+
+ return GetRectRelativeToSelf();
+}
+
+OverflowAreas nsIFrame::GetOverflowAreas() const {
+ if (mOverflow.mType == OverflowStorageType::Large) {
+ // there is an overflow rect, and it's not stored as deltas but as
+ // a separately-allocated rect
+ return *GetOverflowAreasProperty();
+ }
+
+ return OverflowAreas(InkOverflowFromDeltas(),
+ nsRect(nsPoint(0, 0), GetSize()));
+}
+
+OverflowAreas nsIFrame::GetOverflowAreasRelativeToSelf() const {
+ if (IsTransformed()) {
+ if (OverflowAreas* preTransformOverflows =
+ GetProperty(PreTransformOverflowAreasProperty())) {
+ return *preTransformOverflows;
+ }
+ }
+ return GetOverflowAreas();
+}
+
+OverflowAreas nsIFrame::GetOverflowAreasRelativeToParent() const {
+ return GetOverflowAreas() + GetPosition();
+}
+
+OverflowAreas nsIFrame::GetActualAndNormalOverflowAreasRelativeToParent()
+ const {
+ if (MOZ_LIKELY(!IsRelativelyOrStickyPositioned())) {
+ return GetOverflowAreasRelativeToParent();
+ }
+
+ const OverflowAreas overflows = GetOverflowAreas();
+ OverflowAreas actualAndNormalOverflows = overflows + GetPosition();
+ actualAndNormalOverflows.UnionWith(overflows + GetNormalPosition());
+ return actualAndNormalOverflows;
+}
+
+nsRect nsIFrame::ScrollableOverflowRectRelativeToParent() const {
+ return ScrollableOverflowRect() + GetPosition();
+}
+
+nsRect nsIFrame::InkOverflowRectRelativeToParent() const {
+ return InkOverflowRect() + GetPosition();
+}
+
+nsRect nsIFrame::ScrollableOverflowRectRelativeToSelf() const {
+ if (IsTransformed()) {
+ if (OverflowAreas* preTransformOverflows =
+ GetProperty(PreTransformOverflowAreasProperty())) {
+ return preTransformOverflows->ScrollableOverflow();
+ }
+ }
+ return ScrollableOverflowRect();
+}
+
+nsRect nsIFrame::InkOverflowRectRelativeToSelf() const {
+ if (IsTransformed()) {
+ if (OverflowAreas* preTransformOverflows =
+ GetProperty(PreTransformOverflowAreasProperty())) {
+ return preTransformOverflows->InkOverflow();
+ }
+ }
+ return InkOverflowRect();
+}
+
+nsRect nsIFrame::PreEffectsInkOverflowRect() const {
+ nsRect* r = GetProperty(nsIFrame::PreEffectsBBoxProperty());
+ return r ? *r : InkOverflowRectRelativeToSelf();
+}
+
+bool nsIFrame::UpdateOverflow() {
+ MOZ_ASSERT(FrameMaintainsOverflow(),
+ "Non-display SVG do not maintain ink overflow rects");
+
+ nsRect rect(nsPoint(0, 0), GetSize());
+ OverflowAreas overflowAreas(rect, rect);
+
+ if (!ComputeCustomOverflow(overflowAreas)) {
+ // If updating overflow wasn't supported by this frame, then it should
+ // have scheduled any necessary reflows. We can return false to say nothing
+ // changed, and wait for reflow to correct it.
+ return false;
+ }
+
+ UnionChildOverflow(overflowAreas);
+
+ if (FinishAndStoreOverflow(overflowAreas, GetSize())) {
+ if (nsView* view = GetView()) {
+ // Make sure the frame's view is properly sized.
+ nsViewManager* vm = view->GetViewManager();
+ vm->ResizeView(view, overflowAreas.InkOverflow(), true);
+ }
+
+ return true;
+ }
+
+ // Frames that combine their 3d transform with their ancestors
+ // only compute a pre-transform overflow rect, and then contribute
+ // to the normal overflow rect of the preserve-3d root. Always return
+ // true here so that we propagate changes up to the root for final
+ // calculation.
+ return Combines3DTransformWithAncestors();
+}
+
+/* virtual */
+bool nsIFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
+ return true;
+}
+
+bool nsIFrame::DoesClipChildrenInBothAxes() const {
+ if (IsScrollContainer()) {
+ return true;
+ }
+ const nsStyleDisplay* display = StyleDisplay();
+ if (display->IsContainPaint() && SupportsContainLayoutAndPaint()) {
+ return true;
+ }
+ return display->mOverflowX == StyleOverflow::Clip &&
+ display->mOverflowY == StyleOverflow::Clip;
+}
+
+/* virtual */
+void nsIFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
+ if (!DoesClipChildrenInBothAxes()) {
+ nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas);
+ }
+}
+
+// Return true if this form control element's preferred size property (but not
+// percentage max size property) contains a percentage value that should be
+// resolved against zero when calculating its min-content contribution in the
+// corresponding axis.
+//
+// For proper replaced elements, the percentage value in both their max size
+// property or preferred size property should be resolved against zero. This is
+// handled in IsPercentageResolvedAgainstZero().
+inline static bool FormControlShrinksForPercentSize(const nsIFrame* aFrame) {
+ if (!aFrame->IsReplaced()) {
+ // Quick test to reject most frames.
+ return false;
+ }
+
+ LayoutFrameType fType = aFrame->Type();
+ if (fType == LayoutFrameType::Meter || fType == LayoutFrameType::Progress ||
+ fType == LayoutFrameType::Range) {
+ // progress, meter and range do have this shrinking behavior
+ // FIXME: Maybe these should be nsIFormControlFrame?
+ return true;
+ }
+
+ if (!static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
+ // Not a form control. This includes fieldsets, which do not
+ // shrink.
+ return false;
+ }
+
+ if (fType == LayoutFrameType::GfxButtonControl ||
+ fType == LayoutFrameType::HTMLButtonControl) {
+ // Buttons don't have this shrinking behavior. (Note that color
+ // inputs do, even though they inherit from button, so we can't use
+ // do_QueryFrame here.)
+ return false;
+ }
+
+ return true;
+}
+
+bool nsIFrame::IsPercentageResolvedAgainstZero(
+ const StyleSize& aStyleSize, const StyleMaxSize& aStyleMaxSize) const {
+ const bool sizeHasPercent = aStyleSize.HasPercent();
+ return ((sizeHasPercent || aStyleMaxSize.HasPercent()) &&
+ HasReplacedSizing()) ||
+ (sizeHasPercent && FormControlShrinksForPercentSize(this));
+}
+
+// Summary of the Cyclic-Percentage Intrinsic Size Contribution Rules:
+//
+// Element Type | Replaced | Non-replaced
+// Contribution Type | min-content max-content | min-content max-content
+// ---------------------------------------------------------------------------
+// min size | zero zero | zero zero
+// max & preferred size | zero initial | initial initial
+//
+// https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution
+bool nsIFrame::IsPercentageResolvedAgainstZero(const LengthPercentage& aSize,
+ SizeProperty aProperty) const {
+ // Early return to avoid calling the virtual function, IsFrameOfType().
+ if (aProperty == SizeProperty::MinSize) {
+ return true;
+ }
+
+ const bool hasPercentOnReplaced = aSize.HasPercent() && HasReplacedSizing();
+ if (aProperty == SizeProperty::MaxSize) {
+ return hasPercentOnReplaced;
+ }
+
+ MOZ_ASSERT(aProperty == SizeProperty::Size);
+ return hasPercentOnReplaced ||
+ (aSize.HasPercent() && FormControlShrinksForPercentSize(this));
+}
+
+bool nsIFrame::IsBlockWrapper() const {
+ auto pseudoType = Style()->GetPseudoType();
+ return pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
+ pseudoType == PseudoStyleType::buttonContent ||
+ pseudoType == PseudoStyleType::cellContent ||
+ pseudoType == PseudoStyleType::columnSpanWrapper;
+}
+
+bool nsIFrame::IsBlockFrameOrSubclass() const {
+ const nsBlockFrame* thisAsBlock = do_QueryFrame(this);
+ return !!thisAsBlock;
+}
+
+bool nsIFrame::IsImageFrameOrSubclass() const {
+ const nsImageFrame* asImage = do_QueryFrame(this);
+ return !!asImage;
+}
+
+bool nsIFrame::IsSubgrid() const {
+ return IsGridContainerFrame() &&
+ static_cast<const nsGridContainerFrame*>(this)->IsSubgrid();
+}
+
+static nsIFrame* GetNearestBlockContainer(nsIFrame* frame) {
+ while (!frame->IsBlockContainer()) {
+ frame = frame->GetParent();
+ NS_ASSERTION(
+ frame,
+ "How come we got to the root frame without seeing a containing block?");
+ }
+ return frame;
+}
+
+bool nsIFrame::IsBlockContainer() const {
+ // The block wrappers we use to wrap blocks inside inlines aren't
+ // described in the CSS spec. We need to make them not be containing
+ // blocks.
+ // Since the parent of such a block is either a normal block or
+ // another such pseudo, this shouldn't cause anything bad to happen.
+ // Also the anonymous blocks inside table cells are not containing blocks.
+ //
+ // If we ever start skipping table row groups from being containing blocks,
+ // you need to remove the StickyScrollContainer hack referencing bug 1421660.
+ return !IsLineParticipant() && !IsBlockWrapper() && !IsSubgrid() &&
+ // Table rows are not containing blocks either
+ !IsTableRowFrame();
+}
+
+nsIFrame* nsIFrame::GetContainingBlock(
+ uint32_t aFlags, const nsStyleDisplay* aStyleDisplay) const {
+ MOZ_ASSERT(aStyleDisplay == StyleDisplay());
+
+ // Keep this in sync with MightBeContainingBlockFor in ReflowInput.cpp.
+
+ if (!GetParent()) {
+ return nullptr;
+ }
+ // MathML frames might have absolute positioning style, but they would
+ // still be in-flow. So we have to check to make sure that the frame
+ // is really out-of-flow too.
+ nsIFrame* f;
+ if (IsAbsolutelyPositioned(aStyleDisplay)) {
+ f = GetParent(); // the parent is always the containing block
+ } else {
+ f = GetNearestBlockContainer(GetParent());
+ }
+
+ if (aFlags & SKIP_SCROLLED_FRAME && f &&
+ f->Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
+ f = f->GetParent();
+ }
+ return f;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+
+Maybe<uint32_t> nsIFrame::ContentIndexInContainer(const nsIFrame* aFrame) {
+ if (nsIContent* content = aFrame->GetContent()) {
+ return content->ComputeIndexInParentContent();
+ }
+ return Nothing();
+}
+
+nsAutoCString nsIFrame::ListTag() const {
+ nsAutoString tmp;
+ GetFrameName(tmp);
+
+ nsAutoCString tag;
+ tag += NS_ConvertUTF16toUTF8(tmp);
+ tag += nsPrintfCString("@%p", static_cast<const void*>(this));
+ return tag;
+}
+
+std::string nsIFrame::ConvertToString(const LogicalRect& aRect,
+ const WritingMode aWM, ListFlags aFlags) {
+ if (aFlags.contains(ListFlag::DisplayInCSSPixels)) {
+ // Abuse CSSRect to store all LogicalRect's dimensions in CSS pixels.
+ return ToString(mozilla::CSSRect(CSSPixel::FromAppUnits(aRect.IStart(aWM)),
+ CSSPixel::FromAppUnits(aRect.BStart(aWM)),
+ CSSPixel::FromAppUnits(aRect.ISize(aWM)),
+ CSSPixel::FromAppUnits(aRect.BSize(aWM))));
+ }
+ return ToString(aRect);
+}
+
+std::string nsIFrame::ConvertToString(const LogicalSize& aSize,
+ const WritingMode aWM, ListFlags aFlags) {
+ if (aFlags.contains(ListFlag::DisplayInCSSPixels)) {
+ // Abuse CSSSize to store all LogicalSize's dimensions in CSS pixels.
+ return ToString(CSSSize(CSSPixel::FromAppUnits(aSize.ISize(aWM)),
+ CSSPixel::FromAppUnits(aSize.BSize(aWM))));
+ }
+ return ToString(aSize);
+}
+
+// Debugging
+void nsIFrame::ListGeneric(nsACString& aTo, const char* aPrefix,
+ ListFlags aFlags) const {
+ aTo += aPrefix;
+ aTo += ListTag();
+ if (HasView()) {
+ aTo += nsPrintfCString(" [view=%p]", static_cast<void*>(GetView()));
+ }
+ if (GetParent()) {
+ aTo += nsPrintfCString(" parent=%p", static_cast<void*>(GetParent()));
+ }
+ if (GetNextSibling()) {
+ aTo += nsPrintfCString(" next=%p", static_cast<void*>(GetNextSibling()));
+ }
+ if (GetPrevContinuation()) {
+ bool fluid = GetPrevInFlow() == GetPrevContinuation();
+ aTo += nsPrintfCString(" prev-%s=%p", fluid ? "in-flow" : "continuation",
+ static_cast<void*>(GetPrevContinuation()));
+ }
+ if (GetNextContinuation()) {
+ bool fluid = GetNextInFlow() == GetNextContinuation();
+ aTo += nsPrintfCString(" next-%s=%p", fluid ? "in-flow" : "continuation",
+ static_cast<void*>(GetNextContinuation()));
+ }
+ if (const nsAtom* const autoPageValue =
+ GetProperty(AutoPageValueProperty())) {
+ aTo += " AutoPage=";
+ aTo += nsAtomCString(autoPageValue);
+ }
+ if (const nsIFrame::PageValues* const pageValues =
+ GetProperty(PageValuesProperty())) {
+ aTo += " PageValues={";
+ if (pageValues->mStartPageValue) {
+ aTo += nsAtomCString(pageValues->mStartPageValue);
+ } else {
+ aTo += "<null>";
+ }
+ aTo += ", ";
+ if (pageValues->mEndPageValue) {
+ aTo += nsAtomCString(pageValues->mEndPageValue);
+ } else {
+ aTo += "<null>";
+ }
+ aTo += "}";
+ }
+ void* IBsibling = GetProperty(IBSplitSibling());
+ if (IBsibling) {
+ aTo += nsPrintfCString(" IBSplitSibling=%p", IBsibling);
+ }
+ void* IBprevsibling = GetProperty(IBSplitPrevSibling());
+ if (IBprevsibling) {
+ aTo += nsPrintfCString(" IBSplitPrevSibling=%p", IBprevsibling);
+ }
+ if (nsLayoutUtils::FontSizeInflationEnabled(PresContext())) {
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
+ aTo += nsPrintfCString(" FFR");
+ if (nsFontInflationData* data =
+ nsFontInflationData::FindFontInflationDataFor(this)) {
+ aTo += nsPrintfCString(
+ ",enabled=%s,UIS=%s", data->InflationEnabled() ? "yes" : "no",
+ ConvertToString(data->UsableISize(), aFlags).c_str());
+ }
+ }
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
+ aTo += nsPrintfCString(" FIC");
+ }
+ aTo += nsPrintfCString(" FI=%f", nsLayoutUtils::FontSizeInflationFor(this));
+ }
+ aTo += nsPrintfCString(" %s", ConvertToString(mRect, aFlags).c_str());
+
+ mozilla::WritingMode wm = GetWritingMode();
+ if (wm.IsVertical() || wm.IsBidiRTL()) {
+ aTo +=
+ nsPrintfCString(" wm=%s logical-size=(%s)", ToString(wm).c_str(),
+ ConvertToString(GetLogicalSize(), wm, aFlags).c_str());
+ }
+
+ nsIFrame* parent = GetParent();
+ if (parent) {
+ WritingMode pWM = parent->GetWritingMode();
+ if (pWM.IsVertical() || pWM.IsBidiRTL()) {
+ nsSize containerSize = parent->mRect.Size();
+ LogicalRect lr(pWM, mRect, containerSize);
+ aTo += nsPrintfCString(" parent-wm=%s cs=(%s) logical-rect=%s",
+ ToString(pWM).c_str(),
+ ConvertToString(containerSize, aFlags).c_str(),
+ ConvertToString(lr, pWM, aFlags).c_str());
+ }
+ }
+ nsIFrame* f = const_cast<nsIFrame*>(this);
+ if (f->HasOverflowAreas()) {
+ nsRect io = f->InkOverflowRect();
+ if (!io.IsEqualEdges(mRect)) {
+ aTo += nsPrintfCString(" ink-overflow=%s",
+ ConvertToString(io, aFlags).c_str());
+ }
+ nsRect so = f->ScrollableOverflowRect();
+ if (!so.IsEqualEdges(mRect)) {
+ aTo += nsPrintfCString(" scr-overflow=%s",
+ ConvertToString(so, aFlags).c_str());
+ }
+ }
+ if (OverflowAreas* preTransformOverflows =
+ f->GetProperty(PreTransformOverflowAreasProperty())) {
+ nsRect io = preTransformOverflows->InkOverflow();
+ if (!io.IsEqualEdges(mRect) &&
+ (!f->HasOverflowAreas() || !io.IsEqualEdges(f->InkOverflowRect()))) {
+ aTo += nsPrintfCString(" pre-transform-ink-overflow=%s",
+ ConvertToString(io, aFlags).c_str());
+ }
+ nsRect so = preTransformOverflows->ScrollableOverflow();
+ if (!so.IsEqualEdges(mRect) &&
+ (!f->HasOverflowAreas() ||
+ !so.IsEqualEdges(f->ScrollableOverflowRect()))) {
+ aTo += nsPrintfCString(" pre-transform-scr-overflow=%s",
+ ConvertToString(so, aFlags).c_str());
+ }
+ }
+ bool hasNormalPosition;
+ nsPoint normalPosition = GetNormalPosition(&hasNormalPosition);
+ if (hasNormalPosition) {
+ aTo += nsPrintfCString(" normal-position=%s",
+ ConvertToString(normalPosition, aFlags).c_str());
+ }
+ if (HasProperty(BidiDataProperty())) {
+ FrameBidiData bidi = GetBidiData();
+ aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel.Value(),
+ bidi.embeddingLevel.Value(),
+ bidi.precedingControl.Value());
+ }
+ if (IsTransformed()) {
+ aTo += nsPrintfCString(" transformed");
+ }
+ if (ChildrenHavePerspective()) {
+ aTo += nsPrintfCString(" perspective");
+ }
+ if (Extend3DContext()) {
+ aTo += nsPrintfCString(" extend-3d");
+ }
+ if (Combines3DTransformWithAncestors()) {
+ aTo += nsPrintfCString(" combines-3d-transform-with-ancestors");
+ }
+ if (mContent) {
+ aTo += nsPrintfCString(" [content=%p]", static_cast<void*>(mContent));
+ }
+ aTo += nsPrintfCString(" [cs=%p", static_cast<void*>(mComputedStyle));
+ if (mComputedStyle) {
+ auto pseudoType = mComputedStyle->GetPseudoType();
+ aTo += ToString(pseudoType).c_str();
+ }
+ aTo += "]";
+
+ auto contentVisibility = StyleDisplay()->ContentVisibility(*this);
+ if (contentVisibility != StyleContentVisibility::Visible) {
+ aTo += nsPrintfCString(" [content-visibility=");
+ if (contentVisibility == StyleContentVisibility::Auto) {
+ aTo += "auto, "_ns;
+ } else if (contentVisibility == StyleContentVisibility::Hidden) {
+ aTo += "hiden, "_ns;
+ }
+
+ if (HidesContent()) {
+ aTo += "HidesContent=hidden"_ns;
+ } else {
+ aTo += "HidesContent=visibile"_ns;
+ }
+ aTo += "]";
+ }
+
+ if (IsFrameModified()) {
+ aTo += nsPrintfCString(" modified");
+ }
+
+ if (HasModifiedDescendants()) {
+ aTo += nsPrintfCString(" has-modified-descendants");
+ }
+}
+
+void nsIFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+ fprintf_stderr(out, "%s\n", str.get());
+}
+
+void nsIFrame::ListTextRuns(FILE* out) const {
+ nsTHashSet<const void*> seen;
+ ListTextRuns(out, seen);
+}
+
+void nsIFrame::ListTextRuns(FILE* out, nsTHashSet<const void*>& aSeen) const {
+ for (const auto& childList : ChildLists()) {
+ for (const nsIFrame* kid : childList.mList) {
+ kid->ListTextRuns(out, aSeen);
+ }
+ }
+}
+
+void nsIFrame::ListMatchedRules(FILE* out, const char* aPrefix) const {
+ nsTArray<const StyleLockedStyleRule*> rawRuleList;
+ Servo_ComputedValues_GetStyleRuleList(mComputedStyle, &rawRuleList);
+ for (const StyleLockedStyleRule* rawRule : rawRuleList) {
+ nsAutoCString ruleText;
+ Servo_StyleRule_GetCssText(rawRule, &ruleText);
+ fprintf_stderr(out, "%s%s\n", aPrefix, ruleText.get());
+ }
+}
+
+void nsIFrame::ListWithMatchedRules(FILE* out, const char* aPrefix) const {
+ fprintf_stderr(out, "%s%s\n", aPrefix, ListTag().get());
+
+ nsCString rulePrefix;
+ rulePrefix += aPrefix;
+ rulePrefix += " ";
+ ListMatchedRules(out, rulePrefix.get());
+}
+
+nsresult nsIFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Frame"_ns, aResult);
+}
+
+nsresult nsIFrame::MakeFrameName(const nsAString& aType,
+ nsAString& aResult) const {
+ aResult = aType;
+ if (mContent && !mContent->IsText()) {
+ nsAutoString buf;
+ mContent->NodeInfo()->NameAtom()->ToString(buf);
+ if (nsAtom* id = mContent->GetID()) {
+ buf.AppendLiteral(" id=");
+ buf.Append(nsDependentAtomString(id));
+ }
+ if (IsSubDocumentFrame()) {
+ nsAutoString src;
+ mContent->AsElement()->GetAttr(nsGkAtoms::src, src);
+ buf.AppendLiteral(" src=");
+ buf.Append(src);
+ }
+ aResult.Append('(');
+ aResult.Append(buf);
+ aResult.Append(')');
+ }
+ aResult.Append('(');
+ Maybe<uint32_t> index = ContentIndexInContainer(this);
+ if (index.isSome()) {
+ aResult.AppendInt(*index);
+ } else {
+ aResult.AppendInt(-1);
+ }
+ aResult.Append(')');
+ return NS_OK;
+}
+
+void nsIFrame::DumpFrameTree() const {
+ PresShell()->GetRootFrame()->List(stderr);
+}
+
+void nsIFrame::DumpFrameTreeInCSSPixels() const {
+ PresShell()->GetRootFrame()->List(stderr, "", ListFlag::DisplayInCSSPixels);
+}
+
+void nsIFrame::DumpFrameTreeLimited() const { List(stderr); }
+void nsIFrame::DumpFrameTreeLimitedInCSSPixels() const {
+ List(stderr, "", ListFlag::DisplayInCSSPixels);
+}
+
+#endif
+
+bool nsIFrame::IsVisibleForPainting() const {
+ return StyleVisibility()->IsVisible();
+}
+
+bool nsIFrame::IsVisibleOrCollapsedForPainting() const {
+ return StyleVisibility()->IsVisibleOrCollapsed();
+}
+
+/* virtual */
+bool nsIFrame::IsEmpty() {
+ return IsHiddenByContentVisibilityOfInFlowParentForLayout();
+}
+
+bool nsIFrame::CachedIsEmpty() {
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
+ IsHiddenByContentVisibilityOfInFlowParentForLayout(),
+ "Must only be called on reflowed lines or those hidden by "
+ "content-visibility.");
+ return IsEmpty();
+}
+
+/* virtual */
+bool nsIFrame::IsSelfEmpty() {
+ return IsHiddenByContentVisibilityOfInFlowParentForLayout();
+}
+
+nsresult nsIFrame::GetSelectionController(nsPresContext* aPresContext,
+ nsISelectionController** aSelCon) {
+ if (!aPresContext || !aSelCon) return NS_ERROR_INVALID_ARG;
+
+ nsIFrame* frame = this;
+ while (frame && frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION)) {
+ nsITextControlFrame* tcf = do_QueryFrame(frame);
+ if (tcf) {
+ return tcf->GetOwnedSelectionController(aSelCon);
+ }
+ frame = frame->GetParent();
+ }
+
+ *aSelCon = do_AddRef(aPresContext->PresShell()).take();
+ return NS_OK;
+}
+
+already_AddRefed<nsFrameSelection> nsIFrame::GetFrameSelection() {
+ RefPtr<nsFrameSelection> fs =
+ const_cast<nsFrameSelection*>(GetConstFrameSelection());
+ return fs.forget();
+}
+
+const nsFrameSelection* nsIFrame::GetConstFrameSelection() const {
+ nsIFrame* frame = const_cast<nsIFrame*>(this);
+ while (frame && frame->HasAnyStateBits(NS_FRAME_INDEPENDENT_SELECTION)) {
+ nsITextControlFrame* tcf = do_QueryFrame(frame);
+ if (tcf) {
+ return tcf->GetOwnedFrameSelection();
+ }
+ frame = frame->GetParent();
+ }
+
+ return PresShell()->ConstFrameSelection();
+}
+
+bool nsIFrame::IsFrameSelected() const {
+ NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
+ "use the public IsSelected() instead");
+ return GetContent()->IsSelected(0, GetContent()->GetChildCount());
+}
+
+nsresult nsIFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
+ MOZ_ASSERT(outPoint != nullptr, "Null parameter");
+ nsRect contentRect = GetContentRectRelativeToSelf();
+ nsPoint pt = contentRect.TopLeft();
+ if (mContent) {
+ nsIContent* newContent = mContent->GetParent();
+ if (newContent) {
+ const int32_t newOffset = newContent->ComputeIndexOf_Deprecated(mContent);
+
+ // Find the direction of the frame from the EmbeddingLevelProperty,
+ // which is the resolved bidi level set in
+ // nsBidiPresUtils::ResolveParagraph (odd levels = right-to-left).
+ // If the embedding level isn't set, just use the CSS direction
+ // property.
+ bool hasBidiData;
+ FrameBidiData bidiData = GetProperty(BidiDataProperty(), &hasBidiData);
+ bool isRTL = hasBidiData
+ ? bidiData.embeddingLevel.IsRTL()
+ : StyleVisibility()->mDirection == StyleDirection::Rtl;
+ if ((!isRTL && inOffset > newOffset) ||
+ (isRTL && inOffset <= newOffset)) {
+ pt = contentRect.TopRight();
+ }
+ }
+ }
+ *outPoint = pt;
+ return NS_OK;
+}
+
+nsresult nsIFrame::GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength,
+ nsTArray<nsRect>& aOutRect) {
+ /* no text */
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsIFrame::GetChildFrameContainingOffset(int32_t inContentOffset,
+ bool inHint,
+ int32_t* outFrameContentOffset,
+ nsIFrame** outChildFrame) {
+ MOZ_ASSERT(outChildFrame && outFrameContentOffset, "Null parameter");
+ *outFrameContentOffset = (int32_t)inHint;
+ // the best frame to reflect any given offset would be a visible frame if
+ // possible i.e. we are looking for a valid frame to place the blinking caret
+ nsRect rect = GetRect();
+ if (!rect.width || !rect.height) {
+ // if we have a 0 width or height then lets look for another frame that
+ // possibly has the same content. If we have no frames in flow then just
+ // let us return 'this' frame
+ nsIFrame* nextFlow = GetNextInFlow();
+ if (nextFlow)
+ return nextFlow->GetChildFrameContainingOffset(
+ inContentOffset, inHint, outFrameContentOffset, outChildFrame);
+ }
+ *outChildFrame = this;
+ return NS_OK;
+}
+
+//
+// What I've pieced together about this routine:
+// Starting with a block frame (from which a line frame can be gotten)
+// and a line number, drill down and get the first/last selectable
+// frame on that line, depending on aPos->mDirection.
+// aOutSideLimit != 0 means ignore aLineStart, instead work from
+// the end (if > 0) or beginning (if < 0).
+//
+static nsresult GetNextPrevLineFromBlockFrame(PeekOffsetStruct* aPos,
+ nsIFrame* aBlockFrame,
+ int32_t aLineStart,
+ int8_t aOutSideLimit) {
+ MOZ_ASSERT(aPos);
+ MOZ_ASSERT(aBlockFrame);
+
+ nsPresContext* pc = aBlockFrame->PresContext();
+
+ // magic numbers: aLineStart will be -1 for end of block, 0 will be start of
+ // block.
+
+ aPos->mResultFrame = nullptr;
+ aPos->mResultContent = nullptr;
+ aPos->mAttach = aPos->mDirection == eDirNext ? CaretAssociationHint::After
+ : CaretAssociationHint::Before;
+
+ AutoAssertNoDomMutations guard;
+ nsILineIterator* it = aBlockFrame->GetLineIterator();
+ if (!it) {
+ return NS_ERROR_FAILURE;
+ }
+ int32_t searchingLine = aLineStart;
+ int32_t countLines = it->GetNumLines();
+ if (aOutSideLimit > 0) { // start at end
+ searchingLine = countLines;
+ } else if (aOutSideLimit < 0) { // start at beginning
+ searchingLine = -1; //"next" will be 0
+ } else if ((aPos->mDirection == eDirPrevious && searchingLine == 0) ||
+ (aPos->mDirection == eDirNext &&
+ searchingLine >= (countLines - 1))) {
+ // Not found.
+ return NS_ERROR_FAILURE;
+ }
+ nsIFrame* resultFrame = nullptr;
+ nsIFrame* farStoppingFrame = nullptr; // we keep searching until we find a
+ // "this" frame then we go to next line
+ nsIFrame* nearStoppingFrame = nullptr; // if we are backing up from edge,
+ // stop here
+ nsIFrame* firstFrame;
+ nsIFrame* lastFrame;
+ bool isBeforeFirstFrame, isAfterLastFrame;
+ bool found = false;
+
+ while (!found) {
+ if (aPos->mDirection == eDirPrevious)
+ searchingLine--;
+ else
+ searchingLine++;
+ if ((aPos->mDirection == eDirPrevious && searchingLine < 0) ||
+ (aPos->mDirection == eDirNext && searchingLine >= countLines)) {
+ // we need to jump to new block frame.
+ return NS_ERROR_FAILURE;
+ }
+ auto line = it->GetLine(searchingLine).unwrap();
+ if (!line.mNumFramesOnLine) {
+ continue;
+ }
+ lastFrame = firstFrame = line.mFirstFrameOnLine;
+ for (int32_t lineFrameCount = line.mNumFramesOnLine; lineFrameCount > 1;
+ lineFrameCount--) {
+ lastFrame = lastFrame->GetNextSibling();
+ if (!lastFrame) {
+ NS_ERROR("GetLine promised more frames than could be found");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ nsIFrame::GetLastLeaf(&lastFrame);
+
+ if (aPos->mDirection == eDirNext) {
+ nearStoppingFrame = firstFrame;
+ farStoppingFrame = lastFrame;
+ } else {
+ nearStoppingFrame = lastFrame;
+ farStoppingFrame = firstFrame;
+ }
+ nsPoint offset;
+ nsView* view; // used for call of get offset from view
+ aBlockFrame->GetOffsetFromView(offset, &view);
+ nsPoint newDesiredPos =
+ aPos->mDesiredCaretPos -
+ offset; // get desired position into blockframe coords
+ nsresult rv = it->FindFrameAt(searchingLine, newDesiredPos, &resultFrame,
+ &isBeforeFirstFrame, &isAfterLastFrame);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (resultFrame) {
+ // check to see if this is ANOTHER blockframe inside the other one if so
+ // then call into its lines
+ if (resultFrame->CanProvideLineIterator()) {
+ aPos->mResultFrame = resultFrame;
+ return NS_OK;
+ }
+ // resultFrame is not a block frame
+ Maybe<nsFrameIterator> frameIterator;
+ frameIterator.emplace(
+ pc, resultFrame, nsFrameIterator::Type::PostOrder,
+ false, // aVisual
+ aPos->mOptions.contains(PeekOffsetOption::StopAtScroller),
+ false, // aFollowOOFs
+ false // aSkipPopupChecks
+ );
+
+ auto FoundValidFrame = [aPos](const nsIFrame::ContentOffsets& aOffsets,
+ const nsIFrame* aFrame) {
+ if (!aOffsets.content) {
+ return false;
+ }
+ if (!aFrame->IsSelectable(nullptr)) {
+ return false;
+ }
+ if (aPos->mOptions.contains(PeekOffsetOption::ForceEditableRegion) &&
+ !aOffsets.content->IsEditable()) {
+ return false;
+ }
+ return true;
+ };
+
+ nsIFrame* storeOldResultFrame = resultFrame;
+ while (!found) {
+ nsPoint point;
+ nsRect tempRect = resultFrame->GetRect();
+ nsPoint offset;
+ nsView* view; // used for call of get offset from view
+ resultFrame->GetOffsetFromView(offset, &view);
+ if (!view) {
+ return NS_ERROR_FAILURE;
+ }
+ if (resultFrame->GetWritingMode().IsVertical()) {
+ point.y = aPos->mDesiredCaretPos.y;
+ point.x = tempRect.width + offset.x;
+ } else {
+ point.y = tempRect.height + offset.y;
+ point.x = aPos->mDesiredCaretPos.x;
+ }
+
+ if (!resultFrame->HasView()) {
+ nsView* view;
+ nsPoint offset;
+ resultFrame->GetOffsetFromView(offset, &view);
+ nsIFrame::ContentOffsets offsets =
+ resultFrame->GetContentOffsetsFromPoint(
+ point - offset, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
+ aPos->mResultContent = offsets.content;
+ aPos->mContentOffset = offsets.offset;
+ aPos->mAttach = offsets.associate;
+ if (FoundValidFrame(offsets, resultFrame)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (aPos->mDirection == eDirPrevious &&
+ resultFrame == farStoppingFrame) {
+ break;
+ }
+ if (aPos->mDirection == eDirNext && resultFrame == nearStoppingFrame) {
+ break;
+ }
+ // always try previous on THAT line if that fails go the other way
+ resultFrame = frameIterator->Traverse(/* aForward = */ false);
+ if (!resultFrame) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (!found) {
+ resultFrame = storeOldResultFrame;
+ frameIterator.reset();
+ frameIterator.emplace(
+ pc, resultFrame, nsFrameIterator::Type::Leaf,
+ false, // aVisual
+ aPos->mOptions.contains(PeekOffsetOption::StopAtScroller),
+ false, // aFollowOOFs
+ false // aSkipPopupChecks
+ );
+ MOZ_ASSERT(frameIterator);
+ }
+ while (!found) {
+ nsPoint point = aPos->mDesiredCaretPos;
+ nsView* view;
+ nsPoint offset;
+ resultFrame->GetOffsetFromView(offset, &view);
+ nsIFrame::ContentOffsets offsets =
+ resultFrame->GetContentOffsetsFromPoint(
+ point - offset, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
+ aPos->mResultContent = offsets.content;
+ aPos->mContentOffset = offsets.offset;
+ aPos->mAttach = offsets.associate;
+ if (FoundValidFrame(offsets, resultFrame)) {
+ found = true;
+ aPos->mAttach = resultFrame == farStoppingFrame
+ ? CaretAssociationHint::Before
+ : CaretAssociationHint::After;
+ break;
+ }
+ if (aPos->mDirection == eDirPrevious &&
+ (resultFrame == nearStoppingFrame))
+ break;
+ if (aPos->mDirection == eDirNext && (resultFrame == farStoppingFrame))
+ break;
+ // previous didnt work now we try "next"
+ nsIFrame* tempFrame = frameIterator->Traverse(/* aForward = */ true);
+ if (!tempFrame) break;
+ resultFrame = tempFrame;
+ }
+ aPos->mResultFrame = resultFrame;
+ } else {
+ // we need to jump to new block frame.
+ aPos->mAmount = eSelectLine;
+ aPos->mStartOffset = 0;
+ aPos->mAttach = aPos->mDirection == eDirNext
+ ? CaretAssociationHint::Before
+ : CaretAssociationHint::After;
+ if (aPos->mDirection == eDirPrevious)
+ aPos->mStartOffset = -1; // start from end
+ return aBlockFrame->PeekOffset(aPos);
+ }
+ }
+ return NS_OK;
+}
+
+nsIFrame::CaretPosition nsIFrame::GetExtremeCaretPosition(bool aStart) {
+ CaretPosition result;
+
+ FrameTarget targetFrame = DrillDownToSelectionFrame(this, !aStart, 0);
+ FrameContentRange range = GetRangeForFrame(targetFrame.frame);
+ result.mResultContent = range.content;
+ result.mContentOffset = aStart ? range.start : range.end;
+ return result;
+}
+
+// If this is a preformatted text frame, see if it ends with a newline
+static nsContentAndOffset FindLineBreakInText(nsIFrame* aFrame,
+ nsDirection aDirection) {
+ nsContentAndOffset result;
+
+ if (aFrame->IsGeneratedContentFrame() ||
+ !aFrame->HasSignificantTerminalNewline()) {
+ return result;
+ }
+
+ int32_t endOffset = aFrame->GetOffsets().second;
+ result.mContent = aFrame->GetContent();
+ result.mOffset = endOffset - (aDirection == eDirPrevious ? 0 : 1);
+ return result;
+}
+
+// Find the first (or last) descendant of the given frame
+// which is either a block-level frame or a BRFrame, or some other kind of break
+// which stops the line.
+static nsContentAndOffset FindLineBreakingFrame(nsIFrame* aFrame,
+ nsDirection aDirection) {
+ nsContentAndOffset result;
+
+ if (aFrame->IsGeneratedContentFrame()) {
+ return result;
+ }
+
+ // Treat form controls as inline leaves
+ // XXX we really need a way to determine whether a frame is inline-level
+ if (static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
+ return result;
+ }
+
+ // Check the frame itself
+ // Fall through block-in-inline split frames because their mContent is
+ // the content of the inline frames they were created from. The
+ // first/last child of such frames is the real block frame we're
+ // looking for.
+ if ((aFrame->IsBlockOutside() &&
+ !aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) ||
+ aFrame->IsBrFrame()) {
+ nsIContent* content = aFrame->GetContent();
+ result.mContent = content->GetParent();
+ // In some cases (bug 310589, bug 370174) we end up here with a null
+ // content. This probably shouldn't ever happen, but since it sometimes
+ // does, we want to avoid crashing here.
+ NS_ASSERTION(result.mContent, "Unexpected orphan content");
+ if (result.mContent) {
+ result.mOffset = result.mContent->ComputeIndexOf_Deprecated(content) +
+ (aDirection == eDirPrevious ? 1 : 0);
+ }
+ return result;
+ }
+
+ result = FindLineBreakInText(aFrame, aDirection);
+ if (result.mContent) {
+ return result;
+ }
+
+ // Iterate over children and call ourselves recursively
+ if (aDirection == eDirPrevious) {
+ nsIFrame* child = aFrame->PrincipalChildList().LastChild();
+ while (child && !result.mContent) {
+ result = FindLineBreakingFrame(child, aDirection);
+ child = child->GetPrevSibling();
+ }
+ } else { // eDirNext
+ nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
+ while (child && !result.mContent) {
+ result = FindLineBreakingFrame(child, aDirection);
+ child = child->GetNextSibling();
+ }
+ }
+ return result;
+}
+
+nsresult nsIFrame::PeekOffsetForParagraph(PeekOffsetStruct* aPos) {
+ nsIFrame* frame = this;
+ nsContentAndOffset blockFrameOrBR;
+ blockFrameOrBR.mContent = nullptr;
+ bool reachedLimit = frame->IsBlockOutside() || IsEditingHost(frame);
+
+ auto traverse = [&aPos](nsIFrame* current) {
+ return aPos->mDirection == eDirPrevious ? current->GetPrevSibling()
+ : current->GetNextSibling();
+ };
+
+ // Go through containing frames until reaching a block frame.
+ // In each step, search the previous (or next) siblings for the closest
+ // "stop frame" (a block frame or a BRFrame).
+ // If found, set it to be the selection boundary and abort.
+ while (!reachedLimit) {
+ nsIFrame* parent = frame->GetParent();
+ // Treat a frame associated with the root content as if it were a block
+ // frame.
+ if (!frame->mContent || !frame->mContent->GetParent()) {
+ reachedLimit = true;
+ break;
+ }
+
+ if (aPos->mDirection == eDirNext) {
+ // Try to find our own line-break before looking at our siblings.
+ blockFrameOrBR = FindLineBreakInText(frame, eDirNext);
+ }
+
+ nsIFrame* sibling = traverse(frame);
+ while (sibling && !blockFrameOrBR.mContent) {
+ blockFrameOrBR = FindLineBreakingFrame(sibling, aPos->mDirection);
+ sibling = traverse(sibling);
+ }
+ if (blockFrameOrBR.mContent) {
+ aPos->mResultContent = blockFrameOrBR.mContent;
+ aPos->mContentOffset = blockFrameOrBR.mOffset;
+ break;
+ }
+ frame = parent;
+ reachedLimit = frame && (frame->IsBlockOutside() || IsEditingHost(frame));
+ }
+
+ if (reachedLimit) { // no "stop frame" found
+ aPos->mResultContent = frame->GetContent();
+ if (aPos->mDirection == eDirPrevious) {
+ aPos->mContentOffset = 0;
+ } else if (aPos->mResultContent) {
+ aPos->mContentOffset = aPos->mResultContent->GetChildCount();
+ }
+ }
+ return NS_OK;
+}
+
+// Determine movement direction relative to frame
+static bool IsMovingInFrameDirection(const nsIFrame* frame,
+ nsDirection aDirection, bool aVisual) {
+ bool isReverseDirection =
+ aVisual && nsBidiPresUtils::IsReversedDirectionFrame(frame);
+ return aDirection == (isReverseDirection ? eDirPrevious : eDirNext);
+}
+
+// Determines "are we looking for a boundary between whitespace and
+// non-whitespace (in the direction we're moving in)". It is true when moving
+// forward and looking for a beginning of a word, or when moving backwards and
+// looking for an end of a word.
+static bool ShouldWordSelectionEatSpace(const PeekOffsetStruct& aPos) {
+ if (aPos.mWordMovementType != eDefaultBehavior) {
+ // aPos->mWordMovementType possible values:
+ // eEndWord: eat the space if we're moving backwards
+ // eStartWord: eat the space if we're moving forwards
+ return (aPos.mWordMovementType == eEndWord) ==
+ (aPos.mDirection == eDirPrevious);
+ }
+ // Use the hidden preference which is based on operating system
+ // behavior. This pref only affects whether moving forward by word
+ // should go to the end of this word or start of the next word. When
+ // going backwards, the start of the word is always used, on every
+ // operating system.
+ return aPos.mDirection == eDirNext &&
+ StaticPrefs::layout_word_select_eat_space_to_next_word();
+}
+
+enum class OffsetIsAtLineEdge : bool { No, Yes };
+
+static void SetPeekResultFromFrame(PeekOffsetStruct& aPos, nsIFrame* aFrame,
+ int32_t aOffset,
+ OffsetIsAtLineEdge aAtLineEdge) {
+ FrameContentRange range = GetRangeForFrame(aFrame);
+ aPos.mResultFrame = aFrame;
+ aPos.mResultContent = range.content;
+ // Output offset is relative to content, not frame
+ aPos.mContentOffset =
+ aOffset < 0 ? range.end + aOffset + 1 : range.start + aOffset;
+ if (aAtLineEdge == OffsetIsAtLineEdge::Yes) {
+ aPos.mAttach = aPos.mContentOffset == range.start
+ ? CaretAssociationHint::After
+ : CaretAssociationHint::Before;
+ }
+}
+
+void nsIFrame::SelectablePeekReport::TransferTo(PeekOffsetStruct& aPos) const {
+ return SetPeekResultFromFrame(aPos, mFrame, mOffset, OffsetIsAtLineEdge::No);
+}
+
+nsIFrame::SelectablePeekReport::SelectablePeekReport(
+ const mozilla::GenericErrorResult<nsresult>&& aErr) {
+ MOZ_ASSERT(NS_FAILED(aErr.operator nsresult()));
+ // Return an empty report
+}
+
+nsresult nsIFrame::PeekOffsetForCharacter(PeekOffsetStruct* aPos,
+ int32_t aOffset) {
+ SelectablePeekReport current{this, aOffset};
+
+ nsIFrame::FrameSearchResult peekSearchState = CONTINUE;
+
+ while (peekSearchState != FOUND) {
+ const bool movingInFrameDirection = IsMovingInFrameDirection(
+ current.mFrame, aPos->mDirection,
+ aPos->mOptions.contains(PeekOffsetOption::Visual));
+
+ if (current.mJumpedLine) {
+ // If we jumped lines, it's as if we found a character, but we still need
+ // to eat non-renderable content on the new line.
+ peekSearchState = current.PeekOffsetNoAmount(movingInFrameDirection);
+ } else {
+ PeekOffsetCharacterOptions options;
+ options.mRespectClusters = aPos->mAmount == eSelectCluster;
+ peekSearchState =
+ current.PeekOffsetCharacter(movingInFrameDirection, options);
+ }
+
+ current.mMovedOverNonSelectableText |=
+ peekSearchState == CONTINUE_UNSELECTABLE;
+
+ if (peekSearchState != FOUND) {
+ SelectablePeekReport next = current.mFrame->GetFrameFromDirection(*aPos);
+ if (next.Failed()) {
+ return NS_ERROR_FAILURE;
+ }
+ next.mJumpedLine |= current.mJumpedLine;
+ next.mMovedOverNonSelectableText |= current.mMovedOverNonSelectableText;
+ next.mHasSelectableFrame |= current.mHasSelectableFrame;
+ current = next;
+ }
+
+ // Found frame, but because we moved over non selectable text we want
+ // the offset to be at the frame edge. Note that if we are extending the
+ // selection, this doesn't matter.
+ if (peekSearchState == FOUND && current.mMovedOverNonSelectableText &&
+ (!aPos->mOptions.contains(PeekOffsetOption::Extend) ||
+ current.mHasSelectableFrame)) {
+ auto [start, end] = current.mFrame->GetOffsets();
+ current.mOffset = aPos->mDirection == eDirNext ? 0 : end - start;
+ }
+ }
+
+ // Set outputs
+ current.TransferTo(*aPos);
+ // If we're dealing with a text frame and moving backward positions us at
+ // the end of that line, decrease the offset by one to make sure that
+ // we're placed before the linefeed character on the previous line.
+ if (current.mOffset < 0 && current.mJumpedLine &&
+ aPos->mDirection == eDirPrevious &&
+ current.mFrame->HasSignificantTerminalNewline() &&
+ !current.mIgnoredBrFrame) {
+ --aPos->mContentOffset;
+ }
+ return NS_OK;
+}
+
+nsresult nsIFrame::PeekOffsetForWord(PeekOffsetStruct* aPos, int32_t aOffset) {
+ SelectablePeekReport current{this, aOffset};
+ bool shouldStopAtHardBreak =
+ aPos->mWordMovementType == eDefaultBehavior &&
+ StaticPrefs::layout_word_select_eat_space_to_next_word();
+ bool wordSelectEatSpace = ShouldWordSelectionEatSpace(*aPos);
+
+ PeekWordState state;
+ while (true) {
+ bool movingInFrameDirection = IsMovingInFrameDirection(
+ current.mFrame, aPos->mDirection,
+ aPos->mOptions.contains(PeekOffsetOption::Visual));
+
+ FrameSearchResult searchResult = current.mFrame->PeekOffsetWord(
+ movingInFrameDirection, wordSelectEatSpace,
+ aPos->mOptions.contains(PeekOffsetOption::IsKeyboardSelect),
+ &current.mOffset, &state,
+ !aPos->mOptions.contains(PeekOffsetOption::PreserveSpaces));
+ if (searchResult == FOUND) {
+ break;
+ }
+
+ SelectablePeekReport next = [&]() {
+ PeekOffsetOptions options = aPos->mOptions;
+ if (state.mSawInlineCharacter) {
+ // If we've already found a character, we don't want to stop at
+ // placeholder frame boundary if there is in the word.
+ options += PeekOffsetOption::StopAtPlaceholder;
+ }
+ return current.mFrame->GetFrameFromDirection(aPos->mDirection, options);
+ }();
+ if (next.Failed()) {
+ // If we've crossed the line boundary, check to make sure that we
+ // have not consumed a trailing newline as whitespace if it's
+ // significant.
+ if (next.mJumpedLine && wordSelectEatSpace &&
+ current.mFrame->HasSignificantTerminalNewline() &&
+ current.mFrame->StyleText()->mWhiteSpaceCollapse !=
+ StyleWhiteSpaceCollapse::PreserveBreaks) {
+ current.mOffset -= 1;
+ }
+ break;
+ }
+
+ if ((next.mJumpedLine || next.mFoundPlaceholder) && !wordSelectEatSpace &&
+ state.mSawBeforeType) {
+ // We can't jump lines if we're looking for whitespace following
+ // non-whitespace, and we already encountered non-whitespace.
+ break;
+ }
+
+ if (shouldStopAtHardBreak && next.mJumpedHardBreak) {
+ /**
+ * Prev, always: Jump and stop right there
+ * Next, saw inline: just stop
+ * Next, no inline: Jump and consume whitespaces
+ */
+ if (aPos->mDirection == eDirPrevious) {
+ // Try moving to the previous line if exists
+ current.TransferTo(*aPos);
+ current.mFrame->PeekOffsetForCharacter(aPos, current.mOffset);
+ return NS_OK;
+ }
+ if (state.mSawInlineCharacter || current.mJumpedHardBreak) {
+ if (current.mFrame->HasSignificantTerminalNewline()) {
+ current.mOffset -= 1;
+ }
+ current.TransferTo(*aPos);
+ return NS_OK;
+ }
+ // Mark the state as whitespace and continue
+ state.Update(false, true);
+ }
+
+ if (next.mJumpedLine) {
+ state.mContext.Truncate();
+ }
+ current = next;
+ // Jumping a line is equivalent to encountering whitespace
+ // This affects only when it already met an actual character
+ if (wordSelectEatSpace && next.mJumpedLine) {
+ state.SetSawBeforeType();
+ }
+ }
+
+ // Set outputs
+ current.TransferTo(*aPos);
+ return NS_OK;
+}
+
+static nsIFrame* GetFirstSelectableDescendantWithLineIterator(
+ nsIFrame* aParentFrame, bool aForceEditableRegion) {
+ auto FoundValidFrame = [aForceEditableRegion](const nsIFrame* aFrame) {
+ if (!aFrame->IsSelectable(nullptr)) {
+ return false;
+ }
+ if (aForceEditableRegion && !aFrame->GetContent()->IsEditable()) {
+ return false;
+ }
+ return true;
+ };
+
+ for (nsIFrame* child : aParentFrame->PrincipalChildList()) {
+ // some children may not be selectable, e.g. :before / :after pseudoelements
+ // content with user-select: none, or contenteditable="false"
+ // we need to skip them
+ if (child->CanProvideLineIterator() && FoundValidFrame(child)) {
+ return child;
+ }
+ if (nsIFrame* nested = GetFirstSelectableDescendantWithLineIterator(
+ child, aForceEditableRegion)) {
+ return nested;
+ }
+ }
+ return nullptr;
+}
+
+nsresult nsIFrame::PeekOffsetForLine(PeekOffsetStruct* aPos) {
+ nsIFrame* blockFrame = this;
+ nsresult result = NS_ERROR_FAILURE;
+
+ // outer loop
+ // moving to a next block when no more blocks are available in a subtree
+ AutoAssertNoDomMutations guard;
+ while (NS_FAILED(result)) {
+ auto [newBlock, lineFrame] = blockFrame->GetContainingBlockForLine(
+ aPos->mOptions.contains(PeekOffsetOption::StopAtScroller));
+ if (!newBlock) {
+ return NS_ERROR_FAILURE;
+ }
+ blockFrame = newBlock;
+ nsILineIterator* iter = blockFrame->GetLineIterator();
+ int32_t thisLine = iter->FindLineContaining(lineFrame);
+ if (NS_WARN_IF(thisLine < 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int8_t edgeCase = 0; // no edge case. This should look at thisLine
+
+ // this part will find a frame or a block frame. If it's a block frame
+ // it will "drill down" to find a viable frame or it will return an
+ // error.
+ nsIFrame* lastFrame = this;
+
+ // inner loop - crawling the frames within a specific block subtree
+ while (true) {
+ result =
+ GetNextPrevLineFromBlockFrame(aPos, blockFrame, thisLine, edgeCase);
+ // we came back to same spot! keep going
+ if (NS_SUCCEEDED(result) &&
+ (!aPos->mResultFrame || aPos->mResultFrame == lastFrame)) {
+ aPos->mResultFrame = nullptr;
+ lastFrame = nullptr;
+ if (aPos->mDirection == eDirPrevious) {
+ thisLine--;
+ } else {
+ thisLine++;
+ }
+ continue;
+ }
+
+ if (NS_FAILED(result)) {
+ break;
+ }
+
+ lastFrame = aPos->mResultFrame; // set last frame
+ /* SPECIAL CHECK FOR NAVIGATION INTO TABLES
+ * when we hit a frame which doesn't have line iterator, we need to
+ * drill down and find a child with the line iterator to prevent the
+ * crawling process to prematurely finish. Note that this is only sound if
+ * we're guaranteed to not have multiple children implementing
+ * LineIterator.
+ *
+ * So far known cases are:
+ * 1) table wrapper (drill down into table row group)
+ * 2) table cell (drill down into its only anon child)
+ */
+ const bool shouldDrillIntoChildren =
+ aPos->mResultFrame->IsTableWrapperFrame() ||
+ aPos->mResultFrame->IsTableCellFrame();
+
+ if (shouldDrillIntoChildren) {
+ nsIFrame* child = GetFirstSelectableDescendantWithLineIterator(
+ aPos->mResultFrame,
+ aPos->mOptions.contains(PeekOffsetOption::ForceEditableRegion));
+ if (child) {
+ aPos->mResultFrame = child;
+ }
+ }
+
+ if (!aPos->mResultFrame->CanProvideLineIterator()) {
+ // no more selectable content at this level
+ break;
+ }
+
+ if (aPos->mResultFrame == blockFrame) {
+ // Make sure block element is not the same as the one we had before.
+ break;
+ }
+
+ // we've struck another block element with selectable content!
+ if (aPos->mDirection == eDirPrevious) {
+ edgeCase = 1; // far edge, search from end backwards
+ } else {
+ edgeCase = -1; // near edge search from beginning onwards
+ }
+ thisLine = 0; // this line means nothing now.
+ // everything else means something so keep looking "inside" the
+ // block
+ blockFrame = aPos->mResultFrame;
+ }
+ }
+ return result;
+}
+
+nsresult nsIFrame::PeekOffsetForLineEdge(PeekOffsetStruct* aPos) {
+ // Adjusted so that the caret can't get confused when content changes
+ nsIFrame* frame = AdjustFrameForSelectionStyles(this);
+ Element* editingHost = frame->GetContent()->GetEditingHost();
+
+ auto [blockFrame, lineFrame] = frame->GetContainingBlockForLine(
+ aPos->mOptions.contains(PeekOffsetOption::StopAtScroller));
+ if (!blockFrame) {
+ return NS_ERROR_FAILURE;
+ }
+ AutoAssertNoDomMutations guard;
+ nsILineIterator* it = blockFrame->GetLineIterator();
+ int32_t thisLine = it->FindLineContaining(lineFrame);
+ if (thisLine < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* baseFrame = nullptr;
+ bool endOfLine = eSelectEndLine == aPos->mAmount;
+
+ if (aPos->mOptions.contains(PeekOffsetOption::Visual) &&
+ PresContext()->BidiEnabled()) {
+ nsIFrame* firstFrame;
+ bool isReordered;
+ nsIFrame* lastFrame;
+ MOZ_TRY(
+ it->CheckLineOrder(thisLine, &isReordered, &firstFrame, &lastFrame));
+ baseFrame = endOfLine ? lastFrame : firstFrame;
+ } else {
+ auto line = it->GetLine(thisLine).unwrap();
+
+ nsIFrame* frame = line.mFirstFrameOnLine;
+ bool lastFrameWasEditable = false;
+ for (int32_t count = line.mNumFramesOnLine; count;
+ --count, frame = frame->GetNextSibling()) {
+ if (frame->IsGeneratedContentFrame()) {
+ continue;
+ }
+ // When jumping to the end of the line with the "end" key,
+ // try to skip over brFrames
+ if (endOfLine && line.mNumFramesOnLine > 1 && frame->IsBrFrame() &&
+ lastFrameWasEditable == frame->GetContent()->IsEditable()) {
+ continue;
+ }
+ lastFrameWasEditable =
+ frame->GetContent() && frame->GetContent()->IsEditable();
+ baseFrame = frame;
+ if (!endOfLine) {
+ break;
+ }
+ }
+ }
+ if (!baseFrame) {
+ return NS_ERROR_FAILURE;
+ }
+ // Make sure we are not leaving our inline editing host if exists
+ if (editingHost) {
+ if (nsIFrame* frame = editingHost->GetPrimaryFrame()) {
+ if (frame->IsInlineOutside() &&
+ !editingHost->Contains(baseFrame->GetContent())) {
+ baseFrame = frame;
+ if (endOfLine) {
+ baseFrame = baseFrame->LastContinuation();
+ }
+ }
+ }
+ }
+ FrameTarget targetFrame = DrillDownToSelectionFrame(baseFrame, endOfLine, 0);
+ SetPeekResultFromFrame(*aPos, targetFrame.frame, endOfLine ? -1 : 0,
+ OffsetIsAtLineEdge::Yes);
+ if (endOfLine && targetFrame.frame->HasSignificantTerminalNewline()) {
+ // Do not position the caret after the terminating newline if we're
+ // trying to move to the end of line (see bug 596506)
+ --aPos->mContentOffset;
+ }
+ if (!aPos->mResultContent) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsIFrame::PeekOffset(PeekOffsetStruct* aPos) {
+ MOZ_ASSERT(aPos);
+
+ if (NS_WARN_IF(HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
+ // FIXME(Bug 1654362): <caption> currently can remain dirty.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Translate content offset to be relative to frame
+ int32_t offset = aPos->mStartOffset - GetRangeForFrame(this).start;
+
+ switch (aPos->mAmount) {
+ case eSelectCharacter:
+ case eSelectCluster:
+ return PeekOffsetForCharacter(aPos, offset);
+ case eSelectWordNoSpace:
+ // eSelectWordNoSpace means that we should not be eating any whitespace
+ // when moving to the adjacent word. This means that we should set aPos->
+ // mWordMovementType to eEndWord if we're moving forwards, and to
+ // eStartWord if we're moving backwards.
+ if (aPos->mDirection == eDirPrevious) {
+ aPos->mWordMovementType = eStartWord;
+ } else {
+ aPos->mWordMovementType = eEndWord;
+ }
+ // Intentionally fall through the eSelectWord case.
+ [[fallthrough]];
+ case eSelectWord:
+ return PeekOffsetForWord(aPos, offset);
+ case eSelectLine:
+ return PeekOffsetForLine(aPos);
+ case eSelectBeginLine:
+ case eSelectEndLine:
+ return PeekOffsetForLineEdge(aPos);
+ case eSelectParagraph:
+ return PeekOffsetForParagraph(aPos);
+ default: {
+ NS_ASSERTION(false, "Invalid amount");
+ return NS_ERROR_FAILURE;
+ }
+ }
+}
+
+nsIFrame::FrameSearchResult nsIFrame::PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ // Sure, we can stop right here.
+ return FOUND;
+}
+
+nsIFrame::FrameSearchResult nsIFrame::PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ int32_t startOffset = *aOffset;
+ // A negative offset means "end of frame", which in our case means offset 1.
+ if (startOffset < 0) startOffset = 1;
+ if (aForward == (startOffset == 0)) {
+ // We're before the frame and moving forward, or after it and moving
+ // backwards: skip to the other side and we're done.
+ *aOffset = 1 - startOffset;
+ return FOUND;
+ }
+ return CONTINUE;
+}
+
+nsIFrame::FrameSearchResult nsIFrame::PeekOffsetWord(
+ bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
+ int32_t* aOffset, PeekWordState* aState, bool /*aTrimSpaces*/) {
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ int32_t startOffset = *aOffset;
+ // This isn't text, so truncate the context
+ aState->mContext.Truncate();
+ if (startOffset < 0) startOffset = 1;
+ if (aForward == (startOffset == 0)) {
+ // We're before the frame and moving forward, or after it and moving
+ // backwards. If we're looking for non-whitespace, we found it (without
+ // skipping this frame).
+ if (!aState->mAtStart) {
+ if (aState->mLastCharWasPunctuation) {
+ // We're not punctuation, so this is a punctuation boundary.
+ if (BreakWordBetweenPunctuation(aState, aForward, false, false,
+ aIsKeyboardSelect))
+ return FOUND;
+ } else {
+ // This is not a punctuation boundary.
+ if (aWordSelectEatSpace && aState->mSawBeforeType) return FOUND;
+ }
+ }
+ // Otherwise skip to the other side and note that we encountered
+ // non-whitespace.
+ *aOffset = 1 - startOffset;
+ aState->Update(false, // not punctuation
+ false // not whitespace
+ );
+ if (!aWordSelectEatSpace) aState->SetSawBeforeType();
+ }
+ return CONTINUE;
+}
+
+// static
+bool nsIFrame::BreakWordBetweenPunctuation(const PeekWordState* aState,
+ bool aForward, bool aPunctAfter,
+ bool aWhitespaceAfter,
+ bool aIsKeyboardSelect) {
+ NS_ASSERTION(aPunctAfter != aState->mLastCharWasPunctuation,
+ "Call this only at punctuation boundaries");
+ if (aState->mLastCharWasWhitespace) {
+ // We always stop between whitespace and punctuation
+ return true;
+ }
+ if (!StaticPrefs::layout_word_select_stop_at_punctuation()) {
+ // When this pref is false, we never stop at a punctuation boundary unless
+ // it's followed by whitespace (in the relevant direction).
+ return aWhitespaceAfter;
+ }
+ if (!aIsKeyboardSelect) {
+ // mouse caret movement (e.g. word selection) always stops at every
+ // punctuation boundary
+ return true;
+ }
+ bool afterPunct = aForward ? aState->mLastCharWasPunctuation : aPunctAfter;
+ if (!afterPunct) {
+ // keyboard caret movement only stops after punctuation (in content order)
+ return false;
+ }
+ // Stop only if we've seen some non-punctuation since the last whitespace;
+ // don't stop after punctuation that follows whitespace.
+ return aState->mSeenNonPunctuationSinceWhitespace;
+}
+
+std::pair<nsIFrame*, nsIFrame*> nsIFrame::GetContainingBlockForLine(
+ bool aLockScroll) const {
+ const nsIFrame* parentFrame = this;
+ const nsIFrame* frame;
+ while (parentFrame) {
+ frame = parentFrame;
+ if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ // if we are searching for a frame that is not in flow we will not find
+ // it. we must instead look for its placeholder
+ if (frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ // abspos continuations don't have placeholders, get the fif
+ frame = frame->FirstInFlow();
+ }
+ frame = frame->GetPlaceholderFrame();
+ if (!frame) {
+ return std::pair(nullptr, nullptr);
+ }
+ }
+ parentFrame = frame->GetParent();
+ if (parentFrame) {
+ if (aLockScroll && parentFrame->IsScrollFrame()) {
+ return std::pair(nullptr, nullptr);
+ }
+ if (parentFrame->CanProvideLineIterator()) {
+ return std::pair(const_cast<nsIFrame*>(parentFrame),
+ const_cast<nsIFrame*>(frame));
+ }
+ }
+ }
+ return std::pair(nullptr, nullptr);
+}
+
+Result<bool, nsresult> nsIFrame::IsVisuallyAtLineEdge(
+ nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) {
+ auto line = aLineIterator->GetLine(aLine).unwrap();
+
+ const bool lineIsRTL = aLineIterator->IsLineIteratorFlowRTL();
+
+ nsIFrame *firstFrame = nullptr, *lastFrame = nullptr;
+ bool isReordered = false;
+ MOZ_TRY(aLineIterator->CheckLineOrder(aLine, &isReordered, &firstFrame,
+ &lastFrame));
+ if (!firstFrame || !lastFrame) {
+ return true; // XXX: Why true? We check whether `this` is at the edge...
+ }
+
+ nsIFrame* leftmostFrame = lineIsRTL ? lastFrame : firstFrame;
+ nsIFrame* rightmostFrame = lineIsRTL ? firstFrame : lastFrame;
+ auto FrameIsRTL = [](nsIFrame* aFrame) {
+ return nsBidiPresUtils::FrameDirection(aFrame) ==
+ mozilla::intl::BidiDirection::RTL;
+ };
+ if (!lineIsRTL == (aDirection == eDirPrevious)) {
+ nsIFrame* maybeLeftmostFrame = leftmostFrame;
+ for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) {
+ if (maybeLeftmostFrame == this) {
+ return true;
+ }
+ // If left edge of the line starts with placeholder frames, we can ignore
+ // them and should keep checking the following frames.
+ if (!maybeLeftmostFrame->IsPlaceholderFrame()) {
+ if ((FrameIsRTL(maybeLeftmostFrame) == lineIsRTL) ==
+ (aDirection == eDirPrevious)) {
+ nsIFrame::GetFirstLeaf(&maybeLeftmostFrame);
+ } else {
+ nsIFrame::GetLastLeaf(&maybeLeftmostFrame);
+ }
+ return maybeLeftmostFrame == this;
+ }
+ maybeLeftmostFrame = nsBidiPresUtils::GetFrameToRightOf(
+ maybeLeftmostFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine);
+ if (!maybeLeftmostFrame) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ nsIFrame* maybeRightmostFrame = rightmostFrame;
+ for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) {
+ if (maybeRightmostFrame == this) {
+ return true;
+ }
+ // If the line ends with placehlder frames, we can ignore them and should
+ // keep checking the preceding frames.
+ if (!maybeRightmostFrame->IsPlaceholderFrame()) {
+ if ((FrameIsRTL(maybeRightmostFrame) == lineIsRTL) ==
+ (aDirection == eDirPrevious)) {
+ nsIFrame::GetFirstLeaf(&maybeRightmostFrame);
+ } else {
+ nsIFrame::GetLastLeaf(&maybeRightmostFrame);
+ }
+ return maybeRightmostFrame == this;
+ }
+ maybeRightmostFrame = nsBidiPresUtils::GetFrameToLeftOf(
+ maybeRightmostFrame, line.mFirstFrameOnLine, line.mNumFramesOnLine);
+ if (!maybeRightmostFrame) {
+ return false;
+ }
+ }
+ return false;
+}
+
+Result<bool, nsresult> nsIFrame::IsLogicallyAtLineEdge(
+ nsILineIterator* aLineIterator, int32_t aLine, nsDirection aDirection) {
+ auto line = aLineIterator->GetLine(aLine).unwrap();
+ if (!line.mNumFramesOnLine) {
+ return false;
+ }
+ MOZ_ASSERT(line.mFirstFrameOnLine);
+
+ if (aDirection == eDirPrevious) {
+ nsIFrame* maybeFirstFrame = line.mFirstFrameOnLine;
+ for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) {
+ if (maybeFirstFrame == this) {
+ return true;
+ }
+ // If the line starts with placeholder frames, we can ignore them and
+ // should keep checking the following frames.
+ if (!maybeFirstFrame->IsPlaceholderFrame()) {
+ nsIFrame::GetFirstLeaf(&maybeFirstFrame);
+ return maybeFirstFrame == this;
+ }
+ maybeFirstFrame = maybeFirstFrame->GetNextSibling();
+ if (!maybeFirstFrame) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ // eDirNext
+ nsIFrame* maybeLastFrame = line.GetLastFrameOnLine();
+ for ([[maybe_unused]] int32_t i : IntegerRange(line.mNumFramesOnLine)) {
+ if (maybeLastFrame == this) {
+ return true;
+ }
+ // If the line ends with placehlder frames, we can ignore them and should
+ // keep checking the preceding frames.
+ if (!maybeLastFrame->IsPlaceholderFrame()) {
+ nsIFrame::GetLastLeaf(&maybeLastFrame);
+ return maybeLastFrame == this;
+ }
+ maybeLastFrame = maybeLastFrame->GetPrevSibling();
+ }
+ return false;
+}
+
+nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection(
+ nsDirection aDirection, const PeekOffsetOptions& aOptions) {
+ SelectablePeekReport result;
+
+ nsPresContext* presContext = PresContext();
+ const bool needsVisualTraversal =
+ aOptions.contains(PeekOffsetOption::Visual) && presContext->BidiEnabled();
+ const bool followOofs =
+ !aOptions.contains(PeekOffsetOption::StopAtPlaceholder);
+ nsFrameIterator frameIterator(
+ presContext, this, nsFrameIterator::Type::Leaf, needsVisualTraversal,
+ aOptions.contains(PeekOffsetOption::StopAtScroller), followOofs,
+ false // aSkipPopupChecks
+ );
+
+ // Find the prev/next selectable frame
+ bool selectable = false;
+ nsIFrame* traversedFrame = this;
+ AutoAssertNoDomMutations guard;
+ const nsIContent* const nativeAnonymousSubtreeContent =
+ GetClosestNativeAnonymousSubtreeRoot();
+ while (!selectable) {
+ auto [blockFrame, lineFrame] = traversedFrame->GetContainingBlockForLine(
+ aOptions.contains(PeekOffsetOption::StopAtScroller));
+ if (!blockFrame) {
+ return result;
+ }
+
+ nsILineIterator* it = blockFrame->GetLineIterator();
+ int32_t thisLine = it->FindLineContaining(lineFrame);
+ if (thisLine < 0) {
+ return result;
+ }
+
+ bool atLineEdge;
+ MOZ_TRY_VAR(
+ atLineEdge,
+ needsVisualTraversal
+ ? traversedFrame->IsVisuallyAtLineEdge(it, thisLine, aDirection)
+ : traversedFrame->IsLogicallyAtLineEdge(it, thisLine, aDirection));
+ if (atLineEdge) {
+ result.mJumpedLine = true;
+ if (!aOptions.contains(PeekOffsetOption::JumpLines)) {
+ return result; // we are done. cannot jump lines
+ }
+ int32_t lineToCheckWrap =
+ aDirection == eDirPrevious ? thisLine - 1 : thisLine;
+ if (lineToCheckWrap < 0 ||
+ !it->GetLine(lineToCheckWrap).unwrap().mIsWrapped) {
+ result.mJumpedHardBreak = true;
+ }
+ }
+
+ traversedFrame = frameIterator.Traverse(aDirection == eDirNext);
+ if (!traversedFrame) {
+ return result;
+ }
+
+ if (aOptions.contains(PeekOffsetOption::StopAtPlaceholder) &&
+ traversedFrame->IsPlaceholderFrame()) {
+ // XXX If the placeholder frame does not have meaningful content, the user
+ // may want to select as a word around the out-of-flow cotent. However,
+ // non-text frame resets context in nsIFrame::PeekOffsetWord(). Therefore,
+ // next text frame considers the new word starts from its edge. So, it's
+ // not enough to implement such behavior with adding a check here whether
+ // the real frame may change the word with its contents if it were not
+ // out-of-flow.
+ result.mFoundPlaceholder = true;
+ return result;
+ }
+
+ auto IsSelectable =
+ [aOptions, nativeAnonymousSubtreeContent](const nsIFrame* aFrame) {
+ if (!aFrame->IsSelectable(nullptr)) {
+ return false;
+ }
+ // If the new frame is in a native anonymous subtree, we should treat
+ // it as not selectable unless the frame and found frame are in same
+ // subtree.
+ if (aFrame->GetClosestNativeAnonymousSubtreeRoot() !=
+ nativeAnonymousSubtreeContent) {
+ return false;
+ }
+ return !aOptions.contains(PeekOffsetOption::ForceEditableRegion) ||
+ aFrame->GetContent()->IsEditable();
+ };
+
+ // Skip br frames, but only if we can select something before hitting the
+ // end of the line or a non-selectable region.
+ if (atLineEdge && aDirection == eDirPrevious &&
+ traversedFrame->IsBrFrame()) {
+ for (nsIFrame* current = traversedFrame->GetPrevSibling(); current;
+ current = current->GetPrevSibling()) {
+ if (!current->IsBlockOutside() && IsSelectable(current)) {
+ if (!current->IsBrFrame()) {
+ result.mIgnoredBrFrame = true;
+ }
+ break;
+ }
+ }
+ if (result.mIgnoredBrFrame) {
+ continue;
+ }
+ }
+
+ selectable = IsSelectable(traversedFrame);
+ if (!selectable) {
+ if (traversedFrame->IsSelectable(nullptr)) {
+ result.mHasSelectableFrame = true;
+ }
+ result.mMovedOverNonSelectableText = true;
+ }
+ } // while (!selectable)
+
+ result.mOffset = (aDirection == eDirNext) ? 0 : -1;
+
+ if (aOptions.contains(PeekOffsetOption::Visual) &&
+ nsBidiPresUtils::IsReversedDirectionFrame(traversedFrame)) {
+ // The new frame is reverse-direction, go to the other end
+ result.mOffset = -1 - result.mOffset;
+ }
+ result.mFrame = traversedFrame;
+ return result;
+}
+
+nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection(
+ const PeekOffsetStruct& aPos) {
+ return GetFrameFromDirection(aPos.mDirection, aPos.mOptions);
+}
+
+nsView* nsIFrame::GetClosestView(nsPoint* aOffset) const {
+ nsPoint offset(0, 0);
+ for (const nsIFrame* f = this; f; f = f->GetParent()) {
+ if (f->HasView()) {
+ if (aOffset) *aOffset = offset;
+ return f->GetView();
+ }
+ offset += f->GetPosition();
+ }
+
+ MOZ_ASSERT_UNREACHABLE("No view on any parent? How did that happen?");
+ return nullptr;
+}
+
+/* virtual */
+void nsIFrame::ChildIsDirty(nsIFrame* aChild) {
+ MOZ_ASSERT_UNREACHABLE(
+ "should never be called on a frame that doesn't "
+ "inherit from nsContainerFrame");
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsIFrame::AccessibleType() {
+ if (IsTableCaption() && !GetRect().IsEmpty()) {
+ return a11y::eHTMLCaptionType;
+ }
+ return a11y::eNoType;
+}
+#endif
+
+bool nsIFrame::ClearOverflowRects() {
+ if (mOverflow.mType == OverflowStorageType::None) {
+ return false;
+ }
+ if (mOverflow.mType == OverflowStorageType::Large) {
+ RemoveProperty(OverflowAreasProperty());
+ }
+ mOverflow.mType = OverflowStorageType::None;
+ return true;
+}
+
+bool nsIFrame::SetOverflowAreas(const OverflowAreas& aOverflowAreas) {
+ if (mOverflow.mType == OverflowStorageType::Large) {
+ OverflowAreas* overflow = GetOverflowAreasProperty();
+ bool changed = *overflow != aOverflowAreas;
+ *overflow = aOverflowAreas;
+
+ // Don't bother with converting to the deltas form if we already
+ // have a property.
+ return changed;
+ }
+
+ const nsRect& vis = aOverflowAreas.InkOverflow();
+ uint32_t l = -vis.x, // left edge: positive delta is leftwards
+ t = -vis.y, // top: positive is upwards
+ r = vis.XMost() - mRect.width, // right: positive is rightwards
+ b = vis.YMost() - mRect.height; // bottom: positive is downwards
+ if (aOverflowAreas.ScrollableOverflow().IsEqualEdges(
+ nsRect(nsPoint(0, 0), GetSize())) &&
+ l <= InkOverflowDeltas::kMax && t <= InkOverflowDeltas::kMax &&
+ r <= InkOverflowDeltas::kMax && b <= InkOverflowDeltas::kMax &&
+ // we have to check these against zero because we *never* want to
+ // set a frame as having no overflow in this function. This is
+ // because FinishAndStoreOverflow calls this function prior to
+ // SetRect based on whether the overflow areas match aNewSize.
+ // In the case where the overflow areas exactly match mRect but
+ // do not match aNewSize, we need to store overflow in a property
+ // so that our eventual SetRect/SetSize will know that it has to
+ // reset our overflow areas.
+ (l | t | r | b) != 0) {
+ InkOverflowDeltas oldDeltas = mOverflow.mInkOverflowDeltas;
+ // It's a "small" overflow area so we store the deltas for each edge
+ // directly in the frame, rather than allocating a separate rect.
+ // If they're all zero, that's fine; we're setting things to
+ // no-overflow.
+ mOverflow.mInkOverflowDeltas.mLeft = l;
+ mOverflow.mInkOverflowDeltas.mTop = t;
+ mOverflow.mInkOverflowDeltas.mRight = r;
+ mOverflow.mInkOverflowDeltas.mBottom = b;
+ // There was no scrollable overflow before, and there isn't now.
+ return oldDeltas != mOverflow.mInkOverflowDeltas;
+ } else {
+ bool changed =
+ !aOverflowAreas.ScrollableOverflow().IsEqualEdges(
+ nsRect(nsPoint(0, 0), GetSize())) ||
+ !aOverflowAreas.InkOverflow().IsEqualEdges(InkOverflowFromDeltas());
+
+ // it's a large overflow area that we need to store as a property
+ mOverflow.mType = OverflowStorageType::Large;
+ AddProperty(OverflowAreasProperty(), new OverflowAreas(aOverflowAreas));
+ return changed;
+ }
+}
+
+enum class ApplyTransform : bool { No, Yes };
+
+/**
+ * Compute the outline inner rect (so without outline-width and outline-offset)
+ * of aFrame, maybe iterating over its descendants, in aFrame's coordinate space
+ * or its post-transform coordinate space (depending on aApplyTransform).
+ */
+static nsRect ComputeOutlineInnerRect(
+ nsIFrame* aFrame, ApplyTransform aApplyTransform, bool& aOutValid,
+ const nsSize* aSizeOverride = nullptr,
+ const OverflowAreas* aOverflowOverride = nullptr) {
+ const nsRect bounds(nsPoint(0, 0),
+ aSizeOverride ? *aSizeOverride : aFrame->GetSize());
+
+ // The SVG container frames besides SVGTextFrame do not maintain
+ // an accurate mRect. It will make the outline be larger than
+ // we expect, we need to make them narrow to their children's outline.
+ // aOutValid is set to false if the returned nsRect is not valid
+ // and should not be included in the outline rectangle.
+ aOutValid = !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
+ !aFrame->IsSVGContainerFrame() || aFrame->IsSVGTextFrame();
+
+ nsRect u;
+
+ if (!aFrame->FrameMaintainsOverflow()) {
+ return u;
+ }
+
+ // Start from our border-box, transformed. See comment below about
+ // transform of children.
+ bool doTransform =
+ aApplyTransform == ApplyTransform::Yes && aFrame->IsTransformed();
+ TransformReferenceBox boundsRefBox(nullptr, bounds);
+ if (doTransform) {
+ u = nsDisplayTransform::TransformRect(bounds, aFrame, boundsRefBox);
+ } else {
+ u = bounds;
+ }
+
+ if (aOutValid && !StaticPrefs::layout_outline_include_overflow()) {
+ return u;
+ }
+
+ // Only iterate through the children if the overflow areas suggest
+ // that we might need to, and if the frame doesn't clip its overflow
+ // anyway.
+ if (aOverflowOverride) {
+ if (!doTransform && bounds.IsEqualEdges(aOverflowOverride->InkOverflow()) &&
+ bounds.IsEqualEdges(aOverflowOverride->ScrollableOverflow())) {
+ return u;
+ }
+ } else {
+ if (!doTransform && bounds.IsEqualEdges(aFrame->InkOverflowRect()) &&
+ bounds.IsEqualEdges(aFrame->ScrollableOverflowRect())) {
+ return u;
+ }
+ }
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ LayoutFrameType fType = aFrame->Type();
+ if (fType == LayoutFrameType::Scroll ||
+ fType == LayoutFrameType::ListControl ||
+ fType == LayoutFrameType::SVGOuterSVG) {
+ return u;
+ }
+
+ auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp);
+ auto overflowClipMargin = aFrame->OverflowClipMargin(overflowClipAxes);
+ if (overflowClipAxes == nsIFrame::PhysicalAxes::Both &&
+ overflowClipMargin == nsSize()) {
+ return u;
+ }
+
+ const nsStyleEffects* effects = aFrame->StyleEffects();
+ Maybe<nsRect> clipPropClipRect =
+ aFrame->GetClipPropClipRect(disp, effects, bounds.Size());
+
+ // Iterate over all children except pop-up, absolutely-positioned,
+ // float, and overflow ones.
+ const FrameChildListIDs skip = {
+ FrameChildListID::Popup, FrameChildListID::Absolute,
+ FrameChildListID::Fixed, FrameChildListID::Float,
+ FrameChildListID::Overflow};
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ if (skip.contains(listID)) {
+ continue;
+ }
+
+ for (nsIFrame* child : list) {
+ if (child->IsPlaceholderFrame()) {
+ continue;
+ }
+
+ // Note that passing ApplyTransform::Yes when
+ // child->Combines3DTransformWithAncestors() returns true is incorrect if
+ // our aApplyTransform is No... but the opposite would be as well.
+ // This is because elements within a preserve-3d scene are always
+ // transformed up to the top of the scene. This means we don't have a
+ // mechanism for getting a transform up to an intermediate point within
+ // the scene. We choose to over-transform rather than under-transform
+ // because this is consistent with other overflow areas.
+ bool validRect = true;
+ nsRect childRect =
+ ComputeOutlineInnerRect(child, ApplyTransform::Yes, validRect) +
+ child->GetPosition();
+
+ if (!validRect) {
+ continue;
+ }
+
+ if (clipPropClipRect) {
+ // Intersect with the clip before transforming.
+ childRect.IntersectRect(childRect, *clipPropClipRect);
+ }
+
+ // Note that we transform each child separately according to
+ // aFrame's transform, and then union, which gives a different
+ // (smaller) result from unioning and then transforming the
+ // union. This doesn't match the way we handle overflow areas
+ // with 2-D transforms, though it does match the way we handle
+ // overflow areas in preserve-3d 3-D scenes.
+ if (doTransform && !child->Combines3DTransformWithAncestors()) {
+ childRect =
+ nsDisplayTransform::TransformRect(childRect, aFrame, boundsRefBox);
+ }
+
+ // If a SVGContainer has a non-SVGContainer child, we assign
+ // its child's outline to this SVGContainer directly.
+ if (!aOutValid && validRect) {
+ u = childRect;
+ aOutValid = true;
+ } else {
+ u = u.UnionEdges(childRect);
+ }
+ }
+ }
+
+ if (overflowClipAxes != nsIFrame::PhysicalAxes::None) {
+ OverflowAreas::ApplyOverflowClippingOnRect(u, bounds, overflowClipAxes,
+ overflowClipMargin);
+ }
+ return u;
+}
+
+static void ComputeAndIncludeOutlineArea(nsIFrame* aFrame,
+ OverflowAreas& aOverflowAreas,
+ const nsSize& aNewSize) {
+ const nsStyleOutline* outline = aFrame->StyleOutline();
+ if (!outline->ShouldPaintOutline()) {
+ return;
+ }
+
+ // When the outline property is set on a :-moz-block-inside-inline-wrapper
+ // pseudo-element, it inherited that outline from the inline that was broken
+ // because it contained a block. In that case, we don't want a really wide
+ // outline if the block inside the inline is narrow, so union the actual
+ // contents of the anonymous blocks.
+ nsIFrame* frameForArea = aFrame;
+ do {
+ PseudoStyleType pseudoType = frameForArea->Style()->GetPseudoType();
+ if (pseudoType != PseudoStyleType::mozBlockInsideInlineWrapper) break;
+ // If we're done, we really want it and all its later siblings.
+ frameForArea = frameForArea->PrincipalChildList().FirstChild();
+ NS_ASSERTION(frameForArea, "anonymous block with no children?");
+ } while (frameForArea);
+
+ // Find the union of the border boxes of all descendants, or in
+ // the block-in-inline case, all descendants we care about.
+ //
+ // Note that the interesting perspective-related cases are taken
+ // care of by the code that handles those issues for overflow
+ // calling FinishAndStoreOverflow again, which in turn calls this
+ // function again. We still need to deal with preserve-3d a bit.
+ nsRect innerRect;
+ bool validRect = false;
+ if (frameForArea == aFrame) {
+ innerRect = ComputeOutlineInnerRect(aFrame, ApplyTransform::No, validRect,
+ &aNewSize, &aOverflowAreas);
+ } else {
+ for (; frameForArea; frameForArea = frameForArea->GetNextSibling()) {
+ nsRect r =
+ ComputeOutlineInnerRect(frameForArea, ApplyTransform::Yes, validRect);
+
+ // Adjust for offsets transforms up to aFrame's pre-transform
+ // (i.e., normal) coordinate space; see comments in
+ // UnionBorderBoxes for some of the subtlety here.
+ for (nsIFrame *f = frameForArea, *parent = f->GetParent();
+ /* see middle of loop */; f = parent, parent = f->GetParent()) {
+ r += f->GetPosition();
+ if (parent == aFrame) {
+ break;
+ }
+ if (parent->IsTransformed() && !f->Combines3DTransformWithAncestors()) {
+ TransformReferenceBox refBox(parent);
+ r = nsDisplayTransform::TransformRect(r, parent, refBox);
+ }
+ }
+
+ innerRect.UnionRect(innerRect, r);
+ }
+ }
+
+ // Keep this code in sync with nsDisplayOutline::GetInnerRect.
+ if (innerRect == aFrame->GetRectRelativeToSelf()) {
+ aFrame->RemoveProperty(nsIFrame::OutlineInnerRectProperty());
+ } else {
+ SetOrUpdateRectValuedProperty(aFrame, nsIFrame::OutlineInnerRectProperty(),
+ innerRect);
+ }
+
+ nsRect outerRect(innerRect);
+ outerRect.Inflate(outline->EffectiveOffsetFor(outerRect));
+
+ if (outline->mOutlineStyle.IsAuto()) {
+ nsPresContext* pc = aFrame->PresContext();
+
+ pc->Theme()->GetWidgetOverflow(pc->DeviceContext(), aFrame,
+ StyleAppearance::FocusOutline, &outerRect);
+ } else {
+ const nscoord width = outline->GetOutlineWidth();
+ outerRect.Inflate(width);
+ }
+
+ nsRect& vo = aOverflowAreas.InkOverflow();
+ vo = vo.UnionEdges(innerRect.Union(outerRect));
+}
+
+bool nsIFrame::FinishAndStoreOverflow(OverflowAreas& aOverflowAreas,
+ nsSize aNewSize, nsSize* aOldSize,
+ const nsStyleDisplay* aStyleDisplay) {
+ MOZ_ASSERT(FrameMaintainsOverflow(),
+ "Don't call - overflow rects not maintained on these SVG frames");
+
+ const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
+ bool hasTransform = IsTransformed();
+
+ nsRect bounds(nsPoint(0, 0), aNewSize);
+ // Store the passed in overflow area if we are a preserve-3d frame or we have
+ // a transform, and it's not just the frame bounds.
+ if (hasTransform || Combines3DTransformWithAncestors()) {
+ if (!aOverflowAreas.InkOverflow().IsEqualEdges(bounds) ||
+ !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
+ OverflowAreas* initial = GetProperty(nsIFrame::InitialOverflowProperty());
+ if (!initial) {
+ AddProperty(nsIFrame::InitialOverflowProperty(),
+ new OverflowAreas(aOverflowAreas));
+ } else if (initial != &aOverflowAreas) {
+ *initial = aOverflowAreas;
+ }
+ } else {
+ RemoveProperty(nsIFrame::InitialOverflowProperty());
+ }
+#ifdef DEBUG
+ SetProperty(nsIFrame::DebugInitialOverflowPropertyApplied(), true);
+#endif
+ } else {
+#ifdef DEBUG
+ RemoveProperty(nsIFrame::DebugInitialOverflowPropertyApplied());
+#endif
+ }
+
+ nsSize oldSize = mRect.Size();
+ bool sizeChanged = ((aOldSize ? *aOldSize : oldSize) != aNewSize);
+
+ // Our frame size may not have been computed and set yet, but code under
+ // functions such as ComputeEffectsRect (which we're about to call) use the
+ // values that are stored in our frame rect to compute their results. We
+ // need the results from those functions to be based on the frame size that
+ // we *will* have, so we temporarily set our frame size here before calling
+ // those functions.
+ //
+ // XXX Someone should document here why we revert the frame size before we
+ // return rather than just leaving it set.
+ //
+ // We pass false here to avoid invalidating display items for this temporary
+ // change. We sometimes reflow frames multiple times, with the final size
+ // being the same as the initial. The single call to SetSize after reflow is
+ // done will take care of invalidating display items if the size has actually
+ // changed.
+ SetSize(aNewSize, false);
+
+ const auto overflowClipAxes = ShouldApplyOverflowClipping(disp);
+
+ if (ChildrenHavePerspective(disp) && sizeChanged) {
+ RecomputePerspectiveChildrenOverflow(this);
+
+ if (overflowClipAxes != PhysicalAxes::Both) {
+ aOverflowAreas.SetAllTo(bounds);
+ DebugOnly<bool> ok = ComputeCustomOverflow(aOverflowAreas);
+
+ // ComputeCustomOverflow() should not return false, when
+ // FrameMaintainsOverflow() returns true.
+ MOZ_ASSERT(ok, "FrameMaintainsOverflow() != ComputeCustomOverflow()");
+
+ UnionChildOverflow(aOverflowAreas);
+ }
+ }
+
+ // This is now called FinishAndStoreOverflow() instead of
+ // StoreOverflow() because frame-generic ways of adding overflow
+ // can happen here, e.g. CSS2 outline and native theme.
+ // If the overflow area width or height is nscoord_MAX, then a saturating
+ // union may have encountered an overflow, so the overflow may not contain the
+ // frame border-box. Don't warn in that case.
+ // Don't warn for SVG either, since SVG doesn't need the overflow area
+ // to contain the frame bounds.
+#ifdef DEBUG
+ for (const auto otype : AllOverflowTypes()) {
+ const nsRect& r = aOverflowAreas.Overflow(otype);
+ NS_ASSERTION(aNewSize.width == 0 || aNewSize.height == 0 ||
+ r.width == nscoord_MAX || r.height == nscoord_MAX ||
+ HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
+ r.Contains(nsRect(nsPoint(), aNewSize)),
+ "Computed overflow area must contain frame bounds");
+ }
+#endif
+
+ // Overflow area must always include the frame's top-left and bottom-right,
+ // even if the frame rect is empty (so we can scroll to those positions).
+ const bool shouldIncludeBounds = [&] {
+ if (aNewSize.width == 0 && IsInlineFrame()) {
+ // Pending a real fix for bug 426879, don't do this for inline frames with
+ // zero width.
+ return false;
+ }
+ if (HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // Do not do this for SVG either, since it will usually massively increase
+ // the area unnecessarily (except for SVG that applies clipping, since
+ // that's the pre-existing behavior, and breaks pre-rendering otherwise).
+ // FIXME(bug 1770704): This check most likely wants to be removed or check
+ // for specific frame types at least.
+ return overflowClipAxes != PhysicalAxes::None;
+ }
+ return true;
+ }();
+
+ if (shouldIncludeBounds) {
+ for (const auto otype : AllOverflowTypes()) {
+ nsRect& o = aOverflowAreas.Overflow(otype);
+ o = o.UnionEdges(bounds);
+ }
+ }
+
+ // If we clip our children, clear accumulated overflow area in the affected
+ // dimension(s). The children are actually clipped to the padding-box, but
+ // since the overflow area should include the entire border-box, just set it
+ // to the border-box size here.
+ if (overflowClipAxes != PhysicalAxes::None) {
+ aOverflowAreas.ApplyClipping(bounds, overflowClipAxes,
+ OverflowClipMargin(overflowClipAxes));
+ }
+
+ ComputeAndIncludeOutlineArea(this, aOverflowAreas, aNewSize);
+
+ // Nothing in here should affect scrollable overflow.
+ aOverflowAreas.InkOverflow() =
+ ComputeEffectsRect(this, aOverflowAreas.InkOverflow(), aNewSize);
+
+ // Absolute position clipping
+ const nsStyleEffects* effects = StyleEffects();
+ Maybe<nsRect> clipPropClipRect = GetClipPropClipRect(disp, effects, aNewSize);
+ if (clipPropClipRect) {
+ for (const auto otype : AllOverflowTypes()) {
+ nsRect& o = aOverflowAreas.Overflow(otype);
+ o.IntersectRect(o, *clipPropClipRect);
+ }
+ }
+
+ /* If we're transformed, transform the overflow rect by the current
+ * transformation. */
+ if (hasTransform) {
+ SetProperty(nsIFrame::PreTransformOverflowAreasProperty(),
+ new OverflowAreas(aOverflowAreas));
+
+ if (Combines3DTransformWithAncestors()) {
+ /* If we're a preserve-3d leaf frame, then our pre-transform overflow
+ * should be correct. Our post-transform overflow is empty though, because
+ * we only contribute to the overflow area of the preserve-3d root frame.
+ * If we're an intermediate frame then the pre-transform overflow should
+ * contain all our non-preserve-3d children, which is what we want. Again
+ * we have no post-transform overflow.
+ */
+ aOverflowAreas.SetAllTo(nsRect());
+ } else {
+ TransformReferenceBox refBox(this);
+ for (const auto otype : AllOverflowTypes()) {
+ nsRect& o = aOverflowAreas.Overflow(otype);
+ o = nsDisplayTransform::TransformRect(o, this, refBox);
+ }
+
+ /* If we're the root of the 3d context, then we want to include the
+ * overflow areas of all the participants. This won't have happened yet as
+ * the code above set their overflow area to empty. Manually collect these
+ * overflow areas now.
+ */
+ if (Extend3DContext(disp, effects)) {
+ ComputePreserve3DChildrenOverflow(aOverflowAreas);
+ }
+ }
+ } else {
+ RemoveProperty(nsIFrame::PreTransformOverflowAreasProperty());
+ }
+
+ /* Revert the size change in case some caller is depending on this. */
+ SetSize(oldSize, false);
+
+ bool anyOverflowChanged;
+ if (aOverflowAreas != OverflowAreas(bounds, bounds)) {
+ anyOverflowChanged = SetOverflowAreas(aOverflowAreas);
+ } else {
+ anyOverflowChanged = ClearOverflowRects();
+ }
+
+ if (anyOverflowChanged) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ if (nsBlockFrame* block = do_QueryFrame(this)) {
+ // NOTE(emilio): we need to use BeforeReflow::Yes, because we want to
+ // invalidate in cases where we _used_ to have an overflow marker and no
+ // longer do.
+ if (TextOverflow::CanHaveOverflowMarkers(
+ block, TextOverflow::BeforeReflow::Yes)) {
+ DiscardDisplayItems(this, [](nsDisplayItem* aItem) {
+ return aItem->GetType() == DisplayItemType::TYPE_TEXT_OVERFLOW;
+ });
+ SchedulePaint(PAINT_DEFAULT);
+ }
+ }
+ }
+ return anyOverflowChanged;
+}
+
+void nsIFrame::RecomputePerspectiveChildrenOverflow(
+ const nsIFrame* aStartFrame) {
+ for (const auto& childList : ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ if (!child->FrameMaintainsOverflow()) {
+ continue; // frame does not maintain overflow rects
+ }
+ if (child->HasPerspective()) {
+ OverflowAreas* overflow =
+ child->GetProperty(nsIFrame::InitialOverflowProperty());
+ nsRect bounds(nsPoint(0, 0), child->GetSize());
+ if (overflow) {
+ OverflowAreas overflowCopy = *overflow;
+ child->FinishAndStoreOverflow(overflowCopy, bounds.Size());
+ } else {
+ OverflowAreas boundsOverflow;
+ boundsOverflow.SetAllTo(bounds);
+ child->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
+ }
+ } else if (child->GetContent() == aStartFrame->GetContent() ||
+ child->GetClosestFlattenedTreeAncestorPrimaryFrame() ==
+ aStartFrame) {
+ // If a frame is using perspective, then the size used to compute
+ // perspective-origin is the size of the frame belonging to its parent
+ // style. We must find any descendant frames using our size
+ // (by recursing into frames that have the same containing block)
+ // to update their overflow rects too.
+ child->RecomputePerspectiveChildrenOverflow(aStartFrame);
+ }
+ }
+ }
+}
+
+void nsIFrame::ComputePreserve3DChildrenOverflow(
+ OverflowAreas& aOverflowAreas) {
+ // Find all descendants that participate in the 3d context, and include their
+ // overflow. These descendants have an empty overflow, so won't have been
+ // included in the normal overflow calculation. Any children that don't
+ // participate have normal overflow, so will have been included already.
+
+ nsRect childVisual;
+ nsRect childScrollable;
+ for (const auto& childList : ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ // If this child participates in the 3d context, then take the
+ // pre-transform region (which contains all descendants that aren't
+ // participating in the 3d context) and transform it into the 3d context
+ // root coordinate space.
+ if (child->Combines3DTransformWithAncestors()) {
+ OverflowAreas childOverflow = child->GetOverflowAreasRelativeToSelf();
+ TransformReferenceBox refBox(child);
+ for (const auto otype : AllOverflowTypes()) {
+ nsRect& o = childOverflow.Overflow(otype);
+ o = nsDisplayTransform::TransformRect(o, child, refBox);
+ }
+
+ aOverflowAreas.UnionWith(childOverflow);
+
+ // If this child also extends the 3d context, then recurse into it
+ // looking for more participants.
+ if (child->Extend3DContext()) {
+ child->ComputePreserve3DChildrenOverflow(aOverflowAreas);
+ }
+ }
+ }
+ }
+}
+
+bool nsIFrame::ZIndexApplies() const {
+ return StyleDisplay()->IsPositionedStyle() || IsFlexOrGridItem() ||
+ IsMenuPopupFrame();
+}
+
+Maybe<int32_t> nsIFrame::ZIndex() const {
+ if (!ZIndexApplies()) {
+ return Nothing();
+ }
+ const auto& zIndex = StylePosition()->mZIndex;
+ if (zIndex.IsAuto()) {
+ return Nothing();
+ }
+ return Some(zIndex.AsInteger());
+}
+
+bool nsIFrame::IsScrollAnchor(ScrollAnchorContainer** aOutContainer) {
+ if (!mInScrollAnchorChain) {
+ return false;
+ }
+
+ nsIFrame* f = this;
+
+ // FIXME(emilio, bug 1629280): We should find a non-null anchor if we have the
+ // flag set, but bug 1629280 makes it so that we cannot really assert it /
+ // make this just a `while (true)`, and uncomment the below assertion.
+ while (auto* container = ScrollAnchorContainer::FindFor(f)) {
+ // MOZ_ASSERT(f->IsInScrollAnchorChain());
+ if (nsIFrame* anchor = container->AnchorNode()) {
+ if (anchor != this) {
+ return false;
+ }
+ if (aOutContainer) {
+ *aOutContainer = container;
+ }
+ return true;
+ }
+
+ f = container->Frame();
+ }
+
+ return false;
+}
+
+bool nsIFrame::IsInScrollAnchorChain() const { return mInScrollAnchorChain; }
+
+void nsIFrame::SetInScrollAnchorChain(bool aInChain) {
+ mInScrollAnchorChain = aInChain;
+}
+
+uint32_t nsIFrame::GetDepthInFrameTree() const {
+ uint32_t result = 0;
+ for (nsContainerFrame* ancestor = GetParent(); ancestor;
+ ancestor = ancestor->GetParent()) {
+ result++;
+ }
+ return result;
+}
+
+/**
+ * This function takes a frame that is part of a block-in-inline split,
+ * and _if_ that frame is an anonymous block created by an ib split it
+ * returns the block's preceding inline. This is needed because the
+ * split inline's style is the parent of the anonymous block's style.
+ *
+ * If aFrame is not an anonymous block, null is returned.
+ */
+static nsIFrame* GetIBSplitSiblingForAnonymousBlock(const nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "Must have a non-null frame!");
+ NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
+ "GetIBSplitSibling should only be called on ib-split frames");
+
+ if (aFrame->Style()->GetPseudoType() !=
+ PseudoStyleType::mozBlockInsideInlineWrapper) {
+ // it's not an anonymous block
+ return nullptr;
+ }
+
+ // Find the first continuation of the frame. (Ugh. This ends up
+ // being O(N^2) when it is called O(N) times.)
+ aFrame = aFrame->FirstContinuation();
+
+ /*
+ * Now look up the nsGkAtoms::IBSplitPrevSibling
+ * property.
+ */
+ nsIFrame* ibSplitSibling =
+ aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
+ NS_ASSERTION(ibSplitSibling, "Broken frame tree?");
+ return ibSplitSibling;
+}
+
+/**
+ * Get the parent, corrected for the mangled frame tree resulting from
+ * having a block within an inline. The result only differs from the
+ * result of |GetParent| when |GetParent| returns an anonymous block
+ * that was created for an element that was 'display: inline' because
+ * that element contained a block.
+ *
+ * Also skip anonymous scrolled-content parents; inherit directly from the
+ * outer scroll frame.
+ *
+ * Also skip NAC parents if the child frame is NAC.
+ */
+static nsIFrame* GetCorrectedParent(const nsIFrame* aFrame) {
+ nsIFrame* parent = aFrame->GetParent();
+ if (!parent) {
+ return nullptr;
+ }
+
+ // For a table caption we want the _inner_ table frame (unless it's anonymous)
+ // as the style parent.
+ if (aFrame->IsTableCaption()) {
+ nsIFrame* innerTable = parent->PrincipalChildList().FirstChild();
+ if (!innerTable->Style()->IsAnonBox()) {
+ return innerTable;
+ }
+ }
+
+ // Table wrappers are always anon boxes; if we're in here for an outer
+ // table, that actually means its the _inner_ table that wants to
+ // know its parent. So get the pseudo of the inner in that case.
+ auto pseudo = aFrame->Style()->GetPseudoType();
+ if (pseudo == PseudoStyleType::tableWrapper) {
+ pseudo =
+ aFrame->PrincipalChildList().FirstChild()->Style()->GetPseudoType();
+ }
+
+ // Prevent a NAC pseudo-element from inheriting from its NAC parent, and
+ // inherit from the NAC generator element instead.
+ if (pseudo != PseudoStyleType::NotPseudo) {
+ MOZ_ASSERT(aFrame->GetContent());
+ Element* element = Element::FromNode(aFrame->GetContent());
+ // Make sure to avoid doing the fixup for non-element-backed pseudos like
+ // ::first-line and such.
+ if (element && !element->IsRootOfNativeAnonymousSubtree() &&
+ element->GetPseudoElementType() == aFrame->Style()->GetPseudoType()) {
+ while (parent->GetContent() &&
+ !parent->GetContent()->IsRootOfNativeAnonymousSubtree()) {
+ parent = parent->GetInFlowParent();
+ }
+ parent = parent->GetInFlowParent();
+ }
+ }
+
+ return nsIFrame::CorrectStyleParentFrame(parent, pseudo);
+}
+
+/* static */
+nsIFrame* nsIFrame::CorrectStyleParentFrame(nsIFrame* aProspectiveParent,
+ PseudoStyleType aChildPseudo) {
+ MOZ_ASSERT(aProspectiveParent, "Must have a prospective parent");
+
+ if (aChildPseudo != PseudoStyleType::NotPseudo) {
+ // Non-inheriting anon boxes have no style parent frame at all.
+ if (PseudoStyle::IsNonInheritingAnonBox(aChildPseudo)) {
+ return nullptr;
+ }
+
+ // Other anon boxes are parented to their actual parent already, except
+ // for non-elements. Those should not be treated as an anon box.
+ if (PseudoStyle::IsAnonBox(aChildPseudo) &&
+ !nsCSSAnonBoxes::IsNonElement(aChildPseudo)) {
+ NS_ASSERTION(aChildPseudo != PseudoStyleType::mozBlockInsideInlineWrapper,
+ "Should have dealt with kids that have "
+ "NS_FRAME_PART_OF_IBSPLIT elsewhere");
+ return aProspectiveParent;
+ }
+ }
+
+ // Otherwise, walk up out of all anon boxes. For placeholder frames, walk out
+ // of all pseudo-elements as well. Otherwise ReparentComputedStyle could
+ // cause style data to be out of sync with the frame tree.
+ nsIFrame* parent = aProspectiveParent;
+ do {
+ if (parent->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ nsIFrame* sibling = GetIBSplitSiblingForAnonymousBlock(parent);
+
+ if (sibling) {
+ // |parent| was a block in an {ib} split; use the inline as
+ // |the style parent.
+ parent = sibling;
+ }
+ }
+
+ if (!parent->Style()->IsPseudoOrAnonBox()) {
+ return parent;
+ }
+
+ if (!parent->Style()->IsAnonBox() && aChildPseudo != PseudoStyleType::MAX) {
+ // nsPlaceholderFrame passes in PseudoStyleType::MAX for
+ // aChildPseudo (even though that's not a valid pseudo-type) just to
+ // trigger this behavior of walking up to the nearest non-pseudo
+ // ancestor.
+ return parent;
+ }
+
+ parent = parent->GetInFlowParent();
+ } while (parent);
+
+ if (aProspectiveParent->Style()->GetPseudoType() ==
+ PseudoStyleType::viewportScroll) {
+ // aProspectiveParent is the scrollframe for a viewport
+ // and the kids are the anonymous scrollbars
+ return aProspectiveParent;
+ }
+
+ // We can get here if the root element is absolutely positioned.
+ // We can't test for this very accurately, but it can only happen
+ // when the prospective parent is a canvas frame.
+ NS_ASSERTION(aProspectiveParent->IsCanvasFrame(),
+ "Should have found a parent before this");
+ return nullptr;
+}
+
+ComputedStyle* nsIFrame::DoGetParentComputedStyle(
+ nsIFrame** aProviderFrame) const {
+ *aProviderFrame = nullptr;
+
+ // Handle display:contents and the root frame, when there's no parent frame
+ // to inherit from.
+ if (MOZ_LIKELY(mContent)) {
+ Element* parentElement = mContent->GetFlattenedTreeParentElement();
+ if (MOZ_LIKELY(parentElement)) {
+ auto pseudo = Style()->GetPseudoType();
+ if (pseudo == PseudoStyleType::NotPseudo || !mContent->IsElement() ||
+ (!PseudoStyle::IsAnonBox(pseudo) &&
+ // Ensure that we don't return the display:contents style
+ // of the parent content for pseudos that have the same content
+ // as their primary frame (like -moz-list-bullets do):
+ IsPrimaryFrame()) ||
+ /* if next is true then it's really a request for the table frame's
+ parent context, see nsTable[Outer]Frame::GetParentComputedStyle. */
+ pseudo == PseudoStyleType::tableWrapper) {
+ // In some edge cases involving display: contents, we may end up here
+ // for something that's pending to be reframed. In this case we return
+ // the wrong style from here (because we've already lost track of it!),
+ // but it's not a big deal as we're going to be reframed anyway.
+ if (MOZ_LIKELY(parentElement->HasServoData()) &&
+ Servo_Element_IsDisplayContents(parentElement)) {
+ RefPtr<ComputedStyle> style =
+ ServoStyleSet::ResolveServoStyle(*parentElement);
+ // NOTE(emilio): we return a weak reference because the element also
+ // holds the style context alive. This is a bit silly (we could've
+ // returned a weak ref directly), but it's probably not worth
+ // optimizing, given this function has just one caller which is rare,
+ // and this path is rare itself.
+ return style;
+ }
+ }
+ } else {
+ if (Style()->GetPseudoType() == PseudoStyleType::NotPseudo) {
+ // We're a frame for the root. We have no style parent.
+ return nullptr;
+ }
+ }
+ }
+
+ if (!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ /*
+ * If this frame is an anonymous block created when an inline with a block
+ * inside it got split, then the parent style is on its preceding inline. We
+ * can get to it using GetIBSplitSiblingForAnonymousBlock.
+ */
+ if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ nsIFrame* ibSplitSibling = GetIBSplitSiblingForAnonymousBlock(this);
+ if (ibSplitSibling) {
+ return (*aProviderFrame = ibSplitSibling)->Style();
+ }
+ }
+
+ // If this frame is one of the blocks that split an inline, we must
+ // return the "special" inline parent, i.e., the parent that this
+ // frame would have if we didn't mangle the frame structure.
+ *aProviderFrame = GetCorrectedParent(this);
+ return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
+ }
+
+ // We're an out-of-flow frame. For out-of-flow frames, we must
+ // resolve underneath the placeholder's parent. The placeholder is
+ // reached from the first-in-flow.
+ nsPlaceholderFrame* placeholder = FirstInFlow()->GetPlaceholderFrame();
+ if (!placeholder) {
+ MOZ_ASSERT_UNREACHABLE("no placeholder frame for out-of-flow frame");
+ *aProviderFrame = GetCorrectedParent(this);
+ return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
+ }
+ return placeholder->GetParentComputedStyleForOutOfFlow(aProviderFrame);
+}
+
+void nsIFrame::GetLastLeaf(nsIFrame** aFrame) {
+ if (!aFrame || !*aFrame) return;
+ nsIFrame* child = *aFrame;
+ // if we are a block frame then go for the last line of 'this'
+ while (1) {
+ child = child->PrincipalChildList().FirstChild();
+ if (!child) return; // nothing to do
+ nsIFrame* siblingFrame;
+ nsIContent* content;
+ // ignore anonymous elements, e.g. mozTableAdd* mozTableRemove*
+ // see bug 278197 comment #12 #13 for details
+ while ((siblingFrame = child->GetNextSibling()) &&
+ (content = siblingFrame->GetContent()) &&
+ !content->IsRootOfNativeAnonymousSubtree())
+ child = siblingFrame;
+ *aFrame = child;
+ }
+}
+
+void nsIFrame::GetFirstLeaf(nsIFrame** aFrame) {
+ if (!aFrame || !*aFrame) return;
+ nsIFrame* child = *aFrame;
+ while (1) {
+ child = child->PrincipalChildList().FirstChild();
+ if (!child) return; // nothing to do
+ *aFrame = child;
+ }
+}
+
+bool nsIFrame::IsFocusableDueToScrollFrame() {
+ if (!IsScrollFrame()) {
+ if (nsFieldSetFrame* fieldset = do_QueryFrame(this)) {
+ // TODO: Do we have similar special-cases like this where we can have
+ // anonymous scrollable boxes hanging off a primary frame?
+ if (nsIFrame* inner = fieldset->GetInner()) {
+ return inner->IsFocusableDueToScrollFrame();
+ }
+ }
+ return false;
+ }
+ if (!mContent->IsHTMLElement()) {
+ return false;
+ }
+ if (mContent->IsRootOfNativeAnonymousSubtree()) {
+ return false;
+ }
+ if (!mContent->GetParent()) {
+ return false;
+ }
+ if (mContent->AsElement()->HasAttr(nsGkAtoms::tabindex)) {
+ return false;
+ }
+ // Elements with scrollable view are focusable with script & tabbable
+ // Otherwise you couldn't scroll them with keyboard, which is an accessibility
+ // issue (e.g. Section 508 rules) However, we don't make them to be focusable
+ // with the mouse, because the extra focus outlines are considered
+ // unnecessarily ugly. When clicked on, the selection position within the
+ // element will be enough to make them keyboard scrollable.
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(this);
+ if (!scrollFrame) {
+ return false;
+ }
+ if (scrollFrame->IsForTextControlWithNoScrollbars()) {
+ return false;
+ }
+ if (scrollFrame->GetScrollStyles().IsHiddenInBothDirections()) {
+ return false;
+ }
+ if (scrollFrame->GetScrollRange().IsEqualEdges(nsRect(0, 0, 0, 0))) {
+ return false;
+ }
+ return true;
+}
+
+Focusable nsIFrame::IsFocusable(bool aWithMouse, bool aCheckVisibility) {
+ // cannot focus content in print preview mode. Only the root can be focused,
+ // but that's handled elsewhere.
+ if (PresContext()->Type() == nsPresContext::eContext_PrintPreview) {
+ return {};
+ }
+
+ if (!mContent || !mContent->IsElement()) {
+ return {};
+ }
+
+ if (aCheckVisibility && !IsVisibleConsideringAncestors()) {
+ return {};
+ }
+
+ const StyleUserFocus uf = StyleUI()->UserFocus();
+ if (uf == StyleUserFocus::None) {
+ return {};
+ }
+ MOZ_ASSERT(!StyleUI()->IsInert(), "inert implies -moz-user-focus: none");
+
+ const PseudoStyleType pseudo = Style()->GetPseudoType();
+ if (pseudo == PseudoStyleType::anonymousItem) {
+ return {};
+ }
+
+ Focusable focusable;
+ if (auto* xul = nsXULElement::FromNode(mContent)) {
+ // As a legacy special-case, -moz-user-focus controls focusability and
+ // tabability of XUL elements in some circumstances (which default to
+ // -moz-user-focus: ignore).
+ auto focusability = xul->GetXULFocusability(aWithMouse);
+ focusable.mFocusable =
+ focusability.mForcedFocusable.valueOr(uf == StyleUserFocus::Normal);
+ if (focusable) {
+ focusable.mTabIndex = focusability.mForcedTabIndexIfFocusable.valueOr(0);
+ }
+ } else {
+ focusable = mContent->IsFocusableWithoutStyle(aWithMouse);
+ }
+
+ if (focusable) {
+ return focusable;
+ }
+
+ // If we're focusing with the mouse we never focus scroll areas.
+ if (!aWithMouse && IsFocusableDueToScrollFrame()) {
+ return {true, 0};
+ }
+
+ // FIXME(emilio): some callers rely on somewhat broken return values
+ // (focusable = false, but non-negative tab-index) from
+ // IsFocusableWithoutStyle (for image maps in particular).
+ return focusable;
+}
+
+/**
+ * @return true if this text frame ends with a newline character which is
+ * treated as preformatted. It should return false if this is not a text frame.
+ */
+bool nsIFrame::HasSignificantTerminalNewline() const { return false; }
+
+static StyleVerticalAlignKeyword ConvertSVGDominantBaselineToVerticalAlign(
+ StyleDominantBaseline aDominantBaseline) {
+ // Most of these are approximate mappings.
+ switch (aDominantBaseline) {
+ case StyleDominantBaseline::Hanging:
+ case StyleDominantBaseline::TextBeforeEdge:
+ return StyleVerticalAlignKeyword::TextTop;
+ case StyleDominantBaseline::TextAfterEdge:
+ case StyleDominantBaseline::Ideographic:
+ return StyleVerticalAlignKeyword::TextBottom;
+ case StyleDominantBaseline::Central:
+ case StyleDominantBaseline::Middle:
+ case StyleDominantBaseline::Mathematical:
+ return StyleVerticalAlignKeyword::Middle;
+ case StyleDominantBaseline::Auto:
+ case StyleDominantBaseline::Alphabetic:
+ return StyleVerticalAlignKeyword::Baseline;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected aDominantBaseline value");
+ return StyleVerticalAlignKeyword::Baseline;
+ }
+}
+
+Maybe<StyleVerticalAlignKeyword> nsIFrame::VerticalAlignEnum() const {
+ if (IsInSVGTextSubtree()) {
+ StyleDominantBaseline dominantBaseline = StyleSVG()->mDominantBaseline;
+ return Some(ConvertSVGDominantBaselineToVerticalAlign(dominantBaseline));
+ }
+
+ const auto& verticalAlign = StyleDisplay()->mVerticalAlign;
+ if (verticalAlign.IsKeyword()) {
+ return Some(verticalAlign.AsKeyword());
+ }
+
+ return Nothing();
+}
+
+void nsIFrame::UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
+ ServoRestyleState& aRestyleState) {
+#ifdef DEBUG
+ nsIFrame* parent = aChildFrame->GetInFlowParent();
+ if (aChildFrame->IsTableFrame()) {
+ parent = parent->GetParent();
+ }
+ if (parent->IsLineFrame()) {
+ parent = parent->GetParent();
+ }
+ MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent) == this,
+ "This should only be used for children!");
+#endif // DEBUG
+ MOZ_ASSERT(!GetContent() || !aChildFrame->GetContent() ||
+ aChildFrame->GetContent() == GetContent(),
+ "What content node is it a frame for?");
+ MOZ_ASSERT(!aChildFrame->GetPrevContinuation(),
+ "Only first continuations should end up here");
+
+ // We could force the caller to pass in the pseudo, since some callers know it
+ // statically... But this API is a bit nicer.
+ auto pseudo = aChildFrame->Style()->GetPseudoType();
+ MOZ_ASSERT(PseudoStyle::IsAnonBox(pseudo), "Child is not an anon box?");
+ MOZ_ASSERT(!PseudoStyle::IsNonInheritingAnonBox(pseudo),
+ "Why did the caller bother calling us?");
+
+ // Anon boxes inherit from their parent; that's us.
+ RefPtr<ComputedStyle> newContext =
+ aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(pseudo,
+ Style());
+
+ nsChangeHint childHint =
+ UpdateStyleOfOwnedChildFrame(aChildFrame, newContext, aRestyleState);
+
+ // Now that we've updated the style on aChildFrame, check whether it itself
+ // has anon boxes to deal with.
+ ServoRestyleState childrenState(*aChildFrame, aRestyleState, childHint,
+ ServoRestyleState::Type::InFlow);
+ aChildFrame->UpdateStyleOfOwnedAnonBoxes(childrenState);
+
+ // Assuming anon boxes don't have ::backdrop associated with them... if that
+ // ever changes, we'd need to handle that here, like we do in
+ // RestyleManager::ProcessPostTraversal
+
+ // We do need to handle block pseudo-elements here, though. Especially list
+ // bullets.
+ if (nsBlockFrame* block = do_QueryFrame(aChildFrame)) {
+ block->UpdatePseudoElementStyles(childrenState);
+ }
+}
+
+/* static */
+nsChangeHint nsIFrame::UpdateStyleOfOwnedChildFrame(
+ nsIFrame* aChildFrame, ComputedStyle* aNewComputedStyle,
+ ServoRestyleState& aRestyleState,
+ const Maybe<ComputedStyle*>& aContinuationComputedStyle) {
+ MOZ_ASSERT(!aChildFrame->GetAdditionalComputedStyle(0),
+ "We don't handle additional styles here");
+
+ // Figure out whether we have an actual change. It's important that we do
+ // this, for several reasons:
+ //
+ // 1) Even if all the child's changes are due to properties it inherits from
+ // us, it's possible that no one ever asked us for those style structs and
+ // hence changes to them aren't reflected in the changes handled at all.
+ //
+ // 2) Content can change stylesheets that change the styles of pseudos, and
+ // extensions can add/remove stylesheets that change the styles of
+ // anonymous boxes directly.
+ uint32_t equalStructs; // Not used, actually.
+ nsChangeHint childHint = aChildFrame->Style()->CalcStyleDifference(
+ *aNewComputedStyle, &equalStructs);
+
+ // If aChildFrame is out of flow, then aRestyleState's "changes handled by the
+ // parent" doesn't apply to it, because it may have some other parent in the
+ // frame tree.
+ if (!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ childHint = NS_RemoveSubsumedHints(
+ childHint, aRestyleState.ChangesHandledFor(aChildFrame));
+ }
+ if (childHint) {
+ if (childHint & nsChangeHint_ReconstructFrame) {
+ // If we generate a reconstruct here, remove any non-reconstruct hints we
+ // may have already generated for this content.
+ aRestyleState.ChangeList().PopChangesForContent(
+ aChildFrame->GetContent());
+ }
+ aRestyleState.ChangeList().AppendChange(
+ aChildFrame, aChildFrame->GetContent(), childHint);
+ }
+
+ aChildFrame->SetComputedStyle(aNewComputedStyle);
+ ComputedStyle* continuationStyle = aContinuationComputedStyle
+ ? *aContinuationComputedStyle
+ : aNewComputedStyle;
+ for (nsIFrame* kid = aChildFrame->GetNextContinuation(); kid;
+ kid = kid->GetNextContinuation()) {
+ MOZ_ASSERT(!kid->GetAdditionalComputedStyle(0));
+ kid->SetComputedStyle(continuationStyle);
+ }
+
+ return childHint;
+}
+
+/* static */
+void nsIFrame::AddInPopupStateBitToDescendants(nsIFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) &&
+ aFrame->TrackingVisibility()) {
+ // Assume all frames in popups are visible.
+ aFrame->IncApproximateVisibleCount();
+ }
+
+ aFrame->AddStateBits(NS_FRAME_IN_POPUP);
+
+ for (const auto& childList : aFrame->CrossDocChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ AddInPopupStateBitToDescendants(child);
+ }
+ }
+}
+
+/* static */
+void nsIFrame::RemoveInPopupStateBitFromDescendants(nsIFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) ||
+ nsLayoutUtils::IsPopup(aFrame)) {
+ return;
+ }
+
+ aFrame->RemoveStateBits(NS_FRAME_IN_POPUP);
+
+ if (aFrame->TrackingVisibility()) {
+ // We assume all frames in popups are visible, so this decrement balances
+ // out the increment in AddInPopupStateBitToDescendants above.
+ aFrame->DecApproximateVisibleCount();
+ }
+ for (const auto& childList : aFrame->CrossDocChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ RemoveInPopupStateBitFromDescendants(child);
+ }
+ }
+}
+
+void nsIFrame::SetParent(nsContainerFrame* aParent) {
+ // If our parent is a wrapper anon box, our new parent should be too. We
+ // _can_ change parent if our parent is a wrapper anon box, because some
+ // wrapper anon boxes can have continuations.
+ MOZ_ASSERT_IF(ParentIsWrapperAnonBox(),
+ aParent->Style()->IsInheritingAnonBox());
+
+ // Note that the current mParent may already be destroyed at this point.
+ mParent = aParent;
+ MOZ_DIAGNOSTIC_ASSERT(!mParent || PresShell() == mParent->PresShell());
+
+ if (HasAnyStateBits(NS_FRAME_HAS_VIEW | NS_FRAME_HAS_CHILD_WITH_VIEW)) {
+ for (nsIFrame* f = aParent;
+ f && !f->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
+ f = f->GetParent()) {
+ f->AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW);
+ }
+ }
+
+ if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ for (nsIFrame* f = aParent; f; f = f->GetParent()) {
+ if (f->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ break;
+ }
+ f->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+ }
+
+ if (HasAnyStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
+ for (nsIFrame* f = aParent; f; f = f->GetParent()) {
+ if (f->HasAnyStateBits(
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
+ break;
+ }
+ f->AddStateBits(NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
+ }
+ }
+
+ if (HasInvalidFrameInSubtree()) {
+ for (nsIFrame* f = aParent;
+ f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT |
+ NS_FRAME_IS_NONDISPLAY);
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ f->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
+ }
+ }
+
+ if (aParent->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ AddInPopupStateBitToDescendants(this);
+ } else {
+ RemoveInPopupStateBitFromDescendants(this);
+ }
+
+ // If our new parent only has invalid children, then we just invalidate
+ // ourselves too. This is probably faster than clearing the flag all
+ // the way up the frame tree.
+ if (aParent->HasAnyStateBits(NS_FRAME_ALL_DESCENDANTS_NEED_PAINT)) {
+ InvalidateFrame();
+ } else {
+ SchedulePaint();
+ }
+}
+
+bool nsIFrame::IsStackingContext(const nsStyleDisplay* aStyleDisplay,
+ const nsStyleEffects* aStyleEffects) {
+ // Properties that influence the output of this function should be handled in
+ // change_bits_for_longhand as well.
+ if (HasOpacity(aStyleDisplay, aStyleEffects, nullptr)) {
+ return true;
+ }
+ if (IsTransformed()) {
+ return true;
+ }
+ auto willChange = aStyleDisplay->mWillChange.bits;
+ if (aStyleDisplay->IsContainPaint() || aStyleDisplay->IsContainLayout() ||
+ willChange & StyleWillChangeBits::CONTAIN) {
+ if (SupportsContainLayoutAndPaint()) {
+ return true;
+ }
+ }
+ // strictly speaking, 'perspective' doesn't require visual atomicity,
+ // but the spec says it acts like the rest of these
+ if (aStyleDisplay->HasPerspectiveStyle() ||
+ willChange & StyleWillChangeBits::PERSPECTIVE) {
+ if (SupportsCSSTransforms()) {
+ return true;
+ }
+ }
+ if (!StylePosition()->mZIndex.IsAuto() ||
+ willChange & StyleWillChangeBits::Z_INDEX) {
+ if (ZIndexApplies()) {
+ return true;
+ }
+ }
+ return aStyleEffects->mMixBlendMode != StyleBlend::Normal ||
+ SVGIntegrationUtils::UsingEffectsForFrame(this) ||
+ aStyleDisplay->IsPositionForcingStackingContext() ||
+ aStyleDisplay->mIsolation != StyleIsolation::Auto ||
+ willChange & StyleWillChangeBits::STACKING_CONTEXT_UNCONDITIONAL;
+}
+
+bool nsIFrame::IsStackingContext() {
+ return IsStackingContext(StyleDisplay(), StyleEffects());
+}
+
+static bool IsFrameScrolledOutOfView(const nsIFrame* aTarget,
+ const nsRect& aTargetRect,
+ const nsIFrame* aParent) {
+ // The ancestor frame we are checking if it clips out aTargetRect relative to
+ // aTarget.
+ nsIFrame* clipParent = nullptr;
+
+ // find the first scrollable frame or root frame if we are in a fixed pos
+ // subtree
+ for (nsIFrame* f = const_cast<nsIFrame*>(aParent); f;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
+ if (scrollableFrame) {
+ clipParent = f;
+ break;
+ }
+ if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
+ nsLayoutUtils::IsReallyFixedPos(f)) {
+ clipParent = f->GetParent();
+ break;
+ }
+ }
+
+ if (!clipParent) {
+ // Even if we couldn't find the nearest scrollable frame, it might mean we
+ // are in an out-of-process iframe, try to see if |aTarget| frame is
+ // scrolled out of view in an scrollable frame in a cross-process ancestor
+ // document.
+ return nsLayoutUtils::FrameIsScrolledOutOfViewInCrossProcess(aTarget);
+ }
+
+ nsRect clipRect = clipParent->InkOverflowRectRelativeToSelf();
+ // We consider that the target is scrolled out if the scrollable (or root)
+ // frame is empty.
+ if (clipRect.IsEmpty()) {
+ return true;
+ }
+
+ nsRect transformedRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ aTarget, aTargetRect, clipParent);
+
+ if (transformedRect.IsEmpty()) {
+ // If the transformed rect is empty it represents a line or a point that we
+ // should check is outside the the scrollable rect.
+ if (transformedRect.x > clipRect.XMost() ||
+ transformedRect.y > clipRect.YMost() ||
+ clipRect.x > transformedRect.XMost() ||
+ clipRect.y > transformedRect.YMost()) {
+ return true;
+ }
+ } else if (!transformedRect.Intersects(clipRect)) {
+ return true;
+ }
+
+ nsIFrame* parent = clipParent->GetParent();
+ if (!parent) {
+ return false;
+ }
+
+ return IsFrameScrolledOutOfView(aTarget, aTargetRect, parent);
+}
+
+bool nsIFrame::IsScrolledOutOfView() const {
+ nsRect rect = InkOverflowRectRelativeToSelf();
+ return IsFrameScrolledOutOfView(this, rect, this);
+}
+
+gfx::Matrix nsIFrame::ComputeWidgetTransform() const {
+ const nsStyleUIReset* uiReset = StyleUIReset();
+ if (uiReset->mMozWindowTransform.IsNone()) {
+ return gfx::Matrix();
+ }
+
+ TransformReferenceBox refBox(nullptr, nsRect(nsPoint(), GetSize()));
+
+ nsPresContext* presContext = PresContext();
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms(
+ uiReset->mMozWindowTransform, refBox, float(appUnitsPerDevPixel));
+
+ // Apply the -moz-window-transform-origin translation to the matrix.
+ const StyleTransformOrigin& origin = uiReset->mWindowTransformOrigin;
+ Point transformOrigin = nsStyleTransformMatrix::Convert2DPosition(
+ origin.horizontal, origin.vertical, refBox, appUnitsPerDevPixel);
+ matrix.ChangeBasis(Point3D(transformOrigin.x, transformOrigin.y, 0));
+
+ gfx::Matrix result2d;
+ if (!matrix.CanDraw2D(&result2d)) {
+ // FIXME: It would be preferable to reject non-2D transforms at parse time.
+ NS_WARNING(
+ "-moz-window-transform does not describe a 2D transform, "
+ "but only 2d transforms are supported");
+ return gfx::Matrix();
+ }
+
+ return result2d;
+}
+
+void nsIFrame::DoUpdateStyleOfOwnedAnonBoxes(ServoRestyleState& aRestyleState) {
+ // As a special case, we check for {ib}-split block frames here, rather
+ // than have an nsInlineFrame::AppendDirectlyOwnedAnonBoxes implementation
+ // that returns them.
+ //
+ // (If we did handle them in AppendDirectlyOwnedAnonBoxes, we would have to
+ // return *all* of the in-flow {ib}-split block frames, not just the first
+ // one. For restyling, we really just need the first in flow, and the other
+ // user of the AppendOwnedAnonBoxes API, AllChildIterator, doesn't need to
+ // know about them at all, since these block frames never create NAC. So we
+ // avoid any unncessary hashtable lookups for the {ib}-split frames by calling
+ // UpdateStyleOfOwnedAnonBoxesForIBSplit directly here.)
+ if (IsInlineFrame()) {
+ if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ static_cast<nsInlineFrame*>(this)->UpdateStyleOfOwnedAnonBoxesForIBSplit(
+ aRestyleState);
+ }
+ return;
+ }
+
+ AutoTArray<OwnedAnonBox, 4> frames;
+ AppendDirectlyOwnedAnonBoxes(frames);
+ for (OwnedAnonBox& box : frames) {
+ if (box.mUpdateStyleFn) {
+ box.mUpdateStyleFn(this, box.mAnonBoxFrame, aRestyleState);
+ } else {
+ UpdateStyleOfChildAnonBox(box.mAnonBoxFrame, aRestyleState);
+ }
+ }
+}
+
+/* virtual */
+void nsIFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES));
+ MOZ_ASSERT(false, "Why did this get called?");
+}
+
+void nsIFrame::DoAppendOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
+ size_t i = aResult.Length();
+ AppendDirectlyOwnedAnonBoxes(aResult);
+
+ // After appending the directly owned anonymous boxes of this frame to
+ // aResult above, we need to check each of them to see if they own
+ // any anonymous boxes themselves. Note that we keep progressing
+ // through aResult, looking for additional entries in aResult from these
+ // subsequent AppendDirectlyOwnedAnonBoxes calls. (Thus we can't
+ // use a ranged for loop here.)
+
+ while (i < aResult.Length()) {
+ nsIFrame* f = aResult[i].mAnonBoxFrame;
+ if (f->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
+ f->AppendDirectlyOwnedAnonBoxes(aResult);
+ }
+ ++i;
+ }
+}
+
+nsIFrame::CaretPosition::CaretPosition() : mContentOffset(0) {}
+
+nsIFrame::CaretPosition::~CaretPosition() = default;
+
+bool nsIFrame::HasCSSAnimations() {
+ auto* collection = AnimationCollection<CSSAnimation>::Get(this);
+ return collection && !collection->mAnimations.IsEmpty();
+}
+
+bool nsIFrame::HasCSSTransitions() {
+ auto* collection = AnimationCollection<CSSTransition>::Get(this);
+ return collection && !collection->mAnimations.IsEmpty();
+}
+
+void nsIFrame::AddSizeOfExcludingThisForTree(nsWindowSizes& aSizes) const {
+ aSizes.mLayoutFramePropertiesSize +=
+ mProperties.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+
+ // We don't do this for Gecko because this stuff is stored in the nsPresArena
+ // and so measured elsewhere.
+ if (!aSizes.mState.HaveSeenPtr(mComputedStyle)) {
+ mComputedStyle->AddSizeOfIncludingThis(aSizes,
+ &aSizes.mLayoutComputedValuesNonDom);
+ }
+
+ // And our additional styles.
+ int32_t index = 0;
+ while (auto* extra = GetAdditionalComputedStyle(index++)) {
+ if (!aSizes.mState.HaveSeenPtr(extra)) {
+ extra->AddSizeOfIncludingThis(aSizes,
+ &aSizes.mLayoutComputedValuesNonDom);
+ }
+ }
+
+ for (const auto& childList : ChildLists()) {
+ for (const nsIFrame* f : childList.mList) {
+ f->AddSizeOfExcludingThisForTree(aSizes);
+ }
+ }
+}
+
+nsRect nsIFrame::GetCompositorHitTestArea(nsDisplayListBuilder* aBuilder) {
+ nsRect area;
+
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
+ if (scrollFrame) {
+ // If the frame is content of a scrollframe, then we need to pick up the
+ // area corresponding to the overflow rect as well. Otherwise the parts of
+ // the overflow that are not occupied by descendants get skipped and the
+ // APZ code sends touch events to the content underneath instead.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
+ area = ScrollableOverflowRect();
+ } else {
+ area = GetRectRelativeToSelf();
+ }
+
+ if (!area.IsEmpty()) {
+ return area + aBuilder->ToReferenceFrame(this);
+ }
+
+ return area;
+}
+
+CompositorHitTestInfo nsIFrame::GetCompositorHitTestInfo(
+ nsDisplayListBuilder* aBuilder) {
+ CompositorHitTestInfo result = CompositorHitTestInvisibleToHit;
+
+ if (aBuilder->IsInsidePointerEventsNoneDoc()) {
+ // Somewhere up the parent document chain is a subdocument with pointer-
+ // events:none set on it.
+ return result;
+ }
+ if (!GetParent()) {
+ MOZ_ASSERT(IsViewportFrame());
+ // Viewport frames are never event targets, other frames, like canvas
+ // frames, are the event targets for any regions viewport frames may cover.
+ return result;
+ }
+ if (Style()->PointerEvents() == StylePointerEvents::None) {
+ return result;
+ }
+ if (!StyleVisibility()->IsVisible()) {
+ return result;
+ }
+
+ // Anything that didn't match the above conditions is visible to hit-testing.
+ result = CompositorHitTestFlags::eVisibleToHitTest;
+ SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(this, false);
+ if (maskUsage.UsingMaskOrClipPath()) {
+ // If WebRender is enabled, simple clip-paths can be converted into WR
+ // clips that WR knows how to hit-test against, so we don't need to mark
+ // it as an irregular area.
+ if (!maskUsage.IsSimpleClipShape()) {
+ result += CompositorHitTestFlags::eIrregularArea;
+ }
+ }
+
+ if (aBuilder->IsBuildingNonLayerizedScrollbar()) {
+ // Scrollbars may be painted into a layer below the actual layer they will
+ // scroll, and therefore wheel events may be dispatched to the outer frame
+ // instead of the intended scrollframe. To address this, we force a d-t-c
+ // region on scrollbar frames that won't be placed in their own layer. See
+ // bug 1213324 for details.
+ result += CompositorHitTestFlags::eInactiveScrollframe;
+ } else if (aBuilder->GetAncestorHasApzAwareEventHandler()) {
+ result += CompositorHitTestFlags::eApzAwareListeners;
+ } else if (IsRangeFrame()) {
+ // Range frames handle touch events directly without having a touch listener
+ // so we need to let APZ know that this area cares about events.
+ result += CompositorHitTestFlags::eApzAwareListeners;
+ }
+
+ if (aBuilder->IsTouchEventPrefEnabledDoc()) {
+ // Inherit the touch-action flags from the parent, if there is one. We do
+ // this because of how the touch-action on a frame combines the touch-action
+ // from ancestor DOM elements. Refer to the documentation in
+ // TouchActionHelper.cpp for details; this code is meant to be equivalent to
+ // that code, but woven into the top-down recursive display list building
+ // process.
+ CompositorHitTestInfo inheritedTouchAction =
+ aBuilder->GetCompositorHitTestInfo() & CompositorHitTestTouchActionMask;
+
+ nsIFrame* touchActionFrame = this;
+ if (nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::GetScrollableFrameFor(this)) {
+ ScrollStyles ss = scrollFrame->GetScrollStyles();
+ if (ss.mVertical != StyleOverflow::Hidden ||
+ ss.mHorizontal != StyleOverflow::Hidden) {
+ touchActionFrame = do_QueryFrame(scrollFrame);
+ // On scrollframes, stop inheriting the pan-x and pan-y flags; instead,
+ // reset them back to zero to allow panning on the scrollframe unless we
+ // encounter an element that disables it that's inside the scrollframe.
+ // This is equivalent to the |considerPanning| variable in
+ // TouchActionHelper.cpp, but for a top-down traversal.
+ CompositorHitTestInfo panMask(
+ CompositorHitTestFlags::eTouchActionPanXDisabled,
+ CompositorHitTestFlags::eTouchActionPanYDisabled);
+ inheritedTouchAction -= panMask;
+ }
+ }
+
+ result += inheritedTouchAction;
+
+ const StyleTouchAction touchAction = touchActionFrame->UsedTouchAction();
+ // The CSS allows the syntax auto | none | [pan-x || pan-y] | manipulation
+ // so we can eliminate some combinations of things.
+ if (touchAction == StyleTouchAction::AUTO) {
+ // nothing to do
+ } else if (touchAction & StyleTouchAction::MANIPULATION) {
+ result += CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled;
+ } else {
+ // This path handles the cases none | [pan-x || pan-y || pinch-zoom] so
+ // double-tap is disabled in here.
+ if (!(touchAction & StyleTouchAction::PINCH_ZOOM)) {
+ result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled;
+ }
+
+ result += CompositorHitTestFlags::eTouchActionAnimatingZoomDisabled;
+
+ if (!(touchAction & StyleTouchAction::PAN_X)) {
+ result += CompositorHitTestFlags::eTouchActionPanXDisabled;
+ }
+ if (!(touchAction & StyleTouchAction::PAN_Y)) {
+ result += CompositorHitTestFlags::eTouchActionPanYDisabled;
+ }
+ if (touchAction & StyleTouchAction::NONE) {
+ // all the touch-action disabling flags will already have been set above
+ MOZ_ASSERT(result.contains(CompositorHitTestTouchActionMask));
+ }
+ }
+ }
+
+ const Maybe<ScrollDirection> scrollDirection =
+ aBuilder->GetCurrentScrollbarDirection();
+ if (scrollDirection.isSome()) {
+ if (GetContent()->IsXULElement(nsGkAtoms::thumb)) {
+ const bool thumbGetsLayer = aBuilder->GetCurrentScrollbarTarget() !=
+ layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ if (thumbGetsLayer) {
+ result += CompositorHitTestFlags::eScrollbarThumb;
+ } else {
+ result += CompositorHitTestFlags::eInactiveScrollframe;
+ }
+ }
+
+ if (*scrollDirection == ScrollDirection::eVertical) {
+ result += CompositorHitTestFlags::eScrollbarVertical;
+ }
+
+ // includes the ScrollbarFrame, SliderFrame, anything else that
+ // might be inside the xul:scrollbar
+ result += CompositorHitTestFlags::eScrollbar;
+ }
+
+ return result;
+}
+
+// Returns true if we can guarantee there is no visible descendants.
+static bool HasNoVisibleDescendants(const nsIFrame* aFrame) {
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* f : childList.mList) {
+ if (nsPlaceholderFrame::GetRealFrameFor(f)
+ ->IsVisibleOrMayHaveVisibleDescendants()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+nsIScrollableFrame* nsIFrame::GetAsScrollContainer() const {
+ return do_QueryFrame(this);
+}
+
+void nsIFrame::UpdateVisibleDescendantsState() {
+ if (StyleVisibility()->IsVisible()) {
+ // Notify invisible ancestors that a visible descendant exists now.
+ nsIFrame* ancestor;
+ for (ancestor = GetInFlowParent();
+ ancestor && !ancestor->StyleVisibility()->IsVisible();
+ ancestor = ancestor->GetInFlowParent()) {
+ ancestor->mAllDescendantsAreInvisible = false;
+ }
+ } else {
+ mAllDescendantsAreInvisible = HasNoVisibleDescendants(this);
+ }
+}
+
+nsIFrame::PhysicalAxes nsIFrame::ShouldApplyOverflowClipping(
+ const nsStyleDisplay* aDisp) const {
+ MOZ_ASSERT(aDisp == StyleDisplay(), "Wrong display struct");
+
+ // 'contain:paint', which we handle as 'overflow:clip' here. Except for
+ // scrollframes we don't need contain:paint to add any clipping, because
+ // the scrollable frame will already clip overflowing content, and because
+ // 'contain:paint' should prevent all means of escaping that clipping
+ // (e.g. because it forms a fixed-pos containing block).
+ if (aDisp->IsContainPaint() && !IsScrollFrame() &&
+ SupportsContainLayoutAndPaint()) {
+ return PhysicalAxes::Both;
+ }
+
+ // and overflow:hidden that we should interpret as clip
+ if (aDisp->mOverflowX == StyleOverflow::Hidden &&
+ aDisp->mOverflowY == StyleOverflow::Hidden) {
+ // REVIEW: these are the frame types that set up clipping.
+ LayoutFrameType type = Type();
+ switch (type) {
+ case LayoutFrameType::Table:
+ case LayoutFrameType::TableCell:
+ case LayoutFrameType::SVGOuterSVG:
+ case LayoutFrameType::SVGInnerSVG:
+ case LayoutFrameType::SVGSymbol:
+ case LayoutFrameType::SVGForeignObject:
+ return PhysicalAxes::Both;
+ default:
+ if (IsReplacedWithBlock()) {
+ if (type == mozilla::LayoutFrameType::TextInput) {
+ // It has an anonymous scroll frame that handles any overflow.
+ return PhysicalAxes::None;
+ }
+ return PhysicalAxes::Both;
+ }
+ }
+ }
+
+ // clip overflow:clip, except for nsListControlFrame which is
+ // an nsHTMLScrollFrame sub-class.
+ if (MOZ_UNLIKELY((aDisp->mOverflowX == mozilla::StyleOverflow::Clip ||
+ aDisp->mOverflowY == mozilla::StyleOverflow::Clip) &&
+ !IsListControlFrame())) {
+ // FIXME: we could use GetViewportScrollStylesOverrideElement() here instead
+ // if that worked correctly in a print context. (see bug 1654667)
+ const auto* element = Element::FromNodeOrNull(GetContent());
+ if (!element ||
+ !PresContext()->ElementWouldPropagateScrollStyles(*element)) {
+ uint8_t axes = uint8_t(PhysicalAxes::None);
+ if (aDisp->mOverflowX == mozilla::StyleOverflow::Clip) {
+ axes |= uint8_t(PhysicalAxes::Horizontal);
+ }
+ if (aDisp->mOverflowY == mozilla::StyleOverflow::Clip) {
+ axes |= uint8_t(PhysicalAxes::Vertical);
+ }
+ return PhysicalAxes(axes);
+ }
+ }
+
+ if (HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ return PhysicalAxes::None;
+ }
+
+ // If we're paginated and a block, and have NS_BLOCK_CLIP_PAGINATED_OVERFLOW
+ // set, then we want to clip our overflow.
+ bool clip = HasAnyStateBits(NS_BLOCK_CLIP_PAGINATED_OVERFLOW) &&
+ PresContext()->IsPaginated() && IsBlockFrame();
+ return clip ? PhysicalAxes::Both : PhysicalAxes::None;
+}
+
+#ifdef DEBUG
+static void GetTagName(nsIFrame* aFrame, nsIContent* aContent, int aResultSize,
+ char* aResult) {
+ if (aContent) {
+ snprintf(aResult, aResultSize, "%s@%p",
+ nsAtomCString(aContent->NodeInfo()->NameAtom()).get(), aFrame);
+ } else {
+ snprintf(aResult, aResultSize, "@%p", aFrame);
+ }
+}
+
+void nsIFrame::Trace(const char* aMethod, bool aEnter) {
+ if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
+ char tagbuf[40];
+ GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
+ printf_stderr("%s: %s %s", tagbuf, aEnter ? "enter" : "exit", aMethod);
+ }
+}
+
+void nsIFrame::Trace(const char* aMethod, bool aEnter,
+ const nsReflowStatus& aStatus) {
+ if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
+ char tagbuf[40];
+ GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
+ printf_stderr("%s: %s %s, status=%scomplete%s", tagbuf,
+ aEnter ? "enter" : "exit", aMethod,
+ aStatus.IsIncomplete() ? "not" : "",
+ (aStatus.NextInFlowNeedsReflow()) ? "+reflow" : "");
+ }
+}
+
+void nsIFrame::TraceMsg(const char* aFormatString, ...) {
+ if (NS_FRAME_LOG_TEST(sFrameLogModule, NS_FRAME_TRACE_CALLS)) {
+ // Format arguments into a buffer
+ char argbuf[200];
+ va_list ap;
+ va_start(ap, aFormatString);
+ VsprintfLiteral(argbuf, aFormatString, ap);
+ va_end(ap);
+
+ char tagbuf[40];
+ GetTagName(this, mContent, sizeof(tagbuf), tagbuf);
+ printf_stderr("%s: %s", tagbuf, argbuf);
+ }
+}
+
+void nsIFrame::VerifyDirtyBitSet(const nsFrameList& aFrameList) {
+ for (nsIFrame* f : aFrameList) {
+ NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_IS_DIRTY), "dirty bit not set");
+ }
+}
+
+// Start Display Reflow
+DR_cookie::DR_cookie(nsPresContext* aPresContext, nsIFrame* aFrame,
+ const ReflowInput& aReflowInput, ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus)
+ : mPresContext(aPresContext),
+ mFrame(aFrame),
+ mReflowInput(aReflowInput),
+ mMetrics(aMetrics),
+ mStatus(aStatus) {
+ MOZ_COUNT_CTOR(DR_cookie);
+ mValue = nsIFrame::DisplayReflowEnter(aPresContext, mFrame, mReflowInput);
+}
+
+DR_cookie::~DR_cookie() {
+ MOZ_COUNT_DTOR(DR_cookie);
+ nsIFrame::DisplayReflowExit(mPresContext, mFrame, mMetrics, mStatus, mValue);
+}
+
+DR_layout_cookie::DR_layout_cookie(nsIFrame* aFrame) : mFrame(aFrame) {
+ MOZ_COUNT_CTOR(DR_layout_cookie);
+ mValue = nsIFrame::DisplayLayoutEnter(mFrame);
+}
+
+DR_layout_cookie::~DR_layout_cookie() {
+ MOZ_COUNT_DTOR(DR_layout_cookie);
+ nsIFrame::DisplayLayoutExit(mFrame, mValue);
+}
+
+DR_intrinsic_inline_size_cookie::DR_intrinsic_inline_size_cookie(
+ nsIFrame* aFrame, const char* aType, nscoord& aResult)
+ : mFrame(aFrame), mType(aType), mResult(aResult) {
+ MOZ_COUNT_CTOR(DR_intrinsic_inline_size_cookie);
+ mValue = nsIFrame::DisplayIntrinsicISizeEnter(mFrame, mType);
+}
+
+DR_intrinsic_inline_size_cookie::~DR_intrinsic_inline_size_cookie() {
+ MOZ_COUNT_DTOR(DR_intrinsic_inline_size_cookie);
+ nsIFrame::DisplayIntrinsicISizeExit(mFrame, mType, mResult, mValue);
+}
+
+DR_intrinsic_size_cookie::DR_intrinsic_size_cookie(nsIFrame* aFrame,
+ const char* aType,
+ nsSize& aResult)
+ : mFrame(aFrame), mType(aType), mResult(aResult) {
+ MOZ_COUNT_CTOR(DR_intrinsic_size_cookie);
+ mValue = nsIFrame::DisplayIntrinsicSizeEnter(mFrame, mType);
+}
+
+DR_intrinsic_size_cookie::~DR_intrinsic_size_cookie() {
+ MOZ_COUNT_DTOR(DR_intrinsic_size_cookie);
+ nsIFrame::DisplayIntrinsicSizeExit(mFrame, mType, mResult, mValue);
+}
+
+DR_init_constraints_cookie::DR_init_constraints_cookie(
+ nsIFrame* aFrame, ReflowInput* aState, nscoord aCBWidth, nscoord aCBHeight,
+ const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
+ const mozilla::Maybe<mozilla::LogicalMargin> aPadding)
+ : mFrame(aFrame), mState(aState) {
+ MOZ_COUNT_CTOR(DR_init_constraints_cookie);
+ nsMargin border;
+ if (aBorder) {
+ border = aBorder->GetPhysicalMargin(aFrame->GetWritingMode());
+ }
+ nsMargin padding;
+ if (aPadding) {
+ padding = aPadding->GetPhysicalMargin(aFrame->GetWritingMode());
+ }
+ mValue = ReflowInput::DisplayInitConstraintsEnter(
+ mFrame, mState, aCBWidth, aCBHeight, aBorder ? &border : nullptr,
+ aPadding ? &padding : nullptr);
+}
+
+DR_init_constraints_cookie::~DR_init_constraints_cookie() {
+ MOZ_COUNT_DTOR(DR_init_constraints_cookie);
+ ReflowInput::DisplayInitConstraintsExit(mFrame, mState, mValue);
+}
+
+DR_init_offsets_cookie::DR_init_offsets_cookie(
+ nsIFrame* aFrame, SizeComputationInput* aState, nscoord aPercentBasis,
+ WritingMode aCBWritingMode,
+ const mozilla::Maybe<mozilla::LogicalMargin> aBorder,
+ const mozilla::Maybe<mozilla::LogicalMargin> aPadding)
+ : mFrame(aFrame), mState(aState) {
+ MOZ_COUNT_CTOR(DR_init_offsets_cookie);
+ nsMargin border;
+ if (aBorder) {
+ border = aBorder->GetPhysicalMargin(aFrame->GetWritingMode());
+ }
+ nsMargin padding;
+ if (aPadding) {
+ padding = aPadding->GetPhysicalMargin(aFrame->GetWritingMode());
+ }
+ mValue = SizeComputationInput::DisplayInitOffsetsEnter(
+ mFrame, mState, aPercentBasis, aCBWritingMode,
+ aBorder ? &border : nullptr, aPadding ? &padding : nullptr);
+}
+
+DR_init_offsets_cookie::~DR_init_offsets_cookie() {
+ MOZ_COUNT_DTOR(DR_init_offsets_cookie);
+ SizeComputationInput::DisplayInitOffsetsExit(mFrame, mState, mValue);
+}
+
+struct DR_Rule;
+
+struct DR_FrameTypeInfo {
+ DR_FrameTypeInfo(LayoutFrameType aFrameType, const char* aFrameNameAbbrev,
+ const char* aFrameName);
+ ~DR_FrameTypeInfo();
+
+ LayoutFrameType mType;
+ char mNameAbbrev[16];
+ char mName[32];
+ nsTArray<DR_Rule*> mRules;
+
+ private:
+ DR_FrameTypeInfo& operator=(const DR_FrameTypeInfo&) = delete;
+};
+
+struct DR_FrameTreeNode;
+struct DR_Rule;
+
+struct DR_State {
+ DR_State();
+ ~DR_State();
+ void Init();
+ void AddFrameTypeInfo(LayoutFrameType aFrameType,
+ const char* aFrameNameAbbrev, const char* aFrameName);
+ DR_FrameTypeInfo* GetFrameTypeInfo(LayoutFrameType aFrameType);
+ DR_FrameTypeInfo* GetFrameTypeInfo(char* aFrameName);
+ void InitFrameTypeTable();
+ DR_FrameTreeNode* CreateTreeNode(nsIFrame* aFrame,
+ const ReflowInput* aReflowInput);
+ void FindMatchingRule(DR_FrameTreeNode& aNode);
+ bool RuleMatches(DR_Rule& aRule, DR_FrameTreeNode& aNode);
+ bool GetToken(FILE* aFile, char* aBuf, size_t aBufSize);
+ DR_Rule* ParseRule(FILE* aFile);
+ void ParseRulesFile();
+ void AddRule(nsTArray<DR_Rule*>& aRules, DR_Rule& aRule);
+ bool IsWhiteSpace(int c);
+ bool GetNumber(char* aBuf, int32_t& aNumber);
+ void PrettyUC(nscoord aSize, char* aBuf, int aBufSize);
+ void PrintMargin(const char* tag, const nsMargin* aMargin);
+ void DisplayFrameTypeInfo(nsIFrame* aFrame, int32_t aIndent);
+ void DeleteTreeNode(DR_FrameTreeNode& aNode);
+
+ bool mInited;
+ bool mActive;
+ int32_t mCount;
+ int32_t mAssert;
+ int32_t mIndent;
+ bool mIndentUndisplayedFrames;
+ bool mDisplayPixelErrors;
+ nsTArray<DR_Rule*> mWildRules;
+ nsTArray<DR_FrameTypeInfo> mFrameTypeTable;
+ // reflow specific state
+ nsTArray<DR_FrameTreeNode*> mFrameTreeLeaves;
+};
+
+static DR_State* DR_state; // the one and only DR_State
+
+struct DR_RulePart {
+ explicit DR_RulePart(LayoutFrameType aFrameType)
+ : mFrameType(aFrameType), mNext(0) {}
+
+ void Destroy();
+
+ LayoutFrameType mFrameType;
+ DR_RulePart* mNext;
+};
+
+void DR_RulePart::Destroy() {
+ if (mNext) {
+ mNext->Destroy();
+ }
+ delete this;
+}
+
+struct DR_Rule {
+ DR_Rule() : mLength(0), mTarget(nullptr), mDisplay(false) {
+ MOZ_COUNT_CTOR(DR_Rule);
+ }
+ ~DR_Rule() {
+ if (mTarget) mTarget->Destroy();
+ MOZ_COUNT_DTOR(DR_Rule);
+ }
+ void AddPart(LayoutFrameType aFrameType);
+
+ uint32_t mLength;
+ DR_RulePart* mTarget;
+ bool mDisplay;
+};
+
+void DR_Rule::AddPart(LayoutFrameType aFrameType) {
+ DR_RulePart* newPart = new DR_RulePart(aFrameType);
+ newPart->mNext = mTarget;
+ mTarget = newPart;
+ mLength++;
+}
+
+DR_FrameTypeInfo::~DR_FrameTypeInfo() {
+ int32_t numElements;
+ numElements = mRules.Length();
+ for (int32_t i = numElements - 1; i >= 0; i--) {
+ delete mRules.ElementAt(i);
+ }
+}
+
+DR_FrameTypeInfo::DR_FrameTypeInfo(LayoutFrameType aFrameType,
+ const char* aFrameNameAbbrev,
+ const char* aFrameName) {
+ mType = aFrameType;
+ PL_strncpyz(mNameAbbrev, aFrameNameAbbrev, sizeof(mNameAbbrev));
+ PL_strncpyz(mName, aFrameName, sizeof(mName));
+}
+
+struct DR_FrameTreeNode {
+ DR_FrameTreeNode(nsIFrame* aFrame, DR_FrameTreeNode* aParent)
+ : mFrame(aFrame), mParent(aParent), mDisplay(0), mIndent(0) {
+ MOZ_COUNT_CTOR(DR_FrameTreeNode);
+ }
+
+ MOZ_COUNTED_DTOR(DR_FrameTreeNode)
+
+ nsIFrame* mFrame;
+ DR_FrameTreeNode* mParent;
+ bool mDisplay;
+ uint32_t mIndent;
+};
+
+// DR_State implementation
+
+DR_State::DR_State()
+ : mInited(false),
+ mActive(false),
+ mCount(0),
+ mAssert(-1),
+ mIndent(0),
+ mIndentUndisplayedFrames(false),
+ mDisplayPixelErrors(false) {
+ MOZ_COUNT_CTOR(DR_State);
+}
+
+void DR_State::Init() {
+ char* env = PR_GetEnv("GECKO_DISPLAY_REFLOW_ASSERT");
+ int32_t num;
+ if (env) {
+ if (GetNumber(env, num))
+ mAssert = num;
+ else
+ printf("GECKO_DISPLAY_REFLOW_ASSERT - invalid value = %s", env);
+ }
+
+ env = PR_GetEnv("GECKO_DISPLAY_REFLOW_INDENT_START");
+ if (env) {
+ if (GetNumber(env, num))
+ mIndent = num;
+ else
+ printf("GECKO_DISPLAY_REFLOW_INDENT_START - invalid value = %s", env);
+ }
+
+ env = PR_GetEnv("GECKO_DISPLAY_REFLOW_INDENT_UNDISPLAYED_FRAMES");
+ if (env) {
+ if (GetNumber(env, num))
+ mIndentUndisplayedFrames = num;
+ else
+ printf(
+ "GECKO_DISPLAY_REFLOW_INDENT_UNDISPLAYED_FRAMES - invalid value = %s",
+ env);
+ }
+
+ env = PR_GetEnv("GECKO_DISPLAY_REFLOW_FLAG_PIXEL_ERRORS");
+ if (env) {
+ if (GetNumber(env, num))
+ mDisplayPixelErrors = num;
+ else
+ printf("GECKO_DISPLAY_REFLOW_FLAG_PIXEL_ERRORS - invalid value = %s",
+ env);
+ }
+
+ InitFrameTypeTable();
+ ParseRulesFile();
+ mInited = true;
+}
+
+DR_State::~DR_State() {
+ MOZ_COUNT_DTOR(DR_State);
+ int32_t numElements, i;
+ numElements = mWildRules.Length();
+ for (i = numElements - 1; i >= 0; i--) {
+ delete mWildRules.ElementAt(i);
+ }
+ numElements = mFrameTreeLeaves.Length();
+ for (i = numElements - 1; i >= 0; i--) {
+ delete mFrameTreeLeaves.ElementAt(i);
+ }
+}
+
+bool DR_State::GetNumber(char* aBuf, int32_t& aNumber) {
+ if (sscanf(aBuf, "%d", &aNumber) > 0)
+ return true;
+ else
+ return false;
+}
+
+bool DR_State::IsWhiteSpace(int c) {
+ return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
+}
+
+bool DR_State::GetToken(FILE* aFile, char* aBuf, size_t aBufSize) {
+ bool haveToken = false;
+ aBuf[0] = 0;
+ // get the 1st non whitespace char
+ int c = -1;
+ for (c = getc(aFile); (c > 0) && IsWhiteSpace(c); c = getc(aFile)) {
+ }
+
+ if (c > 0) {
+ haveToken = true;
+ aBuf[0] = c;
+ // get everything up to the next whitespace char
+ size_t cX;
+ for (cX = 1; cX + 1 < aBufSize; cX++) {
+ c = getc(aFile);
+ if (c < 0) { // EOF
+ ungetc(' ', aFile);
+ break;
+ } else {
+ if (IsWhiteSpace(c)) {
+ break;
+ } else {
+ aBuf[cX] = c;
+ }
+ }
+ }
+ aBuf[cX] = 0;
+ }
+ return haveToken;
+}
+
+DR_Rule* DR_State::ParseRule(FILE* aFile) {
+ char buf[128];
+ int32_t doDisplay;
+ DR_Rule* rule = nullptr;
+ while (GetToken(aFile, buf, sizeof(buf))) {
+ if (GetNumber(buf, doDisplay)) {
+ if (rule) {
+ rule->mDisplay = !!doDisplay;
+ break;
+ } else {
+ printf("unexpected token - %s \n", buf);
+ }
+ } else {
+ if (!rule) {
+ rule = new DR_Rule;
+ }
+ if (strcmp(buf, "*") == 0) {
+ rule->AddPart(LayoutFrameType::None);
+ } else {
+ DR_FrameTypeInfo* info = GetFrameTypeInfo(buf);
+ if (info) {
+ rule->AddPart(info->mType);
+ } else {
+ printf("invalid frame type - %s \n", buf);
+ }
+ }
+ }
+ }
+ return rule;
+}
+
+void DR_State::AddRule(nsTArray<DR_Rule*>& aRules, DR_Rule& aRule) {
+ int32_t numRules = aRules.Length();
+ for (int32_t ruleX = 0; ruleX < numRules; ruleX++) {
+ DR_Rule* rule = aRules.ElementAt(ruleX);
+ NS_ASSERTION(rule, "program error");
+ if (aRule.mLength > rule->mLength) {
+ aRules.InsertElementAt(ruleX, &aRule);
+ return;
+ }
+ }
+ aRules.AppendElement(&aRule);
+}
+
+static Maybe<bool> ShouldLogReflow(const char* processes) {
+ switch (processes[0]) {
+ case 'A':
+ case 'a':
+ return Some(true);
+ case 'P':
+ case 'p':
+ return Some(XRE_IsParentProcess());
+ case 'C':
+ case 'c':
+ return Some(XRE_IsContentProcess());
+ default:
+ return Nothing{};
+ }
+}
+
+void DR_State::ParseRulesFile() {
+ char* processes = PR_GetEnv("GECKO_DISPLAY_REFLOW_PROCESSES");
+ if (processes) {
+ Maybe<bool> enableLog = ShouldLogReflow(processes);
+ if (enableLog.isNothing()) {
+ MOZ_CRASH("GECKO_DISPLAY_REFLOW_PROCESSES: [a]ll [p]arent [c]ontent");
+ } else if (enableLog.value()) {
+ DR_Rule* rule = new DR_Rule;
+ rule->AddPart(LayoutFrameType::None);
+ rule->mDisplay = true;
+ AddRule(mWildRules, *rule);
+ mActive = true;
+ }
+ return;
+ }
+
+ char* path = PR_GetEnv("GECKO_DISPLAY_REFLOW_RULES_FILE");
+ if (path) {
+ FILE* inFile = fopen(path, "r");
+ if (!inFile) {
+ MOZ_CRASH(
+ "Failed to open the specified rules file; Try `--setpref "
+ "security.sandbox.content.level=2` if the sandbox is at cause");
+ }
+ for (DR_Rule* rule = ParseRule(inFile); rule; rule = ParseRule(inFile)) {
+ if (rule->mTarget) {
+ LayoutFrameType fType = rule->mTarget->mFrameType;
+ if (fType != LayoutFrameType::None) {
+ DR_FrameTypeInfo* info = GetFrameTypeInfo(fType);
+ AddRule(info->mRules, *rule);
+ } else {
+ AddRule(mWildRules, *rule);
+ }
+ mActive = true;
+ }
+ }
+
+ fclose(inFile);
+ }
+}
+
+void DR_State::AddFrameTypeInfo(LayoutFrameType aFrameType,
+ const char* aFrameNameAbbrev,
+ const char* aFrameName) {
+ mFrameTypeTable.EmplaceBack(aFrameType, aFrameNameAbbrev, aFrameName);
+}
+
+DR_FrameTypeInfo* DR_State::GetFrameTypeInfo(LayoutFrameType aFrameType) {
+ int32_t numEntries = mFrameTypeTable.Length();
+ NS_ASSERTION(numEntries != 0, "empty FrameTypeTable");
+ for (int32_t i = 0; i < numEntries; i++) {
+ DR_FrameTypeInfo& info = mFrameTypeTable.ElementAt(i);
+ if (info.mType == aFrameType) {
+ return &info;
+ }
+ }
+ return &mFrameTypeTable.ElementAt(numEntries -
+ 1); // return unknown frame type
+}
+
+DR_FrameTypeInfo* DR_State::GetFrameTypeInfo(char* aFrameName) {
+ int32_t numEntries = mFrameTypeTable.Length();
+ NS_ASSERTION(numEntries != 0, "empty FrameTypeTable");
+ for (int32_t i = 0; i < numEntries; i++) {
+ DR_FrameTypeInfo& info = mFrameTypeTable.ElementAt(i);
+ if ((strcmp(aFrameName, info.mName) == 0) ||
+ (strcmp(aFrameName, info.mNameAbbrev) == 0)) {
+ return &info;
+ }
+ }
+ return &mFrameTypeTable.ElementAt(numEntries -
+ 1); // return unknown frame type
+}
+
+void DR_State::InitFrameTypeTable() {
+ AddFrameTypeInfo(LayoutFrameType::Block, "block", "block");
+ AddFrameTypeInfo(LayoutFrameType::Br, "br", "br");
+ AddFrameTypeInfo(LayoutFrameType::ColorControl, "color", "colorControl");
+ AddFrameTypeInfo(LayoutFrameType::GfxButtonControl, "button",
+ "gfxButtonControl");
+ AddFrameTypeInfo(LayoutFrameType::HTMLButtonControl, "HTMLbutton",
+ "HTMLButtonControl");
+ AddFrameTypeInfo(LayoutFrameType::HTMLCanvas, "HTMLCanvas", "HTMLCanvas");
+ AddFrameTypeInfo(LayoutFrameType::SubDocument, "subdoc", "subDocument");
+ AddFrameTypeInfo(LayoutFrameType::Image, "img", "image");
+ AddFrameTypeInfo(LayoutFrameType::Inline, "inline", "inline");
+ AddFrameTypeInfo(LayoutFrameType::Letter, "letter", "letter");
+ AddFrameTypeInfo(LayoutFrameType::Line, "line", "line");
+ AddFrameTypeInfo(LayoutFrameType::ListControl, "select", "select");
+ AddFrameTypeInfo(LayoutFrameType::Page, "page", "page");
+ AddFrameTypeInfo(LayoutFrameType::Placeholder, "place", "placeholder");
+ AddFrameTypeInfo(LayoutFrameType::Canvas, "canvas", "canvas");
+ AddFrameTypeInfo(LayoutFrameType::Scroll, "scroll", "scroll");
+ AddFrameTypeInfo(LayoutFrameType::TableCell, "cell", "tableCell");
+ AddFrameTypeInfo(LayoutFrameType::TableCol, "col", "tableCol");
+ AddFrameTypeInfo(LayoutFrameType::TableColGroup, "colG", "tableColGroup");
+ AddFrameTypeInfo(LayoutFrameType::Table, "tbl", "table");
+ AddFrameTypeInfo(LayoutFrameType::TableWrapper, "tblW", "tableWrapper");
+ AddFrameTypeInfo(LayoutFrameType::TableRowGroup, "rowG", "tableRowGroup");
+ AddFrameTypeInfo(LayoutFrameType::TableRow, "row", "tableRow");
+ AddFrameTypeInfo(LayoutFrameType::TextInput, "textCtl", "textInput");
+ AddFrameTypeInfo(LayoutFrameType::Text, "text", "text");
+ AddFrameTypeInfo(LayoutFrameType::Viewport, "VP", "viewport");
+ AddFrameTypeInfo(LayoutFrameType::Slider, "Slider", "Slider");
+ AddFrameTypeInfo(LayoutFrameType::None, "unknown", "unknown");
+}
+
+void DR_State::DisplayFrameTypeInfo(nsIFrame* aFrame, int32_t aIndent) {
+ DR_FrameTypeInfo* frameTypeInfo = GetFrameTypeInfo(aFrame->Type());
+ if (frameTypeInfo) {
+ for (int32_t i = 0; i < aIndent; i++) {
+ printf(" ");
+ }
+ if (!strcmp(frameTypeInfo->mNameAbbrev, "unknown")) {
+ if (aFrame) {
+ nsAutoString name;
+ aFrame->GetFrameName(name);
+ printf("%s %p ", NS_LossyConvertUTF16toASCII(name).get(),
+ (void*)aFrame);
+ } else {
+ printf("%s %p ", frameTypeInfo->mNameAbbrev, (void*)aFrame);
+ }
+ } else {
+ printf("%s %p ", frameTypeInfo->mNameAbbrev, (void*)aFrame);
+ }
+ }
+}
+
+bool DR_State::RuleMatches(DR_Rule& aRule, DR_FrameTreeNode& aNode) {
+ NS_ASSERTION(aRule.mTarget, "program error");
+
+ DR_RulePart* rulePart;
+ DR_FrameTreeNode* parentNode;
+ for (rulePart = aRule.mTarget->mNext, parentNode = aNode.mParent;
+ rulePart && parentNode;
+ rulePart = rulePart->mNext, parentNode = parentNode->mParent) {
+ if (rulePart->mFrameType != LayoutFrameType::None) {
+ if (parentNode->mFrame) {
+ if (rulePart->mFrameType != parentNode->mFrame->Type()) {
+ return false;
+ }
+ } else
+ NS_ASSERTION(false, "program error");
+ }
+ // else wild card match
+ }
+ return true;
+}
+
+void DR_State::FindMatchingRule(DR_FrameTreeNode& aNode) {
+ if (!aNode.mFrame) {
+ NS_ASSERTION(false, "invalid DR_FrameTreeNode \n");
+ return;
+ }
+
+ bool matchingRule = false;
+
+ DR_FrameTypeInfo* info = GetFrameTypeInfo(aNode.mFrame->Type());
+ NS_ASSERTION(info, "program error");
+ int32_t numRules = info->mRules.Length();
+ for (int32_t ruleX = 0; ruleX < numRules; ruleX++) {
+ DR_Rule* rule = info->mRules.ElementAt(ruleX);
+ if (rule && RuleMatches(*rule, aNode)) {
+ aNode.mDisplay = rule->mDisplay;
+ matchingRule = true;
+ break;
+ }
+ }
+ if (!matchingRule) {
+ int32_t numWildRules = mWildRules.Length();
+ for (int32_t ruleX = 0; ruleX < numWildRules; ruleX++) {
+ DR_Rule* rule = mWildRules.ElementAt(ruleX);
+ if (rule && RuleMatches(*rule, aNode)) {
+ aNode.mDisplay = rule->mDisplay;
+ break;
+ }
+ }
+ }
+}
+
+DR_FrameTreeNode* DR_State::CreateTreeNode(nsIFrame* aFrame,
+ const ReflowInput* aReflowInput) {
+ // find the frame of the parent reflow input (usually just the parent of
+ // aFrame)
+ nsIFrame* parentFrame;
+ if (aReflowInput) {
+ const ReflowInput* parentRI = aReflowInput->mParentReflowInput;
+ parentFrame = (parentRI) ? parentRI->mFrame : nullptr;
+ } else {
+ parentFrame = aFrame->GetParent();
+ }
+
+ // find the parent tree node leaf
+ DR_FrameTreeNode* parentNode = nullptr;
+
+ DR_FrameTreeNode* lastLeaf = nullptr;
+ if (mFrameTreeLeaves.Length())
+ lastLeaf = mFrameTreeLeaves.ElementAt(mFrameTreeLeaves.Length() - 1);
+ if (lastLeaf) {
+ for (parentNode = lastLeaf;
+ parentNode && (parentNode->mFrame != parentFrame);
+ parentNode = parentNode->mParent) {
+ }
+ }
+ DR_FrameTreeNode* newNode = new DR_FrameTreeNode(aFrame, parentNode);
+ FindMatchingRule(*newNode);
+
+ newNode->mIndent = mIndent;
+ if (newNode->mDisplay || mIndentUndisplayedFrames) {
+ ++mIndent;
+ }
+
+ if (lastLeaf && (lastLeaf == parentNode)) {
+ mFrameTreeLeaves.RemoveLastElement();
+ }
+ mFrameTreeLeaves.AppendElement(newNode);
+ mCount++;
+
+ return newNode;
+}
+
+void DR_State::PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
+ if (NS_UNCONSTRAINEDSIZE == aSize) {
+ strcpy(aBuf, "UC");
+ } else {
+ if ((nscoord)0xdeadbeefU == aSize) {
+ strcpy(aBuf, "deadbeef");
+ } else {
+ snprintf(aBuf, aBufSize, "%d", aSize);
+ }
+ }
+}
+
+void DR_State::PrintMargin(const char* tag, const nsMargin* aMargin) {
+ if (aMargin) {
+ char t[16], r[16], b[16], l[16];
+ PrettyUC(aMargin->top, t, 16);
+ PrettyUC(aMargin->right, r, 16);
+ PrettyUC(aMargin->bottom, b, 16);
+ PrettyUC(aMargin->left, l, 16);
+ printf(" %s=%s,%s,%s,%s", tag, t, r, b, l);
+ } else {
+ // use %p here for consistency with other null-pointer printouts
+ printf(" %s=%p", tag, (void*)aMargin);
+ }
+}
+
+void DR_State::DeleteTreeNode(DR_FrameTreeNode& aNode) {
+ mFrameTreeLeaves.RemoveElement(&aNode);
+ int32_t numLeaves = mFrameTreeLeaves.Length();
+ if ((0 == numLeaves) ||
+ (aNode.mParent != mFrameTreeLeaves.ElementAt(numLeaves - 1))) {
+ mFrameTreeLeaves.AppendElement(aNode.mParent);
+ }
+
+ if (aNode.mDisplay || mIndentUndisplayedFrames) {
+ --mIndent;
+ }
+ // delete the tree node
+ delete &aNode;
+}
+
+static void CheckPixelError(nscoord aSize, int32_t aPixelToTwips) {
+ if (NS_UNCONSTRAINEDSIZE != aSize) {
+ if ((aSize % aPixelToTwips) > 0) {
+ printf("VALUE %d is not a whole pixel \n", aSize);
+ }
+ }
+}
+
+static void DisplayReflowEnterPrint(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ const ReflowInput& aReflowInput,
+ DR_FrameTreeNode& aTreeNode,
+ bool aChanged) {
+ if (aTreeNode.mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, aTreeNode.mIndent);
+
+ char width[16];
+ char height[16];
+
+ DR_state->PrettyUC(aReflowInput.AvailableWidth(), width, 16);
+ DR_state->PrettyUC(aReflowInput.AvailableHeight(), height, 16);
+ printf("Reflow a=%s,%s ", width, height);
+
+ DR_state->PrettyUC(aReflowInput.ComputedWidth(), width, 16);
+ DR_state->PrettyUC(aReflowInput.ComputedHeight(), height, 16);
+ printf("c=%s,%s ", width, height);
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) printf("dirty ");
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN))
+ printf("dirty-children ");
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) printf("special-bsize ");
+
+ if (aReflowInput.IsHResize()) printf("h-resize ");
+
+ if (aReflowInput.IsVResize()) printf("v-resize ");
+
+ nsIFrame* inFlow = aFrame->GetPrevInFlow();
+ if (inFlow) {
+ printf("pif=%p ", (void*)inFlow);
+ }
+ inFlow = aFrame->GetNextInFlow();
+ if (inFlow) {
+ printf("nif=%p ", (void*)inFlow);
+ }
+ if (aChanged)
+ printf("CHANGED \n");
+ else
+ printf("cnt=%d \n", DR_state->mCount);
+ if (DR_state->mDisplayPixelErrors) {
+ int32_t d2a = aPresContext->AppUnitsPerDevPixel();
+ CheckPixelError(aReflowInput.AvailableWidth(), d2a);
+ CheckPixelError(aReflowInput.AvailableHeight(), d2a);
+ CheckPixelError(aReflowInput.ComputedWidth(), d2a);
+ CheckPixelError(aReflowInput.ComputedHeight(), d2a);
+ }
+ }
+}
+
+void* nsIFrame::DisplayReflowEnter(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ const ReflowInput& aReflowInput) {
+ if (!DR_state->mInited) DR_state->Init();
+ if (!DR_state->mActive) return nullptr;
+
+ NS_ASSERTION(aFrame, "invalid call");
+
+ DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, &aReflowInput);
+ if (treeNode) {
+ DisplayReflowEnterPrint(aPresContext, aFrame, aReflowInput, *treeNode,
+ false);
+ }
+ return treeNode;
+}
+
+void* nsIFrame::DisplayLayoutEnter(nsIFrame* aFrame) {
+ if (!DR_state->mInited) DR_state->Init();
+ if (!DR_state->mActive) return nullptr;
+
+ NS_ASSERTION(aFrame, "invalid call");
+
+ DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
+ if (treeNode && treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+ printf("XULLayout\n");
+ }
+ return treeNode;
+}
+
+void* nsIFrame::DisplayIntrinsicISizeEnter(nsIFrame* aFrame,
+ const char* aType) {
+ if (!DR_state->mInited) DR_state->Init();
+ if (!DR_state->mActive) return nullptr;
+
+ NS_ASSERTION(aFrame, "invalid call");
+
+ DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
+ if (treeNode && treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+ printf("Get%sISize\n", aType);
+ }
+ return treeNode;
+}
+
+void* nsIFrame::DisplayIntrinsicSizeEnter(nsIFrame* aFrame, const char* aType) {
+ if (!DR_state->mInited) DR_state->Init();
+ if (!DR_state->mActive) return nullptr;
+
+ NS_ASSERTION(aFrame, "invalid call");
+
+ DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
+ if (treeNode && treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+ printf("Get%sSize\n", aType);
+ }
+ return treeNode;
+}
+
+void nsIFrame::DisplayReflowExit(nsPresContext* aPresContext, nsIFrame* aFrame,
+ ReflowOutput& aMetrics,
+ const nsReflowStatus& aStatus,
+ void* aFrameTreeNode) {
+ if (!DR_state->mActive) return;
+
+ NS_ASSERTION(aFrame, "DisplayReflowExit - invalid call");
+ if (!aFrameTreeNode) return;
+
+ DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
+ if (treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+
+ char width[16];
+ char height[16];
+ char x[16];
+ char y[16];
+ DR_state->PrettyUC(aMetrics.Width(), width, 16);
+ DR_state->PrettyUC(aMetrics.Height(), height, 16);
+ printf("Reflow d=%s,%s", width, height);
+
+ if (!aStatus.IsEmpty()) {
+ printf(" status=%s", ToString(aStatus).c_str());
+ }
+ if (aFrame->HasOverflowAreas()) {
+ DR_state->PrettyUC(aMetrics.InkOverflow().x, x, 16);
+ DR_state->PrettyUC(aMetrics.InkOverflow().y, y, 16);
+ DR_state->PrettyUC(aMetrics.InkOverflow().width, width, 16);
+ DR_state->PrettyUC(aMetrics.InkOverflow().height, height, 16);
+ printf(" vis-o=(%s,%s) %s x %s", x, y, width, height);
+
+ nsRect storedOverflow = aFrame->InkOverflowRect();
+ DR_state->PrettyUC(storedOverflow.x, x, 16);
+ DR_state->PrettyUC(storedOverflow.y, y, 16);
+ DR_state->PrettyUC(storedOverflow.width, width, 16);
+ DR_state->PrettyUC(storedOverflow.height, height, 16);
+ printf(" vis-sto=(%s,%s) %s x %s", x, y, width, height);
+
+ DR_state->PrettyUC(aMetrics.ScrollableOverflow().x, x, 16);
+ DR_state->PrettyUC(aMetrics.ScrollableOverflow().y, y, 16);
+ DR_state->PrettyUC(aMetrics.ScrollableOverflow().width, width, 16);
+ DR_state->PrettyUC(aMetrics.ScrollableOverflow().height, height, 16);
+ printf(" scr-o=(%s,%s) %s x %s", x, y, width, height);
+
+ storedOverflow = aFrame->ScrollableOverflowRect();
+ DR_state->PrettyUC(storedOverflow.x, x, 16);
+ DR_state->PrettyUC(storedOverflow.y, y, 16);
+ DR_state->PrettyUC(storedOverflow.width, width, 16);
+ DR_state->PrettyUC(storedOverflow.height, height, 16);
+ printf(" scr-sto=(%s,%s) %s x %s", x, y, width, height);
+ }
+ printf("\n");
+ if (DR_state->mDisplayPixelErrors) {
+ int32_t d2a = aPresContext->AppUnitsPerDevPixel();
+ CheckPixelError(aMetrics.Width(), d2a);
+ CheckPixelError(aMetrics.Height(), d2a);
+ }
+ }
+ DR_state->DeleteTreeNode(*treeNode);
+}
+
+void nsIFrame::DisplayLayoutExit(nsIFrame* aFrame, void* aFrameTreeNode) {
+ if (!DR_state->mActive) return;
+
+ NS_ASSERTION(aFrame, "non-null frame required");
+ if (!aFrameTreeNode) return;
+
+ DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
+ if (treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+ nsRect rect = aFrame->GetRect();
+ printf("XULLayout=%d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height);
+ }
+ DR_state->DeleteTreeNode(*treeNode);
+}
+
+void nsIFrame::DisplayIntrinsicISizeExit(nsIFrame* aFrame, const char* aType,
+ nscoord aResult,
+ void* aFrameTreeNode) {
+ if (!DR_state->mActive) return;
+
+ NS_ASSERTION(aFrame, "non-null frame required");
+ if (!aFrameTreeNode) return;
+
+ DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
+ if (treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+ char iSize[16];
+ DR_state->PrettyUC(aResult, iSize, 16);
+ printf("Get%sISize=%s\n", aType, iSize);
+ }
+ DR_state->DeleteTreeNode(*treeNode);
+}
+
+void nsIFrame::DisplayIntrinsicSizeExit(nsIFrame* aFrame, const char* aType,
+ nsSize aResult, void* aFrameTreeNode) {
+ if (!DR_state->mActive) return;
+
+ NS_ASSERTION(aFrame, "non-null frame required");
+ if (!aFrameTreeNode) return;
+
+ DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aFrameTreeNode;
+ if (treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+
+ char width[16];
+ char height[16];
+ DR_state->PrettyUC(aResult.width, width, 16);
+ DR_state->PrettyUC(aResult.height, height, 16);
+ printf("Get%sSize=%s,%s\n", aType, width, height);
+ }
+ DR_state->DeleteTreeNode(*treeNode);
+}
+
+/* static */
+void nsIFrame::DisplayReflowStartup() { DR_state = new DR_State(); }
+
+/* static */
+void nsIFrame::DisplayReflowShutdown() {
+ delete DR_state;
+ DR_state = nullptr;
+}
+
+void DR_cookie::Change() const {
+ DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)mValue;
+ if (treeNode && treeNode->mDisplay) {
+ DisplayReflowEnterPrint(mPresContext, mFrame, mReflowInput, *treeNode,
+ true);
+ }
+}
+
+/* static */
+void* ReflowInput::DisplayInitConstraintsEnter(nsIFrame* aFrame,
+ ReflowInput* aState,
+ nscoord aContainingBlockWidth,
+ nscoord aContainingBlockHeight,
+ const nsMargin* aBorder,
+ const nsMargin* aPadding) {
+ MOZ_ASSERT(aFrame, "non-null frame required");
+ MOZ_ASSERT(aState, "non-null state required");
+
+ if (!DR_state->mInited) DR_state->Init();
+ if (!DR_state->mActive) return nullptr;
+
+ DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, aState);
+ if (treeNode && treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+
+ printf("InitConstraints parent=%p", (void*)aState->mParentReflowInput);
+
+ char width[16];
+ char height[16];
+
+ DR_state->PrettyUC(aContainingBlockWidth, width, 16);
+ DR_state->PrettyUC(aContainingBlockHeight, height, 16);
+ printf(" cb=%s,%s", width, height);
+
+ DR_state->PrettyUC(aState->AvailableWidth(), width, 16);
+ DR_state->PrettyUC(aState->AvailableHeight(), height, 16);
+ printf(" as=%s,%s", width, height);
+
+ DR_state->PrintMargin("b", aBorder);
+ DR_state->PrintMargin("p", aPadding);
+ putchar('\n');
+ }
+ return treeNode;
+}
+
+/* static */
+void ReflowInput::DisplayInitConstraintsExit(nsIFrame* aFrame,
+ ReflowInput* aState,
+ void* aValue) {
+ MOZ_ASSERT(aFrame, "non-null frame required");
+ MOZ_ASSERT(aState, "non-null state required");
+
+ if (!DR_state->mActive) return;
+ if (!aValue) return;
+
+ DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aValue;
+ if (treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+ char cmiw[16], cw[16], cmxw[16], cmih[16], ch[16], cmxh[16];
+ DR_state->PrettyUC(aState->ComputedMinWidth(), cmiw, 16);
+ DR_state->PrettyUC(aState->ComputedWidth(), cw, 16);
+ DR_state->PrettyUC(aState->ComputedMaxWidth(), cmxw, 16);
+ DR_state->PrettyUC(aState->ComputedMinHeight(), cmih, 16);
+ DR_state->PrettyUC(aState->ComputedHeight(), ch, 16);
+ DR_state->PrettyUC(aState->ComputedMaxHeight(), cmxh, 16);
+ printf("InitConstraints= cw=(%s <= %s <= %s) ch=(%s <= %s <= %s)", cmiw, cw,
+ cmxw, cmih, ch, cmxh);
+ const nsMargin m = aState->ComputedPhysicalOffsets();
+ DR_state->PrintMargin("co", &m);
+ putchar('\n');
+ }
+ DR_state->DeleteTreeNode(*treeNode);
+}
+
+/* static */
+void* SizeComputationInput::DisplayInitOffsetsEnter(
+ nsIFrame* aFrame, SizeComputationInput* aState, nscoord aPercentBasis,
+ WritingMode aCBWritingMode, const nsMargin* aBorder,
+ const nsMargin* aPadding) {
+ MOZ_ASSERT(aFrame, "non-null frame required");
+ MOZ_ASSERT(aState, "non-null state required");
+
+ if (!DR_state->mInited) DR_state->Init();
+ if (!DR_state->mActive) return nullptr;
+
+ // aState is not necessarily a ReflowInput
+ DR_FrameTreeNode* treeNode = DR_state->CreateTreeNode(aFrame, nullptr);
+ if (treeNode && treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+
+ char pctBasisStr[16];
+ DR_state->PrettyUC(aPercentBasis, pctBasisStr, 16);
+ printf("InitOffsets pct_basis=%s", pctBasisStr);
+
+ DR_state->PrintMargin("b", aBorder);
+ DR_state->PrintMargin("p", aPadding);
+ putchar('\n');
+ }
+ return treeNode;
+}
+
+/* static */
+void SizeComputationInput::DisplayInitOffsetsExit(nsIFrame* aFrame,
+ SizeComputationInput* aState,
+ void* aValue) {
+ MOZ_ASSERT(aFrame, "non-null frame required");
+ MOZ_ASSERT(aState, "non-null state required");
+
+ if (!DR_state->mActive) return;
+ if (!aValue) return;
+
+ DR_FrameTreeNode* treeNode = (DR_FrameTreeNode*)aValue;
+ if (treeNode->mDisplay) {
+ DR_state->DisplayFrameTypeInfo(aFrame, treeNode->mIndent);
+ printf("InitOffsets=");
+ const auto m = aState->ComputedPhysicalMargin();
+ DR_state->PrintMargin("m", &m);
+ const auto p = aState->ComputedPhysicalPadding();
+ DR_state->PrintMargin("p", &p);
+ const auto bp = aState->ComputedPhysicalBorderPadding();
+ DR_state->PrintMargin("b+p", &bp);
+ putchar('\n');
+ }
+ DR_state->DeleteTreeNode(*treeNode);
+}
+
+// End Display Reflow
+
+// Validation of SideIsVertical.
+# define CASE(side, result) \
+ static_assert(SideIsVertical(side) == result, "SideIsVertical is wrong")
+CASE(eSideTop, false);
+CASE(eSideRight, true);
+CASE(eSideBottom, false);
+CASE(eSideLeft, true);
+# undef CASE
+
+// Validation of HalfCornerIsX.
+# define CASE(corner, result) \
+ static_assert(HalfCornerIsX(corner) == result, "HalfCornerIsX is wrong")
+CASE(eCornerTopLeftX, true);
+CASE(eCornerTopLeftY, false);
+CASE(eCornerTopRightX, true);
+CASE(eCornerTopRightY, false);
+CASE(eCornerBottomRightX, true);
+CASE(eCornerBottomRightY, false);
+CASE(eCornerBottomLeftX, true);
+CASE(eCornerBottomLeftY, false);
+# undef CASE
+
+// Validation of HalfToFullCorner.
+# define CASE(corner, result) \
+ static_assert(HalfToFullCorner(corner) == result, \
+ "HalfToFullCorner is " \
+ "wrong")
+CASE(eCornerTopLeftX, eCornerTopLeft);
+CASE(eCornerTopLeftY, eCornerTopLeft);
+CASE(eCornerTopRightX, eCornerTopRight);
+CASE(eCornerTopRightY, eCornerTopRight);
+CASE(eCornerBottomRightX, eCornerBottomRight);
+CASE(eCornerBottomRightY, eCornerBottomRight);
+CASE(eCornerBottomLeftX, eCornerBottomLeft);
+CASE(eCornerBottomLeftY, eCornerBottomLeft);
+# undef CASE
+
+// Validation of FullToHalfCorner.
+# define CASE(corner, vert, result) \
+ static_assert(FullToHalfCorner(corner, vert) == result, \
+ "FullToHalfCorner is wrong")
+CASE(eCornerTopLeft, false, eCornerTopLeftX);
+CASE(eCornerTopLeft, true, eCornerTopLeftY);
+CASE(eCornerTopRight, false, eCornerTopRightX);
+CASE(eCornerTopRight, true, eCornerTopRightY);
+CASE(eCornerBottomRight, false, eCornerBottomRightX);
+CASE(eCornerBottomRight, true, eCornerBottomRightY);
+CASE(eCornerBottomLeft, false, eCornerBottomLeftX);
+CASE(eCornerBottomLeft, true, eCornerBottomLeftY);
+# undef CASE
+
+// Validation of SideToFullCorner.
+# define CASE(side, second, result) \
+ static_assert(SideToFullCorner(side, second) == result, \
+ "SideToFullCorner is wrong")
+CASE(eSideTop, false, eCornerTopLeft);
+CASE(eSideTop, true, eCornerTopRight);
+
+CASE(eSideRight, false, eCornerTopRight);
+CASE(eSideRight, true, eCornerBottomRight);
+
+CASE(eSideBottom, false, eCornerBottomRight);
+CASE(eSideBottom, true, eCornerBottomLeft);
+
+CASE(eSideLeft, false, eCornerBottomLeft);
+CASE(eSideLeft, true, eCornerTopLeft);
+# undef CASE
+
+// Validation of SideToHalfCorner.
+# define CASE(side, second, parallel, result) \
+ static_assert(SideToHalfCorner(side, second, parallel) == result, \
+ "SideToHalfCorner is wrong")
+CASE(eSideTop, false, true, eCornerTopLeftX);
+CASE(eSideTop, false, false, eCornerTopLeftY);
+CASE(eSideTop, true, true, eCornerTopRightX);
+CASE(eSideTop, true, false, eCornerTopRightY);
+
+CASE(eSideRight, false, false, eCornerTopRightX);
+CASE(eSideRight, false, true, eCornerTopRightY);
+CASE(eSideRight, true, false, eCornerBottomRightX);
+CASE(eSideRight, true, true, eCornerBottomRightY);
+
+CASE(eSideBottom, false, true, eCornerBottomRightX);
+CASE(eSideBottom, false, false, eCornerBottomRightY);
+CASE(eSideBottom, true, true, eCornerBottomLeftX);
+CASE(eSideBottom, true, false, eCornerBottomLeftY);
+
+CASE(eSideLeft, false, false, eCornerBottomLeftX);
+CASE(eSideLeft, false, true, eCornerBottomLeftY);
+CASE(eSideLeft, true, false, eCornerTopLeftX);
+CASE(eSideLeft, true, true, eCornerTopLeftY);
+# undef CASE
+
+#endif
diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h
new file mode 100644
index 0000000000..a29786488f
--- /dev/null
+++ b/layout/generic/nsIFrame.h
@@ -0,0 +1,5679 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* interface for all rendering objects */
+
+#ifndef nsIFrame_h___
+#define nsIFrame_h___
+
+#ifndef MOZILLA_INTERNAL_API
+#error This header/class should only be used within Mozilla code. It should not be used by extensions.
+#endif
+
+#if (defined(XP_WIN) && !defined(HAVE_64BIT_BUILD)) || defined(ANDROID)
+// Blink's magic depth limit from its HTML parser (513) plus as much as fits in
+// the default run-time stack on armv7 Android on Dalvik when using display:
+// block minus a bit just to be sure. The Dalvik default stack crashes at 588.
+// ART can do a few frames more. Using the same number for 32-bit Windows for
+// consistency. Over there, Blink's magic depth of 513 doesn't fit in the
+// default stack of 1 MB, but this magic depth fits when the default is grown by
+// mere 192 KB (tested in 64 KB increments).
+//
+// 32-bit Windows has a different limit compared to 64-bit desktop, because the
+// default stack size affects all threads and consumes address space. Fixing
+// that is bug 1257522.
+//
+// 32-bit Android on ARM already happens to have defaults that are close enough
+// to what makes sense as a temporary measure on Windows, so adjusting the
+// Android stack can be a follow-up. The stack on 64-bit ARM needs adjusting in
+// any case before 64-bit ARM can become tier-1. See bug 1400811.
+//
+// Ideally, we'd get rid of this smaller limit and make 32-bit Windows and
+// Android capable of working with the Linux/Mac/Win64 number below.
+# define MAX_REFLOW_DEPTH 585
+#else
+// Blink's magic depth limit from its HTML parser times two. Also just about
+// fits within the system default runtime stack limit of 8 MB on 64-bit Mac and
+// Linux with display: table-cell.
+# define MAX_REFLOW_DEPTH 1026
+#endif
+
+/* nsIFrame is in the process of being deCOMtaminated, i.e., this file is
+ eventually going to be eliminated, and all callers will use nsFrame instead.
+ At the moment we're midway through this process, so you will see inlined
+ functions and member variables in this file. -dwh */
+
+#include <algorithm>
+#include <stdio.h>
+
+#include "FrameProperties.h"
+#include "LayoutConstants.h"
+#include "mozilla/AspectRatio.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Baseline.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RelativeTo.h"
+#include "mozilla/Result.h"
+#include "mozilla/SmallPointerArray.h"
+#include "mozilla/ToString.h"
+#include "mozilla/WritingModes.h"
+#include "nsDirection.h"
+#include "nsFrameList.h"
+#include "nsFrameState.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIContent.h"
+#include "nsITheme.h"
+#include "nsQueryFrame.h"
+#include "mozilla/ComputedStyle.h"
+#include "nsStyleStruct.h"
+#include "Visibility.h"
+#include "nsChangeHint.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/CompositorHitTestInfo.h"
+#include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "nsDisplayItemTypes.h"
+#include "nsPresContext.h"
+#include "nsTHashSet.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/AccTypes.h"
+#endif
+
+/**
+ * New rules of reflow:
+ * 1. you get a WillReflow() followed by a Reflow() followed by a DidReflow() in
+ * order (no separate pass over the tree)
+ * 2. it's the parent frame's responsibility to size/position the child's view
+ * (not the child frame's responsibility as it is today) during reflow (and
+ * before sending the DidReflow() notification)
+ * 3. positioning of child frames (and their views) is done on the way down the
+ * tree, and sizing of child frames (and their views) on the way back up
+ * 4. if you move a frame (outside of the reflow process, or after reflowing
+ * it), then you must make sure that its view (or its child frame's views)
+ * are re-positioned as well. It's reasonable to not position the view until
+ * after all reflowing the entire line, for example, but the frame should
+ * still be positioned and sized (and the view sized) during the reflow
+ * (i.e., before sending the DidReflow() notification)
+ * 5. the view system handles moving of widgets, i.e., it's not our problem
+ */
+
+class nsAtom;
+class nsView;
+class nsFrameSelection;
+class nsIWidget;
+class nsIScrollableFrame;
+class nsISelectionController;
+class nsILineIterator;
+class gfxSkipChars;
+class gfxSkipCharsIterator;
+class gfxContext;
+class nsLineList_iterator;
+class nsAbsoluteContainingBlock;
+class nsContainerFrame;
+class nsPlaceholderFrame;
+class nsStyleChangeList;
+class nsViewManager;
+class nsWindowSizes;
+
+struct CharacterDataChangeInfo;
+
+namespace mozilla {
+
+enum class CaretAssociationHint;
+enum class PeekOffsetOption : uint16_t;
+enum class PseudoStyleType : uint8_t;
+enum class TableSelectionMode : uint32_t;
+
+class nsDisplayItem;
+class nsDisplayList;
+class nsDisplayListBuilder;
+class nsDisplayListSet;
+
+class ServoRestyleState;
+class EffectSet;
+class LazyLogModule;
+class PresShell;
+class WidgetGUIEvent;
+class WidgetMouseEvent;
+
+struct PeekOffsetStruct;
+
+namespace layers {
+class Layer;
+class LayerManager;
+} // namespace layers
+
+namespace layout {
+class ScrollAnchorContainer;
+} // namespace layout
+
+} // namespace mozilla
+
+//----------------------------------------------------------------------
+
+// 1 million CSS pixels less than our max app unit measure.
+// For reflowing with an "infinite" available inline space per [css-sizing].
+// (reflowing with an NS_UNCONSTRAINEDSIZE available inline size isn't allowed
+// and leads to assertions)
+#define INFINITE_ISIZE_COORD nscoord(NS_MAXSIZE - (1000000 * 60))
+
+//----------------------------------------------------------------------
+
+namespace mozilla {
+
+enum class LayoutFrameType : uint8_t {
+#define FRAME_TYPE(ty_, ...) ty_,
+#include "mozilla/FrameTypeList.h"
+#undef FRAME_TYPE
+};
+
+} // namespace mozilla
+
+enum nsSelectionAmount {
+ eSelectCharacter = 0, // a single Unicode character;
+ // do not use this (prefer Cluster) unless you
+ // are really sure it's what you want
+ eSelectCluster = 1, // a grapheme cluster: this is usually the right
+ // choice for movement or selection by "character"
+ // as perceived by the user
+ eSelectWord = 2,
+ eSelectWordNoSpace = 3, // select a "word" without selecting the following
+ // space, no matter what the default platform
+ // behavior is
+ eSelectLine = 4, // previous drawn line in flow.
+ // NOTE that selection code depends on the ordering of the above values,
+ // allowing simple <= tests to check categories of caret movement.
+ // Don't rearrange without checking the usage in nsSelection.cpp!
+
+ eSelectBeginLine = 5,
+ eSelectEndLine = 6,
+ eSelectNoAmount = 7, // just bounce back current offset.
+ eSelectParagraph = 8 // select a "paragraph"
+};
+
+//----------------------------------------------------------------------
+// Reflow status returned by the Reflow() methods.
+class nsReflowStatus final {
+ public:
+ nsReflowStatus()
+ : mFloatClearType(mozilla::StyleClear::None),
+ mInlineBreak(InlineBreak::None),
+ mCompletion(Completion::FullyComplete),
+ mNextInFlowNeedsReflow(false),
+ mFirstLetterComplete(false) {}
+
+ // Reset all the member variables.
+ void Reset() {
+ mFloatClearType = mozilla::StyleClear::None;
+ mInlineBreak = InlineBreak::None;
+ mCompletion = Completion::FullyComplete;
+ mNextInFlowNeedsReflow = false;
+ mFirstLetterComplete = false;
+ }
+
+ // Return true if all member variables have their default values.
+ bool IsEmpty() const {
+ return (IsFullyComplete() && !IsInlineBreak() && !mNextInFlowNeedsReflow &&
+ !mFirstLetterComplete);
+ }
+
+ // There are three possible completion statuses, represented by
+ // mCompletion.
+ //
+ // Incomplete means the frame does *not* map all its content, and the
+ // parent frame should create a continuing frame.
+ //
+ // OverflowIncomplete means that the frame has an overflow that is not
+ // complete, but its own box is complete. (This happens when the content
+ // overflows a fixed-height box.) The reflower should place and size the
+ // frame and continue its reflow, but it needs to create an overflow
+ // container as a continuation for this frame. See "Overflow containers"
+ // documentation in nsContainerFrame.h for more information.
+ //
+ // FullyComplete means the frame is neither Incomplete nor
+ // OverflowIncomplete. This is the default state for a nsReflowStatus.
+ //
+ enum class Completion : uint8_t {
+ // The order of the enum values is important, which represents the
+ // precedence when merging.
+ FullyComplete,
+ OverflowIncomplete,
+ Incomplete,
+ };
+
+ bool IsIncomplete() const { return mCompletion == Completion::Incomplete; }
+ bool IsOverflowIncomplete() const {
+ return mCompletion == Completion::OverflowIncomplete;
+ }
+ bool IsFullyComplete() const {
+ return mCompletion == Completion::FullyComplete;
+ }
+ // Just for convenience; not a distinct state.
+ bool IsComplete() const { return !IsIncomplete(); }
+
+ void SetIncomplete() { mCompletion = Completion::Incomplete; }
+ void SetOverflowIncomplete() { mCompletion = Completion::OverflowIncomplete; }
+
+ // mNextInFlowNeedsReflow bit flag means that the next-in-flow is dirty,
+ // and also needs to be reflowed. This status only makes sense for a frame
+ // that is not complete, i.e. you wouldn't set mNextInFlowNeedsReflow when
+ // IsComplete() is true.
+ bool NextInFlowNeedsReflow() const { return mNextInFlowNeedsReflow; }
+ void SetNextInFlowNeedsReflow() { mNextInFlowNeedsReflow = true; }
+
+ // Merge the frame completion status bits from aStatus into this.
+ void MergeCompletionStatusFrom(const nsReflowStatus& aStatus) {
+ if (mCompletion < aStatus.mCompletion) {
+ mCompletion = aStatus.mCompletion;
+ }
+
+ // These asserts ensure that the mCompletion merging works as we expect.
+ // (Incomplete beats OverflowIncomplete, which beats FullyComplete.)
+ static_assert(
+ Completion::Incomplete > Completion::OverflowIncomplete &&
+ Completion::OverflowIncomplete > Completion::FullyComplete,
+ "mCompletion merging won't work without this!");
+
+ mNextInFlowNeedsReflow |= aStatus.mNextInFlowNeedsReflow;
+ }
+
+ // There are three possible inline-break statuses, represented by
+ // mInlineBreak.
+ //
+ // "None" means no break is requested.
+ // "Before" means the break should occur before the frame.
+ // "After" means the break should occur after the frame.
+ // (Here, "the frame" is the frame whose reflow results are being reported by
+ // this nsReflowStatus.)
+ //
+ enum class InlineBreak : uint8_t {
+ None,
+ Before,
+ After,
+ };
+
+ bool IsInlineBreak() const { return mInlineBreak != InlineBreak::None; }
+ bool IsInlineBreakBefore() const {
+ return mInlineBreak == InlineBreak::Before;
+ }
+ bool IsInlineBreakAfter() const { return mInlineBreak == InlineBreak::After; }
+ mozilla::StyleClear FloatClearType() const { return mFloatClearType; }
+
+ // Set the inline line-break-before status, and reset other bit flags. Note
+ // that other frame completion status isn't expected to matter after calling
+ // this method.
+ //
+ // Here's one scenario where a child frame would report this status. Suppose
+ // the child has "break-inside:avoid" in its style, and the child (and its
+ // content) won't fit in the available block-size. This child would want to
+ // report this status so that it gets pushed (in its entirety) to the next
+ // column/page where it will hopefully fit.
+ void SetInlineLineBreakBeforeAndReset() {
+ Reset();
+ mFloatClearType = mozilla::StyleClear::None;
+ mInlineBreak = InlineBreak::Before;
+ }
+
+ // Set the inline line-break-after status. The clear type can be changed
+ // via the optional aClearType param.
+ void SetInlineLineBreakAfter(
+ mozilla::StyleClear aClearType = mozilla::StyleClear::None) {
+ mFloatClearType = aClearType;
+ mInlineBreak = InlineBreak::After;
+ }
+
+ // mFirstLetterComplete bit flag means the break was induced by
+ // completion of a first-letter.
+ bool FirstLetterComplete() const { return mFirstLetterComplete; }
+ void SetFirstLetterComplete() { mFirstLetterComplete = true; }
+
+ private:
+ mozilla::StyleClear mFloatClearType;
+ InlineBreak mInlineBreak;
+ Completion mCompletion;
+ bool mNextInFlowNeedsReflow : 1;
+ bool mFirstLetterComplete : 1;
+};
+
+// Convert nsReflowStatus to a human-readable string.
+std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus);
+
+namespace mozilla {
+
+// Loosely: https://drafts.csswg.org/css-align-3/#shared-alignment-context
+enum class AlignmentContext {
+ Inline,
+ Table,
+ Flexbox,
+ Grid,
+};
+
+/*
+ * For replaced elements only. Gets the intrinsic dimensions of this element,
+ * which can be specified on a per-axis basis.
+ */
+struct IntrinsicSize {
+ Maybe<nscoord> width;
+ Maybe<nscoord> height;
+
+ IntrinsicSize() = default;
+
+ IntrinsicSize(nscoord aWidth, nscoord aHeight)
+ : width(Some(aWidth)), height(Some(aHeight)) {}
+
+ explicit IntrinsicSize(const nsSize& aSize)
+ : IntrinsicSize(aSize.Width(), aSize.Height()) {}
+
+ Maybe<nsSize> ToSize() const {
+ return width && height ? Some(nsSize(*width, *height)) : Nothing();
+ }
+
+ void Zoom(const StyleZoom& aZoom) {
+ if (width) {
+ *width = aZoom.ZoomCoord(*width);
+ }
+ if (height) {
+ *height = aZoom.ZoomCoord(*height);
+ }
+ }
+
+ bool operator==(const IntrinsicSize& rhs) const {
+ return width == rhs.width && height == rhs.height;
+ }
+ bool operator!=(const IntrinsicSize& rhs) const { return !(*this == rhs); }
+};
+
+// Pseudo bidi embedding level indicating nonexistence.
+constexpr mozilla::intl::BidiEmbeddingLevel kBidiLevelNone(0xff);
+
+struct FrameBidiData {
+ mozilla::intl::BidiEmbeddingLevel baseLevel;
+ mozilla::intl::BidiEmbeddingLevel embeddingLevel;
+ // The embedding level of virtual bidi formatting character before
+ // this frame if any. kBidiLevelNone is used to indicate nonexistence
+ // or unnecessity of such virtual character.
+ mozilla::intl::BidiEmbeddingLevel precedingControl;
+};
+
+} // namespace mozilla
+
+/// Generic destructor for frame properties. Calls delete.
+template <typename T>
+static void DeleteValue(T* aPropertyValue) {
+ delete aPropertyValue;
+}
+
+/// Generic destructor for frame properties. Calls Release().
+template <typename T>
+static void ReleaseValue(T* aPropertyValue) {
+ aPropertyValue->Release();
+}
+
+//----------------------------------------------------------------------
+
+/**
+ * nsIFrame logging constants. We redefine the nspr
+ * PRLogModuleInfo.level field to be a bitfield. Each bit controls a
+ * specific type of logging. Each logging operation has associated
+ * inline methods defined below.
+ *
+ * Due to the redefinition of the level field we cannot use MOZ_LOG directly
+ * as that will cause assertions due to invalid log levels.
+ */
+#define NS_FRAME_TRACE_CALLS 0x1
+#define NS_FRAME_TRACE_PUSH_PULL 0x2
+#define NS_FRAME_TRACE_CHILD_REFLOW 0x4
+#define NS_FRAME_TRACE_NEW_FRAMES 0x8
+
+#define NS_FRAME_LOG_TEST(_lm, _bit) \
+ (int(((mozilla::LogModule*)(_lm))->Level()) & (_bit))
+
+#ifdef DEBUG
+# define NS_FRAME_LOG(_bit, _args) \
+ PR_BEGIN_MACRO \
+ if (NS_FRAME_LOG_TEST(nsIFrame::sFrameLogModule, _bit)) { \
+ printf_stderr _args; \
+ } \
+ PR_END_MACRO
+#else
+# define NS_FRAME_LOG(_bit, _args)
+#endif
+
+// XXX Need to rework this so that logging is free when it's off
+#ifdef DEBUG
+# define NS_FRAME_TRACE_IN(_method) Trace(_method, true)
+
+# define NS_FRAME_TRACE_OUT(_method) Trace(_method, false)
+
+# define NS_FRAME_TRACE(_bit, _args) \
+ PR_BEGIN_MACRO \
+ if (NS_FRAME_LOG_TEST(nsIFrame::sFrameLogModule, _bit)) { \
+ TraceMsg _args; \
+ } \
+ PR_END_MACRO
+
+# define NS_FRAME_TRACE_REFLOW_IN(_method) Trace(_method, true)
+
+# define NS_FRAME_TRACE_REFLOW_OUT(_method, _status) \
+ Trace(_method, false, _status)
+
+#else
+# define NS_FRAME_TRACE(_bits, _args)
+# define NS_FRAME_TRACE_IN(_method)
+# define NS_FRAME_TRACE_OUT(_method)
+# define NS_FRAME_TRACE_REFLOW_IN(_method)
+# define NS_FRAME_TRACE_REFLOW_OUT(_method, _status)
+#endif
+
+//----------------------------------------------------------------------
+
+// Frame allocation boilerplate macros. Every subclass of nsFrame must
+// either use NS_{DECL,IMPL}_FRAMEARENA_HELPERS pair for allocating
+// memory correctly, or use NS_DECL_ABSTRACT_FRAME to declare a frame
+// class abstract and stop it from being instantiated. If a frame class
+// without its own operator new and GetFrameId gets instantiated, the
+// per-frame recycler lists in nsPresArena will not work correctly,
+// with potentially catastrophic consequences (not enough memory is
+// allocated for a frame object).
+
+#define NS_DECL_FRAMEARENA_HELPERS(class) \
+ NS_DECL_QUERYFRAME_TARGET(class) \
+ static constexpr nsIFrame::ClassID kClassID = nsIFrame::ClassID::class##_id; \
+ void* operator new(size_t, mozilla::PresShell*) MOZ_MUST_OVERRIDE; \
+ nsQueryFrame::FrameIID GetFrameId() const override MOZ_MUST_OVERRIDE { \
+ return nsQueryFrame::class##_id; \
+ }
+
+#define NS_IMPL_FRAMEARENA_HELPERS(class) \
+ void* class ::operator new(size_t sz, mozilla::PresShell * aShell) { \
+ return aShell->AllocateFrame(nsQueryFrame::class##_id, sz); \
+ }
+
+#define NS_DECL_ABSTRACT_FRAME(class) \
+ void* operator new(size_t, mozilla::PresShell*) MOZ_MUST_OVERRIDE = delete; \
+ nsQueryFrame::FrameIID GetFrameId() const override MOZ_MUST_OVERRIDE = 0;
+
+//----------------------------------------------------------------------
+
+namespace mozilla {
+
+// A simple class to group stuff that we need to keep around when tearing down
+// a frame tree.
+//
+// Native anonymous content created by the frames need to get unbound _after_
+// the frame has been destroyed, see bug 1400618.
+//
+// We destroy the anonymous content bottom-up (so, in reverse order), because
+// it's a bit simpler, though we generally don't have that much nested anonymous
+// content (except for scrollbars).
+struct MOZ_RAII FrameDestroyContext {
+ explicit FrameDestroyContext(PresShell* aPs) : mPresShell(aPs) {}
+
+ void AddAnonymousContent(already_AddRefed<nsIContent>&& aContent) {
+ if (RefPtr<nsIContent> content = aContent) {
+ mAnonymousContent.AppendElement(std::move(content));
+ }
+ }
+
+ ~FrameDestroyContext();
+
+ private:
+ PresShell* const mPresShell;
+ AutoTArray<RefPtr<nsIContent>, 100> mAnonymousContent;
+};
+
+/**
+ * Bit-flags specific to a given layout class id.
+ */
+enum class LayoutFrameClassFlags : uint16_t {
+ None = 0,
+ Leaf = 1 << 0,
+ LeafDynamic = 1 << 1,
+ MathML = 1 << 2,
+ SVG = 1 << 3,
+ SVGContainer = 1 << 4,
+ BidiInlineContainer = 1 << 5,
+ // The frame is for a replaced element, such as an image
+ Replaced = 1 << 6,
+ // Frame that contains a block but looks like a replaced element from the
+ // outside.
+ ReplacedContainsBlock = 1 << 7,
+ // A replaced element that has replaced-element sizing characteristics (i.e.,
+ // like images or iframes), as opposed to inline-block sizing characteristics
+ // (like form controls).
+ ReplacedSizing = 1 << 8,
+ // A frame that participates in inline reflow, i.e., one that requires
+ // ReflowInput::mLineLayout.
+ LineParticipant = 1 << 9,
+ // Whether this frame is a table part (but not a table or table wrapper).
+ TablePart = 1 << 10,
+ CanContainOverflowContainers = 1 << 11,
+ // Whether the frame supports CSS transforms.
+ SupportsCSSTransforms = 1 << 12,
+ // Whether this frame class supports 'contain: layout' and 'contain: paint'
+ // (supporting one is equivalent to supporting the other).
+ SupportsContainLayoutAndPaint = 1 << 13,
+ // Whether this frame class supports the `aspect-ratio` property.
+ SupportsAspectRatio = 1 << 14,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(LayoutFrameClassFlags)
+
+} // namespace mozilla
+
+/**
+ * A frame in the layout model. This interface is supported by all frame
+ * objects.
+ *
+ * Frames can have multiple child lists: the default child list
+ * (referred to as the <i>principal</i> child list, and additional named
+ * child lists. There is an ordering of frames within a child list, but
+ * there is no order defined between frames in different child lists of
+ * the same parent frame.
+ *
+ * Frames are NOT reference counted. Use the Destroy() member function
+ * to destroy a frame. The lifetime of the frame hierarchy is bounded by the
+ * lifetime of the presentation shell which owns the frames.
+ *
+ * nsIFrame is a private Gecko interface. If you are not Gecko then you
+ * should not use it. If you're not in layout, then you won't be able to
+ * link to many of the functions defined here. Too bad.
+ *
+ * If you're not in layout but you must call functions in here, at least
+ * restrict yourself to calling virtual methods, which won't hurt you as badly.
+ */
+class nsIFrame : public nsQueryFrame {
+ public:
+ using AlignmentContext = mozilla::AlignmentContext;
+ using BaselineSharingGroup = mozilla::BaselineSharingGroup;
+ using BaselineExportContext = mozilla::BaselineExportContext;
+ template <typename T>
+ using Maybe = mozilla::Maybe<T>;
+ template <typename T, typename E>
+ using Result = mozilla::Result<T, E>;
+ using Nothing = mozilla::Nothing;
+ using OnNonvisible = mozilla::OnNonvisible;
+ using ReflowInput = mozilla::ReflowInput;
+ using ReflowOutput = mozilla::ReflowOutput;
+ using Visibility = mozilla::Visibility;
+ using LengthPercentage = mozilla::LengthPercentage;
+ using ContentRelevancy = mozilla::ContentRelevancy;
+
+ using nsDisplayItem = mozilla::nsDisplayItem;
+ using nsDisplayList = mozilla::nsDisplayList;
+ using nsDisplayListSet = mozilla::nsDisplayListSet;
+ using nsDisplayListBuilder = mozilla::nsDisplayListBuilder;
+
+ typedef mozilla::ComputedStyle ComputedStyle;
+ typedef mozilla::FrameProperties FrameProperties;
+ typedef mozilla::layers::LayerManager LayerManager;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Matrix Matrix;
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+ typedef mozilla::gfx::Matrix4x4Flagged Matrix4x4Flagged;
+ typedef mozilla::Sides Sides;
+ typedef mozilla::LogicalSides LogicalSides;
+ typedef mozilla::SmallPointerArray<nsDisplayItem> DisplayItemArray;
+
+ typedef nsQueryFrame::ClassID ClassID;
+
+ using ClassFlags = mozilla::LayoutFrameClassFlags;
+
+ protected:
+ using ChildList = mozilla::FrameChildList;
+ using ChildListID = mozilla::FrameChildListID;
+ using ChildListIDs = mozilla::FrameChildListIDs;
+
+ public:
+ // nsQueryFrame
+ NS_DECL_QUERYFRAME
+ NS_DECL_QUERYFRAME_TARGET(nsIFrame)
+
+ explicit nsIFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : mContent(nullptr),
+ mComputedStyle(aStyle),
+ mPresContext(aPresContext),
+ mParent(nullptr),
+ mNextSibling(nullptr),
+ mPrevSibling(nullptr),
+ mState(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY),
+ mWritingMode(aStyle),
+ mClass(aID),
+ mMayHaveRoundedCorners(false),
+ mHasImageRequest(false),
+ mHasFirstLetterChild(false),
+ mParentIsWrapperAnonBox(false),
+ mIsWrapperBoxNeedingRestyle(false),
+ mReflowRequestedForCharDataChange(false),
+ mForceDescendIntoIfVisible(false),
+ mBuiltDisplayList(false),
+ mFrameIsModified(false),
+ mHasModifiedDescendants(false),
+ mHasOverrideDirtyRegion(false),
+ mMayHaveWillChangeBudget(false),
+#ifdef DEBUG
+ mWasVisitedByAutoFrameConstructionPageName(false),
+#endif
+ mIsPrimaryFrame(false),
+ mMayHaveTransformAnimation(false),
+ mMayHaveOpacityAnimation(false),
+ mAllDescendantsAreInvisible(false),
+ mHasBSizeChange(false),
+ mHasPaddingChange(false),
+ mInScrollAnchorChain(false),
+ mHasColumnSpanSiblings(false),
+ mDescendantMayDependOnItsStaticPosition(false) {
+ MOZ_ASSERT(mComputedStyle);
+ MOZ_ASSERT(mPresContext);
+ mozilla::PodZero(&mOverflow);
+ MOZ_COUNT_CTOR(nsIFrame);
+ }
+ explicit nsIFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, ClassID::nsIFrame_id) {}
+
+ nsPresContext* PresContext() const { return mPresContext; }
+
+ mozilla::PresShell* PresShell() const { return PresContext()->PresShell(); }
+
+ virtual nsQueryFrame::FrameIID GetFrameId() const MOZ_MUST_OVERRIDE {
+ return kFrameIID;
+ }
+
+ /**
+ * Called to initialize the frame. This is called immediately after creating
+ * the frame.
+ *
+ * If the frame is a continuing frame, then aPrevInFlow indicates the previous
+ * frame (the frame that was split).
+ *
+ * Each subclass that need a view should override this method and call
+ * CreateView() after calling its base class Init().
+ *
+ * @param aContent the content object associated with the frame
+ * @param aParent the parent frame
+ * @param aPrevInFlow the prev-in-flow frame
+ */
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow);
+
+ void* operator new(size_t, mozilla::PresShell*) MOZ_MUST_OVERRIDE;
+
+ using DestroyContext = mozilla::FrameDestroyContext;
+
+ /**
+ * Flags for PeekOffsetCharacter, PeekOffsetNoAmount, PeekOffsetWord return
+ * values.
+ */
+ enum FrameSearchResult {
+ // Peek found a appropriate offset within frame.
+ FOUND = 0x00,
+ // try next frame for offset.
+ CONTINUE = 0x1,
+ // offset not found because the frame was empty of text.
+ CONTINUE_EMPTY = 0x2 | CONTINUE,
+ // offset not found because the frame didn't contain any text that could be
+ // selected.
+ CONTINUE_UNSELECTABLE = 0x4 | CONTINUE,
+ };
+
+ /**
+ * Options for PeekOffsetCharacter().
+ */
+ struct MOZ_STACK_CLASS PeekOffsetCharacterOptions {
+ // Whether to restrict result to valid cursor locations (between grapheme
+ // clusters) - if this is included, maintains "normal" behavior, otherwise,
+ // used for selection by "code unit" (instead of "character")
+ bool mRespectClusters;
+ // Whether to check user-select style value - if this is included, checks
+ // if user-select is all, then, it may return CONTINUE_UNSELECTABLE.
+ bool mIgnoreUserStyleAll;
+
+ PeekOffsetCharacterOptions()
+ : mRespectClusters(true), mIgnoreUserStyleAll(false) {}
+ };
+
+ virtual void Destroy(DestroyContext&);
+
+ protected:
+ /**
+ * Return true if the frame is part of a Selection.
+ * Helper method to implement the public IsSelected() API.
+ */
+ virtual bool IsFrameSelected() const;
+
+ template <class Source>
+ friend class do_QueryFrameHelper; // to read mClass
+ friend class nsBlockFrame; // for GetCaretBaseline
+ friend class nsContainerFrame; // for ReparentFrameViewTo
+
+ virtual ~nsIFrame();
+
+ // Overridden to prevent the global delete from being called, since
+ // the memory came out of an arena instead of the heap.
+ //
+ // Ideally this would be private and undefined, like the normal
+ // operator new. Unfortunately, the C++ standard requires an
+ // overridden operator delete to be accessible to any subclass that
+ // defines a virtual destructor, so we can only make it protected;
+ // worse, some C++ compilers will synthesize calls to this function
+ // from the "deleting destructors" that they emit in case of
+ // delete-expressions, so it can't even be undefined.
+ void operator delete(void* aPtr, size_t sz);
+
+ private:
+ // Left undefined; nsFrame objects are never allocated from the heap.
+ void* operator new(size_t sz) noexcept(true);
+
+ // Returns true if this frame has any kind of CSS animations.
+ bool HasCSSAnimations();
+
+ // Returns true if this frame has any kind of CSS transitions.
+ bool HasCSSTransitions();
+
+ public:
+ /**
+ * Get the content object associated with this frame. Does not add a
+ * reference.
+ */
+ nsIContent* GetContent() const { return mContent; }
+
+ /**
+ * @brief Get the closest native anonymous subtree root if the content is in a
+ * native anonymous subtree.
+ *
+ * @return The root of native anonymous subtree which the content belongs to.
+ * Otherwise, nullptr.
+ */
+ nsIContent* GetClosestNativeAnonymousSubtreeRoot() const {
+ return mContent ? mContent->GetClosestNativeAnonymousSubtreeRoot()
+ : nullptr;
+ }
+
+ /**
+ * Get the frame that should be the parent for the frames of child elements
+ * May return nullptr during reflow
+ */
+ virtual nsContainerFrame* GetContentInsertionFrame() { return nullptr; }
+
+ /**
+ * Move any frames on our overflow list to the end of our principal list.
+ * @return true if there were any overflow frames
+ */
+ virtual bool DrainSelfOverflowList() { return false; }
+
+ /**
+ * Get the frame that should be scrolled if the content associated
+ * with this frame is targeted for scrolling. For frames implementing
+ * nsIScrollableFrame this will return the frame itself. For frames
+ * like nsTextControlFrame that contain a scrollframe, will return
+ * that scrollframe.
+ */
+ virtual nsIScrollableFrame* GetScrollTargetFrame() const { return nullptr; }
+
+ /**
+ * Get the offsets of the frame. most will be 0,0
+ *
+ */
+ virtual std::pair<int32_t, int32_t> GetOffsets() const;
+
+ /**
+ * Reset the offsets when splitting frames during Bidi reordering
+ *
+ */
+ virtual void AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) {}
+
+ /**
+ * Get the style associated with this frame.
+ */
+ ComputedStyle* Style() const { return mComputedStyle; }
+
+ void AssertNewStyleIsSane(ComputedStyle&)
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ ;
+#else
+ {
+ }
+#endif
+
+ void SetComputedStyle(ComputedStyle* aStyle) {
+ if (aStyle != mComputedStyle) {
+ AssertNewStyleIsSane(*aStyle);
+ RefPtr<ComputedStyle> oldComputedStyle = std::move(mComputedStyle);
+ mComputedStyle = aStyle;
+ DidSetComputedStyle(oldComputedStyle);
+ }
+ }
+
+ /**
+ * SetComputedStyleWithoutNotification is for changes to the style
+ * context that should suppress style change processing, in other
+ * words, those that aren't really changes. This generally means only
+ * changes that happen during frame construction.
+ */
+ void SetComputedStyleWithoutNotification(ComputedStyle* aStyle) {
+ if (aStyle != mComputedStyle) {
+ mComputedStyle = aStyle;
+ }
+ }
+
+ protected:
+ // Style post processing hook
+ // Attention: the old style is the one we're forgetting,
+ // and hence possibly completely bogus for GetStyle* purposes.
+ // Use PeekStyleData instead.
+ virtual void DidSetComputedStyle(ComputedStyle* aOldComputedStyle);
+
+ public:
+/**
+ * Define typesafe getter functions for each style struct by
+ * preprocessing the list of style structs. These functions are the
+ * preferred way to get style data. The macro creates functions like:
+ * const nsStyleBorder* StyleBorder();
+ * const nsStyleColor* StyleColor();
+ *
+ * Callers outside of libxul should use nsIDOMWindow::GetComputedStyle()
+ * instead of these accessors.
+ *
+ * Callers can use Style*WithOptionalParam if they're in a function that
+ * accepts an *optional* pointer the style struct.
+ */
+#define STYLE_STRUCT(name_) \
+ const nsStyle##name_* Style##name_() const MOZ_NONNULL_RETURN { \
+ NS_ASSERTION(mComputedStyle, "No style found!"); \
+ return mComputedStyle->Style##name_(); \
+ } \
+ const nsStyle##name_* Style##name_##WithOptionalParam( \
+ const nsStyle##name_* aStyleStruct) const MOZ_NONNULL_RETURN { \
+ if (aStyleStruct) { \
+ MOZ_ASSERT(aStyleStruct == Style##name_()); \
+ return aStyleStruct; \
+ } \
+ return Style##name_(); \
+ }
+#include "nsStyleStructList.h"
+#undef STYLE_STRUCT
+
+ /** Also forward GetVisitedDependentColor to the style */
+ template <typename T, typename S>
+ nscolor GetVisitedDependentColor(T S::*aField) {
+ return mComputedStyle->GetVisitedDependentColor(aField);
+ }
+
+ /**
+ * These methods are to access any additional ComputedStyles that
+ * the frame may be holding.
+ *
+ * These are styles that are children of the frame's primary style and are NOT
+ * used as styles for any child frames.
+ *
+ * These contexts also MUST NOT have any child styles whatsoever. If you need
+ * to insert styles into the style tree, then you should create pseudo element
+ * frames to own them.
+ *
+ * The indicies must be consecutive and implementations MUST return null if
+ * asked for an index that is out of range.
+ */
+ virtual ComputedStyle* GetAdditionalComputedStyle(int32_t aIndex) const;
+
+ virtual void SetAdditionalComputedStyle(int32_t aIndex,
+ ComputedStyle* aComputedStyle);
+
+ /**
+ * @param aSelectionStatus nsISelectionController::getDisplaySelection.
+ */
+ already_AddRefed<ComputedStyle> ComputeSelectionStyle(
+ int16_t aSelectionStatus) const;
+
+ already_AddRefed<ComputedStyle> ComputeHighlightSelectionStyle(
+ nsAtom* aHighlightName);
+
+ /**
+ * Accessor functions for geometric parent.
+ */
+ nsContainerFrame* GetParent() const { return mParent; }
+
+ bool CanBeDynamicReflowRoot() const;
+
+ /**
+ * Gets the parent of a frame, using the parent of the placeholder for
+ * out-of-flow frames.
+ */
+ inline nsContainerFrame* GetInFlowParent() const;
+
+ /**
+ * Gets the primary frame of the closest flattened tree ancestor that has a
+ * frame (flattened tree ancestors may not have frames in presence of display:
+ * contents).
+ */
+ inline nsIFrame* GetClosestFlattenedTreeAncestorPrimaryFrame() const;
+
+ /**
+ * Return the placeholder for this frame (which must be out-of-flow).
+ * @note this will only return non-null if |this| is the first-in-flow
+ * although we don't assert that here for legacy reasons.
+ */
+ inline nsPlaceholderFrame* GetPlaceholderFrame() const {
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+ return GetProperty(PlaceholderFrameProperty());
+ }
+
+ /**
+ * Set this frame's parent to aParent.
+ * If the frame may have moved into or out of a scrollframe's
+ * frame subtree,
+ * StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary must
+ * also be called.
+ */
+ void SetParent(nsContainerFrame* aParent);
+
+ /**
+ * The frame's writing-mode, used for logical layout computations.
+ * It's usually the 'writing-mode' computed value, but there are exceptions:
+ * * inner table frames copy the value from the table frame
+ * (@see nsTableRowGroupFrame::Init, nsTableRowFrame::Init etc)
+ * * the root element frame propagates its value to its ancestors.
+ * The value may obtain from the principal <body> element.
+ * (@see nsCSSFrameConstructor::ConstructDocElementFrame)
+ * * the internal anonymous frames of the root element copy their value
+ * from the parent.
+ * (@see nsIFrame::Init)
+ * * a scrolled frame propagates its value to its ancestor scroll frame
+ * (@see nsHTMLScrollFrame::ReloadChildFrames)
+ */
+ mozilla::WritingMode GetWritingMode() const { return mWritingMode; }
+
+ /**
+ * Construct a writing mode for line layout in this frame. This is
+ * the writing mode of this frame, except that if this frame is styled with
+ * unicode-bidi:plaintext, we reset the direction to the resolved paragraph
+ * level of the given subframe (typically the first frame on the line),
+ * because the container frame could be split by hard line breaks into
+ * multiple paragraphs with different base direction.
+ * @param aSelfWM the WM of 'this'
+ */
+ mozilla::WritingMode WritingModeForLine(mozilla::WritingMode aSelfWM,
+ nsIFrame* aSubFrame) const;
+
+ /**
+ * Bounding rect of the frame.
+ *
+ * For frames that are laid out according to CSS box model rules the values
+ * are in app units, and the origin is relative to the upper-left of the
+ * geometric parent. The size includes the content area, borders, and
+ * padding.
+ *
+ * Frames that are laid out according to SVG's coordinate space based rules
+ * (frames with the NS_FRAME_SVG_LAYOUT bit set, which *excludes*
+ * SVGOuterSVGFrame) are different. Many frames of this type do not set or
+ * use mRect, in which case the frame rect is undefined. The exceptions are:
+ *
+ * - SVGInnerSVGFrame
+ * - SVGGeometryFrame (used for <path>, <circle>, etc.)
+ * - SVGImageFrame
+ * - SVGForeignObjectFrame
+ *
+ * For these frames the frame rect contains the frame's element's userspace
+ * bounds including fill, stroke and markers, but converted to app units
+ * rather than being in user units (CSS px). In the SVG code "userspace" is
+ * defined to be the coordinate system for the attributes that define an
+ * element's geometry (such as the 'cx' attribute for <circle>). For more
+ * precise details see these frames' implementations of the ReflowSVG method
+ * where mRect is set.
+ *
+ * Note: moving or sizing the frame does not affect the view's size or
+ * position.
+ */
+ nsRect GetRect() const { return mRect; }
+ nsPoint GetPosition() const { return mRect.TopLeft(); }
+ nsSize GetSize() const { return mRect.Size(); }
+ nsRect GetRectRelativeToSelf() const {
+ return nsRect(nsPoint(0, 0), mRect.Size());
+ }
+
+ /**
+ * Like the frame's rect (see |GetRect|), which is the border rect,
+ * other rectangles of the frame, in app units, relative to the parent.
+ */
+ nsRect GetPaddingRect() const;
+ nsRect GetPaddingRectRelativeToSelf() const;
+ nsRect GetContentRect() const;
+ nsRect GetContentRectRelativeToSelf() const;
+ nsRect GetMarginRect() const;
+ nsRect GetMarginRectRelativeToSelf() const;
+
+ /**
+ * Dimensions and position in logical coordinates in the frame's writing mode
+ * or another writing mode
+ */
+ mozilla::LogicalRect GetLogicalRect(const nsSize& aContainerSize) const {
+ return GetLogicalRect(GetWritingMode(), aContainerSize);
+ }
+ mozilla::LogicalPoint GetLogicalPosition(const nsSize& aContainerSize) const {
+ return GetLogicalPosition(GetWritingMode(), aContainerSize);
+ }
+ mozilla::LogicalSize GetLogicalSize() const {
+ return GetLogicalSize(GetWritingMode());
+ }
+ mozilla::LogicalRect GetLogicalRect(mozilla::WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ return mozilla::LogicalRect(aWritingMode, GetRect(), aContainerSize);
+ }
+ mozilla::LogicalPoint GetLogicalPosition(mozilla::WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ return GetLogicalRect(aWritingMode, aContainerSize).Origin(aWritingMode);
+ }
+ mozilla::LogicalSize GetLogicalSize(mozilla::WritingMode aWritingMode) const {
+ return mozilla::LogicalSize(aWritingMode, GetSize());
+ }
+ nscoord IStart(const nsSize& aContainerSize) const {
+ return IStart(GetWritingMode(), aContainerSize);
+ }
+ nscoord IStart(mozilla::WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ return GetLogicalPosition(aWritingMode, aContainerSize).I(aWritingMode);
+ }
+ nscoord BStart(const nsSize& aContainerSize) const {
+ return BStart(GetWritingMode(), aContainerSize);
+ }
+ nscoord BStart(mozilla::WritingMode aWritingMode,
+ const nsSize& aContainerSize) const {
+ return GetLogicalPosition(aWritingMode, aContainerSize).B(aWritingMode);
+ }
+ nscoord ISize() const { return ISize(GetWritingMode()); }
+ nscoord ISize(mozilla::WritingMode aWritingMode) const {
+ return GetLogicalSize(aWritingMode).ISize(aWritingMode);
+ }
+ nscoord BSize() const { return BSize(GetWritingMode()); }
+ nscoord BSize(mozilla::WritingMode aWritingMode) const {
+ return GetLogicalSize(aWritingMode).BSize(aWritingMode);
+ }
+ mozilla::LogicalSize ContentSize() const {
+ return ContentSize(GetWritingMode());
+ }
+ mozilla::LogicalSize ContentSize(mozilla::WritingMode aWritingMode) const {
+ mozilla::WritingMode wm = GetWritingMode();
+ const auto bp = GetLogicalUsedBorderAndPadding(wm)
+ .ApplySkipSides(GetLogicalSkipSides())
+ .ConvertTo(aWritingMode, wm);
+ const auto size = GetLogicalSize(aWritingMode);
+ return mozilla::LogicalSize(
+ aWritingMode,
+ std::max(0, size.ISize(aWritingMode) - bp.IStartEnd(aWritingMode)),
+ std::max(0, size.BSize(aWritingMode) - bp.BStartEnd(aWritingMode)));
+ }
+ nscoord ContentISize(mozilla::WritingMode aWritingMode) const {
+ return ContentSize(aWritingMode).ISize(aWritingMode);
+ }
+ nscoord ContentBSize(mozilla::WritingMode aWritingMode) const {
+ return ContentSize(aWritingMode).BSize(aWritingMode);
+ }
+
+ /**
+ * When we change the size of the frame's border-box rect, we may need to
+ * reset the overflow rect if it was previously stored as deltas.
+ * (If it is currently a "large" overflow and could be re-packed as deltas,
+ * we don't bother as the cost of the allocation has already been paid.)
+ * @param aRebuildDisplayItems If true, then adds this frame to the
+ * list of modified frames for display list building if the rect has changed.
+ * Only pass false if you're sure that the relevant display items will be
+ * rebuilt already (possibly by an ancestor being in the modified list), or if
+ * this is a temporary change.
+ */
+ void SetRect(const nsRect& aRect, bool aRebuildDisplayItems = true) {
+ if (aRect == mRect) {
+ return;
+ }
+ if (mOverflow.mType != OverflowStorageType::Large &&
+ mOverflow.mType != OverflowStorageType::None) {
+ mozilla::OverflowAreas overflow = GetOverflowAreas();
+ mRect = aRect;
+ SetOverflowAreas(overflow);
+ } else {
+ mRect = aRect;
+ }
+ if (aRebuildDisplayItems) {
+ MarkNeedsDisplayItemRebuild();
+ }
+ }
+ /**
+ * Set this frame's rect from a logical rect in its own writing direction
+ */
+ void SetRect(const mozilla::LogicalRect& aRect,
+ const nsSize& aContainerSize) {
+ SetRect(GetWritingMode(), aRect, aContainerSize);
+ }
+ /**
+ * Set this frame's rect from a logical rect in a different writing direction
+ * (GetPhysicalRect will assert if the writing mode doesn't match)
+ */
+ void SetRect(mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalRect& aRect,
+ const nsSize& aContainerSize) {
+ SetRect(aRect.GetPhysicalRect(aWritingMode, aContainerSize));
+ }
+
+ /**
+ * Set this frame's size from a logical size in its own writing direction.
+ * This leaves the frame's logical position unchanged, which means its
+ * physical position may change (for right-to-left modes).
+ */
+ void SetSize(const mozilla::LogicalSize& aSize) {
+ SetSize(GetWritingMode(), aSize);
+ }
+ /*
+ * Set this frame's size from a logical size in a different writing direction.
+ * This leaves the frame's logical position in the given mode unchanged,
+ * which means its physical position may change (for right-to-left modes).
+ */
+ void SetSize(mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalSize& aSize) {
+ if (aWritingMode.IsPhysicalRTL()) {
+ nscoord oldWidth = mRect.Width();
+ SetSize(aSize.GetPhysicalSize(aWritingMode));
+ mRect.x -= mRect.Width() - oldWidth;
+ } else {
+ SetSize(aSize.GetPhysicalSize(aWritingMode));
+ }
+ }
+
+ /**
+ * Set this frame's physical size. This leaves the frame's physical position
+ * (topLeft) unchanged.
+ * @param aRebuildDisplayItems If true, then adds this frame to the
+ * list of modified frames for display list building if the size has changed.
+ * Only pass false if you're sure that the relevant display items will be
+ * rebuilt already (possibly by an ancestor being in the modified list), or if
+ * this is a temporary change.
+ */
+ void SetSize(const nsSize& aSize, bool aRebuildDisplayItems = true) {
+ SetRect(nsRect(mRect.TopLeft(), aSize), aRebuildDisplayItems);
+ }
+
+ void SetPosition(const nsPoint& aPt);
+ void SetPosition(mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalPoint& aPt,
+ const nsSize& aContainerSize) {
+ // We subtract mRect.Size() from the container size to account for
+ // the fact that logical origins in RTL coordinate systems are at
+ // the top right of the frame instead of the top left.
+ SetPosition(
+ aPt.GetPhysicalPoint(aWritingMode, aContainerSize - mRect.Size()));
+ }
+
+ /**
+ * Move the frame, accounting for relative positioning. Use this when
+ * adjusting the frame's position by a known amount, to properly update its
+ * saved normal position (see GetNormalPosition below).
+ *
+ * This must be used only when moving a frame *after*
+ * ReflowInput::ApplyRelativePositioning is called. When moving
+ * a frame during the reflow process prior to calling
+ * ReflowInput::ApplyRelativePositioning, the position should
+ * simply be adjusted directly (e.g., using SetPosition()).
+ */
+ void MovePositionBy(const nsPoint& aTranslation);
+
+ /**
+ * As above, using a logical-point delta in a given writing mode.
+ */
+ void MovePositionBy(mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalPoint& aTranslation) {
+ // The LogicalPoint represents a vector rather than a point within a
+ // rectangular coordinate space, so we use a null containerSize when
+ // converting logical to physical.
+ const nsSize nullContainerSize;
+ MovePositionBy(
+ aTranslation.GetPhysicalPoint(aWritingMode, nullContainerSize));
+ }
+
+ /**
+ * Return frame's rect without relative positioning
+ */
+ nsRect GetNormalRect() const;
+ mozilla::LogicalRect GetLogicalNormalRect(
+ mozilla::WritingMode aWritingMode, const nsSize& aContainerSize) const {
+ return mozilla::LogicalRect(aWritingMode, GetNormalRect(), aContainerSize);
+ }
+
+ /**
+ * Returns frame's rect as required by the GetBoundingClientRect() DOM API.
+ */
+ nsRect GetBoundingClientRect();
+
+ /**
+ * Return frame's position without relative positioning.
+ * If aHasProperty is provided, returns whether the normal position
+ * was stored in a frame property.
+ */
+ inline nsPoint GetNormalPosition(bool* aHasProperty = nullptr) const;
+ inline mozilla::LogicalPoint GetLogicalNormalPosition(
+ mozilla::WritingMode aWritingMode, const nsSize& aContainerSize) const;
+
+ virtual nsPoint GetPositionOfChildIgnoringScrolling(const nsIFrame* aChild) {
+ return aChild->GetPosition();
+ }
+
+ nsPoint GetPositionIgnoringScrolling() const;
+
+#define NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(prop, type, dtor) \
+ static const mozilla::FramePropertyDescriptor<type>* prop() { \
+ /* Use of constexpr caused startup crashes with MSVC2015u1 PGO. */ \
+ static const auto descriptor = \
+ mozilla::FramePropertyDescriptor<type>::NewWithDestructor<dtor>(); \
+ return &descriptor; \
+ }
+
+// Don't use this unless you really know what you're doing!
+#define NS_DECLARE_FRAME_PROPERTY_WITH_FRAME_IN_DTOR(prop, type, dtor) \
+ static const mozilla::FramePropertyDescriptor<type>* prop() { \
+ /* Use of constexpr caused startup crashes with MSVC2015u1 PGO. */ \
+ static const auto descriptor = mozilla::FramePropertyDescriptor< \
+ type>::NewWithDestructorWithFrame<dtor>(); \
+ return &descriptor; \
+ }
+
+#define NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(prop, type) \
+ static const mozilla::FramePropertyDescriptor<type>* prop() { \
+ /* Use of constexpr caused startup crashes with MSVC2015u1 PGO. */ \
+ static const auto descriptor = \
+ mozilla::FramePropertyDescriptor<type>::NewWithoutDestructor(); \
+ return &descriptor; \
+ }
+
+#define NS_DECLARE_FRAME_PROPERTY_DELETABLE(prop, type) \
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(prop, type, DeleteValue)
+
+#define NS_DECLARE_FRAME_PROPERTY_RELEASABLE(prop, type) \
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(prop, type, ReleaseValue)
+
+#define NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(prop, type) \
+ static void AssertOnDestroyingProperty##prop(type*) { \
+ MOZ_ASSERT_UNREACHABLE( \
+ "Frame property " #prop \
+ " should never be destroyed by the FrameProperties class"); \
+ } \
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(prop, type, \
+ AssertOnDestroyingProperty##prop)
+
+#define NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(prop, type) \
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(prop, mozilla::SmallValueHolder<type>)
+
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(IBSplitSibling, nsContainerFrame)
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(IBSplitPrevSibling, nsContainerFrame)
+
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(NormalPositionProperty, nsPoint)
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(ComputedOffsetProperty, nsMargin)
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(OutlineInnerRectProperty, nsRect)
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(PreEffectsBBoxProperty, nsRect)
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(PreTransformOverflowAreasProperty,
+ mozilla::OverflowAreas)
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(OverflowAreasProperty,
+ mozilla::OverflowAreas)
+
+ // The initial overflow area passed to FinishAndStoreOverflow. This is only
+ // set on frames that Preserve3D() or HasPerspective() or IsTransformed(), and
+ // when at least one of the overflow areas differs from the frame bound rect.
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(InitialOverflowProperty,
+ mozilla::OverflowAreas)
+
+#ifdef DEBUG
+ // InitialOverflowPropertyDebug is added to the frame to indicate that either
+ // the InitialOverflowProperty has been stored or the InitialOverflowProperty
+ // has been suppressed due to being set to the default value (frame bounds)
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(DebugInitialOverflowPropertyApplied,
+ bool)
+#endif
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(UsedMarginProperty, nsMargin)
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(UsedPaddingProperty, nsMargin)
+
+ // This tracks the start and end page value for a frame.
+ //
+ // https://www.w3.org/TR/css-page-3/#using-named-pages
+ //
+ // This is only tracked during paginated frame construction.
+ // This is used to implement fragmentation based on CSS page names. During
+ // frame construction, we insert page breaks when we begin a new page box and
+ // the previous page box had a different name.
+ struct PageValues {
+ // A value of null indicates that the value is equal to what auto resolves
+ // to for this frame.
+ RefPtr<const nsAtom> mStartPageValue = nullptr;
+ RefPtr<const nsAtom> mEndPageValue = nullptr;
+ };
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(PageValuesProperty, PageValues)
+
+ const nsAtom* GetStartPageValue() const {
+ if (const PageValues* const values =
+ FirstInFlow()->GetProperty(PageValuesProperty())) {
+ return values->mStartPageValue;
+ }
+ return nullptr;
+ }
+
+ const nsAtom* GetEndPageValue() const {
+ if (const PageValues* const values =
+ FirstInFlow()->GetProperty(PageValuesProperty())) {
+ return values->mEndPageValue;
+ }
+ return nullptr;
+ }
+
+ // Returns the page name based on style information for this frame, or null
+ // if the value is auto.
+ const nsAtom* GetStylePageName() const {
+ const mozilla::StylePageName& pageName = StylePage()->mPage;
+ if (pageName.IsPageName()) {
+ return pageName.AsPageName().AsAtom();
+ }
+ MOZ_ASSERT(pageName.IsAuto(), "Impossible page name");
+ return nullptr;
+ }
+
+ private:
+ // The value that the CSS page-name "auto" keyword resolves to for children
+ // of this frame.
+ //
+ // A missing value for this property indicates that the auto value is the
+ // empty string, which is the default if no ancestors of a frame specify a
+ // page name. This avoids ever storing this property if the document doesn't
+ // use named pages.
+ //
+ // https://www.w3.org/TR/css-page-3/#using-named-pages
+ //
+ // Ideally this would be a const atom, but that isn't possible with the
+ // Release() call. This isn't too bad, since it's hidden behind constness-
+ // preserving getter/setter.
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(AutoPageValueProperty, nsAtom)
+
+ public:
+ // Get the value that the CSS page-name "auto" keyword resolves to for
+ // children of this frame.
+ // This is needed when propagating page-name values up the frame tree.
+ const nsAtom* GetAutoPageValue() const {
+ if (const nsAtom* const atom = GetProperty(AutoPageValueProperty())) {
+ return atom;
+ }
+ return nsGkAtoms::_empty;
+ }
+ void SetAutoPageValue(const nsAtom* aAtom) {
+ MOZ_ASSERT(aAtom, "Atom must not be null");
+ nsAtom* const atom = const_cast<nsAtom*>(aAtom);
+ if (atom != nsGkAtoms::_empty) {
+ SetProperty(AutoPageValueProperty(), do_AddRef(atom).take());
+ }
+ }
+
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(LineBaselineOffset, nscoord)
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(InvalidationRect, nsRect)
+
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(RefusedAsyncAnimationProperty, bool)
+
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FragStretchBSizeProperty, nscoord)
+
+ // The block-axis margin-box size associated with eBClampMarginBoxMinSize.
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BClampMarginBoxMinSizeProperty, nscoord)
+
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(IBaselinePadProperty, nscoord)
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BBaselinePadProperty, nscoord)
+
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BidiDataProperty,
+ mozilla::FrameBidiData)
+
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(PlaceholderFrameProperty,
+ nsPlaceholderFrame)
+
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(OffsetPathCache, mozilla::gfx::Path)
+
+ mozilla::FrameBidiData GetBidiData() const {
+ bool exists;
+ mozilla::FrameBidiData bidiData = GetProperty(BidiDataProperty(), &exists);
+ if (!exists) {
+ bidiData.precedingControl = mozilla::kBidiLevelNone;
+ }
+ return bidiData;
+ }
+
+ mozilla::intl::BidiEmbeddingLevel GetBaseLevel() const {
+ return GetBidiData().baseLevel;
+ }
+
+ mozilla::intl::BidiEmbeddingLevel GetEmbeddingLevel() const {
+ return GetBidiData().embeddingLevel;
+ }
+
+ /**
+ * Return the distance between the border edge of the frame and the
+ * margin edge of the frame. Like GetRect(), returns the dimensions
+ * as of the most recent reflow.
+ *
+ * This doesn't include any margin collapsing that may have occurred.
+ * It also doesn't consider GetSkipSides()/GetLogicalSkipSides(), so
+ * may report nonzero values on sides that are actually skipped for
+ * this fragment.
+ *
+ * It also treats 'auto' margins as zero, and treats any margins that
+ * should have been turned into 'auto' because of overconstraint as
+ * having their original values.
+ */
+ virtual nsMargin GetUsedMargin() const;
+ virtual mozilla::LogicalMargin GetLogicalUsedMargin(
+ mozilla::WritingMode aWritingMode) const {
+ return mozilla::LogicalMargin(aWritingMode, GetUsedMargin());
+ }
+
+ /**
+ * Return the distance between the border edge of the frame (which is
+ * its rect) and the padding edge of the frame. Like GetRect(), returns
+ * the dimensions as of the most recent reflow.
+ *
+ * This doesn't consider GetSkipSides()/GetLogicalSkipSides(), so
+ * may report nonzero values on sides that are actually skipped for
+ * this fragment.
+ *
+ * Note that this differs from StyleBorder()->GetComputedBorder() in
+ * that this describes a region of the frame's box, and
+ * StyleBorder()->GetComputedBorder() describes a border. They differ
+ * for tables (particularly border-collapse tables) and themed
+ * elements.
+ */
+ virtual nsMargin GetUsedBorder() const;
+ virtual mozilla::LogicalMargin GetLogicalUsedBorder(
+ mozilla::WritingMode aWritingMode) const {
+ return mozilla::LogicalMargin(aWritingMode, GetUsedBorder());
+ }
+
+ /**
+ * Return the distance between the padding edge of the frame and the
+ * content edge of the frame. Like GetRect(), returns the dimensions
+ * as of the most recent reflow.
+ *
+ * This doesn't consider GetSkipSides()/GetLogicalSkipSides(), so
+ * may report nonzero values on sides that are actually skipped for
+ * this fragment.
+ */
+ virtual nsMargin GetUsedPadding() const;
+ virtual mozilla::LogicalMargin GetLogicalUsedPadding(
+ mozilla::WritingMode aWritingMode) const {
+ return mozilla::LogicalMargin(aWritingMode, GetUsedPadding());
+ }
+
+ nsMargin GetUsedBorderAndPadding() const {
+ return GetUsedBorder() + GetUsedPadding();
+ }
+ mozilla::LogicalMargin GetLogicalUsedBorderAndPadding(
+ mozilla::WritingMode aWritingMode) const {
+ return mozilla::LogicalMargin(aWritingMode, GetUsedBorderAndPadding());
+ }
+
+ /**
+ * The area to paint box-shadows around. The default is the border rect.
+ * (nsFieldSetFrame overrides this).
+ */
+ virtual nsRect VisualBorderRectRelativeToSelf() const {
+ return nsRect(0, 0, mRect.Width(), mRect.Height());
+ }
+
+ /**
+ * Get the size, in app units, of the border radii. It returns FALSE iff all
+ * returned radii == 0 (so no border radii), TRUE otherwise.
+ * For the aRadii indexes, use the enum HalfCorner constants in gfx/2d/Types.h
+ * If a side is skipped via aSkipSides, its corners are forced to 0.
+ *
+ * All corner radii are then adjusted so they do not require more
+ * space than aBorderArea, according to the algorithm in css3-background.
+ *
+ * aFrameSize is used as the basis for percentage widths and heights.
+ * aBorderArea is used for the adjustment of radii that might be too
+ * large.
+ *
+ * Return whether any radii are nonzero.
+ */
+ static bool ComputeBorderRadii(const mozilla::BorderRadius&,
+ const nsSize& aFrameSize,
+ const nsSize& aBorderArea, Sides aSkipSides,
+ nscoord aRadii[8]);
+
+ /*
+ * Given a set of border radii for one box (e.g., border box), convert
+ * it to the equivalent set of radii for another box (e.g., in to
+ * padding box, out to outline box) by reducing radii or increasing
+ * nonzero radii as appropriate.
+ *
+ * Indices into aRadii are the enum HalfCorner constants in gfx/2d/Types.h
+ *
+ * Note that insetting the radii is lossy, since it can turn nonzero radii
+ * into zero, and re-adjusting does not inflate zero radii.
+ *
+ * Therefore, callers should always adjust directly from the original value
+ * coming from style.
+ */
+ static void AdjustBorderRadii(nscoord aRadii[8], const nsMargin& aOffsets);
+
+ /**
+ * Fill in border radii for this frame. Return whether any are nonzero.
+ * Indices into aRadii are the enum HalfCorner constants in gfx/2d/Types.h
+ * aSkipSides is a union of SideBits::eLeft/Right/Top/Bottom bits that says
+ * which side(s) to skip.
+ *
+ * Note: GetMarginBoxBorderRadii() and GetShapeBoxBorderRadii() work only
+ * on frames that establish block formatting contexts since they don't
+ * participate in margin-collapsing.
+ */
+ virtual bool GetBorderRadii(const nsSize& aFrameSize,
+ const nsSize& aBorderArea, Sides aSkipSides,
+ nscoord aRadii[8]) const;
+ bool GetBorderRadii(nscoord aRadii[8]) const;
+ bool GetMarginBoxBorderRadii(nscoord aRadii[8]) const;
+ bool GetPaddingBoxBorderRadii(nscoord aRadii[8]) const;
+ bool GetContentBoxBorderRadii(nscoord aRadii[8]) const;
+ bool GetBoxBorderRadii(nscoord aRadii[8], const nsMargin& aOffset) const;
+ bool GetShapeBoxBorderRadii(nscoord aRadii[8]) const;
+
+ /**
+ * `GetNaturalBaselineBOffset`, but determines the baseline sharing group
+ * through `GetDefaultBaselineSharingGroup` (If not specified), assuming line
+ * layout context, and never fails, returning a synthesized baseline through
+ * `SynthesizeFallbackBaseline`. Unlike `GetNaturalBaselineBOffset`, Result is
+ * always relative to the block start of the frame.
+ */
+ nscoord GetLogicalBaseline(mozilla::WritingMode aWM) const;
+ /**
+ * Same as the above, but with baseline sharing group & export
+ * context specified.
+ */
+ nscoord GetLogicalBaseline(mozilla::WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const;
+
+ /**
+ * Return true if the frame has a first(last) inline-axis baseline per
+ * CSS Box Alignment. If so, the returned baseline is the distance from
+ * the relevant block-axis border-box edge (Start for
+ * BaselineSharingGroup::First, end for BaselineSharingGroup::Last), where
+ * a positive value points towards the content-box.
+ * Some frames can export different baselines depending if it's in a line
+ * layout context or any other context (e.g. Flex, grid).
+ * https://drafts.csswg.org/css-align-3/#baseline-export
+ * @note The returned value is only valid when reflow is not needed.
+ * @note You should only call this on frames with a WM that's parallel to aWM.
+ * @note We're approaching `nsLayoutUtils::Get(First|Last)LineBaseline` ==
+ * `GetNaturalBaselineBOffset(aWM, (First|Last), Other)`. Grid relies on
+ * baseline synthesis behaviour in `nsLayoutUtils` implementations (bug
+ * 1609403), which blocks its removal.
+ * @param aWM the writing-mode of the alignment context.
+ * @return the baseline offset, if one exists
+ */
+ virtual Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext aExportContext) const {
+ return Nothing{};
+ }
+
+ struct CaretBlockAxisMetrics {
+ nscoord mOffset = 0;
+ nscoord mExtent = 0;
+ };
+ /**
+ * Get the offset (Block start of the caret) and extent of the caret in this
+ * frame. The returned offset is corrected for vertical & line-inverted
+ * writing modes.
+ */
+ CaretBlockAxisMetrics GetCaretBlockAxisMetrics(mozilla::WritingMode,
+ const nsFontMetrics&) const;
+ // Gets the page-name value to be used for the page that contains this frame
+ // during paginated reflow.
+ // This only inspects the first in-flow child of this frame, and if that
+ // is a container frame then its first in-flow child, until it reaches the
+ // deepest child of the tree.
+ // This will resolve auto values, including the case where no frame has a
+ // page-name set in which case it will return the empty atom. It will never
+ // return null.
+ // This is intended to be used either on the root frame to find the first
+ // page's page-name, or on a newly created continuation to find what the new
+ // page's page-name will be.
+ const nsAtom* ComputePageValue() const MOZ_NONNULL_RETURN;
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // The public visibility API.
+ ///////////////////////////////////////////////////////////////////////////////
+
+ /// @return true if we're tracking visibility for this frame.
+ bool TrackingVisibility() const {
+ return HasAnyStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
+ }
+
+ /// @return the visibility state of this frame. See the Visibility enum
+ /// for the possible return values and their meanings.
+ Visibility GetVisibility() const;
+
+ /// Update the visibility state of this frame synchronously.
+ /// XXX(seth): Avoid using this method; we should be relying on the refresh
+ /// driver for visibility updates. This method, which replaces
+ /// nsLayoutUtils::UpdateApproximateFrameVisibility(), exists purely as a
+ /// temporary measure to avoid changing behavior during the transition from
+ /// the old image visibility code.
+ void UpdateVisibilitySynchronously();
+
+ // A frame property which stores the visibility state of this frame. Right
+ // now that consists of an approximate visibility counter represented as a
+ // uint32_t. When the visibility of this frame is not being tracked, this
+ // property is absent.
+ NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(VisibilityStateProperty, uint32_t);
+
+ protected:
+ /**
+ * Get the position of the baseline on which the caret needs to be placed,
+ * relative to the top of the frame. This is mostly needed for frames
+ * which return a baseline from GetBaseline which is not useful for
+ * caret positioning.
+ */
+ virtual nscoord GetCaretBaseline() const {
+ return GetLogicalBaseline(GetWritingMode());
+ }
+
+ /**
+ * Subclasses can call this method to enable visibility tracking for this
+ * frame.
+ *
+ * If visibility tracking was previously disabled, this will schedule an
+ * update an asynchronous update of visibility.
+ */
+ void EnableVisibilityTracking();
+
+ /**
+ * Subclasses can call this method to disable visibility tracking for this
+ * frame.
+ *
+ * Note that if visibility tracking was previously enabled, disabling
+ * visibility tracking will cause a synchronous call to OnVisibilityChange().
+ */
+ void DisableVisibilityTracking();
+
+ /**
+ * Called when a frame transitions between visibility states (for example,
+ * from nonvisible to visible, or from visible to nonvisible).
+ *
+ * @param aNewVisibility The new visibility state.
+ * @param aNonvisibleAction A requested action if the frame has become
+ * nonvisible. If Nothing(), no action is
+ * requested. If DISCARD_IMAGES is specified, the
+ * frame is requested to ask any images it's
+ * associated with to discard their surfaces if
+ * possible.
+ *
+ * Subclasses which override this method should call their parent class's
+ * implementation.
+ */
+ virtual void OnVisibilityChange(
+ Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+
+ /**
+ * Synthesize a baseline for this element. The returned baseline is the
+ * distance from the relevant block-axis border-box edge (Start for
+ * BaselineSharingGroup::First, end for BaselineSharingGroup::Last), where a
+ * positive value points towards the content-box.
+ * @note This always returns a synthesized baseline, even if the element may
+ * have an actual baseline.
+ */
+ virtual nscoord SynthesizeFallbackBaseline(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const;
+
+ public:
+ /**
+ * Get the suitable baseline sharing group for this element, assuming line
+ * layout.
+ */
+ virtual BaselineSharingGroup GetDefaultBaselineSharingGroup() const {
+ return BaselineSharingGroup::First;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Internal implementation for the approximate frame visibility API.
+ ///////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * We track the approximate visibility of frames using a counter; if it's
+ * non-zero, then the frame is considered visible. Using a counter allows us
+ * to account for situations where the frame may be visible in more than one
+ * place (for example, via -moz-element), and it simplifies the
+ * implementation of our approximate visibility tracking algorithms.
+ *
+ * @param aNonvisibleAction A requested action if the frame has become
+ * nonvisible. If Nothing(), no action is
+ * requested. If DISCARD_IMAGES is specified, the
+ * frame is requested to ask any images it's
+ * associated with to discard their surfaces if
+ * possible.
+ */
+ void DecApproximateVisibleCount(
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+ void IncApproximateVisibleCount();
+
+ /**
+ * Get the specified child list.
+ *
+ * @param aListID identifies the requested child list.
+ * @return the child list. If the requested list is unsupported by this
+ * frame type, an empty list will be returned.
+ */
+ virtual const nsFrameList& GetChildList(ChildListID aListID) const;
+ const nsFrameList& PrincipalChildList() const {
+ return GetChildList(mozilla::FrameChildListID::Principal);
+ }
+
+ /**
+ * Sub-classes should override this methods if they want to append their own
+ * child lists into aLists.
+ */
+ virtual void GetChildLists(nsTArray<ChildList>* aLists) const;
+
+ /**
+ * Returns the child lists for this frame.
+ */
+ AutoTArray<ChildList, 4> ChildLists() const {
+ AutoTArray<ChildList, 4> childLists;
+ GetChildLists(&childLists);
+ return childLists;
+ }
+
+ /**
+ * Returns the child lists for this frame, including ones belong to a child
+ * document.
+ */
+ AutoTArray<ChildList, 4> CrossDocChildLists();
+
+ /**
+ * Child frames are linked together in a doubly-linked list
+ */
+ nsIFrame* GetNextSibling() const { return mNextSibling; }
+ void SetNextSibling(nsIFrame* aNextSibling) {
+ NS_ASSERTION(this != aNextSibling,
+ "Creating a circular frame list, this is very bad.");
+ if (mNextSibling && mNextSibling->GetPrevSibling() == this) {
+ mNextSibling->mPrevSibling = nullptr;
+ }
+ mNextSibling = aNextSibling;
+ if (mNextSibling) {
+ mNextSibling->mPrevSibling = this;
+ }
+ }
+
+ nsIFrame* GetPrevSibling() const { return mPrevSibling; }
+
+ /**
+ * Builds the display lists for the content represented by this frame
+ * and its descendants. The background+borders of this element must
+ * be added first, before any other content.
+ *
+ * This should only be called by methods in nsFrame. Instead of calling this
+ * directly, call either BuildDisplayListForStackingContext or
+ * BuildDisplayListForChild.
+ *
+ * See nsDisplayList.h for more information about display lists.
+ */
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {}
+ /**
+ * Displays the caret onto the given display list builder. The caret is
+ * painted on top of the rest of the display list items.
+ */
+ void DisplayCaret(nsDisplayListBuilder* aBuilder, nsDisplayList* aList);
+
+ /**
+ * Get the preferred caret color at the offset.
+ *
+ * @param aOffset is offset of the content.
+ */
+ virtual nscolor GetCaretColorAt(int32_t aOffset);
+
+ bool IsThemed(nsITheme::Transparency* aTransparencyState = nullptr) const {
+ return IsThemed(StyleDisplay(), aTransparencyState);
+ }
+ bool IsThemed(const nsStyleDisplay* aDisp,
+ nsITheme::Transparency* aTransparencyState = nullptr) const {
+ if (!aDisp->HasAppearance()) {
+ return false;
+ }
+ nsIFrame* mutable_this = const_cast<nsIFrame*>(this);
+ nsPresContext* pc = PresContext();
+ nsITheme* theme = pc->Theme();
+ if (!theme->ThemeSupportsWidget(pc, mutable_this,
+ aDisp->EffectiveAppearance())) {
+ return false;
+ }
+ if (aTransparencyState) {
+ *aTransparencyState = theme->GetWidgetTransparency(
+ mutable_this, aDisp->EffectiveAppearance());
+ }
+ return true;
+ }
+
+ /**
+ * Builds a display list for the content represented by this frame,
+ * treating this frame as the root of a stacking context.
+ * Optionally sets aCreatedContainerItem to true if we created a
+ * single container display item for the stacking context, and no
+ * other wrapping items are needed.
+ */
+ void BuildDisplayListForStackingContext(
+ nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
+ bool* aCreatedContainerItem = nullptr);
+
+ enum class DisplayChildFlag {
+ ForcePseudoStackingContext,
+ ForceStackingContext,
+ Inline,
+ };
+ using DisplayChildFlags = mozilla::EnumSet<DisplayChildFlag>;
+
+ /**
+ * Adjusts aDirtyRect for the child's offset, checks that the dirty rect
+ * actually intersects the child (or its descendants), calls BuildDisplayList
+ * on the child if necessary, and puts things in the right lists if the child
+ * is positioned.
+ *
+ * @param aFlags a set of of DisplayChildFlag values that are applicable for
+ * this operation.
+ */
+ void BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aChild,
+ const nsDisplayListSet& aLists,
+ DisplayChildFlags aFlags = {});
+
+ void BuildDisplayListForSimpleChild(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aChild,
+ const nsDisplayListSet& aLists);
+
+ /**
+ * Helper for BuildDisplayListForChild, to implement this special-case for
+ * grid (and flex) items from the spec:
+ * The painting order of grid items is exactly the same as inline blocks,
+ * except that [...], and 'z-index' values other than 'auto' create a
+ * stacking context even if 'position' is 'static' (behaving exactly as if
+ * 'position' were 'relative'). https://drafts.csswg.org/css-grid/#z-order
+ *
+ * Flex items also have the same special-case described in
+ * https://drafts.csswg.org/css-flexbox/#painting
+ */
+ static DisplayChildFlags DisplayFlagsForFlexOrGridItem() {
+ return DisplayChildFlags{DisplayChildFlag::ForcePseudoStackingContext};
+ }
+
+ bool RefusedAsyncAnimation() const {
+ return GetProperty(RefusedAsyncAnimationProperty());
+ }
+
+ /**
+ * Returns true if this frame is transformed (e.g. has CSS, SVG, or custom
+ * transforms) or if its parent is an SVG frame that has children-only
+ * transforms (e.g. an SVG viewBox attribute) or if its transform-style is
+ * preserve-3d or the frame has transform animations.
+ */
+ bool IsTransformed() const;
+
+ /**
+ * Same as IsTransformed, except that it doesn't take SVG transforms
+ * into account.
+ */
+ bool IsCSSTransformed() const;
+
+ /**
+ * True if this frame has any animation of transform in effect.
+ */
+ bool HasAnimationOfTransform() const;
+
+ /**
+ * True if this frame has any animation of opacity in effect.
+ *
+ * EffectSet is just an optimization.
+ */
+ bool HasAnimationOfOpacity(mozilla::EffectSet* = nullptr) const;
+
+ /**
+ * Returns true if the frame is translucent or the frame has opacity
+ * animations for the purposes of creating a stacking context.
+ *
+ * @param aStyleDisplay: This function needs style display struct.
+ *
+ * @param aStyleEffects: This function needs style effects struct.
+ *
+ * @param aEffectSet: This function may need to look up EffectSet property.
+ * If a caller already have one, pass it in can save property look up
+ * time; otherwise, just leave it as nullptr.
+ */
+ bool HasOpacity(const nsStyleDisplay* aStyleDisplay,
+ const nsStyleEffects* aStyleEffects,
+ mozilla::EffectSet* aEffectSet = nullptr) const {
+ return HasOpacityInternal(1.0f, aStyleDisplay, aStyleEffects, aEffectSet);
+ }
+ /**
+ * Returns true if the frame is translucent for display purposes.
+ *
+ * @param aStyleDisplay: This function needs style display struct.
+ *
+ * @param aStyleEffects: This function needs style effects struct.
+ *
+ * @param aEffectSet: This function may need to look up EffectSet property.
+ * If a caller already have one, pass it in can save property look up
+ * time; otherwise, just leave it as nullptr.
+ */
+ bool HasVisualOpacity(const nsStyleDisplay* aStyleDisplay,
+ const nsStyleEffects* aStyleEffects,
+ mozilla::EffectSet* aEffectSet = nullptr) const {
+ // Treat an opacity value of 0.99 and above as opaque. This is an
+ // optimization aimed at Web content which use opacity:0.99 as a hint for
+ // creating a stacking context only.
+ return HasOpacityInternal(0.99f, aStyleDisplay, aStyleEffects, aEffectSet);
+ }
+
+ /**
+ * Returns a matrix (in pixels) for the current frame. The matrix should be
+ * relative to the current frame's coordinate space.
+ *
+ * @param aFrame The frame to compute the transform for.
+ * @param aAppUnitsPerPixel The number of app units per graphics unit.
+ */
+ using ComputeTransformFunction = Matrix4x4 (*)(const nsIFrame*,
+ float aAppUnitsPerPixel);
+ /** Returns the transform getter of this frame, if any. */
+ virtual ComputeTransformFunction GetTransformGetter() const {
+ return nullptr;
+ }
+
+ /**
+ * Returns true if this frame is an SVG frame that has SVG transforms applied
+ * to it, or if its parent frame is an SVG frame that has children-only
+ * transforms (e.g. an SVG viewBox attribute).
+ * If aOwnTransforms is non-null and the frame has its own SVG transforms,
+ * aOwnTransforms will be set to these transforms. If aFromParentTransforms
+ * is non-null and the frame has an SVG parent with children-only transforms,
+ * then aFromParentTransforms will be set to these transforms.
+ */
+ virtual bool IsSVGTransformed(Matrix* aOwnTransforms = nullptr,
+ Matrix* aFromParentTransforms = nullptr) const;
+
+ /**
+ * Returns whether this frame will attempt to extend the 3d transforms of its
+ * children. This requires transform-style: preserve-3d, as well as no
+ * clipping or svg effects.
+ *
+ * @param aStyleDisplay: If the caller has this->StyleDisplay(), providing
+ * it here will improve performance.
+ *
+ * @param aStyleEffects: If the caller has this->StyleEffects(), providing
+ * it here will improve performance.
+ *
+ * @param aEffectSetForOpacity: This function may need to look up the
+ * EffectSet for opacity animations on this frame.
+ * If the caller already has looked up this EffectSet, it may pass it in to
+ * save an extra property lookup.
+ */
+ bool Extend3DContext(
+ const nsStyleDisplay* aStyleDisplay, const nsStyleEffects* aStyleEffects,
+ mozilla::EffectSet* aEffectSetForOpacity = nullptr) const;
+ bool Extend3DContext(
+ mozilla::EffectSet* aEffectSetForOpacity = nullptr) const {
+ return Extend3DContext(StyleDisplay(), StyleEffects(),
+ aEffectSetForOpacity);
+ }
+
+ /**
+ * Returns whether this frame has a parent that Extend3DContext() and has
+ * its own transform (or hidden backface) to be combined with the parent's
+ * transform.
+ */
+ bool Combines3DTransformWithAncestors() const;
+
+ /**
+ * Returns whether this frame has a hidden backface and has a parent that
+ * Extend3DContext(). This is useful because in some cases the hidden
+ * backface can safely be ignored if it could not be visible anyway.
+ *
+ */
+ bool In3DContextAndBackfaceIsHidden() const;
+
+ bool IsPreserve3DLeaf(const nsStyleDisplay* aStyleDisplay,
+ mozilla::EffectSet* aEffectSet = nullptr) const {
+ return Combines3DTransformWithAncestors() &&
+ !Extend3DContext(aStyleDisplay, StyleEffects(), aEffectSet);
+ }
+ bool IsPreserve3DLeaf(mozilla::EffectSet* aEffectSet = nullptr) const {
+ return IsPreserve3DLeaf(StyleDisplay(), aEffectSet);
+ }
+
+ bool HasPerspective() const;
+
+ bool ChildrenHavePerspective(const nsStyleDisplay* aStyleDisplay) const;
+ bool ChildrenHavePerspective() const {
+ return ChildrenHavePerspective(StyleDisplay());
+ }
+
+ /**
+ * Includes the overflow area of all descendants that participate in the
+ * current 3d context into aOverflowAreas.
+ */
+ void ComputePreserve3DChildrenOverflow(
+ mozilla::OverflowAreas& aOverflowAreas);
+
+ void RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame);
+
+ /**
+ * Returns whether z-index applies to this frame.
+ */
+ bool ZIndexApplies() const;
+
+ /**
+ * Returns the computed z-index for this frame, returning Nothing() for
+ * z-index: auto, and for frames that don't support z-index.
+ */
+ Maybe<int32_t> ZIndex() const;
+
+ /**
+ * Returns whether this frame is the anchor of some ancestor scroll frame. As
+ * this frame is moved, the scroll frame will apply adjustments to keep this
+ * scroll frame in the same relative position.
+ *
+ * aOutContainer will optionally be set to the scroll anchor container for
+ * this frame if this frame is an anchor.
+ */
+ bool IsScrollAnchor(
+ mozilla::layout::ScrollAnchorContainer** aOutContainer = nullptr);
+
+ /**
+ * Returns whether this frame is the anchor of some ancestor scroll frame, or
+ * has a descendant which is the scroll anchor.
+ */
+ bool IsInScrollAnchorChain() const;
+ void SetInScrollAnchorChain(bool aInChain);
+
+ /**
+ * Returns the number of ancestors between this and the root of our frame tree
+ */
+ uint32_t GetDepthInFrameTree() const;
+
+ /**
+ * Event handling of GUI events.
+ *
+ * @param aEvent event structure describing the type of event and rge widget
+ * where the event originated. The |point| member of this is in the coordinate
+ * system of the view returned by GetOffsetFromView.
+ *
+ * @param aEventStatus a return value indicating whether the event was
+ * handled and whether default processing should be done
+ *
+ * XXX From a frame's perspective it's unclear what the effect of the event
+ * status is. Does it cause the event to continue propagating through the
+ * frame hierarchy or is it just returned to the widgets?
+ *
+ * @see WidgetGUIEvent
+ * @see nsEventStatus
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * Search for selectable content at point and attempt to select
+ * based on the start and end selection behaviours.
+ *
+ * @param aPresContext Presentation context
+ * @param aPoint Point at which selection will occur. Coordinates
+ * should be relative to this frame.
+ * @param aBeginAmountType, aEndAmountType Selection behavior, see
+ * nsIFrame for definitions.
+ * @param aSelectFlags Selection flags defined in nsIFrame.h.
+ * @return success or failure at finding suitable content to select.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ SelectByTypeAtPoint(nsPresContext* aPresContext, const nsPoint& aPoint,
+ nsSelectionAmount aBeginAmountType,
+ nsSelectionAmount aEndAmountType, uint32_t aSelectFlags);
+
+ MOZ_CAN_RUN_SCRIPT nsresult PeekBackwardAndForward(
+ nsSelectionAmount aAmountBack, nsSelectionAmount aAmountForward,
+ int32_t aStartPos, bool aJumpLines, uint32_t aSelectFlags);
+
+ enum { SELECT_ACCUMULATE = 0x01 };
+
+ protected:
+ // Fire DOM event. If no aContent argument use frame's mContent.
+ void FireDOMEvent(const nsAString& aDOMEventName,
+ nsIContent* aContent = nullptr);
+
+ // Selection Methods
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
+ HandlePress(nsPresContext* aPresContext, mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * MoveCaretToEventPoint() moves caret at the point of aMouseEvent.
+ *
+ * @param aPresContext Must not be nullptr.
+ * @param aMouseEvent Must not be nullptr, the message must be
+ * eMouseDown and its button must be primary or
+ * middle button.
+ * @param aEventStatus [out] Must not be nullptr. This method ignores
+ * its initial value, but callees may refer it.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult MoveCaretToEventPoint(
+ nsPresContext* aPresContext, mozilla::WidgetMouseEvent* aMouseEvent,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * Check whether aSecondaryButtonMouseEvent should or should not cause moving
+ * caret at event point. This is designed only for the secondary mouse button
+ * event (i.e., right button event in general).
+ *
+ * @param aFrameSelection The nsFrameSelection which should handle the
+ * caret move with.
+ * @param aSecondaryButtonEvent Must be the button value is
+ * MouseButton::eSecondary.
+ * @param aContentAtEventPoint The content node at the event point.
+ * @param aOffsetAtEventPoint The offset in aContentAtEventPoint where
+ * aSecondaryButtonEvent clicked.
+ */
+ [[nodiscard]] bool MovingCaretToEventPointAllowedIfSecondaryButtonEvent(
+ const nsFrameSelection& aFrameSelection,
+ mozilla::WidgetMouseEvent& aSecondaryButtonEvent,
+ const nsIContent& aContentAtEventPoint,
+ int32_t aOffsetAtEventPoint) const;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD HandleMultiplePress(
+ nsPresContext* aPresContext, mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus, bool aControlHeld);
+
+ /**
+ * @param aPresContext must be non-nullptr.
+ * @param aEvent must be non-nullptr.
+ * @param aEventStatus must be non-nullptr.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ NS_IMETHOD HandleDrag(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
+ HandleRelease(nsPresContext* aPresContext, mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus);
+
+ // Test if we are selecting a table object:
+ // Most table/cell selection requires that Ctrl (Cmd on Mac) key is down
+ // during a mouse click or drag. Exception is using Shift+click when
+ // already in "table/cell selection mode" to extend a block selection
+ // Get the parent content node and offset of the frame
+ // of the enclosing cell or table (if not inside a cell)
+ // aTarget tells us what table element to select (currently only cell and
+ // table supported) (enums for this are defined in nsIFrame.h)
+ nsresult GetDataForTableSelection(const nsFrameSelection* aFrameSelection,
+ mozilla::PresShell* aPresShell,
+ mozilla::WidgetMouseEvent* aMouseEvent,
+ nsIContent** aParentContent,
+ int32_t* aContentOffset,
+ mozilla::TableSelectionMode* aTarget);
+
+ /**
+ * @return see nsISelectionController.idl's `getDisplaySelection`.
+ */
+ int16_t DetermineDisplaySelection();
+
+ public:
+ virtual nsresult GetContentForEvent(const mozilla::WidgetEvent* aEvent,
+ nsIContent** aContent);
+
+ // This structure keeps track of the content node and offsets associated with
+ // a point; there is a primary and a secondary offset associated with any
+ // point. The primary and secondary offsets differ when the point is over a
+ // non-text object. The primary offset is the expected position of the
+ // cursor calculated from a point; the secondary offset, when it is different,
+ // indicates that the point is in the boundaries of some selectable object.
+ // Note that the primary offset can be after the secondary offset; for places
+ // that need the beginning and end of the object, the StartOffset and
+ // EndOffset helpers can be used.
+ struct MOZ_STACK_CLASS ContentOffsets {
+ ContentOffsets() = default;
+ bool IsNull() { return !content; }
+ // Helpers for places that need the ends of the offsets and expect them in
+ // numerical order, as opposed to wanting the primary and secondary offsets
+ int32_t StartOffset() { return std::min(offset, secondaryOffset); }
+ int32_t EndOffset() { return std::max(offset, secondaryOffset); }
+
+ nsCOMPtr<nsIContent> content;
+ int32_t offset = 0;
+ int32_t secondaryOffset = 0;
+ // This value indicates whether the associated content is before or after
+ // the offset; the most visible use is to allow the caret to know which line
+ // to display on.
+ mozilla::CaretAssociationHint associate{0}; // Before
+ };
+ enum {
+ IGNORE_SELECTION_STYLE = 1 << 0,
+ // Treat visibility:hidden frames as non-selectable
+ SKIP_HIDDEN = 1 << 1,
+ // Do not return content in native anonymous subtree (if the frame is in a
+ // native anonymous subtree, the method may return content in same subtree).
+ IGNORE_NATIVE_ANONYMOUS_SUBTREE = 1 << 2,
+ };
+ /**
+ * This function calculates the content offsets for selection relative to
+ * a point. Note that this should generally only be callled on the event
+ * frame associated with an event because this function does not account
+ * for frame lists other than the primary one.
+ * @param aPoint point relative to this frame
+ */
+ ContentOffsets GetContentOffsetsFromPoint(const nsPoint& aPoint,
+ uint32_t aFlags = 0);
+
+ virtual ContentOffsets GetContentOffsetsFromPointExternal(
+ const nsPoint& aPoint, uint32_t aFlags = 0) {
+ return GetContentOffsetsFromPoint(aPoint, aFlags);
+ }
+
+ // Helper for GetContentAndOffsetsFromPoint; calculation of content offsets
+ // in this function assumes there is no child frame that can be targeted.
+ virtual ContentOffsets CalcContentOffsetsFromFramePoint(
+ const nsPoint& aPoint);
+
+ /**
+ * Ensure that `this` gets notifed when `aImage`s underlying image request
+ * loads or animates.
+ *
+ * This in practice is only needed for the canvas frame and table cell
+ * backgrounds, which are the only cases that should paint a background that
+ * isn't its own. The canvas paints the background from the root element or
+ * body, and the table cell paints the background for its row.
+ *
+ * For regular frames, this is done in DidSetComputedStyle.
+ *
+ * NOTE: It's unclear if we even actually _need_ this for the second case, as
+ * invalidating the row should invalidate all the cells. For the canvas case
+ * this is definitely needed as it paints the background from somewhere "down"
+ * in the frame tree.
+ *
+ * Returns whether the image was in fact associated with the frame.
+ */
+ [[nodiscard]] bool AssociateImage(const mozilla::StyleImage&);
+
+ /**
+ * This needs to be called if the above caller returned true, once the above
+ * caller doesn't care about getting notified anymore.
+ */
+ void DisassociateImage(const mozilla::StyleImage&);
+
+ mozilla::StyleImageRendering UsedImageRendering() const;
+ mozilla::StyleTouchAction UsedTouchAction() const;
+
+ enum class AllowCustomCursorImage {
+ No,
+ Yes,
+ };
+
+ /**
+ * This structure holds information about a cursor. AllowCustomCursorImage
+ * is `No`, then no cursor image should be loaded from the style specified on
+ * `mStyle`, or the frame's style.
+ *
+ * The `mStyle` member is used for `<area>` elements.
+ */
+ struct MOZ_STACK_CLASS Cursor {
+ mozilla::StyleCursorKind mCursor = mozilla::StyleCursorKind::Auto;
+ AllowCustomCursorImage mAllowCustomCursor = AllowCustomCursorImage::Yes;
+ RefPtr<mozilla::ComputedStyle> mStyle;
+ };
+
+ /**
+ * Get the cursor for a given frame.
+ */
+ virtual Cursor GetCursor(const nsPoint&);
+
+ /**
+ * Get a point (in the frame's coordinate space) given an offset into
+ * the content. This point should be on the baseline of text with
+ * the correct horizontal offset
+ */
+ virtual nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint);
+
+ /**
+ * Get a list of character rects in a given range.
+ * This is similar version of GetPointFromOffset.
+ */
+ virtual nsresult GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength,
+ nsTArray<nsRect>& aRects);
+
+ /**
+ * Get the child frame of this frame which contains the given
+ * content offset. outChildFrame may be this frame, or nullptr on return.
+ * outContentOffset returns the content offset relative to the start
+ * of the returned node. You can also pass a hint which tells the method
+ * to stick to the end of the first found frame or the beginning of the
+ * next in case the offset falls on a boundary.
+ */
+ virtual nsresult GetChildFrameContainingOffset(
+ int32_t inContentOffset,
+ bool inHint, // false stick left
+ int32_t* outFrameContentOffset, nsIFrame** outChildFrame);
+
+ /**
+ * Get the current frame-state value for this frame. aResult is
+ * filled in with the state bits.
+ */
+ nsFrameState GetStateBits() const { return mState; }
+
+ /**
+ * Update the current frame-state value for this frame.
+ */
+ void AddStateBits(nsFrameState aBits) { mState |= aBits; }
+ void RemoveStateBits(nsFrameState aBits) { mState &= ~aBits; }
+ void AddOrRemoveStateBits(nsFrameState aBits, bool aVal) {
+ aVal ? AddStateBits(aBits) : RemoveStateBits(aBits);
+ }
+
+ /**
+ * Checks if the current frame-state includes all of the listed bits
+ */
+ bool HasAllStateBits(nsFrameState aBits) const {
+ return (mState & aBits) == aBits;
+ }
+
+ /**
+ * Checks if the current frame-state includes any of the listed bits
+ */
+ bool HasAnyStateBits(nsFrameState aBits) const { return mState & aBits; }
+
+ private:
+ /**
+ * Called when this frame becomes primary for mContent.
+ */
+ void InitPrimaryFrame();
+ /**
+ * Called when the primary frame style changes.
+ *
+ * Kind of like DidSetComputedStyle, but the first computed style is set
+ * before SetPrimaryFrame, so we need this tweak.
+ */
+ void HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle);
+
+ public:
+ /**
+ * Return true if this frame is the primary frame for mContent.
+ */
+ bool IsPrimaryFrame() const { return mIsPrimaryFrame; }
+
+ void SetIsPrimaryFrame(bool aIsPrimary) {
+ mIsPrimaryFrame = aIsPrimary;
+ if (aIsPrimary) {
+ InitPrimaryFrame();
+ }
+ }
+
+ bool IsPrimaryFrameOfRootOrBodyElement() const;
+
+ /**
+ * @return true if this frame is used as a fieldset's rendered legend.
+ */
+ bool IsRenderedLegend() const;
+
+ /**
+ * This call is invoked on the primary frame for a character data content
+ * node, when it is changed in the content tree.
+ */
+ virtual nsresult CharacterDataChanged(const CharacterDataChangeInfo&);
+
+ /**
+ * This call is invoked when the value of a content objects's attribute
+ * is changed.
+ * The first frame that maps that content is asked to deal
+ * with the change by doing whatever is appropriate.
+ *
+ * @param aNameSpaceID the namespace of the attribute
+ * @param aAttribute the atom name of the attribute
+ * @param aModType Whether or not the attribute was added, changed, or
+ * removed. The constants are defined in MutationEvent.webidl.
+ */
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType);
+
+ /**
+ * When the element states of mContent change, this method is invoked on the
+ * primary frame of that element.
+ *
+ * @param aStates the changed states
+ */
+ virtual void ElementStateChanged(mozilla::dom::ElementState aStates);
+
+ /**
+ * Continuation member functions
+ */
+ virtual nsIFrame* GetPrevContinuation() const;
+ virtual void SetPrevContinuation(nsIFrame*);
+ virtual nsIFrame* GetNextContinuation() const;
+ virtual void SetNextContinuation(nsIFrame*);
+ virtual nsIFrame* FirstContinuation() const {
+ return const_cast<nsIFrame*>(this);
+ }
+ virtual nsIFrame* LastContinuation() const {
+ return const_cast<nsIFrame*>(this);
+ }
+
+ /**
+ * GetTailContinuation gets the last non-overflow-container continuation
+ * in the continuation chain, i.e. where the next sibling element
+ * should attach).
+ */
+ nsIFrame* GetTailContinuation();
+
+ /**
+ * Flow member functions
+ */
+ virtual nsIFrame* GetPrevInFlow() const;
+ virtual void SetPrevInFlow(nsIFrame*);
+
+ virtual nsIFrame* GetNextInFlow() const;
+ virtual void SetNextInFlow(nsIFrame*);
+
+ /**
+ * Return the first frame in our current flow.
+ */
+ virtual nsIFrame* FirstInFlow() const { return const_cast<nsIFrame*>(this); }
+
+ /**
+ * Return the last frame in our current flow.
+ */
+ virtual nsIFrame* LastInFlow() const { return const_cast<nsIFrame*>(this); }
+
+ /**
+ * Note: "width" in the names and comments on the following methods
+ * means inline-size, which could be height in vertical layout
+ */
+
+ /**
+ * Mark any stored intrinsic width information as dirty (requiring
+ * re-calculation). Note that this should generally not be called
+ * directly; PresShell::FrameNeedsReflow() will call it instead.
+ */
+ virtual void MarkIntrinsicISizesDirty();
+
+ public:
+ /**
+ * Make this frame and all descendants dirty (if not already).
+ * Exceptions: TableColGroupFrame children.
+ */
+ void MarkSubtreeDirty();
+
+ /**
+ * Get the min-content intrinsic inline size of the frame. This must be
+ * less than or equal to the max-content intrinsic inline size.
+ *
+ * This is *not* affected by the CSS 'min-width', 'width', and
+ * 'max-width' properties on this frame, but it is affected by the
+ * values of those properties on this frame's descendants. (It may be
+ * called during computation of the values of those properties, so it
+ * cannot depend on any values in the nsStylePosition for this frame.)
+ *
+ * The value returned should **NOT** include the space required for
+ * padding and border.
+ *
+ * Note that many frames will cache the result of this function call
+ * unless MarkIntrinsicISizesDirty is called.
+ *
+ * It is not acceptable for a frame to mark itself dirty when this
+ * method is called.
+ *
+ * This method must not return a negative value.
+ */
+ virtual nscoord GetMinISize(gfxContext* aRenderingContext);
+
+ /**
+ * Get the max-content intrinsic inline size of the frame. This must be
+ * greater than or equal to the min-content intrinsic inline size.
+ *
+ * Otherwise, all the comments for |GetMinISize| above apply.
+ */
+ virtual nscoord GetPrefISize(gfxContext* aRenderingContext);
+
+ /**
+ * |InlineIntrinsicISize| represents the intrinsic width information
+ * in inline layout. Code that determines the intrinsic width of a
+ * region of inline layout accumulates the result into this structure.
+ * This pattern is needed because we need to maintain state
+ * information about whitespace (for both collapsing and trimming).
+ */
+ struct InlineIntrinsicISizeData {
+ InlineIntrinsicISizeData()
+ : mLine(nullptr),
+ mLineContainer(nullptr),
+ mPrevLines(0),
+ mCurrentLine(0),
+ mTrailingWhitespace(0),
+ mSkipWhitespace(true) {}
+
+ // The line. This may be null if the inlines are not associated with
+ // a block or if we just don't know the line.
+ const nsLineList_iterator* mLine;
+
+ // The line container. Private, to ensure we always use SetLineContainer
+ // to update it.
+ //
+ // Note that nsContainerFrame::DoInlineIntrinsicISize will clear the
+ // |mLine| and |mLineContainer| fields when following a next-in-flow link,
+ // so we must not assume these can always be dereferenced.
+ private:
+ nsIFrame* mLineContainer;
+
+ // Setter and getter for the lineContainer field:
+ public:
+ void SetLineContainer(nsIFrame* aLineContainer) {
+ mLineContainer = aLineContainer;
+ }
+ nsIFrame* LineContainer() const { return mLineContainer; }
+
+ // The maximum intrinsic width for all previous lines.
+ nscoord mPrevLines;
+
+ // The maximum intrinsic width for the current line. At a line
+ // break (mandatory for preferred width; allowed for minimum width),
+ // the caller should call |Break()|.
+ nscoord mCurrentLine;
+
+ // This contains the width of the trimmable whitespace at the end of
+ // |mCurrentLine|; it is zero if there is no such whitespace.
+ nscoord mTrailingWhitespace;
+
+ // True if initial collapsable whitespace should be skipped. This
+ // should be true at the beginning of a block, after hard breaks
+ // and when the last text ended with whitespace.
+ bool mSkipWhitespace;
+
+ // Floats encountered in the lines.
+ class FloatInfo {
+ public:
+ FloatInfo(const nsIFrame* aFrame, nscoord aWidth)
+ : mFrame(aFrame), mWidth(aWidth) {}
+ const nsIFrame* Frame() const { return mFrame; }
+ nscoord Width() const { return mWidth; }
+
+ private:
+ const nsIFrame* mFrame;
+ nscoord mWidth;
+ };
+
+ nsTArray<FloatInfo> mFloats;
+ };
+
+ struct InlineMinISizeData : public InlineIntrinsicISizeData {
+ InlineMinISizeData() : mAtStartOfLine(true) {}
+
+ // The default implementation for nsIFrame::AddInlineMinISize.
+ void DefaultAddInlineMinISize(nsIFrame* aFrame, nscoord aISize,
+ bool aAllowBreak = true);
+
+ // We need to distinguish forced and optional breaks for cases where the
+ // current line total is negative. When it is, we need to ignore
+ // optional breaks to prevent min-width from ending up bigger than
+ // pref-width.
+ void ForceBreak();
+
+ // If the break here is actually taken, aHyphenWidth must be added to the
+ // width of the current line.
+ void OptionallyBreak(nscoord aHyphenWidth = 0);
+
+ // Whether we're currently at the start of the line. If we are, we
+ // can't break (for example, between the text-indent and the first
+ // word).
+ bool mAtStartOfLine;
+ };
+
+ struct InlinePrefISizeData : public InlineIntrinsicISizeData {
+ InlinePrefISizeData() : mLineIsEmpty(true) {}
+
+ /**
+ * Finish the current line and start a new line.
+ *
+ * @param aClearType controls whether isize of floats are considered
+ * and what floats are kept for the next line:
+ * * |None| skips handling floats, which means no floats are
+ * removed, and isizes of floats are not considered either.
+ * * |Both| takes floats into consideration when computing isize
+ * of the current line, and removes all floats after that.
+ * * |Left| and |Right| do the same as |Both| except that they only
+ * remove floats on the given side, and any floats on the other
+ * side that are prior to a float on the given side that has a
+ * 'clear' property that clears them.
+ */
+ void ForceBreak(mozilla::StyleClear aClearType = mozilla::StyleClear::Both);
+
+ // The default implementation for nsIFrame::AddInlinePrefISize.
+ void DefaultAddInlinePrefISize(nscoord aISize);
+
+ // True if the current line contains nothing other than placeholders.
+ bool mLineIsEmpty;
+ };
+
+ /**
+ * Add the intrinsic minimum width of a frame in a way suitable for
+ * use in inline layout to an |InlineIntrinsicISizeData| object that
+ * represents the intrinsic width information of all the previous
+ * frames in the inline layout region.
+ *
+ * All *allowed* breakpoints within the frame determine what counts as
+ * a line for the |InlineIntrinsicISizeData|. This means that
+ * |aData->mTrailingWhitespace| will always be zero (unlike for
+ * AddInlinePrefISize).
+ *
+ * All the comments for |GetMinISize| apply, except that this function
+ * is responsible for adding padding, border, and margin and for
+ * considering the effects of 'width', 'min-width', and 'max-width'.
+ *
+ * This may be called on any frame. Frames that do not participate in
+ * line breaking can inherit the default implementation on nsFrame,
+ * which calls |GetMinISize|.
+ */
+ virtual void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData);
+
+ /**
+ * Add the intrinsic preferred width of a frame in a way suitable for
+ * use in inline layout to an |InlineIntrinsicISizeData| object that
+ * represents the intrinsic width information of all the previous
+ * frames in the inline layout region.
+ *
+ * All the comments for |AddInlineMinISize| and |GetPrefISize| apply,
+ * except that this fills in an |InlineIntrinsicISizeData| structure
+ * based on using all *mandatory* breakpoints within the frame.
+ */
+ virtual void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData);
+
+ /**
+ * Intrinsic size of a frame in a single axis.
+ *
+ * This can represent either isize or bsize.
+ */
+ struct IntrinsicSizeOffsetData {
+ nscoord padding = 0;
+ nscoord border = 0;
+ nscoord margin = 0;
+ nscoord BorderPadding() const { return border + padding; };
+ };
+
+ /**
+ * Return the isize components of padding, border, and margin
+ * that contribute to the intrinsic width that applies to the parent.
+ * @param aPercentageBasis the percentage basis to use for padding/margin -
+ * i.e. the Containing Block's inline-size
+ */
+ virtual IntrinsicSizeOffsetData IntrinsicISizeOffsets(
+ nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE);
+
+ /**
+ * Return the bsize components of padding, border, and margin
+ * that contribute to the intrinsic width that applies to the parent.
+ * @param aPercentageBasis the percentage basis to use for padding/margin -
+ * i.e. the Containing Block's inline-size
+ */
+ IntrinsicSizeOffsetData IntrinsicBSizeOffsets(
+ nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE);
+
+ virtual mozilla::IntrinsicSize GetIntrinsicSize();
+
+ /**
+ * Get the preferred aspect ratio of this frame, or a default-constructed
+ * AspectRatio if it has none.
+ *
+ * https://drafts.csswg.org/css-sizing-4/#preferred-aspect-ratio
+ */
+ mozilla::AspectRatio GetAspectRatio() const;
+
+ /**
+ * Get the intrinsic aspect ratio of this frame, or a default-constructed
+ * AspectRatio if it has no intrinsic ratio.
+ *
+ * The intrinsic ratio is the ratio of the width/height of a box with an
+ * intrinsic size or the intrinsic aspect ratio of a scalable vector image
+ * without an intrinsic size. A frame class implementing a replaced element
+ * should override this method if it has a intrinsic ratio.
+ */
+ virtual mozilla::AspectRatio GetIntrinsicRatio() const;
+
+ /**
+ * Compute the size that a frame will occupy. Called while
+ * constructing the ReflowInput to be used to Reflow the frame,
+ * in order to fill its mComputedWidth and mComputedHeight member
+ * variables.
+ *
+ * Note that the reason that border and padding need to be passed
+ * separately is so that the 'box-sizing' property can be handled.
+ * Thus aMargin includes absolute positioning offsets as well.
+ *
+ * @param aWM The writing mode to use for the returned size (need not match
+ * this frame's writing mode). This is also the writing mode of
+ * the passed-in LogicalSize parameters.
+ * @param aCBSize The size of the element's containing block. (Well,
+ * the BSize() component isn't really.)
+ * @param aAvailableISize The available inline-size for 'auto' inline-size.
+ * This is usually the same as aCBSize.ISize(),
+ * but differs in cases such as block
+ * formatting context roots next to floats, or
+ * in some cases of float reflow in quirks
+ * mode.
+ * @param aMargin The sum of the inline / block margins ***AND***
+ * absolute positioning offsets (inset-block and
+ * inset-inline) of the frame, including actual values
+ * resulting from percentages and from the
+ * "hypothetical box" for absolute positioning, but
+ * not including actual values resulting from 'auto'
+ * margins or ignored 'auto' values in absolute
+ * positioning.
+ * @param aBorderPadding The sum of the frame's inline / block border-widths
+ * and padding (including actual values resulting from
+ * percentage padding values).
+ * @param aSizeOverride Optional override values for size properties, which
+ * this function will use internally instead of the
+ * actual property values.
+ * @param aFlags Flags to further customize behavior (definitions in
+ * LayoutConstants.h).
+ *
+ * The return value includes the computed LogicalSize and AspectRatioUsage
+ * which indicates whether the inline/block size is affected by aspect-ratio
+ * or not. The BSize() of the returned LogicalSize may be
+ * NS_UNCONSTRAINEDSIZE, but the ISize() must not be. We need AspectRatioUsage
+ * during reflow because the final size may be affected by the content size
+ * after applying aspect-ratio.
+ * https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
+ *
+ */
+ enum class AspectRatioUsage : uint8_t {
+ None,
+ ToComputeISize,
+ ToComputeBSize,
+ };
+ struct SizeComputationResult {
+ mozilla::LogicalSize mLogicalSize;
+ AspectRatioUsage mAspectRatioUsage = AspectRatioUsage::None;
+ };
+ virtual SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags);
+
+ protected:
+ /**
+ * A helper, used by |nsIFrame::ComputeSize| (for frames that need to
+ * override only this part of ComputeSize), that computes the size
+ * that should be returned when inline-size, block-size, and
+ * [min|max]-[inline-size|block-size] are all 'auto' or equivalent.
+ *
+ * In general, frames that can accept any computed inline-size/block-size
+ * should override only ComputeAutoSize, and frames that cannot do so need to
+ * override ComputeSize to enforce their inline-size/block-size invariants.
+ *
+ * Implementations may optimize by returning a garbage inline-size if
+ * StylePosition()->ISize() is not 'auto' (or inline-size override in
+ * aSizeOverrides is not 'auto' if provided), and likewise for BSize(), since
+ * in such cases the result is guaranteed to be unused.
+ *
+ * Most of the frame are not expected to check the aSizeOverrides parameter
+ * apart from checking the inline size override for 'auto' if they want to
+ * optimize and return garbage inline-size.
+ */
+ virtual mozilla::LogicalSize ComputeAutoSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags);
+
+ /**
+ * Utility function for ComputeAutoSize implementations. Return
+ * max(GetMinISize(), min(aISizeInCB, GetPrefISize()))
+ */
+ nscoord ShrinkISizeToFit(gfxContext* aRenderingContext, nscoord aISizeInCB,
+ mozilla::ComputeSizeFlags aFlags);
+
+ public:
+ /**
+ * Compute a tight bounding rectangle for the frame. This is a rectangle
+ * that encloses the pixels that are actually drawn. We're allowed to be
+ * conservative and currently we don't try very hard. The rectangle is
+ * in appunits and relative to the origin of this frame.
+ *
+ * This probably only needs to include frame bounds, glyph bounds, and
+ * text decorations, but today it sometimes includes other things that
+ * contribute to ink overflow.
+ *
+ * @param aDrawTarget a draw target that can be used if we need
+ * to do measurement
+ */
+ virtual nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const;
+
+ /**
+ * This function is similar to GetPrefISize and ComputeTightBounds: it
+ * computes the left and right coordinates of a preferred tight bounding
+ * rectangle for the frame. This is a rectangle that would enclose the pixels
+ * that are drawn if we lay out the element without taking any optional line
+ * breaks. The rectangle is in appunits and relative to the origin of this
+ * frame. Currently, this function is only implemented for nsBlockFrame and
+ * nsTextFrame and is used to determine intrinsic widths of MathML token
+ * elements.
+
+ * @param aContext a rendering context that can be used if we need
+ * to do measurement
+ * @param aX computed left coordinate of the tight bounding rectangle
+ * @param aXMost computed intrinsic width of the tight bounding rectangle
+ *
+ */
+ virtual nsresult GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
+ nscoord* aXMost);
+
+ /**
+ * The frame is given an available size and asked for its desired
+ * size. This is the frame's opportunity to reflow its children.
+ *
+ * If the frame has the NS_FRAME_IS_DIRTY bit set then it is
+ * responsible for completely reflowing itself and all of its
+ * descendants.
+ *
+ * Otherwise, if the frame has the NS_FRAME_HAS_DIRTY_CHILDREN bit
+ * set, then it is responsible for reflowing at least those
+ * children that have NS_FRAME_HAS_DIRTY_CHILDREN or NS_FRAME_IS_DIRTY
+ * set.
+ *
+ * If a difference in available size from the previous reflow causes
+ * the frame's size to change, it should reflow descendants as needed.
+ *
+ * Calculates the size of this frame after reflowing (calling Reflow on, and
+ * updating the size and position of) its children, as necessary. The
+ * calculated size is returned to the caller via the ReflowOutput
+ * outparam. (The caller is responsible for setting the actual size and
+ * position of this frame.)
+ *
+ * A frame's children must _all_ be reflowed if the frame is dirty (the
+ * NS_FRAME_IS_DIRTY bit is set on it). Otherwise, individual children
+ * must be reflowed if they are dirty or have the NS_FRAME_HAS_DIRTY_CHILDREN
+ * bit set on them. Otherwise, whether children need to be reflowed depends
+ * on the frame's type (it's up to individual Reflow methods), and on what
+ * has changed. For example, a change in the width of the frame may require
+ * all of its children to be reflowed (even those without dirty bits set on
+ * them), whereas a change in its height might not.
+ * (ReflowInput::ShouldReflowAllKids may be helpful in deciding whether
+ * to reflow all the children, but for some frame types it might result in
+ * over-reflow.)
+ *
+ * Note: if it's only the overflow rect(s) of a frame that need to be
+ * updated, then UpdateOverflow should be called instead of Reflow.
+ *
+ * @param aReflowOutput <i>out</i> parameter where you should return the
+ * desired size and ascent/descent info. You should include any
+ * space you want for border/padding in the desired size you return.
+ *
+ * It's okay to return a desired size that exceeds the avail
+ * size if that's the smallest you can be, i.e. it's your
+ * minimum size.
+ *
+ * For an incremental reflow you are responsible for invalidating
+ * any area within your frame that needs repainting (including
+ * borders). If your new desired size is different than your current
+ * size, then your parent frame is responsible for making sure that
+ * the difference between the two rects is repainted
+ *
+ * @param aReflowInput information about your reflow including the reason
+ * for the reflow and the available space in which to lay out. Each
+ * dimension of the available space can either be constrained or
+ * unconstrained (a value of NS_UNCONSTRAINEDSIZE).
+ *
+ * Note that the available space can be negative. In this case you
+ * still must return an accurate desired size. If you're a container
+ * you must <b>always</b> reflow at least one frame regardless of the
+ * available space
+ *
+ * @param aStatus a return value indicating whether the frame is complete
+ * and whether the next-in-flow is dirty and needs to be reflowed
+ */
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput, nsReflowStatus& aStatus);
+
+ // Option flags for ReflowChild(), FinishReflowChild(), and
+ // SyncFrameViewAfterReflow().
+ enum class ReflowChildFlags : uint32_t {
+ Default = 0,
+
+ // Don't position the frame's view. Set this if you don't want to
+ // automatically sync the frame and view.
+ NoMoveView = 1 << 0,
+
+ // Don't move the frame. Also implies NoMoveView.
+ NoMoveFrame = (1 << 1) | NoMoveView,
+
+ // Don't size the frame's view.
+ NoSizeView = 1 << 2,
+
+ // Only applies to ReflowChild; if true, don't delete the next-in-flow, even
+ // if the reflow is fully complete.
+ NoDeleteNextInFlowChild = 1 << 3,
+
+ // Only applies to FinishReflowChild. Tell it to call
+ // ApplyRelativePositioning.
+ ApplyRelativePositioning = 1 << 4,
+ };
+
+ /**
+ * Post-reflow hook. After a frame is reflowed this method will be called
+ * informing the frame that this reflow process is complete, and telling the
+ * frame the status returned by the Reflow member function.
+ *
+ * This call may be invoked many times, while NS_FRAME_IN_REFLOW is set,
+ * before it is finally called once with a NS_FRAME_REFLOW_COMPLETE value.
+ * When called with a NS_FRAME_REFLOW_COMPLETE value the NS_FRAME_IN_REFLOW
+ * bit in the frame state will be cleared.
+ *
+ * XXX This doesn't make sense. If the frame is reflowed but not complete,
+ * then the status should have IsIncomplete() equal to true.
+ * XXX Don't we want the semantics to dictate that we only call this once for
+ * a given reflow?
+ */
+ virtual void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput);
+
+ void FinishReflowWithAbsoluteFrames(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ bool aConstrainBSize = true);
+
+ /**
+ * Updates the overflow areas of the frame. This can be called if an
+ * overflow area of the frame's children has changed without reflowing.
+ * @return true if either of the overflow areas for this frame have changed.
+ */
+ bool UpdateOverflow();
+
+ /**
+ * Computes any overflow area created by the frame itself (outside of the
+ * frame bounds) and includes it into aOverflowAreas.
+ *
+ * Returns false if updating overflow isn't supported for this frame.
+ * If the frame requires a reflow instead, then it is responsible
+ * for scheduling one.
+ */
+ virtual bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas);
+
+ /**
+ * Computes any overflow area created by children of this frame and
+ * includes it into aOverflowAreas.
+ */
+ virtual void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas);
+
+ // Returns the applicable overflow-clip-margin values.
+ using PhysicalAxes = mozilla::PhysicalAxes;
+
+ nsSize OverflowClipMargin(PhysicalAxes aClipAxes) const;
+ // Returns the axes on which this frame should apply overflow clipping.
+ PhysicalAxes ShouldApplyOverflowClipping(const nsStyleDisplay* aDisp) const;
+
+ /**
+ * Helper method used by block reflow to identify runs of text so
+ * that proper word-breaking can be done.
+ *
+ * @return
+ * true if we can continue a "text run" through the frame. A
+ * text run is text that should be treated contiguously for line
+ * and word breaking.
+ */
+ virtual bool CanContinueTextRun() const;
+
+ /**
+ * Computes an approximation of the rendered text of the frame and its
+ * continuations. Returns nothing for non-text frames.
+ * The appended text will often not contain all the whitespace from source,
+ * depending on CSS white-space processing.
+ * if aEndOffset goes past end, use the text up to the string's end.
+ * Call this on the primary frame for a text node.
+ * aStartOffset and aEndOffset can be content offsets or offsets in the
+ * rendered text, depending on aOffsetType.
+ * Returns a string, as well as offsets identifying the start of the text
+ * within the rendered text for the whole node, and within the text content
+ * of the node.
+ */
+ struct RenderedText {
+ nsAutoString mString;
+ uint32_t mOffsetWithinNodeRenderedText;
+ int32_t mOffsetWithinNodeText;
+ RenderedText()
+ : mOffsetWithinNodeRenderedText(0), mOffsetWithinNodeText(0) {}
+ };
+ enum class TextOffsetType {
+ // Passed-in start and end offsets are within the content text.
+ OffsetsInContentText,
+ // Passed-in start and end offsets are within the rendered text.
+ OffsetsInRenderedText,
+ };
+ enum class TrailingWhitespace {
+ Trim,
+ // Spaces preceding a caret at the end of a line should not be trimmed
+ DontTrim,
+ };
+ virtual RenderedText GetRenderedText(
+ uint32_t aStartOffset = 0, uint32_t aEndOffset = UINT32_MAX,
+ TextOffsetType aOffsetType = TextOffsetType::OffsetsInContentText,
+ TrailingWhitespace aTrimTrailingWhitespace = TrailingWhitespace::Trim) {
+ return RenderedText();
+ }
+
+ /**
+ * Returns true if the frame contains any non-collapsed characters.
+ * This method is only available for text frames, and it will return false
+ * for all other frame types.
+ */
+ virtual bool HasAnyNoncollapsedCharacters() { return false; }
+
+ /**
+ * Returns true if events of the given type targeted at this frame
+ * should only be dispatched to the system group.
+ */
+ virtual bool OnlySystemGroupDispatch(mozilla::EventMessage aMessage) const {
+ return false;
+ }
+
+ //
+ // Accessor functions to an associated view object:
+ //
+ bool HasView() const { return !!(mState & NS_FRAME_HAS_VIEW); }
+
+ // Returns true iff this frame's computed block-size property is one of the
+ // intrinsic-sizing keywords.
+ bool HasIntrinsicKeywordForBSize() const {
+ const auto& bSize = StylePosition()->BSize(GetWritingMode());
+ return bSize.IsFitContent() || bSize.IsMinContent() ||
+ bSize.IsMaxContent() || bSize.IsFitContentFunction();
+ }
+ /**
+ * Helper method to create a view for a frame. Only used by a few sub-classes
+ * that need a view.
+ */
+ void CreateView();
+
+ protected:
+ virtual nsView* GetViewInternal() const {
+ MOZ_ASSERT_UNREACHABLE("method should have been overridden by subclass");
+ return nullptr;
+ }
+ virtual void SetViewInternal(nsView* aView) {
+ MOZ_ASSERT_UNREACHABLE("method should have been overridden by subclass");
+ }
+
+ public:
+ nsView* GetView() const {
+ if (MOZ_LIKELY(!HasView())) {
+ return nullptr;
+ }
+ nsView* view = GetViewInternal();
+ MOZ_ASSERT(view, "GetViewInternal() should agree with HasView()");
+ return view;
+ }
+ void SetView(nsView* aView);
+
+ /**
+ * Find the closest view (on |this| or an ancestor).
+ * If aOffset is non-null, it will be set to the offset of |this|
+ * from the returned view.
+ */
+ nsView* GetClosestView(nsPoint* aOffset = nullptr) const;
+
+ /**
+ * Find the closest ancestor (excluding |this| !) that has a view
+ */
+ nsIFrame* GetAncestorWithView() const;
+
+ /**
+ * Sets the view's attributes from the frame style.
+ * Call this for nsChangeHint_SyncFrameView style changes or when the view
+ * has just been created.
+ * @param aView the frame's view or use GetView() if nullptr is given
+ */
+ void SyncFrameViewProperties(nsView* aView = nullptr);
+
+ /**
+ * Get the offset between the coordinate systems of |this| and aOther.
+ * Adding the return value to a point in the coordinate system of |this|
+ * will transform the point to the coordinate system of aOther.
+ *
+ * aOther must be non-null.
+ *
+ * This function is fastest when aOther is an ancestor of |this|.
+ *
+ * This function _DOES NOT_ work across document boundaries.
+ * Use this function only when |this| and aOther are in the same document.
+ *
+ * NOTE: this actually returns the offset from aOther to |this|, but
+ * that offset is added to transform _coordinates_ from |this| to
+ * aOther.
+ */
+ nsPoint GetOffsetTo(const nsIFrame* aOther) const;
+
+ /**
+ * Just like GetOffsetTo, but treats all scrollframes as scrolled to
+ * their origin.
+ */
+ nsPoint GetOffsetToIgnoringScrolling(const nsIFrame* aOther) const;
+
+ /**
+ * Get the offset between the coordinate systems of |this| and aOther
+ * expressed in appunits per dev pixel of |this|' document. Adding the return
+ * value to a point that is relative to the origin of |this| will make the
+ * point relative to the origin of aOther but in the appunits per dev pixel
+ * ratio of |this|.
+ *
+ * aOther must be non-null.
+ *
+ * This function is fastest when aOther is an ancestor of |this|.
+ *
+ * This function works across document boundaries.
+ *
+ * Because this function may cross document boundaries that have different
+ * app units per dev pixel ratios it needs to be used very carefully.
+ *
+ * NOTE: this actually returns the offset from aOther to |this|, but
+ * that offset is added to transform _coordinates_ from |this| to
+ * aOther.
+ */
+ nsPoint GetOffsetToCrossDoc(const nsIFrame* aOther) const;
+
+ /**
+ * Like GetOffsetToCrossDoc, but the caller can specify which appunits
+ * to return the result in.
+ */
+ nsPoint GetOffsetToCrossDoc(const nsIFrame* aOther, const int32_t aAPD) const;
+
+ /**
+ * Get the rect of the frame relative to the top-left corner of the
+ * screen in CSS pixels.
+ * @return the CSS pixel rect of the frame relative to the top-left
+ * corner of the screen.
+ */
+ mozilla::CSSIntRect GetScreenRect() const;
+
+ /**
+ * Get the screen rect of the frame in app units.
+ * @return the app unit rect of the frame in screen coordinates.
+ */
+ nsRect GetScreenRectInAppUnits() const;
+
+ /**
+ * Returns the offset from this frame to the closest geometric parent that
+ * has a view. Also returns the containing view or null in case of error
+ */
+ void GetOffsetFromView(nsPoint& aOffset, nsView** aView) const;
+
+ /**
+ * Returns the nearest widget containing this frame. If this frame has a
+ * view and the view has a widget, then this frame's widget is
+ * returned, otherwise this frame's geometric parent is checked
+ * recursively upwards.
+ */
+ nsIWidget* GetNearestWidget() const;
+
+ /**
+ * Whether the frame is a subgrid right now.
+ */
+ bool IsSubgrid() const;
+
+ /**
+ * Same as GetNearestWidget() above but uses an outparam to return the offset
+ * of this frame to the returned widget expressed in appunits of |this| (the
+ * widget might be in a different document with a different zoom).
+ */
+ nsIWidget* GetNearestWidget(nsPoint& aOffset) const;
+
+ /**
+ * Whether the content for this frame is disabled, used for event handling.
+ */
+ bool IsContentDisabled() const;
+
+ enum class IncludeContentVisibility {
+ Auto,
+ Hidden,
+ };
+
+ constexpr static mozilla::EnumSet<IncludeContentVisibility>
+ IncludeAllContentVisibility() {
+ return {IncludeContentVisibility::Auto, IncludeContentVisibility::Hidden};
+ }
+
+ /**
+ * Returns true if this frame's `content-visibility: auto` element is
+ * considered relevant content.
+ */
+ bool IsContentRelevant() const;
+
+ /**
+ * Whether this frame hides its contents via the `content-visibility`
+ * property.
+ * @param aInclude specifies what kind of `content-visibility` to include.
+ */
+ bool HidesContent(const mozilla::EnumSet<IncludeContentVisibility>& =
+ IncludeAllContentVisibility()) const;
+
+ /**
+ * Whether this frame hides its contents via the `content-visibility`
+ * property, while doing layout. This might be true when `HidesContent()` is
+ * true in the case that hidden content is being forced to lay out by position
+ * or size queries from script.
+ */
+ bool HidesContentForLayout() const;
+
+ /**
+ * returns the closest ancestor with `content-visibility` property.
+ * @param aInclude specifies what kind of `content-visibility` to include.
+ */
+ nsIFrame* GetClosestContentVisibilityAncestor(
+ const mozilla::EnumSet<IncludeContentVisibility>& =
+ IncludeAllContentVisibility()) const;
+
+ /**
+ * Returns true if this frame is entirely hidden due the `content-visibility`
+ * property on an ancestor.
+ * @param aInclude specifies what kind of `content-visibility` to include.
+ */
+ bool IsHiddenByContentVisibilityOnAnyAncestor(
+ const mozilla::EnumSet<IncludeContentVisibility>& =
+ IncludeAllContentVisibility()) const;
+
+ /**
+ * Returns true is this frame is hidden by its first unskipped in flow
+ * ancestor due to `content-visibility`.
+ */
+ bool IsHiddenByContentVisibilityOfInFlowParentForLayout() const;
+
+ /**
+ * Returns true if this frame has a SelectionType::eNormal type selection in
+ * somewhere in its subtree of frames. This is used to determine content
+ * relevancy for `content-visibility: auto`.
+ */
+ bool HasSelectionInSubtree();
+
+ /**
+ * Update the whether or not this frame is considered relevant content for the
+ * purposes of `content-visibility: auto` according to the rules specified in
+ * https://drafts.csswg.org/css-contain-2/#relevant-to-the-user.
+ * Returns true if the over-all relevancy changed.
+ */
+ bool UpdateIsRelevantContent(const ContentRelevancy& aRelevancyToUpdate);
+
+ /**
+ * Get the "type" of the frame.
+ *
+ * @see mozilla::LayoutFrameType
+ */
+ mozilla::LayoutFrameType Type() const {
+ MOZ_ASSERT(uint8_t(mClass) < mozilla::ArrayLength(sLayoutFrameTypes));
+ return sLayoutFrameTypes[uint8_t(mClass)];
+ }
+
+ /**
+ * Get the type flags of the frame.
+ *
+ * @see mozilla::LayoutFrameType
+ */
+ ClassFlags GetClassFlags() const {
+ MOZ_ASSERT(uint8_t(mClass) < mozilla::ArrayLength(sLayoutFrameClassFlags));
+ return sLayoutFrameClassFlags[uint8_t(mClass)];
+ }
+
+ bool HasAnyClassFlag(ClassFlags aFlag) const {
+ return bool(GetClassFlags() & aFlag);
+ }
+
+ /**
+ * Is this a leaf frame? Frames that want the frame constructor to be able to
+ * construct kids for them should return false, all others should return true.
+ *
+ * Note that returning true here does not mean that the frame _can't_ have
+ * kids. It could still have kids created via nsIAnonymousContentCreator.
+ *
+ * Returning true indicates that "normal" (non-anonymous, CSS generated
+ * content, etc) children should not be constructed.
+ */
+ bool IsLeaf() const {
+ auto bits = GetClassFlags();
+ if (MOZ_UNLIKELY(bits & ClassFlags::LeafDynamic)) {
+ return IsLeafDynamic();
+ }
+ return bool(bits & ClassFlags::Leaf);
+ }
+ virtual bool IsLeafDynamic() const { return false; }
+
+#define CLASS_FLAG_METHOD(name_, flag_) \
+ bool name_() const { return HasAnyClassFlag(ClassFlags::flag_); }
+#define CLASS_FLAG_METHOD0(name_) CLASS_FLAG_METHOD(name_, name_)
+
+ CLASS_FLAG_METHOD(IsMathMLFrame, MathML);
+ CLASS_FLAG_METHOD(IsSVGFrame, SVG);
+ CLASS_FLAG_METHOD(IsSVGContainerFrame, SVGContainer);
+ CLASS_FLAG_METHOD(IsBidiInlineContainer, BidiInlineContainer);
+ CLASS_FLAG_METHOD(IsLineParticipant, LineParticipant);
+ CLASS_FLAG_METHOD(IsReplaced, Replaced);
+ CLASS_FLAG_METHOD(IsReplacedWithBlock, ReplacedContainsBlock);
+ CLASS_FLAG_METHOD(HasReplacedSizing, ReplacedSizing);
+ CLASS_FLAG_METHOD(IsTablePart, TablePart);
+ CLASS_FLAG_METHOD0(CanContainOverflowContainers)
+ CLASS_FLAG_METHOD0(SupportsCSSTransforms);
+ CLASS_FLAG_METHOD0(SupportsContainLayoutAndPaint)
+ CLASS_FLAG_METHOD0(SupportsAspectRatio)
+
+#undef CLASS_FLAG_METHOD
+#undef CLASS_FLAG_METHOD0
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunknown-pragmas"
+# pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare"
+#endif
+
+#define FRAME_TYPE(name_, first_class_, last_class_) \
+ bool Is##name_##Frame() const { \
+ return uint8_t(mClass) >= uint8_t(ClassID::first_class_##_id) && \
+ uint8_t(mClass) <= uint8_t(ClassID::last_class_##_id); \
+ }
+#include "mozilla/FrameTypeList.h"
+#undef FRAME_TYPE
+
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+ /**
+ * Returns a transformation matrix that converts points in this frame's
+ * coordinate space to points in some ancestor frame's coordinate space.
+ * The frame decides which ancestor it will use as a reference point.
+ * If this frame has no ancestor, aOutAncestor will be set to null.
+ *
+ * @param aViewportType specifies whether the starting point is layout
+ * or visual coordinates
+ * @param aStopAtAncestor don't look further than aStopAtAncestor. If null,
+ * all ancestors (including across documents) will be traversed.
+ * @param aOutAncestor [out] The ancestor frame the frame has chosen. If
+ * this frame has no ancestor, *aOutAncestor will be set to null. If
+ * this frame is not a root frame, then *aOutAncestor will be in the same
+ * document as this frame. If this frame IsTransformed(), then *aOutAncestor
+ * will be the parent frame (if not preserve-3d) or the nearest
+ * non-transformed ancestor (if preserve-3d).
+ * @return A Matrix4x4 that converts points in the coordinate space
+ * RelativeTo{this, aViewportType} into points in aOutAncestor's
+ * coordinate space.
+ */
+ enum {
+ IN_CSS_UNITS = 1 << 0,
+ STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT = 1 << 1
+ };
+ Matrix4x4Flagged GetTransformMatrix(mozilla::ViewportType aViewportType,
+ mozilla::RelativeTo aStopAtAncestor,
+ nsIFrame** aOutAncestor,
+ uint32_t aFlags = 0) const;
+
+ /**
+ * Return true if this frame's preferred size property or max size property
+ * contains a percentage value that should be resolved against zero when
+ * calculating its min-content contribution in the corresponding axis.
+ *
+ * This is a special case for webcompat required by CSS Sizing 3 §5.2.1c
+ * https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution,
+ * and applies only to some replaced elements and form control elements. See
+ * CSS Sizing 3 §5.2.2 for the list of elements this rule applies to.
+ * https://drafts.csswg.org/css-sizing-3/#min-content-zero
+ *
+ * Bug 1463700: some callers may not match the spec by resolving the entire
+ * preferred size property or max size property against zero.
+ */
+ bool IsPercentageResolvedAgainstZero(
+ const mozilla::StyleSize& aStyleSize,
+ const mozilla::StyleMaxSize& aStyleMaxSize) const;
+
+ // Type of preferred size/min size/max size.
+ enum class SizeProperty { Size, MinSize, MaxSize };
+ /**
+ * This is simliar to the above method but accepts LengthPercentage. Return
+ * true if the frame's preferred size property or max size property contains
+ * a percentage value that should be resolved against zero. For min size, it
+ * always returns true.
+ */
+ bool IsPercentageResolvedAgainstZero(const mozilla::LengthPercentage& aSize,
+ SizeProperty aProperty) const;
+
+ /**
+ * Returns true if the frame is a block wrapper.
+ */
+ bool IsBlockWrapper() const;
+
+ /**
+ * Returns true if the frame is an instance of nsBlockFrame or one of its
+ * subclasses.
+ */
+ bool IsBlockFrameOrSubclass() const;
+
+ /**
+ * Returns true if the frame is an instance of nsImageFrame or one of its
+ * subclasses.
+ */
+ bool IsImageFrameOrSubclass() const;
+
+ /**
+ * Get this frame's CSS containing block.
+ *
+ * The algorithm is defined in
+ * http://www.w3.org/TR/CSS2/visudet.html#containing-block-details.
+ *
+ * NOTE: This is guaranteed to return a non-null pointer when invoked on any
+ * frame other than the root frame.
+ *
+ * Requires SKIP_SCROLLED_FRAME to get behaviour matching the spec, otherwise
+ * it can return anonymous inner scrolled frames. Bug 1204044 is filed for
+ * investigating whether any of the callers actually require the default
+ * behaviour.
+ */
+ enum {
+ // If the containing block is an anonymous scrolled frame, then skip over
+ // this and return the outer scroll frame.
+ SKIP_SCROLLED_FRAME = 0x01
+ };
+ nsIFrame* GetContainingBlock(uint32_t aFlags,
+ const nsStyleDisplay* aStyleDisplay) const;
+ nsIFrame* GetContainingBlock(uint32_t aFlags = 0) const {
+ return GetContainingBlock(aFlags, StyleDisplay());
+ }
+
+ /**
+ * If this frame can be a block container, i.e. whether it can serve as a
+ * containing block for its descendants. See also GetNearestBlockContainer()
+ * and GetContainingBlock().
+ */
+ bool IsBlockContainer() const;
+
+ /**
+ * Is this frame a containing block for floating elements?
+ * Note that very few frames are, so default to false.
+ */
+ virtual bool IsFloatContainingBlock() const { return false; }
+
+ /**
+ * Marks all display items created by this frame as needing a repaint,
+ * and calls SchedulePaint() if requested and one is not already pending.
+ *
+ * This includes all display items created by this frame, including
+ * container types.
+ *
+ * @param aDisplayItemKey If specified, only issues an invalidate
+ * if this frame painted a display item of that type during the
+ * previous paint. SVG rendering observers are always notified.
+ * @param aRebuildDisplayItems If true, then adds this frame to the
+ * list of modified frames for display list building. Only pass false
+ * if you're sure that the relevant display items will be rebuilt
+ * already (possibly by an ancestor being in the modified list).
+ */
+ virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true);
+
+ /**
+ * Same as InvalidateFrame(), but only mark a fixed rect as needing
+ * repainting.
+ *
+ * @param aRect The rect to invalidate, relative to the TopLeft of the
+ * frame's border box.
+ * @param aDisplayItemKey If specified, only issues an invalidate
+ * if this frame painted a display item of that type during the
+ * previous paint. SVG rendering observers are always notified.
+ * @param aRebuildDisplayItems If true, then adds this frame to the
+ * list of modified frames for display list building. Only pass false
+ * if you're sure that the relevant display items will be rebuilt
+ * already (possibly by an ancestor being in the modified list).
+ */
+ virtual void InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true);
+
+ /**
+ * Calls InvalidateFrame() on all frames descendant frames (including
+ * this one).
+ *
+ * This function doesn't walk through placeholder frames to invalidate
+ * the out-of-flow frames.
+ *
+ * @param aRebuildDisplayItems If true, then adds this frame to the
+ * list of modified frames for display list building. Only pass false
+ * if you're sure that the relevant display items will be rebuilt
+ * already (possibly by an ancestor being in the modified list).
+ */
+ void InvalidateFrameSubtree(bool aRebuildDisplayItems = true);
+
+ /**
+ * Called when a frame is about to be removed and needs to be invalidated.
+ * Normally does nothing since DLBI handles removed frames.
+ */
+ virtual void InvalidateFrameForRemoval() {}
+
+ /**
+ * When HasUserData(frame->LayerIsPrerenderedDataKey()), then the
+ * entire overflow area of this frame has been rendered in its
+ * layer(s).
+ */
+ static void* LayerIsPrerenderedDataKey() {
+ return &sLayerIsPrerenderedDataKey;
+ }
+ static uint8_t sLayerIsPrerenderedDataKey;
+
+ /**
+ * Checks if a frame has had InvalidateFrame() called on it since the
+ * last paint.
+ *
+ * If true, then the invalid rect is returned in aRect, with an
+ * empty rect meaning all pixels drawn by this frame should be
+ * invalidated.
+ * If false, aRect is left unchanged.
+ */
+ bool IsInvalid(nsRect& aRect);
+
+ /**
+ * Check if any frame within the frame subtree (including this frame)
+ * returns true for IsInvalid().
+ */
+ bool HasInvalidFrameInSubtree() {
+ return HasAnyStateBits(NS_FRAME_NEEDS_PAINT |
+ NS_FRAME_DESCENDANT_NEEDS_PAINT);
+ }
+
+ /**
+ * Removes the invalid state from the current frame and all
+ * descendant frames.
+ */
+ void ClearInvalidationStateBits();
+
+ /**
+ * Ensures that the refresh driver is running, and schedules a view
+ * manager flush on the next tick.
+ *
+ * The view manager flush will update the layer tree, repaint any
+ * invalid areas in the layer tree and schedule a layer tree
+ * composite operation to display the layer tree.
+ *
+ * In general it is not necessary for frames to call this when they change.
+ * For example, changes that result in a reflow will have this called for
+ * them by PresContext::DoReflow when the reflow begins. Style changes that
+ * do not trigger a reflow should have this called for them by
+ * DoApplyRenderingChangeToTree.
+ *
+ * @param aType PAINT_COMPOSITE_ONLY : No changes have been made
+ * that require a layer tree update, so only schedule a layer
+ * tree composite.
+ */
+ enum PaintType { PAINT_DEFAULT = 0, PAINT_COMPOSITE_ONLY };
+ void SchedulePaint(PaintType aType = PAINT_DEFAULT,
+ bool aFrameChanged = true);
+
+ // Similar to SchedulePaint() but without calling
+ // InvalidateRenderingObservers() for SVG.
+ void SchedulePaintWithoutInvalidatingObservers(
+ PaintType aType = PAINT_DEFAULT);
+
+ /**
+ * Checks if the layer tree includes a dedicated layer for this
+ * frame/display item key pair, and invalidates at least aDamageRect
+ * area within that layer.
+ *
+ * If no layer is found, calls InvalidateFrame() instead.
+ *
+ * @param aDamageRect Area of the layer to invalidate.
+ * @param aFrameDamageRect If no layer is found, the area of the frame to
+ * invalidate. If null, the entire frame will be
+ * invalidated.
+ * @param aDisplayItemKey Display item type.
+ * @param aFlags UPDATE_IS_ASYNC : Will skip the invalidation
+ * if the found layer is being composited by a remote
+ * compositor.
+ * @return Layer, if found, nullptr otherwise.
+ */
+ enum { UPDATE_IS_ASYNC = 1 << 0 };
+ void InvalidateLayer(DisplayItemType aDisplayItemKey,
+ const nsIntRect* aDamageRect = nullptr,
+ const nsRect* aFrameDamageRect = nullptr,
+ uint32_t aFlags = 0);
+
+ void MarkNeedsDisplayItemRebuild();
+
+ /**
+ * Returns a rect that encompasses everything that might be painted by
+ * this frame. This includes this frame, all its descendant frames, this
+ * frame's outline, and descendant frames' outline, but does not include
+ * areas clipped out by the CSS "overflow" and "clip" properties.
+ *
+ * HasOverflowAreas() (below) will return true when this overflow
+ * rect has been explicitly set, even if it matches mRect.
+ * XXX Note: because of a space optimization using the formula above,
+ * during reflow this function does not give accurate data if
+ * FinishAndStoreOverflow has been called but mRect hasn't yet been
+ * updated yet. FIXME: This actually isn't true, but it should be.
+ *
+ * The ink overflow rect should NEVER be used for things that
+ * affect layout. The scrollable overflow rect is permitted to affect
+ * layout.
+ *
+ * @return the rect relative to this frame's origin, but after
+ * CSS transforms have been applied (i.e. not really this frame's coordinate
+ * system, and may not contain the frame's border-box, e.g. if there
+ * is a CSS transform scaling it down)
+ */
+ nsRect InkOverflowRect() const {
+ return GetOverflowRect(mozilla::OverflowType::Ink);
+ }
+
+ /**
+ * Returns a rect that encompasses the area of this frame that the
+ * user should be able to scroll to reach. This is similar to
+ * InkOverflowRect, but does not include outline or shadows, and
+ * may in the future include more margins than ink overflow does.
+ * It does not include areas clipped out by the CSS "overflow" and
+ * "clip" properties.
+ *
+ * HasOverflowAreas() (below) will return true when this overflow
+ * rect has been explicitly set, even if it matches mRect.
+ * XXX Note: because of a space optimization using the formula above,
+ * during reflow this function does not give accurate data if
+ * FinishAndStoreOverflow has been called but mRect hasn't yet been
+ * updated yet.
+ *
+ * @return the rect relative to this frame's origin, but after
+ * CSS transforms have been applied (i.e. not really this frame's coordinate
+ * system, and may not contain the frame's border-box, e.g. if there
+ * is a CSS transform scaling it down)
+ */
+ nsRect ScrollableOverflowRect() const {
+ return GetOverflowRect(mozilla::OverflowType::Scrollable);
+ }
+
+ mozilla::OverflowAreas GetOverflowAreas() const;
+
+ /**
+ * Same as GetOverflowAreas, except in this frame's coordinate
+ * system (before transforms are applied).
+ *
+ * @return the overflow areas relative to this frame, before any CSS
+ * transforms have been applied, i.e. in this frame's coordinate system
+ */
+ mozilla::OverflowAreas GetOverflowAreasRelativeToSelf() const;
+
+ /**
+ * Same as GetOverflowAreas, except relative to the parent frame.
+ *
+ * @return the overflow area relative to the parent frame, in the parent
+ * frame's coordinate system
+ */
+ mozilla::OverflowAreas GetOverflowAreasRelativeToParent() const;
+
+ /**
+ * Same as GetOverflowAreasRelativeToParent(), except that it also unions in
+ * the normal position overflow area if this frame is relatively or sticky
+ * positioned.
+ *
+ * @return the overflow area relative to the parent frame, in the parent
+ * frame's coordinate system
+ */
+ mozilla::OverflowAreas GetActualAndNormalOverflowAreasRelativeToParent()
+ const;
+
+ /**
+ * Same as ScrollableOverflowRect, except relative to the parent
+ * frame.
+ *
+ * @return the rect relative to the parent frame, in the parent frame's
+ * coordinate system
+ */
+ nsRect ScrollableOverflowRectRelativeToParent() const;
+
+ /**
+ * Same as ScrollableOverflowRect, except in this frame's coordinate
+ * system (before transforms are applied).
+ *
+ * @return the rect relative to this frame, before any CSS transforms have
+ * been applied, i.e. in this frame's coordinate system
+ */
+ nsRect ScrollableOverflowRectRelativeToSelf() const;
+
+ /**
+ * Like InkOverflowRect, except in this frame's
+ * coordinate system (before transforms are applied).
+ *
+ * @return the rect relative to this frame, before any CSS transforms have
+ * been applied, i.e. in this frame's coordinate system
+ */
+ nsRect InkOverflowRectRelativeToSelf() const;
+
+ /**
+ * Same as InkOverflowRect, except relative to the parent
+ * frame.
+ *
+ * @return the rect relative to the parent frame, in the parent frame's
+ * coordinate system
+ */
+ nsRect InkOverflowRectRelativeToParent() const;
+
+ /**
+ * Returns this frame's ink overflow rect as it would be before taking
+ * account of SVG effects or transforms. The rect returned is relative to
+ * this frame.
+ */
+ nsRect PreEffectsInkOverflowRect() const;
+
+ /**
+ * Store the overflow area in the frame's mOverflow.mInkOverflowDeltas
+ * fields or as a frame property in OverflowAreasProperty() so that it can
+ * be retrieved later without reflowing the frame. Returns true if either of
+ * the overflow areas changed.
+ */
+ bool FinishAndStoreOverflow(mozilla::OverflowAreas& aOverflowAreas,
+ nsSize aNewSize, nsSize* aOldSize = nullptr,
+ const nsStyleDisplay* aStyleDisplay = nullptr);
+
+ bool FinishAndStoreOverflow(ReflowOutput* aMetrics,
+ const nsStyleDisplay* aStyleDisplay = nullptr) {
+ return FinishAndStoreOverflow(aMetrics->mOverflowAreas,
+ nsSize(aMetrics->Width(), aMetrics->Height()),
+ nullptr, aStyleDisplay);
+ }
+
+ /**
+ * Returns whether the frame has an overflow rect that is different from
+ * its border-box.
+ */
+ bool HasOverflowAreas() const {
+ return mOverflow.mType != OverflowStorageType::None;
+ }
+
+ /**
+ * Removes any stored overflow rects (visual and scrollable) from the frame.
+ * Returns true if the overflow changed.
+ */
+ bool ClearOverflowRects();
+
+ /**
+ * Determine whether borders, padding, margins etc should NOT be applied
+ * on certain sides of the frame.
+ * @see mozilla::Sides in gfx/2d/BaseMargin.h
+ * @see mozilla::LogicalSides in layout/generic/WritingModes.h
+ *
+ * @note (See also bug 743402, comment 11) GetSkipSides() checks to see
+ * if this frame has a previous or next continuation to determine
+ * if a side should be skipped.
+ * So this only works after the entire frame tree has been reflowed.
+ * During reflow, if this frame can be split in the block axis, you
+ * should use nsSplittableFrame::PreReflowBlockLevelLogicalSkipSides().
+ */
+ Sides GetSkipSides() const;
+ virtual LogicalSides GetLogicalSkipSides() const {
+ return LogicalSides(mWritingMode);
+ }
+
+ /**
+ * @returns true if this frame is selected.
+ */
+ bool IsSelected() const {
+ return (GetContent() && GetContent()->IsMaybeSelected()) ? IsFrameSelected()
+ : false;
+ }
+
+ /**
+ * Shouldn't be called if this is a `nsTextFrame`. Call the
+ * `nsTextFrame::SelectionStateChanged` overload instead.
+ */
+ void SelectionStateChanged() {
+ MOZ_ASSERT(!IsTextFrame());
+ InvalidateFrameSubtree(); // TODO: should this deal with continuations?
+ }
+
+ /**
+ * Called to discover where this frame, or a parent frame has user-select
+ * style applied, which affects that way that it is selected.
+ *
+ * @param aSelectStyle out param. Returns the type of selection style found
+ * (using values defined in nsStyleConsts.h).
+ *
+ * @return Whether the frame can be selected (i.e. is not affected by
+ * user-select: none)
+ */
+ bool IsSelectable(mozilla::StyleUserSelect* aSelectStyle) const;
+
+ /**
+ * Returns whether this frame should have the content-block-size of a line,
+ * even if empty.
+ */
+ bool ShouldHaveLineIfEmpty() const;
+
+ /**
+ * Called to retrieve the SelectionController associated with the frame.
+ *
+ * @param aSelCon will contain the selection controller associated with
+ * the frame.
+ */
+ nsresult GetSelectionController(nsPresContext* aPresContext,
+ nsISelectionController** aSelCon);
+
+ /**
+ * Call to get nsFrameSelection for this frame.
+ */
+ already_AddRefed<nsFrameSelection> GetFrameSelection();
+
+ /**
+ * GetConstFrameSelection returns an object which methods are safe to use for
+ * example in nsIFrame code.
+ */
+ const nsFrameSelection* GetConstFrameSelection() const;
+
+ /**
+ * called to find the previous/next character, word, or line. Returns the
+ * actual nsIFrame and the frame offset. THIS DOES NOT CHANGE SELECTION STATE.
+ * Uses frame's begin selection state to start. If no selection on this frame
+ * will return NS_ERROR_FAILURE.
+ *
+ * @param aPos is defined in nsFrameSelection
+ */
+ virtual nsresult PeekOffset(mozilla::PeekOffsetStruct* aPos);
+
+ private:
+ nsresult PeekOffsetForCharacter(mozilla::PeekOffsetStruct* aPos,
+ int32_t aOffset);
+ nsresult PeekOffsetForWord(mozilla::PeekOffsetStruct* aPos, int32_t aOffset);
+ nsresult PeekOffsetForLine(mozilla::PeekOffsetStruct* aPos);
+ nsresult PeekOffsetForLineEdge(mozilla::PeekOffsetStruct* aPos);
+
+ /**
+ * Search for the first paragraph boundary before or after the given position
+ * @param aPos See description in nsFrameSelection.h. The following fields
+ * are used by this method:
+ * Input: mDirection
+ * Output: mResultContent, mContentOffset
+ */
+ nsresult PeekOffsetForParagraph(mozilla::PeekOffsetStruct* aPos);
+
+ public:
+ // given a frame five me the first/last leaf available
+ // XXX Robert O'Callahan wants to move these elsewhere
+ static void GetLastLeaf(nsIFrame** aFrame);
+ static void GetFirstLeaf(nsIFrame** aFrame);
+
+ struct SelectablePeekReport {
+ /** the previous/next selectable leaf frame */
+ nsIFrame* mFrame = nullptr;
+ /**
+ * 0 indicates that we arrived at the beginning of the output frame; -1
+ * indicates that we arrived at its end.
+ */
+ int32_t mOffset = 0;
+ /** whether the input frame and the returned frame are on different lines */
+ bool mJumpedLine = false;
+ /** whether we met a hard break between the input and the returned frame */
+ bool mJumpedHardBreak = false;
+ /** whether we met a child placeholder frame */
+ bool mFoundPlaceholder = false;
+ /** whether we jumped over a non-selectable frame during the search */
+ bool mMovedOverNonSelectableText = false;
+ /** whether we met selectable text frame that isn't editable during the
+ * search */
+ bool mHasSelectableFrame = false;
+ /** whether we ignored a br frame */
+ bool mIgnoredBrFrame = false;
+
+ FrameSearchResult PeekOffsetNoAmount(bool aForward) {
+ return mFrame->PeekOffsetNoAmount(aForward, &mOffset);
+ }
+ FrameSearchResult PeekOffsetCharacter(bool aForward,
+ PeekOffsetCharacterOptions aOptions) {
+ return mFrame->PeekOffsetCharacter(aForward, &mOffset, aOptions);
+ };
+
+ /** Transfers frame and offset info for PeekOffset() result */
+ void TransferTo(mozilla::PeekOffsetStruct& aPos) const;
+ bool Failed() { return !mFrame; }
+
+ explicit SelectablePeekReport(nsIFrame* aFrame = nullptr,
+ int32_t aOffset = 0)
+ : mFrame(aFrame), mOffset(aOffset) {}
+ MOZ_IMPLICIT SelectablePeekReport(
+ const mozilla::GenericErrorResult<nsresult>&& aErr);
+ };
+
+ /**
+ * Called to find the previous/next non-anonymous selectable leaf frame.
+ *
+ * @param aDirection the direction to move in (eDirPrevious or eDirNext)
+ * @param aOptions the other options which is same as
+ * PeekOffsetStruct::mOptions.
+ * FIXME: Due to the include hell, we cannot use the alias, PeekOffsetOptions
+ * is not available in this header file.
+ */
+ SelectablePeekReport GetFrameFromDirection(
+ nsDirection aDirection,
+ const mozilla::EnumSet<mozilla::PeekOffsetOption>& aOptions);
+ SelectablePeekReport GetFrameFromDirection(
+ const mozilla::PeekOffsetStruct& aPos);
+
+ /**
+ * Return:
+ * (1) the containing block frame for a line; i.e. the frame which
+ * supports a line iterator, or null if none can be found; and
+ * (2) the frame to use to get a line number, which will be direct child of
+ * the returned containing block.
+ * @param aLockScroll true to avoid breaking outside scrollframes.
+ */
+ std::pair<nsIFrame*, nsIFrame*> GetContainingBlockForLine(
+ bool aLockScroll) const;
+
+ private:
+ Result<bool, nsresult> IsVisuallyAtLineEdge(nsILineIterator* aLineIterator,
+ int32_t aLine,
+ nsDirection aDirection);
+ Result<bool, nsresult> IsLogicallyAtLineEdge(nsILineIterator* aLineIterator,
+ int32_t aLine,
+ nsDirection aDirection);
+
+ public:
+ /**
+ * Called to tell a frame that one of its child frames is dirty (i.e.,
+ * has the NS_FRAME_IS_DIRTY *or* NS_FRAME_HAS_DIRTY_CHILDREN bit
+ * set). This should always set the NS_FRAME_HAS_DIRTY_CHILDREN on
+ * the frame, and may do other work.
+ */
+ virtual void ChildIsDirty(nsIFrame* aChild);
+
+ /**
+ * Called to retrieve this frame's accessible.
+ * If this frame implements Accessibility return a valid accessible
+ * If not return NS_ERROR_NOT_IMPLEMENTED.
+ * Note: LocalAccessible must be refcountable. Do not implement directly on
+ * your frame Use a mediatior of some kind.
+ */
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType();
+#endif
+
+ /**
+ * Get the frame whose style should be the parent of this frame's style (i.e.,
+ * provide the parent style).
+ *
+ * This frame must either be an ancestor of this frame or a child. If
+ * this returns a child frame, then the child frame must be sure to
+ * return a grandparent or higher! Furthermore, if a child frame is
+ * returned it must have the same GetContent() as this frame.
+ *
+ * @param aProviderFrame (out) the frame associated with the returned value
+ * or nullptr if the style is for display:contents content.
+ * @return The style that should be the parent of this frame's style. Null is
+ * permitted, and means that this frame's style should be the root of
+ * the style tree.
+ */
+ virtual ComputedStyle* GetParentComputedStyle(
+ nsIFrame** aProviderFrame) const {
+ return DoGetParentComputedStyle(aProviderFrame);
+ }
+
+ /**
+ * Do the work for getting the parent ComputedStyle frame so that
+ * other frame's |GetParentComputedStyle| methods can call this
+ * method on *another* frame. (This function handles out-of-flow
+ * frames by using the frame manager's placeholder map and it also
+ * handles block-within-inline and generated content wrappers.)
+ *
+ * @param aProviderFrame (out) the frame associated with the returned value
+ * or null if the ComputedStyle is for display:contents content.
+ * @return The ComputedStyle that should be the parent of this frame's
+ * ComputedStyle. Null is permitted, and means that this frame's
+ * ComputedStyle should be the root of the ComputedStyle tree.
+ */
+ ComputedStyle* DoGetParentComputedStyle(nsIFrame** aProviderFrame) const;
+
+ /**
+ * Adjust the given parent frame to the right ComputedStyle parent frame for
+ * the child, given the pseudo-type of the prospective child. This handles
+ * things like walking out of table pseudos and so forth.
+ *
+ * @param aProspectiveParent what GetParent() on the child returns.
+ * Must not be null.
+ * @param aChildPseudo the child's pseudo type, if any.
+ */
+ static nsIFrame* CorrectStyleParentFrame(
+ nsIFrame* aProspectiveParent, mozilla::PseudoStyleType aChildPseudo);
+
+ /**
+ * Called by RestyleManager to update the style of anonymous boxes
+ * directly associated with this frame.
+ *
+ * The passed-in ServoRestyleState can be used to create new ComputedStyles as
+ * needed, as well as posting changes to the change list.
+ *
+ * It's guaranteed to already have a change in it for this frame and this
+ * frame's content.
+ *
+ * This function will be called after this frame's style has already been
+ * updated. This function will only be called on frames which have the
+ * NS_FRAME_OWNS_ANON_BOXES bit set.
+ */
+ void UpdateStyleOfOwnedAnonBoxes(mozilla::ServoRestyleState& aRestyleState) {
+ if (HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
+ DoUpdateStyleOfOwnedAnonBoxes(aRestyleState);
+ }
+ }
+
+ mozilla::ContainSizeAxes GetContainSizeAxes() const {
+ return StyleDisplay()->GetContainSizeAxes(*this);
+ }
+
+ // Common steps to all replaced elements given an unconstrained intrinsic
+ // size.
+ mozilla::IntrinsicSize FinishIntrinsicSize(
+ const mozilla::ContainSizeAxes& aAxes,
+ const mozilla::IntrinsicSize& aUncontainedSize) const {
+ auto result = aAxes.ContainIntrinsicSize(aUncontainedSize, *this);
+ result.Zoom(Style()->EffectiveZoom());
+ return result;
+ }
+
+ Maybe<nscoord> ContainIntrinsicBSize(nscoord aNoneValue = 0) const {
+ return GetContainSizeAxes().ContainIntrinsicBSize(*this, aNoneValue);
+ }
+
+ Maybe<nscoord> ContainIntrinsicISize(nscoord aNoneValue = 0) const {
+ return GetContainSizeAxes().ContainIntrinsicISize(*this, aNoneValue);
+ }
+
+ protected:
+ // This does the actual work of UpdateStyleOfOwnedAnonBoxes. It calls
+ // AppendDirectlyOwnedAnonBoxes to find all of the anonymous boxes
+ // owned by this frame, and then updates styles on each of them.
+ void DoUpdateStyleOfOwnedAnonBoxes(mozilla::ServoRestyleState& aRestyleState);
+
+ // A helper for DoUpdateStyleOfOwnedAnonBoxes for the specific case
+ // of the owned anon box being a child of this frame.
+ void UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
+ mozilla::ServoRestyleState& aRestyleState);
+
+ // Allow ServoRestyleState to call UpdateStyleOfChildAnonBox.
+ friend class mozilla::ServoRestyleState;
+
+ public:
+ // A helper both for UpdateStyleOfChildAnonBox, and to update frame-backed
+ // pseudo-elements in RestyleManager.
+ //
+ // This gets a ComputedStyle that will be the new style for `aChildFrame`, and
+ // takes care of updating it, calling CalcStyleDifference, and adding to the
+ // change list as appropriate.
+ //
+ // If aContinuationComputedStyle is not Nothing, it should be used for
+ // continuations instead of aNewComputedStyle. In either case, changehints
+ // are only computed based on aNewComputedStyle.
+ //
+ // Returns the generated change hint for the frame.
+ static nsChangeHint UpdateStyleOfOwnedChildFrame(
+ nsIFrame* aChildFrame, ComputedStyle* aNewComputedStyle,
+ mozilla::ServoRestyleState& aRestyleState,
+ const Maybe<ComputedStyle*>& aContinuationComputedStyle = Nothing());
+
+ struct OwnedAnonBox {
+ typedef void (*UpdateStyleFn)(nsIFrame* aOwningFrame, nsIFrame* aAnonBox,
+ mozilla::ServoRestyleState& aRestyleState);
+
+ explicit OwnedAnonBox(nsIFrame* aAnonBoxFrame,
+ UpdateStyleFn aUpdateStyleFn = nullptr)
+ : mAnonBoxFrame(aAnonBoxFrame), mUpdateStyleFn(aUpdateStyleFn) {}
+
+ nsIFrame* mAnonBoxFrame;
+ UpdateStyleFn mUpdateStyleFn;
+ };
+
+ /**
+ * Appends information about all of the anonymous boxes owned by this frame,
+ * including other anonymous boxes owned by those which this frame owns
+ * directly.
+ */
+ void AppendOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
+ if (HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) {
+ if (IsInlineFrame()) {
+ // See comment in nsIFrame::DoUpdateStyleOfOwnedAnonBoxes for why
+ // we skip nsInlineFrames.
+ return;
+ }
+ DoAppendOwnedAnonBoxes(aResult);
+ }
+ }
+
+ protected:
+ // This does the actual work of AppendOwnedAnonBoxes.
+ void DoAppendOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult);
+
+ public:
+ /**
+ * Hook subclasses can override to return their owned anonymous boxes.
+ *
+ * This function only appends anonymous boxes that are directly owned by
+ * this frame, i.e. direct children or (for certain frames) a wrapper
+ * parent, unlike AppendOwnedAnonBoxes, which will append all anonymous
+ * boxes transitively owned by this frame.
+ */
+ virtual void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult);
+
+ /**
+ * Determines whether a frame is visible for painting;
+ * taking into account whether it is painting a selection or printing.
+ */
+ bool IsVisibleForPainting() const;
+ /**
+ * Determines whether a frame is visible for painting or collapsed;
+ * taking into account whether it is painting a selection or printing,
+ */
+ bool IsVisibleOrCollapsedForPainting() const;
+
+ /**
+ * Determines if this frame is a stacking context.
+ */
+ bool IsStackingContext(const nsStyleDisplay*, const nsStyleEffects*);
+ bool IsStackingContext();
+
+ // Whether we should paint backgrounds or not.
+ struct ShouldPaintBackground {
+ bool mColor = false;
+ bool mImage = false;
+ };
+ ShouldPaintBackground ComputeShouldPaintBackground() const;
+
+ /**
+ * Determine whether the frame is logically empty, which is roughly
+ * whether the layout would be the same whether or not the frame is
+ * present. Placeholder frames should return true. Block frames
+ * should be considered empty whenever margins collapse through them,
+ * even though those margins are relevant. Text frames containing
+ * only whitespace that does not contribute to the height of the line
+ * should return true.
+ */
+ virtual bool IsEmpty();
+ /**
+ * Return the same as IsEmpty(). This may only be called after the frame
+ * has been reflowed and before any further style or content changes.
+ */
+ virtual bool CachedIsEmpty();
+ /**
+ * Determine whether the frame is logically empty, assuming that all
+ * its children are empty.
+ */
+ virtual bool IsSelfEmpty();
+
+ /**
+ * IsGeneratedContentFrame returns whether a frame corresponds to
+ * generated content
+ *
+ * @return whether the frame correspods to generated content
+ */
+ bool IsGeneratedContentFrame() const {
+ return HasAnyStateBits(NS_FRAME_GENERATED_CONTENT);
+ }
+
+ /**
+ * IsPseudoFrame returns whether a frame is a pseudo frame (eg an
+ * anonymous table-row frame created for a CSS table-cell without an
+ * enclosing table-row.
+ *
+ * @param aParentContent the content node corresponding to the parent frame
+ * @return whether the frame is a pseudo frame
+ */
+ bool IsPseudoFrame(const nsIContent* aParentContent) {
+ return mContent == aParentContent;
+ }
+
+ /**
+ * Support for reading and writing properties on the frame.
+ * These call through to the frame's FrameProperties object, if it
+ * exists, but avoid creating it if no property is ever set.
+ */
+ template <typename T>
+ FrameProperties::PropertyType<T> GetProperty(
+ FrameProperties::Descriptor<T> aProperty,
+ bool* aFoundResult = nullptr) const {
+ return mProperties.Get(aProperty, aFoundResult);
+ }
+
+ template <typename T>
+ bool HasProperty(FrameProperties::Descriptor<T> aProperty) const {
+ return mProperties.Has(aProperty);
+ }
+
+ /**
+ * Add a property, or update an existing property for the given descriptor.
+ *
+ * Note: This function asserts if updating an existing nsFrameList property.
+ */
+ template <typename T>
+ void SetProperty(FrameProperties::Descriptor<T> aProperty,
+ FrameProperties::PropertyType<T> aValue) {
+ if constexpr (std::is_same_v<T, nsFrameList>) {
+ MOZ_ASSERT(aValue, "Shouldn't set nullptr to a nsFrameList property!");
+ MOZ_ASSERT(!HasProperty(aProperty),
+ "Shouldn't update an existing nsFrameList property!");
+ }
+ mProperties.Set(aProperty, aValue, this);
+ }
+
+ // Unconditionally add a property; use ONLY if the descriptor is known
+ // to NOT already be present.
+ template <typename T>
+ void AddProperty(FrameProperties::Descriptor<T> aProperty,
+ FrameProperties::PropertyType<T> aValue) {
+ mProperties.Add(aProperty, aValue);
+ }
+
+ /**
+ * Remove a property and return its value without destroying it. May return
+ * nullptr.
+ *
+ * Note: The caller is responsible for handling the life cycle of the returned
+ * value.
+ */
+ template <typename T>
+ [[nodiscard]] FrameProperties::PropertyType<T> TakeProperty(
+ FrameProperties::Descriptor<T> aProperty, bool* aFoundResult = nullptr) {
+ return mProperties.Take(aProperty, aFoundResult);
+ }
+
+ template <typename T>
+ void RemoveProperty(FrameProperties::Descriptor<T> aProperty) {
+ mProperties.Remove(aProperty, this);
+ }
+
+ void RemoveAllProperties() { mProperties.RemoveAll(this); }
+
+ // nsIFrames themselves are in the nsPresArena, and so are not measured here.
+ // Instead, this measures heap-allocated things hanging off the nsIFrame, and
+ // likewise for its descendants.
+ virtual void AddSizeOfExcludingThisForTree(nsWindowSizes& aWindowSizes) const;
+
+ /**
+ * Return true if and only if this frame obeys visibility:hidden.
+ * if it does not, then nsContainerFrame will hide its view even though
+ * this means children can't be made visible again.
+ */
+ virtual bool SupportsVisibilityHidden() { return true; }
+
+ /**
+ * Returns the clip rect set via the 'clip' property, if the 'clip' property
+ * applies to this frame; otherwise returns Nothing(). The 'clip' property
+ * applies to HTML frames if they are absolutely positioned. The 'clip'
+ * property applies to SVG frames regardless of the value of the 'position'
+ * property.
+ *
+ * The coordinates of the returned rectangle are relative to this frame's
+ * origin.
+ */
+ Maybe<nsRect> GetClipPropClipRect(const nsStyleDisplay* aDisp,
+ const nsStyleEffects* aEffects,
+ const nsSize& aSize) const;
+
+ /**
+ * Check if this frame is focusable and in the current tab order.
+ * Tabbable is indicated by a nonnegative tabindex & is a subset of focusable.
+ * For example, only the selected radio button in a group is in the
+ * tab order, unless the radio group has no selection in which case
+ * all of the visible, non-disabled radio buttons in the group are
+ * in the tab order. On the other hand, all of the visible, non-disabled
+ * radio buttons are always focusable via clicking or script.
+ * Also, depending on the pref accessibility.tabfocus some widgets may be
+ * focusable but removed from the tab order. This is the default on
+ * Mac OS X, where fewer items are focusable.
+ * @param [in, optional] aWithMouse, is this focus query for mouse clicking
+ * @param [in, optional] aCheckVisibility, whether to treat an invisible
+ * frame as not focusable
+ * @return whether the frame is focusable via mouse, kbd or script.
+ */
+ [[nodiscard]] Focusable IsFocusable(bool aWithMouse = false,
+ bool aCheckVisibility = true);
+
+ protected:
+ // Helper for IsFocusable.
+ bool IsFocusableDueToScrollFrame();
+
+ /**
+ * Returns true if this box clips its children, e.g., if this box is an
+ * scrollbox or has overflow: clip in both axes.
+ */
+ bool DoesClipChildrenInBothAxes() const;
+
+ /**
+ * NOTE: aStatus is assumed to be already-initialized. The reflow statuses of
+ * any reflowed absolute children will be merged into aStatus; aside from
+ * that, this method won't modify aStatus.
+ */
+ void ReflowAbsoluteFrames(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ bool aConstrainBSize = true);
+
+ private:
+ Maybe<nscoord> ComputeInlineSizeFromAspectRatio(
+ mozilla::WritingMode aWM, const mozilla::LogicalSize& aCBSize,
+ const mozilla::LogicalSize& aContentEdgeToBoxSizing,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) const;
+
+ public:
+ /**
+ * @return true if this text frame ends with a newline character. It
+ * should return false if this is not a text frame.
+ */
+ virtual bool HasSignificantTerminalNewline() const;
+
+ struct CaretPosition {
+ CaretPosition();
+ ~CaretPosition();
+
+ nsCOMPtr<nsIContent> mResultContent;
+ int32_t mContentOffset;
+ };
+
+ /**
+ * gets the first or last possible caret position within the frame
+ *
+ * @param [in] aStart
+ * true for getting the first possible caret position
+ * false for getting the last possible caret position
+ * @return The caret position in a CaretPosition.
+ * the returned value is a 'best effort' in case errors
+ * are encountered rummaging through the frame.
+ */
+ CaretPosition GetExtremeCaretPosition(bool aStart);
+
+ /**
+ * Query whether this frame supports getting a line iterator.
+ * @return true if a line iterator is supported.
+ */
+ virtual bool CanProvideLineIterator() const { return false; }
+
+ /**
+ * Get a line iterator for this frame, if supported.
+ *
+ * @return nullptr if no line iterator is supported.
+ * @note dispose the line iterator using nsILineIterator::DisposeLineIterator
+ */
+ virtual nsILineIterator* GetLineIterator() { return nullptr; }
+
+ /**
+ * If this frame is a next-in-flow, and its prev-in-flow has something on its
+ * overflow list, pull those frames into the child list of this one.
+ */
+ virtual void PullOverflowsFromPrevInFlow() {}
+
+ /**
+ * Accessors for the absolute containing block.
+ */
+ bool IsAbsoluteContainer() const {
+ return !!(mState & NS_FRAME_HAS_ABSPOS_CHILDREN);
+ }
+ bool HasAbsolutelyPositionedChildren() const;
+ nsAbsoluteContainingBlock* GetAbsoluteContainingBlock() const;
+ void MarkAsAbsoluteContainingBlock();
+ void MarkAsNotAbsoluteContainingBlock();
+ // Child frame types override this function to select their own child list
+ // name
+ virtual mozilla::FrameChildListID GetAbsoluteListID() const {
+ return mozilla::FrameChildListID::Absolute;
+ }
+
+ // Checks if we (or any of our descendants) have NS_FRAME_PAINTED_THEBES set,
+ // and clears this bit if so.
+ bool CheckAndClearPaintedState();
+
+ // Checks if we (or any of our descendents) have mBuiltDisplayList set, and
+ // clears this bit if so.
+ bool CheckAndClearDisplayListState();
+
+ // CSS visibility just doesn't cut it because it doesn't inherit through
+ // documents. Also if this frame is in a hidden card of a deck then it isn't
+ // visible either and that isn't expressed using CSS visibility. Also if it
+ // is in a hidden view (there are a few cases left and they are hopefully
+ // going away soon).
+ // If the VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY flag is passed then we
+ // ignore the chrome/content boundary, otherwise we stop looking when we
+ // reach it.
+ enum { VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY = 0x01 };
+ bool IsVisibleConsideringAncestors(uint32_t aFlags = 0) const;
+
+ struct FrameWithDistance {
+ nsIFrame* mFrame;
+ nscoord mXDistance;
+ nscoord mYDistance;
+ };
+
+ /**
+ * Finds a frame that is closer to a specified point than a current
+ * distance. Distance is measured as for text selection -- a closer x
+ * distance beats a closer y distance.
+ *
+ * Normally, this function will only check the distance between this
+ * frame's rectangle and the specified point. SVGTextFrame overrides
+ * this so that it can manage all of its descendant frames and take
+ * into account any SVG text layout.
+ *
+ * If aPoint is closer to this frame's rectangle than aCurrentBestFrame
+ * indicates, then aCurrentBestFrame is updated with the distance between
+ * aPoint and this frame's rectangle, and with a pointer to this frame.
+ * If aPoint is not closer, then aCurrentBestFrame is left unchanged.
+ *
+ * @param aPoint The point to check for its distance to this frame.
+ * @param aCurrentBestFrame Pointer to a struct that will be updated with
+ * a pointer to this frame and its distance to aPoint, if this frame
+ * is indeed closer than the current distance in aCurrentBestFrame.
+ */
+ virtual void FindCloserFrameForSelection(
+ const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame);
+
+ /**
+ * Is this a flex item? (i.e. a non-abs-pos child of a flex container)
+ */
+ inline bool IsFlexItem() const;
+ /**
+ * Is this a grid item? (i.e. a non-abs-pos child of a grid container)
+ */
+ inline bool IsGridItem() const;
+ /**
+ * Is this a flex or grid item? (i.e. a non-abs-pos child of a flex/grid
+ * container)
+ */
+ inline bool IsFlexOrGridItem() const;
+ inline bool IsFlexOrGridContainer() const;
+
+ /**
+ * Return true if this frame has masonry layout in aAxis.
+ * @note only valid to call on nsGridContainerFrames
+ */
+ inline bool IsMasonry(mozilla::LogicalAxis aAxis) const;
+
+ /**
+ * @return true if this frame is used as a table caption.
+ */
+ inline bool IsTableCaption() const;
+
+ inline bool IsBlockOutside() const;
+ inline bool IsInlineOutside() const;
+ inline mozilla::StyleDisplay GetDisplay() const;
+ inline bool IsFloating() const;
+ inline bool IsAbsPosContainingBlock() const;
+ inline bool IsFixedPosContainingBlock() const;
+ inline bool IsRelativelyOrStickyPositioned() const;
+
+ // Note: In general, you'd want to call IsRelativelyOrStickyPositioned()
+ // unless you want to deal with "position:relative" and "position:sticky"
+ // differently.
+ inline bool IsRelativelyPositioned() const;
+ inline bool IsStickyPositioned() const;
+
+ inline bool IsAbsolutelyPositioned(
+ const nsStyleDisplay* aStyleDisplay = nullptr) const;
+ inline bool IsTrueOverflowContainer() const;
+
+ // Does this frame have "column-span: all" style.
+ //
+ // Note this only checks computed style, but not testing whether the
+ // containing block formatting context was established by a multicol. Callers
+ // need to use IsColumnSpanInMulticolSubtree() to check whether multi-column
+ // effects apply or not.
+ inline bool IsColumnSpan() const;
+
+ // Like IsColumnSpan(), but this also checks whether the frame has a
+ // multi-column ancestor or not.
+ inline bool IsColumnSpanInMulticolSubtree() const;
+
+ /**
+ * Returns the vertical-align value to be used for layout, if it is one
+ * of the enumerated values. If this is an SVG text frame, it returns a value
+ * that corresponds to the value of dominant-baseline. If the
+ * vertical-align property has length or percentage value, this returns
+ * Nothing().
+ */
+ Maybe<mozilla::StyleVerticalAlignKeyword> VerticalAlignEnum() const;
+
+ /**
+ * Adds the NS_FRAME_IN_POPUP state bit to aFrame, and
+ * all descendant frames (including cross-doc ones).
+ */
+ static void AddInPopupStateBitToDescendants(nsIFrame* aFrame);
+ /**
+ * Removes the NS_FRAME_IN_POPUP state bit from aFrame and
+ * all descendant frames (including cross-doc ones), unless
+ * the frame is a popup itself.
+ */
+ static void RemoveInPopupStateBitFromDescendants(nsIFrame* aFrame);
+
+ /**
+ * Return true if aFrame is in an {ib} split and is NOT one of the
+ * continuations of the first inline in it.
+ */
+ bool FrameIsNonFirstInIBSplit() const {
+ return HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
+ FirstContinuation()->GetProperty(nsIFrame::IBSplitPrevSibling());
+ }
+
+ /**
+ * Return true if aFrame is in an {ib} split and is NOT one of the
+ * continuations of the last inline in it.
+ */
+ bool FrameIsNonLastInIBSplit() const {
+ return HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
+ FirstContinuation()->GetProperty(nsIFrame::IBSplitSibling());
+ }
+
+ /**
+ * Return whether this is a frame whose width is used when computing
+ * the font size inflation of its descendants.
+ */
+ bool IsContainerForFontSizeInflation() const {
+ return HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER);
+ }
+
+ /**
+ * Return whether this frame or any of its children is dirty.
+ */
+ bool IsSubtreeDirty() const {
+ return HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+
+ /**
+ * Returns true if the frame is an SVGTextFrame or one of its descendants.
+ */
+ bool IsInSVGTextSubtree() const {
+ return HasAnyStateBits(NS_FRAME_IS_SVG_TEXT);
+ }
+
+ // https://drafts.csswg.org/css-overflow-3/#scroll-container
+ bool IsScrollContainer() const {
+ const bool result = IsScrollFrame() || IsListControlFrame();
+ MOZ_ASSERT(result == !!GetAsScrollContainer());
+ return result;
+ }
+ nsIScrollableFrame* GetAsScrollContainer() const;
+
+ /**
+ * Returns true if the frame is an SVG Rendering Observer container.
+ */
+ bool IsRenderingObserverContainer() const {
+ // NS_FRAME_SVG_LAYOUT is used as a proxy to check for an SVG frame because
+ // NS_STATE_SVG_RENDERING_OBSERVER_CONTAINER is an SVG specific state bit.
+ return HasAllStateBits(NS_FRAME_SVG_LAYOUT |
+ NS_STATE_SVG_RENDERING_OBSERVER_CONTAINER) ||
+ IsSVGOuterSVGFrame();
+ }
+
+ /**
+ * Return whether this frame keeps track of overflow areas. (Frames for
+ * non-display SVG elements -- e.g. <clipPath> -- do not maintain overflow
+ * areas, because they're never painted.)
+ */
+ bool FrameMaintainsOverflow() const {
+ return !HasAllStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY) &&
+ !(IsSVGOuterSVGFrame() && HasAnyStateBits(NS_FRAME_IS_NONDISPLAY));
+ }
+
+ /*
+ * @param aStyleDisplay: If the caller has this->StyleDisplay(), providing
+ * it here will improve performance.
+ */
+ bool BackfaceIsHidden(const nsStyleDisplay* aStyleDisplay) const {
+ MOZ_ASSERT(aStyleDisplay == StyleDisplay());
+ return aStyleDisplay->BackfaceIsHidden();
+ }
+ bool BackfaceIsHidden() const { return StyleDisplay()->BackfaceIsHidden(); }
+
+ /**
+ * Returns true if the frame is scrolled out of view.
+ */
+ bool IsScrolledOutOfView() const;
+
+ /**
+ * Computes a 2D matrix from the -moz-window-transform and
+ * -moz-window-transform-origin properties on aFrame.
+ * Values that don't result in a 2D matrix will be ignored and an identity
+ * matrix will be returned instead.
+ */
+ Matrix ComputeWidgetTransform() const;
+
+ /**
+ * @return true iff this frame has one or more associated image requests.
+ * @see mozilla::css::ImageLoader.
+ */
+ bool HasImageRequest() const { return mHasImageRequest; }
+
+ /**
+ * Update this frame's image request state.
+ */
+ void SetHasImageRequest(bool aHasRequest) { mHasImageRequest = aHasRequest; }
+
+ /**
+ * Whether this frame has a first-letter child. If it does, the frame is
+ * actually an nsContainerFrame and the first-letter frame can be gotten by
+ * walking up to the nearest ancestor blockframe and getting its first
+ * continuation's nsContainerFrame::FirstLetterProperty() property. This will
+ * only return true for the first continuation of the first-letter's parent.
+ */
+ bool HasFirstLetterChild() const { return mHasFirstLetterChild; }
+
+ /**
+ * Whether this frame's parent is a wrapper anonymous box. See documentation
+ * for mParentIsWrapperAnonBox.
+ */
+ bool ParentIsWrapperAnonBox() const { return mParentIsWrapperAnonBox; }
+ void SetParentIsWrapperAnonBox() { mParentIsWrapperAnonBox = true; }
+
+ /**
+ * Whether this is a wrapper anonymous box needing a restyle.
+ */
+ bool IsWrapperAnonBoxNeedingRestyle() const {
+ return mIsWrapperBoxNeedingRestyle;
+ }
+ void SetIsWrapperAnonBoxNeedingRestyle(bool aNeedsRestyle) {
+ mIsWrapperBoxNeedingRestyle = aNeedsRestyle;
+ }
+
+ bool MayHaveTransformAnimation() const { return mMayHaveTransformAnimation; }
+ void SetMayHaveTransformAnimation() {
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ mMayHaveTransformAnimation = true;
+ }
+ bool MayHaveOpacityAnimation() const { return mMayHaveOpacityAnimation; }
+ void SetMayHaveOpacityAnimation() { mMayHaveOpacityAnimation = true; }
+
+ // Returns true if this frame is visible or may have visible descendants.
+ // Note: This function is accurate only on primary frames, because
+ // mAllDescendantsAreInvisible is not updated on continuations.
+ bool IsVisibleOrMayHaveVisibleDescendants() const {
+ return !mAllDescendantsAreInvisible || StyleVisibility()->IsVisible();
+ }
+ // Update mAllDescendantsAreInvisible flag for this frame and ancestors.
+ void UpdateVisibleDescendantsState();
+
+ /**
+ * If this returns true, the frame it's called on should get the
+ * NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
+ * if it's already in reflow, or via calling FrameNeedsReflow() to schedule a
+ * reflow.
+ */
+ virtual bool RenumberFrameAndDescendants(int32_t* aOrdinal, int32_t aDepth,
+ int32_t aIncrement,
+ bool aForCounting) {
+ return false;
+ }
+
+ enum class ExtremumLength {
+ MinContent,
+ MaxContent,
+ MozAvailable,
+ FitContent,
+ FitContentFunction,
+ };
+
+ template <typename SizeOrMaxSize>
+ static Maybe<ExtremumLength> ToExtremumLength(const SizeOrMaxSize& aSize) {
+ switch (aSize.tag) {
+ case SizeOrMaxSize::Tag::MinContent:
+ return mozilla::Some(ExtremumLength::MinContent);
+ case SizeOrMaxSize::Tag::MaxContent:
+ return mozilla::Some(ExtremumLength::MaxContent);
+ case SizeOrMaxSize::Tag::MozAvailable:
+ return mozilla::Some(ExtremumLength::MozAvailable);
+ case SizeOrMaxSize::Tag::FitContent:
+ return mozilla::Some(ExtremumLength::FitContent);
+ case SizeOrMaxSize::Tag::FitContentFunction:
+ return mozilla::Some(ExtremumLength::FitContentFunction);
+ default:
+ return mozilla::Nothing();
+ }
+ }
+
+ /**
+ * Helper function - computes the content-box inline size for aSize, which is
+ * a more complex version to resolve a StyleExtremumLength.
+ * @param aAvailableISizeOverride If this has a value, it is used as the
+ * available inline-size instead of
+ * aContainingBlockSize.ISize(aWM) when
+ * resolving fit-content.
+ */
+ struct ISizeComputationResult {
+ nscoord mISize = 0;
+ AspectRatioUsage mAspectRatioUsage = AspectRatioUsage::None;
+ };
+ ISizeComputationResult ComputeISizeValue(
+ gfxContext* aRenderingContext, const mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aContainingBlockSize,
+ const mozilla::LogicalSize& aContentEdgeToBoxSizing,
+ nscoord aBoxSizingToMarginEdge, ExtremumLength aSize,
+ Maybe<nscoord> aAvailableISizeOverride,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags);
+
+ /**
+ * Helper function - computes the content-box inline size for aSize, which is
+ * a simpler version to resolve a LengthPercentage.
+ */
+ nscoord ComputeISizeValue(const mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aContainingBlockSize,
+ const mozilla::LogicalSize& aContentEdgeToBoxSizing,
+ const LengthPercentage& aSize);
+
+ template <typename SizeOrMaxSize>
+ ISizeComputationResult ComputeISizeValue(
+ gfxContext* aRenderingContext, const mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aContainingBlockSize,
+ const mozilla::LogicalSize& aContentEdgeToBoxSizing,
+ nscoord aBoxSizingToMarginEdge, const SizeOrMaxSize& aSize,
+ const mozilla::StyleSizeOverrides& aSizeOverrides = {},
+ mozilla::ComputeSizeFlags aFlags = {}) {
+ if (aSize.IsLengthPercentage()) {
+ return {ComputeISizeValue(aWM, aContainingBlockSize,
+ aContentEdgeToBoxSizing,
+ aSize.AsLengthPercentage())};
+ }
+ auto length = ToExtremumLength(aSize);
+ MOZ_ASSERT(length, "This doesn't handle none / auto");
+ Maybe<nscoord> availbleISizeOverride;
+ if (aSize.IsFitContentFunction()) {
+ availbleISizeOverride.emplace(aSize.AsFitContentFunction().Resolve(
+ aContainingBlockSize.ISize(aWM)));
+ }
+ return ComputeISizeValue(aRenderingContext, aWM, aContainingBlockSize,
+ aContentEdgeToBoxSizing, aBoxSizingToMarginEdge,
+ length.valueOr(ExtremumLength::MinContent),
+ availbleISizeOverride, aSizeOverrides, aFlags);
+ }
+
+ DisplayItemArray& DisplayItems() { return mDisplayItems; }
+ const DisplayItemArray& DisplayItems() const { return mDisplayItems; }
+
+ void AddDisplayItem(nsDisplayItem* aItem);
+ bool RemoveDisplayItem(nsDisplayItem* aItem);
+ void RemoveDisplayItemDataForDeletion();
+ bool HasDisplayItems();
+ bool HasDisplayItem(nsDisplayItem* aItem);
+ bool HasDisplayItem(uint32_t aKey);
+
+ static void PrintDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList,
+ bool aDumpHtml = false);
+ static void PrintDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList,
+ std::stringstream& aStream,
+ bool aDumpHtml = false);
+ static void PrintDisplayItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem, std::stringstream& aStream,
+ uint32_t aIndent = 0, bool aDumpSublist = false,
+ bool aDumpHtml = false);
+#ifdef MOZ_DUMP_PAINTING
+ static void PrintDisplayListSet(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aSet,
+ std::stringstream& aStream,
+ bool aDumpHtml = false);
+#endif
+
+ /**
+ * Adds display items for standard CSS background if necessary.
+ * Does not check IsVisibleForPainting.
+ * @return whether a themed background item was created.
+ */
+ bool DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists);
+ /**
+ * Adds display items for standard CSS borders, background and outline for
+ * for this frame, as necessary. Checks IsVisibleForPainting and won't
+ * display anything if the frame is not visible.
+ */
+ void DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists);
+ /**
+ * Add a display item for the CSS outline. Does not check visibility.
+ */
+ void DisplayOutlineUnconditional(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists);
+ /**
+ * Add a display item for the CSS outline, after calling
+ * IsVisibleForPainting to confirm we are visible.
+ */
+ void DisplayOutline(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists);
+
+ /**
+ * Add a display item for CSS inset box shadows. Does not check visibility.
+ */
+ void DisplayInsetBoxShadowUnconditional(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList);
+
+ /**
+ * Add a display item for CSS inset box shadow, after calling
+ * IsVisibleForPainting to confirm we are visible.
+ */
+ void DisplayInsetBoxShadow(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList);
+
+ /**
+ * Add a display item for CSS outset box shadows. Does not check visibility.
+ */
+ void DisplayOutsetBoxShadowUnconditional(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList);
+
+ /**
+ * Add a display item for CSS outset box shadow, after calling
+ * IsVisibleForPainting to confirm we are visible.
+ */
+ void DisplayOutsetBoxShadow(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList);
+
+ bool ForceDescendIntoIfVisible() const { return mForceDescendIntoIfVisible; }
+ void SetForceDescendIntoIfVisible(bool aForce) {
+ mForceDescendIntoIfVisible = aForce;
+ }
+
+ bool BuiltDisplayList() const { return mBuiltDisplayList; }
+ void SetBuiltDisplayList(const bool aBuilt) { mBuiltDisplayList = aBuilt; }
+
+ bool IsFrameModified() const { return mFrameIsModified; }
+ void SetFrameIsModified(const bool aFrameIsModified) {
+ mFrameIsModified = aFrameIsModified;
+ }
+
+ bool HasModifiedDescendants() const { return mHasModifiedDescendants; }
+ void SetHasModifiedDescendants(const bool aHasModifiedDescendants) {
+ mHasModifiedDescendants = aHasModifiedDescendants;
+ }
+
+ bool HasOverrideDirtyRegion() const { return mHasOverrideDirtyRegion; }
+ void SetHasOverrideDirtyRegion(const bool aHasDirtyRegion) {
+ mHasOverrideDirtyRegion = aHasDirtyRegion;
+ }
+
+ bool MayHaveWillChangeBudget() const { return mMayHaveWillChangeBudget; }
+ void SetMayHaveWillChangeBudget(const bool aHasBudget) {
+ mMayHaveWillChangeBudget = aHasBudget;
+ }
+
+ bool HasBSizeChange() const { return mHasBSizeChange; }
+ void SetHasBSizeChange(const bool aHasBSizeChange) {
+ mHasBSizeChange = aHasBSizeChange;
+ }
+
+ bool HasPaddingChange() const { return mHasPaddingChange; }
+ void SetHasPaddingChange(const bool aHasPaddingChange) {
+ mHasPaddingChange = aHasPaddingChange;
+ }
+
+ bool HasColumnSpanSiblings() const { return mHasColumnSpanSiblings; }
+ void SetHasColumnSpanSiblings(bool aHasColumnSpanSiblings) {
+ mHasColumnSpanSiblings = aHasColumnSpanSiblings;
+ }
+
+ bool DescendantMayDependOnItsStaticPosition() const {
+ return mDescendantMayDependOnItsStaticPosition;
+ }
+ void SetDescendantMayDependOnItsStaticPosition(bool aValue) {
+ mDescendantMayDependOnItsStaticPosition = aValue;
+ }
+
+ /**
+ * Returns the hit test area of the frame.
+ */
+ nsRect GetCompositorHitTestArea(nsDisplayListBuilder* aBuilder);
+
+ /**
+ * Returns the set of flags indicating the properties of the frame that the
+ * compositor might care about for hit-testing purposes. Note that this
+ * function must be called during Gecko display list construction time (i.e
+ * while the frame tree is being traversed) because that is when the display
+ * list builder has the necessary state set up correctly.
+ */
+ mozilla::gfx::CompositorHitTestInfo GetCompositorHitTestInfo(
+ nsDisplayListBuilder* aBuilder);
+
+ /**
+ * Copies aWM to mWritingMode on 'this' and all its ancestors.
+ */
+ inline void PropagateWritingModeToSelfAndAncestors(mozilla::WritingMode aWM);
+
+ /**
+ * Observes or unobserves the element with an internal ResizeObserver,
+ * depending on whether it needs to update its last remembered size.
+ * Also removes a previously stored last remembered size if the element
+ * can no longer have it.
+ * @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered}
+ */
+ void HandleLastRememberedSize();
+
+ protected:
+ /**
+ * Reparent this frame's view if it has one.
+ */
+ void ReparentFrameViewTo(nsViewManager* aViewManager, nsView* aNewParentView);
+
+ // Members
+ nsRect mRect;
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<ComputedStyle> mComputedStyle;
+
+ private:
+ nsPresContext* const mPresContext;
+ nsContainerFrame* mParent;
+ nsIFrame* mNextSibling; // doubly-linked list of frames
+ nsIFrame* mPrevSibling; // Do not touch outside SetNextSibling!
+
+ DisplayItemArray mDisplayItems;
+
+ void MarkAbsoluteFramesForDisplayList(nsDisplayListBuilder* aBuilder);
+
+ protected:
+ void MarkInReflow() {
+#ifdef DEBUG_dbaron_off
+ // bug 81268
+ NS_ASSERTION(!(mState & NS_FRAME_IN_REFLOW), "frame is already in reflow");
+#endif
+ AddStateBits(NS_FRAME_IN_REFLOW);
+ }
+
+ private:
+ nsFrameState mState;
+
+ protected:
+ /**
+ * List of properties attached to the frame.
+ */
+ FrameProperties mProperties;
+
+ // When there is no scrollable overflow area, and the ink overflow area only
+ // slightly larger than mRect, the ink overflow area may be stored a set of
+ // four 1-byte deltas from the edges of mRect rather than allocating a whole
+ // separate rectangle property. If all four deltas are zero, this means that
+ // no overflow area has actually been set (this is the initial state of
+ // newly-created frames).
+ //
+ // Note that these are unsigned values, all measured "outwards" from the edges
+ // of mRect, so mLeft and mTop are reversed from our normal coordinate system.
+ struct InkOverflowDeltas {
+ // The maximum delta value we can store in any of the four edges.
+ static constexpr uint8_t kMax = 0xfe;
+
+ uint8_t mLeft;
+ uint8_t mTop;
+ uint8_t mRight;
+ uint8_t mBottom;
+ bool operator==(const InkOverflowDeltas& aOther) const {
+ return mLeft == aOther.mLeft && mTop == aOther.mTop &&
+ mRight == aOther.mRight && mBottom == aOther.mBottom;
+ }
+ bool operator!=(const InkOverflowDeltas& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+ enum class OverflowStorageType : uint32_t {
+ // No overflow area; code relies on this being an all-zero value.
+ None = 0x00000000u,
+
+ // Ink overflow is too large to stored in InkOverflowDeltas.
+ Large = 0x000000ffu,
+ };
+ // If mOverflow.mType is OverflowStorageType::Large, then the delta values are
+ // not meaningful and the overflow area is stored in OverflowAreasProperty()
+ // instead.
+ union {
+ OverflowStorageType mType;
+ InkOverflowDeltas mInkOverflowDeltas;
+ } mOverflow;
+
+ /** @see GetWritingMode() */
+ mozilla::WritingMode mWritingMode;
+
+ /** The ClassID of the concrete class of this instance. */
+ ClassID mClass; // 1 byte
+
+ bool mMayHaveRoundedCorners : 1;
+
+ /**
+ * True iff this frame has one or more associated image requests.
+ * @see mozilla::css::ImageLoader.
+ */
+ bool mHasImageRequest : 1;
+
+ /**
+ * True if this frame has a continuation that has a first-letter frame, or its
+ * placeholder, as a child. In that case this frame has a blockframe ancestor
+ * that has the first-letter frame hanging off it in the
+ * nsContainerFrame::FirstLetterProperty() property.
+ */
+ bool mHasFirstLetterChild : 1;
+
+ /**
+ * True if this frame's parent is a wrapper anonymous box (e.g. a table
+ * anonymous box as specified at
+ * <https://www.w3.org/TR/CSS21/tables.html#anonymous-boxes>).
+ *
+ * We could compute this information directly when we need it, but it wouldn't
+ * be all that cheap, and since this information is immutable for the lifetime
+ * of the frame we might as well cache it.
+ *
+ * Note that our parent may itself have mParentIsWrapperAnonBox set to true.
+ */
+ bool mParentIsWrapperAnonBox : 1;
+
+ /**
+ * True if this is a wrapper anonymous box needing a restyle. This is used to
+ * track, during stylo post-traversal, whether we've already recomputed the
+ * style of this anonymous box, if we end up seeing it twice.
+ */
+ bool mIsWrapperBoxNeedingRestyle : 1;
+
+ /**
+ * This bit is used in nsTextFrame::CharacterDataChanged() as an optimization
+ * to skip redundant reflow-requests when the character data changes multiple
+ * times between reflows. If this flag is set, then it implies that the
+ * NS_FRAME_IS_DIRTY state bit is also set (and that intrinsic sizes have
+ * been marked as dirty on our ancestor chain).
+ *
+ * XXXdholbert This bit is *only* used on nsTextFrame, but it lives here on
+ * nsIFrame simply because this is where we've got unused state bits
+ * available in a gap. If bits become more scarce, we should perhaps consider
+ * expanding the range of frame-specific state bits in nsFrameStateBits.h and
+ * moving this to be one of those (e.g. by swapping one of the adjacent
+ * general-purpose bits to take the place of this bool:1 here, so we can grow
+ * that range of frame-specific bits by 1).
+ */
+ bool mReflowRequestedForCharDataChange : 1;
+
+ /**
+ * This bit is used during BuildDisplayList to mark frames that need to
+ * have display items rebuilt. We will descend into them if they are
+ * currently visible, even if they don't intersect the dirty area.
+ */
+ bool mForceDescendIntoIfVisible : 1;
+
+ /**
+ * True if we have built display items for this frame since
+ * the last call to CheckAndClearDisplayListState, false
+ * otherwise. Used for the reftest harness to verify minimal
+ * display list building.
+ */
+ bool mBuiltDisplayList : 1;
+
+ /**
+ * True if the frame has been marked modified by
+ * |MarkNeedsDisplayItemRebuild()|, usually due to a style change or reflow.
+ */
+ bool mFrameIsModified : 1;
+
+ /**
+ * True if the frame has modified descendants. Set before display list
+ * preprocessing and only used during partial display list builds.
+ */
+ bool mHasModifiedDescendants : 1;
+
+ /**
+ * Used by merging based retained display lists to restrict the dirty area
+ * during partial display list builds.
+ */
+ bool mHasOverrideDirtyRegion : 1;
+
+ /**
+ * True if frame has will-change, and currently has display
+ * items consuming some of the will-change budget.
+ */
+ bool mMayHaveWillChangeBudget : 1;
+
+#ifdef DEBUG
+ public:
+ /**
+ * True if this frame has already been been visited by
+ * nsCSSFrameConstructor::AutoFrameConstructionPageName.
+ *
+ * This is used to assert that we have visited each frame only once, and is
+ * not useful otherwise.
+ */
+ bool mWasVisitedByAutoFrameConstructionPageName : 1;
+#endif
+
+ private:
+ /**
+ * True if this is the primary frame for mContent.
+ */
+ bool mIsPrimaryFrame : 1;
+
+ bool mMayHaveTransformAnimation : 1;
+ bool mMayHaveOpacityAnimation : 1;
+
+ /**
+ * True if we are certain that all descendants are not visible.
+ *
+ * This flag is conservative in that it might sometimes be false even if, in
+ * fact, all descendants are invisible.
+ * For example; an element is visibility:visible and has a visibility:hidden
+ * child. This flag is stil false in such case.
+ */
+ bool mAllDescendantsAreInvisible : 1;
+
+ bool mHasBSizeChange : 1;
+
+ /**
+ * True if the frame seems to be in the process of being reflowed with a
+ * different amount of inline-axis padding as compared to its most recent
+ * reflow. This flag's purpose is to detect cases where the frame's
+ * inline-axis content-box-size has changed, without any style change or any
+ * change to the border-box size, so that we can mark/invalidate things
+ * appropriately in ReflowInput::InitResizeFlags().
+ *
+ * This flag is set in SizeComputationResult::InitOffsets() and cleared in
+ * nsIFrame::DidReflow().
+ */
+ bool mHasPaddingChange : 1;
+
+ /**
+ * True if we are or contain the scroll anchor for a scrollable frame.
+ */
+ bool mInScrollAnchorChain : 1;
+
+ /**
+ * Suppose a frame was split into multiple parts to separate parts containing
+ * column-spans from parts not containing column-spans. This bit is set on all
+ * continuations *not* containing column-spans except for the those after the
+ * last column-span/non-column-span boundary (i.e., the bit really means it
+ * has a *later* sibling across a split). Note that the last part is always
+ * created to containing no columns-spans even if it has no children. See
+ * nsCSSFrameConstructor::CreateColumnSpanSiblings() for the implementation.
+ *
+ * If the frame having this bit set is removed, we need to reframe the
+ * multi-column container.
+ */
+ bool mHasColumnSpanSiblings : 1;
+
+ /**
+ * True if we may have any descendant whose positioning may depend on its
+ * static position (and thus which we need to recompute the position for if we
+ * move).
+ */
+ bool mDescendantMayDependOnItsStaticPosition : 1;
+
+ protected:
+ // Helpers
+ /**
+ * Can we stop inside this frame when we're skipping non-rendered whitespace?
+ *
+ * @param aForward [in] Are we moving forward (or backward) in content order.
+ *
+ * @param aOffset [in/out] At what offset into the frame to start looking.
+ * at offset was reached (whether or not we found a place to stop).
+ *
+ * @return
+ * * STOP: An appropriate offset was found within this frame,
+ * and is given by aOffset.
+ * * CONTINUE: Not found within this frame, need to try the next frame.
+ * See enum FrameSearchResult for more details.
+ */
+ virtual FrameSearchResult PeekOffsetNoAmount(bool aForward, int32_t* aOffset);
+
+ /**
+ * Search the frame for the next character
+ *
+ * @param aForward [in] Are we moving forward (or backward) in content order.
+ *
+ * @param aOffset [in/out] At what offset into the frame to start looking.
+ * on output - what offset was reached (whether or not we found a place to
+ * stop).
+ *
+ * @param aOptions [in] Options, see the comment in PeekOffsetCharacterOptions
+ * for the detail.
+ *
+ * @return
+ * * STOP: An appropriate offset was found within this frame, and is given
+ * by aOffset.
+ * * CONTINUE: Not found within this frame, need to try the next frame. See
+ * enum FrameSearchResult for more details.
+ */
+ virtual FrameSearchResult PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset,
+ PeekOffsetCharacterOptions aOptions = PeekOffsetCharacterOptions());
+ static_assert(sizeof(PeekOffsetCharacterOptions) <= sizeof(intptr_t),
+ "aOptions should be changed to const reference");
+
+ struct PeekWordState {
+ // true when we're still at the start of the search, i.e., we can't return
+ // this point as a valid offset!
+ bool mAtStart;
+ // true when we've encountered at least one character of the type before the
+ // boundary we're looking for:
+ // 1. If we're moving forward and eating whitepace, looking for a word
+ // beginning (i.e. a boundary between whitespace and non-whitespace),
+ // then mSawBeforeType==true means "we already saw some whitespace".
+ // 2. Otherwise, looking for a word beginning (i.e. a boundary between
+ // non-whitespace and whitespace), then mSawBeforeType==true means "we
+ // already saw some non-whitespace".
+ bool mSawBeforeType;
+ // true when we've encountered at least one non-newline character
+ bool mSawInlineCharacter;
+ // true when the last character encountered was punctuation
+ bool mLastCharWasPunctuation;
+ // true when the last character encountered was whitespace
+ bool mLastCharWasWhitespace;
+ // true when we've seen non-punctuation since the last whitespace
+ bool mSeenNonPunctuationSinceWhitespace;
+ // text that's *before* the current frame when aForward is true, *after*
+ // the current frame when aForward is false. Only includes the text
+ // on the current line.
+ nsAutoString mContext;
+
+ PeekWordState()
+ : mAtStart(true),
+ mSawBeforeType(false),
+ mSawInlineCharacter(false),
+ mLastCharWasPunctuation(false),
+ mLastCharWasWhitespace(false),
+ mSeenNonPunctuationSinceWhitespace(false) {}
+ void SetSawBeforeType() { mSawBeforeType = true; }
+ void SetSawInlineCharacter() { mSawInlineCharacter = true; }
+ void Update(bool aAfterPunctuation, bool aAfterWhitespace) {
+ mLastCharWasPunctuation = aAfterPunctuation;
+ mLastCharWasWhitespace = aAfterWhitespace;
+ if (aAfterWhitespace) {
+ mSeenNonPunctuationSinceWhitespace = false;
+ } else if (!aAfterPunctuation) {
+ mSeenNonPunctuationSinceWhitespace = true;
+ }
+ mAtStart = false;
+ }
+ };
+
+ /**
+ * Search the frame for the next word boundary
+ * @param aForward [in] Are we moving forward (or backward) in content order.
+ * @param aWordSelectEatSpace [in] true: look for non-whitespace following
+ * whitespace (in the direction of movement).
+ * false: look for whitespace following non-whitespace (in the
+ * direction of movement).
+ * @param aIsKeyboardSelect [in] Was the action initiated by a keyboard
+ * operation? If true, punctuation immediately following a word is considered
+ * part of that word. Otherwise, a sequence of punctuation is always
+ * considered as a word on its own.
+ * @param aOffset [in/out] At what offset into the frame to start looking.
+ * on output - what offset was reached (whether or not we found a
+ * place to stop).
+ * @param aState [in/out] the state that is carried from frame to frame
+ */
+ virtual FrameSearchResult PeekOffsetWord(
+ bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
+ int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces);
+
+ protected:
+ /**
+ * Check whether we should break at a boundary between punctuation and
+ * non-punctuation. Only call it at a punctuation boundary
+ * (i.e. exactly one of the previous and next characters are punctuation).
+ * @param aForward true if we're moving forward in content order
+ * @param aPunctAfter true if the next character is punctuation
+ * @param aWhitespaceAfter true if the next character is whitespace
+ */
+ static bool BreakWordBetweenPunctuation(const PeekWordState* aState,
+ bool aForward, bool aPunctAfter,
+ bool aWhitespaceAfter,
+ bool aIsKeyboardSelect);
+
+ private:
+ nsRect GetOverflowRect(mozilla::OverflowType aType) const;
+
+ // Get a pointer to the overflow areas property attached to the frame.
+ mozilla::OverflowAreas* GetOverflowAreasProperty() const {
+ MOZ_ASSERT(mOverflow.mType == OverflowStorageType::Large);
+ mozilla::OverflowAreas* overflow = GetProperty(OverflowAreasProperty());
+ MOZ_ASSERT(overflow);
+ return overflow;
+ }
+
+ nsRect InkOverflowFromDeltas() const {
+ MOZ_ASSERT(mOverflow.mType != OverflowStorageType::Large,
+ "should not be called when overflow is in a property");
+ // Calculate the rect using deltas from the frame's border rect.
+ // Note that the mOverflow.mInkOverflowDeltas fields are unsigned, but we
+ // will often need to return negative values for the left and top, so take
+ // care to cast away the unsigned-ness.
+ return nsRect(-(int32_t)mOverflow.mInkOverflowDeltas.mLeft,
+ -(int32_t)mOverflow.mInkOverflowDeltas.mTop,
+ mRect.Width() + mOverflow.mInkOverflowDeltas.mRight +
+ mOverflow.mInkOverflowDeltas.mLeft,
+ mRect.Height() + mOverflow.mInkOverflowDeltas.mBottom +
+ mOverflow.mInkOverflowDeltas.mTop);
+ }
+
+ /**
+ * Set the OverflowArea rect, storing it as deltas or a separate rect
+ * depending on its size in relation to the primary frame rect.
+ *
+ * @return true if any overflow changed.
+ */
+ bool SetOverflowAreas(const mozilla::OverflowAreas& aOverflowAreas);
+
+ bool HasOpacityInternal(float aThreshold, const nsStyleDisplay* aStyleDisplay,
+ const nsStyleEffects* aStyleEffects,
+ mozilla::EffectSet* aEffectSet = nullptr) const;
+
+ static constexpr size_t kFrameClassCount =
+#define FRAME_ID(...) 1 +
+#define ABSTRACT_FRAME_ID(...)
+#include "mozilla/FrameIdList.h"
+#undef FRAME_ID
+#undef ABSTRACT_FRAME_ID
+ 0;
+
+ // Maps mClass to LayoutFrameType.
+ static const mozilla::LayoutFrameType sLayoutFrameTypes[kFrameClassCount];
+ // Maps mClass to LayoutFrameTypeFlags.
+ static const ClassFlags sLayoutFrameClassFlags[kFrameClassCount];
+
+#ifdef DEBUG_FRAME_DUMP
+ public:
+ static void IndentBy(FILE* out, int32_t aIndent) {
+ while (--aIndent >= 0) fputs(" ", out);
+ }
+ void ListTag(FILE* out) const { fputs(ListTag().get(), out); }
+ nsAutoCString ListTag() const;
+
+ enum class ListFlag{TraverseSubdocumentFrames, DisplayInCSSPixels};
+ using ListFlags = mozilla::EnumSet<ListFlag>;
+
+ template <typename T>
+ static std::string ConvertToString(const T& aValue, ListFlags aFlags) {
+ // This method can convert all physical types in app units to CSS pixels.
+ return aFlags.contains(ListFlag::DisplayInCSSPixels)
+ ? mozilla::ToString(mozilla::CSSPixel::FromAppUnits(aValue))
+ : mozilla::ToString(aValue);
+ }
+ static std::string ConvertToString(const mozilla::LogicalRect& aRect,
+ const mozilla::WritingMode aWM,
+ ListFlags aFlags);
+ static std::string ConvertToString(const mozilla::LogicalSize& aSize,
+ const mozilla::WritingMode aWM,
+ ListFlags aFlags);
+
+ void ListGeneric(nsACString& aTo, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const;
+ virtual void List(FILE* out = stderr, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const;
+
+ void ListTextRuns(FILE* out = stderr) const;
+ virtual void ListTextRuns(FILE* out, nsTHashSet<const void*>& aSeen) const;
+
+ virtual void ListWithMatchedRules(FILE* out = stderr,
+ const char* aPrefix = "") const;
+ void ListMatchedRules(FILE* out, const char* aPrefix) const;
+
+ /**
+ * Dump the frame tree beginning from the root frame.
+ */
+ void DumpFrameTree() const;
+ void DumpFrameTreeInCSSPixels() const;
+
+ /**
+ * Dump the frame tree beginning from ourselves.
+ */
+ void DumpFrameTreeLimited() const;
+ void DumpFrameTreeLimitedInCSSPixels() const;
+
+ /**
+ * Get a printable from of the name of the frame type.
+ * XXX This should be eliminated and we use GetType() instead...
+ */
+ virtual nsresult GetFrameName(nsAString& aResult) const;
+ nsresult MakeFrameName(const nsAString& aType, nsAString& aResult) const;
+ // Helper function to return the index in parent of the frame's content
+ // object. Returns Nothing on error or if the frame doesn't have a content
+ // object
+ static mozilla::Maybe<uint32_t> ContentIndexInContainer(
+ const nsIFrame* aFrame);
+#endif
+
+#ifdef DEBUG
+ /**
+ * Tracing method that writes a method enter/exit routine to the
+ * nspr log using the nsIFrame log module. The tracing is only
+ * done when the NS_FRAME_TRACE_CALLS bit is set in the log module's
+ * level field.
+ */
+ void Trace(const char* aMethod, bool aEnter);
+ void Trace(const char* aMethod, bool aEnter, const nsReflowStatus& aStatus);
+ void TraceMsg(const char* aFormatString, ...) MOZ_FORMAT_PRINTF(2, 3);
+
+ // Helper function that verifies that each frame in the list has the
+ // NS_FRAME_IS_DIRTY bit set
+ static void VerifyDirtyBitSet(const nsFrameList& aFrameList);
+
+ // Display Reflow Debugging
+ static void* DisplayReflowEnter(nsPresContext* aPresContext, nsIFrame* aFrame,
+ const ReflowInput& aReflowInput);
+ static void* DisplayLayoutEnter(nsIFrame* aFrame);
+ static void* DisplayIntrinsicISizeEnter(nsIFrame* aFrame, const char* aType);
+ static void* DisplayIntrinsicSizeEnter(nsIFrame* aFrame, const char* aType);
+ static void DisplayReflowExit(nsPresContext* aPresContext, nsIFrame* aFrame,
+ ReflowOutput& aMetrics,
+ const nsReflowStatus& aStatus,
+ void* aFrameTreeNode);
+ static void DisplayLayoutExit(nsIFrame* aFrame, void* aFrameTreeNode);
+ static void DisplayIntrinsicISizeExit(nsIFrame* aFrame, const char* aType,
+ nscoord aResult, void* aFrameTreeNode);
+ static void DisplayIntrinsicSizeExit(nsIFrame* aFrame, const char* aType,
+ nsSize aResult, void* aFrameTreeNode);
+
+ static void DisplayReflowStartup();
+ static void DisplayReflowShutdown();
+
+ static mozilla::LazyLogModule sFrameLogModule;
+#endif
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsIFrame::ReflowChildFlags)
+
+//----------------------------------------------------------------------
+
+/**
+ * AutoWeakFrame can be used to keep a reference to a nsIFrame in a safe way.
+ * Whenever an nsIFrame object is deleted, the AutoWeakFrames pointing
+ * to it will be cleared. AutoWeakFrame is for variables on the stack or
+ * in static storage only, there is also a WeakFrame below for heap uses.
+ *
+ * Create AutoWeakFrame object when it is sure that nsIFrame object
+ * is alive and after some operations which may destroy the nsIFrame
+ * (for example any DOM modifications) use IsAlive() or GetFrame() methods to
+ * check whether it is safe to continue to use the nsIFrame object.
+ *
+ * @note The usage of this class should be kept to a minimum.
+ */
+class WeakFrame;
+class MOZ_NONHEAP_CLASS AutoWeakFrame {
+ public:
+ explicit AutoWeakFrame() : mPrev(nullptr), mFrame(nullptr) {}
+
+ AutoWeakFrame(const AutoWeakFrame& aOther) : mPrev(nullptr), mFrame(nullptr) {
+ Init(aOther.GetFrame());
+ }
+
+ MOZ_IMPLICIT AutoWeakFrame(const WeakFrame& aOther);
+
+ MOZ_IMPLICIT AutoWeakFrame(nsIFrame* aFrame)
+ : mPrev(nullptr), mFrame(nullptr) {
+ Init(aFrame);
+ }
+
+ AutoWeakFrame& operator=(AutoWeakFrame& aOther) {
+ Init(aOther.GetFrame());
+ return *this;
+ }
+
+ AutoWeakFrame& operator=(nsIFrame* aFrame) {
+ Init(aFrame);
+ return *this;
+ }
+
+ nsIFrame* operator->() { return mFrame; }
+
+ operator nsIFrame*() { return mFrame; }
+
+ void Clear(mozilla::PresShell* aPresShell);
+
+ bool IsAlive() const { return !!mFrame; }
+
+ nsIFrame* GetFrame() const { return mFrame; }
+
+ AutoWeakFrame* GetPreviousWeakFrame() { return mPrev; }
+
+ void SetPreviousWeakFrame(AutoWeakFrame* aPrev) { mPrev = aPrev; }
+
+ ~AutoWeakFrame();
+
+ private:
+ // Not available for the heap!
+ void* operator new(size_t) = delete;
+ void* operator new[](size_t) = delete;
+ void operator delete(void*) = delete;
+ void operator delete[](void*) = delete;
+
+ void Init(nsIFrame* aFrame);
+
+ AutoWeakFrame* mPrev;
+ nsIFrame* mFrame;
+};
+
+// Use nsIFrame's fast-path to avoid QueryFrame:
+inline do_QueryFrameHelper<nsIFrame> do_QueryFrame(AutoWeakFrame& s) {
+ return do_QueryFrameHelper<nsIFrame>(s.GetFrame());
+}
+
+/**
+ * @see AutoWeakFrame
+ */
+class MOZ_HEAP_CLASS WeakFrame {
+ public:
+ WeakFrame() : mFrame(nullptr) {}
+
+ WeakFrame(const WeakFrame& aOther) : mFrame(nullptr) {
+ Init(aOther.GetFrame());
+ }
+
+ MOZ_IMPLICIT WeakFrame(const AutoWeakFrame& aOther) : mFrame(nullptr) {
+ Init(aOther.GetFrame());
+ }
+
+ MOZ_IMPLICIT WeakFrame(nsIFrame* aFrame) : mFrame(nullptr) { Init(aFrame); }
+
+ ~WeakFrame() {
+ Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
+ }
+
+ WeakFrame& operator=(WeakFrame& aOther) {
+ Init(aOther.GetFrame());
+ return *this;
+ }
+
+ WeakFrame& operator=(nsIFrame* aFrame) {
+ Init(aFrame);
+ return *this;
+ }
+
+ nsIFrame* operator->() { return mFrame; }
+ operator nsIFrame*() { return mFrame; }
+
+ bool operator==(nsIFrame* const aOther) const { return mFrame == aOther; }
+
+ void Clear(mozilla::PresShell* aPresShell);
+
+ bool IsAlive() const { return !!mFrame; }
+ nsIFrame* GetFrame() const { return mFrame; }
+
+ private:
+ void Init(nsIFrame* aFrame);
+
+ nsIFrame* mFrame;
+};
+
+// Use nsIFrame's fast-path to avoid QueryFrame:
+inline do_QueryFrameHelper<nsIFrame> do_QueryFrame(WeakFrame& s) {
+ return do_QueryFrameHelper<nsIFrame>(s.GetFrame());
+}
+
+inline bool nsFrameList::ContinueRemoveFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(!aFrame->GetPrevSibling() || !aFrame->GetNextSibling(),
+ "Forgot to call StartRemoveFrame?");
+ if (aFrame == mLastChild) {
+ MOZ_ASSERT(!aFrame->GetNextSibling(), "broken frame list");
+ nsIFrame* prevSibling = aFrame->GetPrevSibling();
+ if (!prevSibling) {
+ MOZ_ASSERT(aFrame == mFirstChild, "broken frame list");
+ mFirstChild = mLastChild = nullptr;
+ return true;
+ }
+ MOZ_ASSERT(prevSibling->GetNextSibling() == aFrame, "Broken frame linkage");
+ prevSibling->SetNextSibling(nullptr);
+ mLastChild = prevSibling;
+ return true;
+ }
+ if (aFrame == mFirstChild) {
+ MOZ_ASSERT(!aFrame->GetPrevSibling(), "broken frame list");
+ mFirstChild = aFrame->GetNextSibling();
+ aFrame->SetNextSibling(nullptr);
+ MOZ_ASSERT(mFirstChild, "broken frame list");
+ return true;
+ }
+ return false;
+}
+
+inline bool nsFrameList::StartRemoveFrame(nsIFrame* aFrame) {
+ if (aFrame->GetPrevSibling() && aFrame->GetNextSibling()) {
+ UnhookFrameFromSiblings(aFrame);
+ return true;
+ }
+ return ContinueRemoveFrame(aFrame);
+}
+
+// Operators of nsFrameList::Iterator
+// ---------------------------------------------------
+
+inline nsIFrame* nsFrameList::ForwardFrameTraversal::Next(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ return aFrame->GetNextSibling();
+}
+inline nsIFrame* nsFrameList::ForwardFrameTraversal::Prev(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ return aFrame->GetPrevSibling();
+}
+
+inline nsIFrame* nsFrameList::BackwardFrameTraversal::Next(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ return aFrame->GetPrevSibling();
+}
+inline nsIFrame* nsFrameList::BackwardFrameTraversal::Prev(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ return aFrame->GetNextSibling();
+}
+
+#endif /* nsIFrame_h___ */
diff --git a/layout/generic/nsIFrameInlines.h b/layout/generic/nsIFrameInlines.h
new file mode 100644
index 0000000000..e187f0dfb0
--- /dev/null
+++ b/layout/generic/nsIFrameInlines.h
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIFrameInlines_h___
+#define nsIFrameInlines_h___
+
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "nsContainerFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsFrameManager.h"
+
+bool nsIFrame::IsFlexItem() const {
+ return GetParent() && GetParent()->IsFlexContainerFrame() &&
+ !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+}
+
+bool nsIFrame::IsGridItem() const {
+ return GetParent() && GetParent()->IsGridContainerFrame() &&
+ !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+}
+
+bool nsIFrame::IsFlexOrGridContainer() const {
+ return IsFlexContainerFrame() || IsGridContainerFrame();
+}
+
+bool nsIFrame::IsFlexOrGridItem() const {
+ return !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && GetParent() &&
+ GetParent()->IsFlexOrGridContainer();
+}
+
+bool nsIFrame::IsMasonry(mozilla::LogicalAxis aAxis) const {
+ MOZ_DIAGNOSTIC_ASSERT(IsGridContainerFrame());
+ return HasAnyStateBits(aAxis == mozilla::eLogicalAxisBlock
+ ? NS_STATE_GRID_IS_ROW_MASONRY
+ : NS_STATE_GRID_IS_COL_MASONRY);
+}
+
+bool nsIFrame::IsTableCaption() const {
+ return StyleDisplay()->mDisplay == mozilla::StyleDisplay::TableCaption &&
+ GetParent()->Style()->GetPseudoType() ==
+ mozilla::PseudoStyleType::tableWrapper;
+}
+
+bool nsIFrame::IsFloating() const {
+ return HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ StyleDisplay()->IsFloating(this);
+}
+
+bool nsIFrame::IsAbsPosContainingBlock() const {
+ return Style()->IsAbsPosContainingBlock(this);
+}
+
+bool nsIFrame::IsFixedPosContainingBlock() const {
+ return Style()->IsFixedPosContainingBlock(this);
+}
+
+bool nsIFrame::IsRelativelyOrStickyPositioned() const {
+ return StyleDisplay()->IsRelativelyOrStickyPositioned(this);
+}
+
+bool nsIFrame::IsRelativelyPositioned() const {
+ return StyleDisplay()->IsRelativelyPositioned(this);
+}
+
+bool nsIFrame::IsStickyPositioned() const {
+ return StyleDisplay()->IsStickyPositioned(this);
+}
+
+bool nsIFrame::IsAbsolutelyPositioned(
+ const nsStyleDisplay* aStyleDisplay) const {
+ return HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ StyleDisplayWithOptionalParam(aStyleDisplay)
+ ->IsAbsolutelyPositioned(this);
+}
+
+inline bool nsIFrame::IsTrueOverflowContainer() const {
+ return HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
+ !IsAbsolutelyPositioned();
+ // XXXfr This check isn't quite correct, because it doesn't handle cases
+ // where the out-of-flow has overflow.. but that's rare.
+ // We'll need to revisit the way abspos continuations are handled later
+ // for various reasons, this detail is one of them. See bug 154892
+}
+
+bool nsIFrame::IsBlockOutside() const {
+ return StyleDisplay()->IsBlockOutside(this);
+}
+
+bool nsIFrame::IsInlineOutside() const {
+ return StyleDisplay()->IsInlineOutside(this);
+}
+
+bool nsIFrame::IsColumnSpan() const {
+ return IsBlockOutside() && StyleColumn()->IsColumnSpanStyle();
+}
+
+bool nsIFrame::IsColumnSpanInMulticolSubtree() const {
+ return IsColumnSpan() &&
+ (HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
+ // A frame other than inline and block won't have
+ // NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR. We instead test its parent.
+ (GetParent() && GetParent()->Style()->GetPseudoType() ==
+ mozilla::PseudoStyleType::columnSpanWrapper));
+}
+
+mozilla::StyleDisplay nsIFrame::GetDisplay() const {
+ return StyleDisplay()->GetDisplay(this);
+}
+
+void nsIFrame::PropagateWritingModeToSelfAndAncestors(
+ mozilla::WritingMode aWM) {
+ MOZ_ASSERT(IsCanvasFrame());
+ for (auto f = this; f; f = f->GetParent()) {
+ f->mWritingMode = aWM;
+ }
+}
+
+nsContainerFrame* nsIFrame::GetInFlowParent() const {
+ if (HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ nsIFrame* ph =
+ FirstContinuation()->GetProperty(nsIFrame::PlaceholderFrameProperty());
+ return ph->GetParent();
+ }
+
+ return GetParent();
+}
+
+// We generally want to follow the style tree for preserve-3d, jumping through
+// display: contents.
+//
+// There are various fun mismatches between the flattened tree and the frame
+// tree which makes this non-trivial to do looking at the frame tree state:
+//
+// - Anon boxes. You'd have to step through them, because you generally want to
+// ignore them.
+//
+// - IB-splits, which produce a frame tree where frames for the block inside
+// the inline are not children of any frame from the inline.
+//
+// - display: contents, which makes DOM ancestors not have frames even when a
+// descendant does.
+//
+// See GetFlattenedTreeParentElementForStyle for the difference between it and
+// plain GetFlattenedTreeParentElement.
+nsIFrame* nsIFrame::GetClosestFlattenedTreeAncestorPrimaryFrame() const {
+ if (!mContent) {
+ return nullptr;
+ }
+ mozilla::dom::Element* parent =
+ mContent->GetFlattenedTreeParentElementForStyle();
+ while (parent) {
+ if (nsIFrame* frame = parent->GetPrimaryFrame()) {
+ return frame;
+ }
+ // NOTE(emilio): This should be an assert except we have code in tree which
+ // violates invariants like the <frameset> frame construction code.
+ if (MOZ_UNLIKELY(!parent->IsDisplayContents())) {
+ return nullptr;
+ }
+ parent = parent->GetFlattenedTreeParentElementForStyle();
+ }
+ return nullptr;
+}
+
+nsPoint nsIFrame::GetNormalPosition(bool* aHasProperty) const {
+ bool hasProperty;
+ nsPoint normalPosition = GetProperty(NormalPositionProperty(), &hasProperty);
+ if (aHasProperty) {
+ *aHasProperty = hasProperty;
+ }
+ return hasProperty ? normalPosition : GetPosition();
+}
+
+mozilla::LogicalPoint nsIFrame::GetLogicalNormalPosition(
+ mozilla::WritingMode aWritingMode, const nsSize& aContainerSize) const {
+ // Subtract the size of this frame from the container size to get
+ // the correct position in rtl frames where the origin is on the
+ // right instead of the left
+ return mozilla::LogicalPoint(aWritingMode, GetNormalPosition(),
+ aContainerSize - mRect.Size());
+}
+
+#endif
diff --git a/layout/generic/nsILineIterator.cpp b/layout/generic/nsILineIterator.cpp
new file mode 100644
index 0000000000..7a340ad4c6
--- /dev/null
+++ b/layout/generic/nsILineIterator.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsILineIterator.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+
+void LineFrameFinder::Scan(nsIFrame* aFrame) {
+ if (mDone) {
+ return;
+ }
+
+ if (!mFirstFrame) {
+ mFirstFrame = aFrame;
+ }
+
+ const LogicalRect rect = aFrame->GetLogicalRect(mWM, mContainerSize);
+ if (rect.ISize(mWM) == 0) {
+ return;
+ }
+ // If pos.I() is inside this frame - this is it
+ if (rect.IStart(mWM) <= mPos.I(mWM) && rect.IEnd(mWM) > mPos.I(mWM)) {
+ mClosestFromStart = mClosestFromEnd = aFrame;
+ mDone = true;
+ return;
+ }
+ if (rect.IStart(mWM) < mPos.I(mWM)) {
+ if (!mClosestFromStart ||
+ rect.IEnd(mWM) >
+ mClosestFromStart->GetLogicalRect(mWM, mContainerSize).IEnd(mWM)) {
+ mClosestFromStart = aFrame;
+ }
+ } else {
+ if (!mClosestFromEnd ||
+ rect.IStart(mWM) <
+ mClosestFromEnd->GetLogicalRect(mWM, mContainerSize).IStart(mWM)) {
+ mClosestFromEnd = aFrame;
+ }
+ }
+}
+
+void LineFrameFinder::Finish(nsIFrame** aFrameFound,
+ bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) {
+ if (!mClosestFromStart && !mClosestFromEnd) {
+ // All frames were zero-width. Just take the first one.
+ mClosestFromStart = mClosestFromEnd = mFirstFrame;
+ }
+ *aPosIsBeforeFirstFrame = mIsReversed ? !mClosestFromEnd : !mClosestFromStart;
+ *aPosIsAfterLastFrame = mIsReversed ? !mClosestFromStart : !mClosestFromEnd;
+ if (mClosestFromStart == mClosestFromEnd) {
+ *aFrameFound = mClosestFromStart;
+ } else if (!mClosestFromStart) {
+ *aFrameFound = mClosestFromEnd;
+ } else if (!mClosestFromEnd) {
+ *aFrameFound = mClosestFromStart;
+ } else { // we're between two frames
+ nscoord delta =
+ mClosestFromEnd->GetLogicalRect(mWM, mContainerSize).IStart(mWM) -
+ mClosestFromStart->GetLogicalRect(mWM, mContainerSize).IEnd(mWM);
+ if (mPos.I(mWM) <
+ mClosestFromStart->GetLogicalRect(mWM, mContainerSize).IEnd(mWM) +
+ delta / 2) {
+ *aFrameFound = mClosestFromStart;
+ } else {
+ *aFrameFound = mClosestFromEnd;
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/generic/nsILineIterator.h b/layout/generic/nsILineIterator.h
new file mode 100644
index 0000000000..743eaaffae
--- /dev/null
+++ b/layout/generic/nsILineIterator.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsILineIterator_h___
+#define nsILineIterator_h___
+
+#include "nscore.h"
+#include "nsINode.h"
+#include "nsRect.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Result.h"
+#include "mozilla/WritingModes.h"
+
+class nsIFrame;
+
+/**
+ * Line iterator API.
+ *
+ * Lines are numbered from 0 to N, where 0 is the top line and N is
+ * the bottom line.
+ *
+ * Obtain this interface from frames via nsIFrame::GetLineIterator.
+ * This iterator belongs to the frame from which it was obtained, and should
+ * not be deleted by the caller.
+ * Note that any modification of the frame will invalidate the iterator!
+ * Users must get a new iterator any time the target may have been touched.
+ */
+class nsILineIterator {
+ protected:
+ ~nsILineIterator() = default;
+
+ public:
+ /**
+ * The number of lines in the block
+ */
+ virtual int32_t GetNumLines() const = 0;
+
+ /**
+ * Returns whether our lines are rtl.
+ */
+ virtual bool IsLineIteratorFlowRTL() = 0;
+
+ struct LineInfo {
+ /** The first frame on the line. */
+ nsIFrame* mFirstFrameOnLine = nullptr;
+ /** The numbers of frames on the line. */
+ int32_t mNumFramesOnLine = 0;
+ /**
+ * The bounding box of the line (which is based on the in-flow position of
+ * the frames on the line; if a frame was moved because of relative
+ * positioning then its coordinates may be outside the line bounds)
+ */
+ nsRect mLineBounds;
+ /** Whether the line is wrapped at the end */
+ bool mIsWrapped = false;
+
+ /**
+ * Return last frame of the line if there is no enough siblings of
+ * mFirstFrameOnLine.
+ * Otherwise, nullptr including in the unexpected error cases.
+ */
+ nsIFrame* GetLastFrameOnLine() const;
+ };
+
+ // Return miscellaneous information about a line.
+ virtual mozilla::Result<LineInfo, nsresult> GetLine(int32_t aLineNumber) = 0;
+
+ /**
+ * Given a frame that's a child of the block, find which line its on
+ * and return that line index, as long as it's at least as big as
+ * aStartLine. Returns -1 if the frame cannot be found on lines
+ * starting with aStartLine.
+ */
+ virtual int32_t FindLineContaining(nsIFrame* aFrame,
+ int32_t aStartLine = 0) = 0;
+
+ // Given a line number and a coordinate, find the frame on the line
+ // that is nearest to aPos along the inline axis. (The block-axis coord
+ // of aPos is irrelevant.)
+ // The aPosIsBeforeFirstFrame and aPosIsAfterLastFrame flags are updated
+ // appropriately.
+ NS_IMETHOD FindFrameAt(int32_t aLineNumber, nsPoint aPos,
+ nsIFrame** aFrameFound, bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) = 0;
+
+ // Check whether visual and logical order of frames within a line are
+ // identical.
+ // If not, return the first and last visual frames
+ NS_IMETHOD CheckLineOrder(int32_t aLine, bool* aIsReordered,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) = 0;
+};
+
+namespace mozilla {
+
+// Helper struct for FindFrameAt.
+struct LineFrameFinder {
+ LineFrameFinder(const nsPoint& aPos, const nsSize& aContainerSize,
+ WritingMode aWM, bool aIsReversed)
+ : mPos(aWM, aPos, aContainerSize),
+ mContainerSize(aContainerSize),
+ mWM(aWM),
+ mIsReversed(aIsReversed) {}
+
+ void Scan(nsIFrame*);
+ void Finish(nsIFrame**, bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame);
+
+ const LogicalPoint mPos;
+ const nsSize mContainerSize;
+ const WritingMode mWM;
+ const bool mIsReversed;
+
+ bool IsDone() const { return mDone; }
+
+ private:
+ bool mDone = false;
+ nsIFrame* mFirstFrame = nullptr;
+ nsIFrame* mClosestFromStart = nullptr;
+ nsIFrame* mClosestFromEnd = nullptr;
+};
+
+} // namespace mozilla
+
+/**
+ * Helper intended to be used in a scope where we're using an nsILineIterator
+ * and want to verify that no DOM mutations (which would invalidate the
+ * iterator) occur while we're using it.
+ */
+class MOZ_STACK_CLASS AutoAssertNoDomMutations final {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsMutationGuard mGuard;
+#endif
+ public:
+ ~AutoAssertNoDomMutations() { MOZ_DIAGNOSTIC_ASSERT(!mGuard.Mutated(0)); }
+};
+
+#endif /* nsILineIterator_h___ */
diff --git a/layout/generic/nsIScrollPositionListener.h b/layout/generic/nsIScrollPositionListener.h
new file mode 100644
index 0000000000..eadae2fe0c
--- /dev/null
+++ b/layout/generic/nsIScrollPositionListener.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIScrollPositionListener_h___
+#define nsIScrollPositionListener_h___
+
+#include "nsCoord.h"
+
+/**
+ * Provides a way to learn about scroll position changes of
+ * nsIScrollableFrame's.
+ */
+class nsIScrollPositionListener {
+ public:
+ virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) = 0;
+ virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) = 0;
+};
+
+#endif /* nsIScrollPositionListener_h___ */
diff --git a/layout/generic/nsIScrollableFrame.h b/layout/generic/nsIScrollableFrame.h
new file mode 100644
index 0000000000..8d8f20030e
--- /dev/null
+++ b/layout/generic/nsIScrollableFrame.h
@@ -0,0 +1,645 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * interface that provides scroll APIs implemented by scrollable frames
+ */
+
+#ifndef nsIScrollFrame_h___
+#define nsIScrollFrame_h___
+
+#include "nsCoord.h"
+#include "mozilla/dom/WindowBinding.h" // for mozilla::dom::ScrollBehavior
+#include "mozilla/Maybe.h"
+#include "mozilla/ScrollOrigin.h"
+#include "mozilla/ScrollPositionUpdate.h"
+#include "mozilla/ScrollStyles.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/gfx/Point.h"
+#include "nsIScrollbarMediator.h"
+#include "Units.h"
+#include "FrameMetrics.h"
+
+class gfxContext;
+class nsIScrollPositionListener;
+class nsIFrame;
+class nsPresContext;
+class nsIContent;
+
+namespace mozilla {
+class DisplayItemClip;
+class nsDisplayListBuilder;
+enum class StyleScrollSnapAlignKeyword : uint8_t;
+
+namespace layers {
+struct ScrollMetadata;
+class Layer;
+class WebRenderLayerManager;
+} // namespace layers
+namespace layout {
+class ScrollAnchorContainer;
+} // namespace layout
+} // namespace mozilla
+
+/**
+ * Interface for frames that are scrollable. This interface exposes
+ * APIs for examining scroll state, observing changes to scroll state,
+ * and triggering scrolling.
+ */
+class nsIScrollableFrame : public nsIScrollbarMediator {
+ public:
+ using CSSIntPoint = mozilla::CSSIntPoint;
+ using ScrollSnapInfo = mozilla::ScrollSnapInfo;
+ using ScrollAnchorContainer = mozilla::layout::ScrollAnchorContainer;
+ using ScrollMode = mozilla::ScrollMode;
+ using ScrollOrigin = mozilla::ScrollOrigin;
+ using PhysicalScrollSnapAlign =
+ std::pair<mozilla::StyleScrollSnapAlignKeyword,
+ mozilla::StyleScrollSnapAlignKeyword>;
+
+ NS_DECL_QUERYFRAME_TARGET(nsIScrollableFrame)
+
+ /**
+ * Get the frame for the content that we are scrolling within
+ * this scrollable frame.
+ */
+ virtual nsIFrame* GetScrolledFrame() const = 0;
+
+ /**
+ * Get the overflow styles (StyleOverflow::Scroll, StyleOverflow::Hidden, or
+ * StyleOverflow::Auto) governing the horizontal and vertical scrollbars for
+ * this frame.
+ *
+ * This is special because they can be propagated from the <body> element,
+ * unlike other styles.
+ */
+ virtual mozilla::ScrollStyles GetScrollStyles() const = 0;
+
+ /**
+ * Returns whether this scroll frame is for a text control element with no
+ * scrollbars (for <input>, basically).
+ */
+ virtual bool IsForTextControlWithNoScrollbars() const = 0;
+
+ /**
+ * Returns whether we already have anonymous content nodes for all our needed
+ * scrollbar parts (or a superset thereof).
+ */
+ virtual bool HasAllNeededScrollbars() const = 0;
+
+ /**
+ * Get the overscroll-behavior styles.
+ */
+ virtual mozilla::layers::OverscrollBehaviorInfo GetOverscrollBehaviorInfo()
+ const = 0;
+
+ /**
+ * Return the scrollbars which are visible. It's OK to call this during reflow
+ * of the scrolled contents, in which case it will reflect the current
+ * assumptions about scrollbar visibility.
+ */
+ virtual mozilla::layers::ScrollDirections GetScrollbarVisibility() const = 0;
+ /**
+ * Returns the directions in which scrolling is allowed (if the scroll range
+ * is at least one device pixel in that direction).
+ */
+ mozilla::layers::ScrollDirections GetAvailableScrollingDirections() const;
+ /**
+ * Returns the directions in which scrolling is allowed when taking into
+ * account the visual viewport size and overflow hidden. (An (apz) zoomed in
+ * overflow hidden scrollframe is actually user scrollable.)
+ */
+ virtual mozilla::layers::ScrollDirections
+ GetAvailableScrollingDirectionsForUserInputEvents() const = 0;
+ /**
+ * Return the actual sizes of all possible scrollbars. Returns 0 for scrollbar
+ * positions that don't have a scrollbar or where the scrollbar is not
+ * visible. Do not call this while this frame's descendants are being
+ * reflowed, it won't be accurate.
+ * INCLUDE_VISUAL_VIEWPORT_SCROLLBARS means we include the size of layout
+ * scrollbars that are only visible to scroll the visual viewport inside the
+ * layout viewport (ie the layout viewport cannot be scrolled) even though
+ * there is no layout space set aside for these scrollbars.
+ */
+ enum class ScrollbarSizesOptions { NONE, INCLUDE_VISUAL_VIEWPORT_SCROLLBARS };
+ virtual nsMargin GetActualScrollbarSizes(
+ ScrollbarSizesOptions aOptions = ScrollbarSizesOptions::NONE) const = 0;
+ /**
+ * Return the sizes of all scrollbars assuming that any scrollbars that could
+ * be visible due to overflowing content, are. This can be called during
+ * reflow of the scrolled contents.
+ */
+ virtual nsMargin GetDesiredScrollbarSizes() const = 0;
+ /**
+ * Get the layout size of this frame.
+ * Note that this is a value which is not expanded by the minimum scale size.
+ * For scroll frames other than the root content document's scroll frame, this
+ * value will be the same as GetScrollPortRect().Size().
+ *
+ * This value is used for Element.clientWidth and clientHeight.
+ */
+ virtual nsSize GetLayoutSize() const = 0;
+ /**
+ * GetScrolledRect is designed to encapsulate deciding which
+ * directions of overflow should be reachable by scrolling and which
+ * should not. Callers should NOT depend on it having any particular
+ * behavior.
+ *
+ * This should only be called when the scrolled frame has been
+ * reflowed with the scroll port size given in mScrollPort.
+ *
+ * Currently it allows scrolling down and to the right for
+ * nsHTMLScrollFrames with LTR directionality, and allows scrolling down and
+ * to the left for nsHTMLScrollFrames with RTL directionality.
+ */
+ virtual nsRect GetScrolledRect() const = 0;
+ /**
+ * Get the area of the scrollport relative to the origin of this frame's
+ * border-box.
+ * This is the area of this frame minus border and scrollbars.
+ */
+ virtual nsRect GetScrollPortRect() const = 0;
+ /**
+ * Get the offset of the scrollport origin relative to the scrolled
+ * frame origin. Typically the position will be non-negative.
+ * This will always be a multiple of device pixels.
+ */
+ virtual nsPoint GetScrollPosition() const = 0;
+ /**
+ * For LTR frames, the logical scroll position is the offset of the top left
+ * corner of the frame from the top left corner of the scroll port (same as
+ * GetScrollPosition).
+ * For RTL frames, it is the offset of the top right corner of the frame from
+ * the top right corner of the scroll port.
+ */
+ virtual nsPoint GetLogicalScrollPosition() const = 0;
+
+ /**
+ * Get the area that must contain the scroll position. Typically
+ * (but not always, e.g. for RTL content) x and y will be 0, and
+ * width or height will be nonzero if the content can be scrolled in
+ * that direction. Since scroll positions must be a multiple of
+ * device pixels, the range extrema will also be a multiple of
+ * device pixels.
+ */
+ virtual nsRect GetScrollRange() const = 0;
+ /**
+ * Get the size of the view port to use when clamping the scroll
+ * position.
+ */
+ virtual nsSize GetVisualViewportSize() const = 0;
+ /**
+ * Returns the offset of the visual viewport relative to
+ * the origin of the scrolled content. Note that only the RCD-RSF
+ * has a distinct visual viewport; for other scroll frames, the
+ * visual viewport always coincides with the layout viewport, and
+ * consequently the offset this function returns is equal to
+ * GetScrollPosition().
+ */
+ virtual nsPoint GetVisualViewportOffset() const = 0;
+ /**
+ * Set the visual viewport offset associated with a root scroll frame. This is
+ * only valid when called on a root scroll frame and will assert otherwise.
+ * aRepaint indicates if we need to ask for a main thread paint if this
+ * changes scrollbar positions or not. For example, if the compositor has
+ * already put the scrollbars at this position then they don't need to move so
+ * we can skip the repaint. Returns true if the offset changed and the scroll
+ * frame is still alive after this call.
+ */
+ virtual bool SetVisualViewportOffset(const nsPoint& aOffset,
+ bool aRepaint) = 0;
+ /**
+ * Get the area that must contain the visual viewport offset.
+ */
+ virtual nsRect GetVisualScrollRange() const = 0;
+ /**
+ * Like GetVisualScrollRange but also takes into account overflow: hidden.
+ */
+ virtual nsRect GetScrollRangeForUserInputEvents() const = 0;
+ /**
+ * Return how much we would try to scroll by in each direction if
+ * asked to scroll by one "line" vertically and horizontally.
+ */
+ virtual nsSize GetLineScrollAmount() const = 0;
+ /**
+ * Return how much we would try to scroll by in each direction if
+ * asked to scroll by one "page" vertically and horizontally.
+ */
+ virtual nsSize GetPageScrollAmount() const = 0;
+
+ /**
+ * Return scroll-padding value of this frame.
+ */
+ virtual nsMargin GetScrollPadding() const = 0;
+ /**
+ * Some platforms (OSX) may generate additional scrolling events even
+ * after the user has stopped scrolling, simulating a momentum scrolling
+ * effect resulting from fling gestures.
+ * SYNTHESIZED_MOMENTUM_EVENT indicates that the scrolling is being requested
+ * by such a synthesized event and may be ignored if another scroll has
+ * been started since the last actual user input.
+ */
+ enum ScrollMomentum { NOT_MOMENTUM, SYNTHESIZED_MOMENTUM_EVENT };
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * Clamps aScrollPosition to GetScrollRange and sets the scroll position
+ * to that value.
+ * @param aRange If non-null, specifies area which contains aScrollPosition
+ * and can be used for choosing a performance-optimized scroll position.
+ * Any point within this area can be chosen.
+ * The choosen point will be as close as possible to aScrollPosition.
+ */
+ virtual void ScrollTo(
+ nsPoint aScrollPosition, ScrollMode aMode, const nsRect* aRange = nullptr,
+ mozilla::ScrollSnapFlags aSnapFlags = mozilla::ScrollSnapFlags::Disabled,
+ mozilla::ScrollTriggeredByScript aTriggeredByScript =
+ mozilla::ScrollTriggeredByScript::No) = 0;
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * Scrolls to a particular position in integer CSS pixels.
+ * Keeps the exact current horizontal or vertical position if the current
+ * position, rounded to CSS pixels, matches aScrollPosition. If
+ * aScrollPosition.x/y is different from the current CSS pixel position,
+ * makes sure we only move in the direction given by the difference.
+ *
+ * When aMode is SMOOTH, INSTANT, or NORMAL, GetRoundedScrollPositionCSSPixels
+ * (the scroll position after rounding to CSS pixels) will be exactly
+ * aScrollPosition at the end of the scroll animation.
+ *
+ * When aMode is SMOOTH_MSD, intermediate animation frames may be outside the
+ * range and / or moving in any direction; GetRoundedScrollPositionCSSPixels
+ * will be exactly aScrollPosition at the end of the scroll animation unless
+ * the SMOOTH_MSD animation is interrupted.
+ */
+ virtual void ScrollToCSSPixels(const CSSIntPoint& aScrollPosition,
+ ScrollMode aMode = ScrollMode::Instant) = 0;
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * Scrolls to a particular position in float CSS pixels.
+ * This does not guarantee that GetRoundedScrollPositionCSSPixels equals
+ * aScrollPosition afterward. It tries to scroll as close to
+ * aScrollPosition as possible while scrolling by an integer
+ * number of layer pixels (so the operation is fast and looks clean).
+ */
+ virtual void ScrollToCSSPixelsForApz(
+ const mozilla::CSSPoint& aScrollPosition,
+ mozilla::ScrollSnapTargetIds&& aLastSnapTargetIds) = 0;
+
+ /**
+ * Returns the scroll position in integer CSS pixels, rounded to the nearest
+ * pixel.
+ */
+ virtual CSSIntPoint GetRoundedScrollPositionCSSPixels() = 0;
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * Modifies the current scroll position by aDelta units given by aUnit,
+ * clamping it to GetScrollRange. If WHOLE is specified as the unit,
+ * content is scrolled all the way in the direction(s) given by aDelta.
+ * @param aOverflow if non-null, returns the amount that scrolling
+ * was clamped by in each direction (how far we moved the scroll position
+ * to bring it back into the legal range). This is never negative. The
+ * values are in device pixels.
+ */
+ virtual void ScrollBy(nsIntPoint aDelta, mozilla::ScrollUnit aUnit,
+ ScrollMode aMode, nsIntPoint* aOverflow = nullptr,
+ ScrollOrigin aOrigin = ScrollOrigin::NotSpecified,
+ ScrollMomentum aMomentum = NOT_MOMENTUM,
+ mozilla::ScrollSnapFlags aSnapFlags =
+ mozilla::ScrollSnapFlags::Disabled) = 0;
+
+ virtual void ScrollByCSSPixels(const CSSIntPoint& aDelta,
+ ScrollMode aMode = ScrollMode::Instant) = 0;
+
+ /**
+ * Perform scroll snapping, possibly resulting in a smooth scroll to
+ * maintain the scroll snap position constraints. Velocity sampled from
+ * main thread scrolling is used to determine best matching snap point
+ * when called after a fling gesture on a trackpad or mouse wheel.
+ */
+ virtual void ScrollSnap() = 0;
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ * This tells the scroll frame to try scrolling to the scroll
+ * position that was restored from the history. This must be called
+ * at least once after state has been restored. It is called by the
+ * scrolled frame itself during reflow, but sometimes state can be
+ * restored after reflows are done...
+ * XXX should we take an aMode parameter here? Currently it's instant.
+ */
+ virtual void ScrollToRestoredPosition() = 0;
+
+ /**
+ * Add a scroll position listener. This listener must be removed
+ * before it is destroyed.
+ */
+ virtual void AddScrollPositionListener(
+ nsIScrollPositionListener* aListener) = 0;
+ /**
+ * Remove a scroll position listener.
+ */
+ virtual void RemoveScrollPositionListener(
+ nsIScrollPositionListener* aListener) = 0;
+
+ /**
+ * Internal method used by scrollbars to notify their scrolling
+ * container of changes.
+ */
+ virtual void CurPosAttributeChanged(nsIContent* aChild) = 0;
+
+ /**
+ * Allows the docshell to request that the scroll frame post an event
+ * after being restored from history.
+ */
+ NS_IMETHOD PostScrolledAreaEventForCurrentArea() = 0;
+
+ /**
+ * Returns true if this scrollframe is being "actively scrolled".
+ * This basically means that we should allocate resources in the
+ * expectation that scrolling is going to happen.
+ */
+ virtual bool IsScrollingActive() const = 0;
+
+ /**
+ * Returns true if this scroll frame might be scrolled
+ * asynchronously by the compositor.
+ */
+ virtual bool IsMaybeAsynchronouslyScrolled() const = 0;
+
+ /**
+ * Was the current presentation state for this frame restored from history?
+ */
+ virtual bool DidHistoryRestore() const = 0;
+ /**
+ * Clear the flag so that DidHistoryRestore() returns false until the next
+ * RestoreState call.
+ * @see nsIStatefulFrame::RestoreState
+ */
+ virtual void ClearDidHistoryRestore() = 0;
+ /**
+ * Mark the frame as having been scrolled at least once, so that it remains
+ * active and we can also start storing its scroll position when saving state.
+ */
+ virtual void MarkEverScrolled() = 0;
+ /**
+ * Determine if the passed in rect is nearly visible according to the frame
+ * visibility heuristics for how close it is to the visible scrollport.
+ */
+ virtual bool IsRectNearlyVisible(const nsRect& aRect) const = 0;
+ /**
+ * Expand the given rect taking into account which directions we can scroll
+ * and how far we want to expand for frame visibility purposes.
+ */
+ virtual nsRect ExpandRectToNearlyVisible(const nsRect& aRect) const = 0;
+ /**
+ * Returns the origin that triggered the last instant scroll. Will equal
+ * ScrollOrigin::Apz when the compositor's replica frame metrics includes the
+ * latest instant scroll.
+ */
+ virtual ScrollOrigin LastScrollOrigin() const = 0;
+
+ /**
+ * Gets the async scroll animation state of this scroll frame.
+ *
+ * There are five possible kinds that can overlap.
+ * MainThread means async scroll animated by the main thread.
+ * APZ scroll animations that are requested from the main thread go through
+ * three states: 1) pending, when the main thread has recorded that it wants
+ * apz to do a scroll animation, 2) requested, when the main thread has sent
+ * the request to the compositor (but it hasn't necessarily arrived yet), and
+ * 3) in progress, after apz has responded to the main thread that it got the
+ * request.
+ * TriggeredByScript means that the async scroll animation was triggered by
+ * script, e.g. Element.scrollTo().
+ */
+ enum class AnimationState {
+ MainThread, // mAsyncScroll || mAsyncSmoothMSDScroll
+ APZPending, // mScrollUpdates.LastElement() is Smooth or SmoothMsd
+ APZRequested, // mApzAnimationRequested
+ APZInProgress, // mCurrentAPZScrollAnimationType !=
+ // APZScrollAniationType::No
+ TriggeredByScript // The animation was triggered with
+ // ScrollTriggeredByScript::Yes
+ };
+ virtual mozilla::EnumSet<AnimationState> ScrollAnimationState() const = 0;
+
+ /**
+ * Returns the current generation counter for the scrollframe. This counter
+ * increments every time the scroll position is set.
+ */
+ virtual mozilla::MainThreadScrollGeneration CurrentScrollGeneration()
+ const = 0;
+ /**
+ * The APZ scroll generation associated with the last APZ scroll offset for
+ * which we processed a repaint request.
+ */
+ virtual mozilla::APZScrollGeneration ScrollGenerationOnApz() const = 0;
+ /**
+ * LastScrollDestination returns the destination of the most recently
+ * requested smooth scroll animation.
+ */
+ virtual nsPoint LastScrollDestination() = 0;
+ /**
+ * Returns the list of scroll position updates since the last call to
+ * NotifyApzTransaction().
+ */
+ virtual nsTArray<mozilla::ScrollPositionUpdate> GetScrollUpdates() const = 0;
+ /**
+ * Returns true if the scroll frame has any scroll position updates since
+ * the last call to NotifyApzTransaction().
+ */
+ virtual bool HasScrollUpdates() const = 0;
+
+ enum class InScrollingGesture : bool { No, Yes };
+ /**
+ * Clears the "origin of last scroll" property stored in this frame, if
+ * the generation counter passed in matches the current scroll generation
+ * counter, and clears the "origin of last smooth scroll" property if the
+ * generation counter matches. It also resets whether there's an ongoing apz
+ * animation.
+ */
+ virtual void ResetScrollInfoIfNeeded(
+ const mozilla::MainThreadScrollGeneration& aGeneration,
+ const mozilla::APZScrollGeneration& aGenerationOnApz,
+ mozilla::APZScrollAnimationType aAPZScrollAnimationType,
+ InScrollingGesture aInScrollingGesture) = 0;
+ /**
+ * Determine whether it is desirable to be able to asynchronously scroll this
+ * scroll frame.
+ */
+ virtual bool WantAsyncScroll() const = 0;
+ /**
+ * Returns the ScrollMetadata contributed by this frame, if there is one.
+ */
+ virtual mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
+ mozilla::layers::WebRenderLayerManager* aLayerManager,
+ const nsIFrame* aItemFrame,
+ const nsPoint& aOffsetToReferenceFrame) const = 0;
+
+ /**
+ * Mark the scrollbar frames for reflow.
+ */
+ virtual void MarkScrollbarsDirtyForReflow() const = 0;
+
+ /**
+ * Invalidate the scrollbar after the marks have been changed.
+ */
+ virtual void InvalidateScrollbars() const = 0;
+
+ virtual void UpdateScrollbarPosition() = 0;
+
+ virtual void SetTransformingByAPZ(bool aTransforming) = 0;
+ virtual bool IsTransformingByAPZ() const = 0;
+
+ /**
+ * Notify this scroll frame that it can be scrolled by APZ. In particular,
+ * this is called *after* the APZ code has created an APZC for this scroll
+ * frame and verified that it is not a scrollinfo layer. Therefore, setting an
+ * async transform on it is actually user visible.
+ */
+ virtual void SetScrollableByAPZ(bool aScrollable) = 0;
+
+ /**
+ * Notify this scroll frame that it can be zoomed by APZ.
+ */
+ virtual void SetZoomableByAPZ(bool aZoomable) = 0;
+
+ /**
+ * Mark this scroll frame as having out-of-flow content inside a CSS filter.
+ * Such content will move incorrectly during async-scrolling; to mitigate
+ * this, paint skipping is disabled for such scroll frames.
+ */
+ virtual void SetHasOutOfFlowContentInsideFilter() = 0;
+
+ /**
+ * Determine if we should build a scrollable layer for this scroll frame and
+ * return the result. It will also record this result on the scroll frame.
+ * Pass the visible rect in aVisibleRect. On return it will be set to the
+ * displayport if there is one.
+ * Pass the dirty rect in aDirtyRect. On return it will be set to the
+ * dirty rect inside the displayport (ie the dirty rect that should be used).
+ * This function will set the display port base rect if aSetBase is true.
+ * aSetBase is only allowed to be false if there has been a call with it
+ * set to true before on the same paint.
+ */
+ virtual bool DecideScrollableLayer(mozilla::nsDisplayListBuilder* aBuilder,
+ nsRect* aVisibleRect, nsRect* aDirtyRect,
+ bool aSetBase) = 0;
+
+ /**
+ * Notify the scrollframe that the current scroll offset and origin have been
+ * sent over in a layers transaction.
+ *
+ * This sets a flag on the scrollframe that indicates subsequent changes
+ * to the scroll position by "weaker" origins are permitted to overwrite the
+ * the scroll origin. Scroll origins that
+ * nsLayoutUtils::CanScrollOriginClobberApz returns false for are considered
+ * "weaker" than scroll origins for which that function returns true.
+ *
+ * This function must be called for a scrollframe after all calls to
+ * ComputeScrollMetadata in a layers transaction have been completed.
+ *
+ */
+ virtual void NotifyApzTransaction() = 0;
+
+ /**
+ * Notification that this scroll frame is getting its frame visibility
+ * updated. aIgnoreDisplayPort indicates that the display port was ignored
+ * (because there was no suitable base rect)
+ */
+ virtual void NotifyApproximateFrameVisibilityUpdate(
+ bool aIgnoreDisplayPort) = 0;
+
+ /**
+ * Returns true if this scroll frame had a display port at the last frame
+ * visibility update and fills in aDisplayPort with that displayport. Returns
+ * false otherwise, and doesn't touch aDisplayPort.
+ */
+ virtual bool GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
+ nsRect* aDisplayPort) = 0;
+
+ /**
+ * This is called when a descendant scrollframe's has its displayport expired.
+ * This function will check to see if this scrollframe may safely expire its
+ * own displayport and schedule a timer to do that if it is safe.
+ */
+ virtual void TriggerDisplayPortExpiration() = 0;
+
+ /**
+ * Returns information required to determine where to snap to after a scroll.
+ */
+ virtual ScrollSnapInfo GetScrollSnapInfo() = 0;
+
+ virtual void TryResnap() = 0;
+ /**
+ * Post a pending re-snap request if the given |aFrame| is one of the snap
+ * points on the last scroll operation.
+ */
+ virtual void PostPendingResnapIfNeeded(const nsIFrame* aFrame) = 0;
+ virtual void PostPendingResnap() = 0;
+
+ /**
+ * Returns a pair of the scroll-snap-align property value both on X and Y axes
+ * for the given |aFrame| considering the scroll-snap-type of this scroll
+ * container. For example, if the scroll-snap-type is `none`, the pair of
+ * scroll-snap-align is also `none none`.
+ */
+ virtual PhysicalScrollSnapAlign GetScrollSnapAlignFor(
+ const nsIFrame* aFrame) const = 0;
+
+ /**
+ * Given the drag event aEvent, determine whether the mouse is near the edge
+ * of the scrollable area, and scroll the view in the direction of that edge
+ * if so. If scrolling occurred, true is returned. When false is returned, the
+ * caller should look for an ancestor to scroll.
+ */
+ virtual bool DragScroll(mozilla::WidgetEvent* aEvent) = 0;
+
+ virtual void AsyncScrollbarDragInitiated(
+ uint64_t aDragBlockId, mozilla::layers::ScrollDirection aDirection) = 0;
+ virtual void AsyncScrollbarDragRejected() = 0;
+
+ /**
+ * Returns whether this scroll frame is the root scroll frame of the document
+ * that it is in. Note that some documents don't have root scroll frames at
+ * all (ie XUL documents) even though they may contain other scroll frames.
+ */
+ virtual bool IsRootScrollFrameOfDocument() const = 0;
+
+ /**
+ * Returns the paint sequence number if this scroll frame was the first
+ * scrollable frame for the paint.
+ */
+ virtual mozilla::Maybe<uint32_t> IsFirstScrollableFrameSequenceNumber()
+ const = 0;
+
+ /**
+ * Set the paint sequence number for the paint in which this was the first
+ * scrollable frame that was encountered.
+ */
+ virtual void SetIsFirstScrollableFrameSequenceNumber(
+ mozilla::Maybe<uint32_t> aValue) = 0;
+
+ /**
+ * Returns the scroll anchor associated with this scrollable frame. This is
+ * never null.
+ */
+ virtual const ScrollAnchorContainer* Anchor() const = 0;
+ virtual ScrollAnchorContainer* Anchor() = 0;
+
+ virtual bool SmoothScrollVisual(
+ const nsPoint& aVisualViewportOffset,
+ mozilla::layers::FrameMetrics::ScrollOffsetUpdateType aUpdateType) = 0;
+
+ /**
+ * Returns true if this scroll frame should perform smooth scroll with the
+ * given |aBehavior|.
+ */
+ virtual bool IsSmoothScroll(mozilla::dom::ScrollBehavior aBehavior =
+ mozilla::dom::ScrollBehavior::Auto) const = 0;
+};
+
+#endif
diff --git a/layout/generic/nsIStatefulFrame.h b/layout/generic/nsIStatefulFrame.h
new file mode 100644
index 0000000000..33a24e5e05
--- /dev/null
+++ b/layout/generic/nsIStatefulFrame.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * interface for rendering objects whose state is saved in
+ * session-history (back-forward navigation)
+ */
+
+#ifndef _nsIStatefulFrame_h
+#define _nsIStatefulFrame_h
+
+#include "nsContentUtils.h"
+#include "nsQueryFrame.h"
+
+namespace mozilla {
+class PresState;
+} // namespace mozilla
+
+class nsIStatefulFrame {
+ public:
+ NS_DECL_QUERYFRAME_TARGET(nsIStatefulFrame)
+
+ // Save the state for this frame.
+ virtual mozilla::UniquePtr<mozilla::PresState> SaveState() = 0;
+
+ // Restore the state for this frame from aState
+ NS_IMETHOD RestoreState(mozilla::PresState* aState) = 0;
+
+ // Generate a key for this stateful frame
+ virtual void GenerateStateKey(nsIContent* aContent,
+ mozilla::dom::Document* aDocument,
+ nsACString& aKey) {
+ nsContentUtils::GenerateStateKey(aContent, aDocument, aKey);
+ };
+};
+
+#endif /* _nsIStatefulFrame_h */
diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp
new file mode 100644
index 0000000000..e0a6243ed2
--- /dev/null
+++ b/layout/generic/nsImageFrame.cpp
@@ -0,0 +1,2906 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for replaced elements with image data */
+
+#include "nsImageFrame.h"
+
+#include "TextDrawTarget.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/dom/GeneratedImageContent.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/ResponsiveImageSelector.h"
+#include "mozilla/dom/LargestContentfulPaint.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/Unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsIFrameInlines.h"
+#include "nsIImageLoadingContent.h"
+#include "nsImageLoadingContent.h"
+#include "nsImageRenderer.h"
+#include "nsObjectLoadingContent.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsStyleConsts.h"
+#include "nsStyleUtil.h"
+#include "nsTransform2D.h"
+#include "nsImageMap.h"
+#include "nsILoadGroup.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsCSSRendering.h"
+#include "nsNameSpaceManager.h"
+#include <algorithm>
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Selection.h"
+#include "nsIURIMutator.h"
+
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+
+#include "nsCSSFrameConstructor.h"
+#include "nsRange.h"
+
+#include "nsError.h"
+#include "nsBidiUtils.h"
+#include "nsBidiPresUtils.h"
+
+#include "gfxRect.h"
+#include "ImageRegion.h"
+#include "ImageContainer.h"
+#include "mozilla/ServoStyleSet.h"
+#include "nsBlockFrame.h"
+#include "nsStyleStructInlines.h"
+
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/Link.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/BrowserChild.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+
+using mozilla::layout::TextDrawTarget;
+
+class nsDisplayGradient final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayGradient(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayGradient);
+ }
+ ~nsDisplayGradient() final { MOZ_COUNT_DTOR(nsDisplayGradient); }
+
+ nsRect GetBounds(bool* aSnap) const {
+ *aSnap = true;
+ return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final {
+ return GetBounds(aSnap);
+ }
+
+ void Paint(nsDisplayListBuilder*, gfxContext* aCtx) final;
+
+ bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder&,
+ mozilla::wr::IpcResourceUpdateQueue&,
+ const StackingContextHelper&,
+ mozilla::layers::RenderRootStateManager*,
+ nsDisplayListBuilder*) final;
+
+ NS_DISPLAY_DECL_NAME("Gradient", TYPE_GRADIENT)
+};
+
+void nsDisplayGradient::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ auto* frame = static_cast<nsImageFrame*>(Frame());
+ nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
+ aBuilder->GetImageRendererFlags());
+ nsSize size = frame->GetSize();
+ imageRenderer.SetPreferredSize({}, size);
+
+ ImgDrawResult result;
+ if (!imageRenderer.PrepareImage()) {
+ result = imageRenderer.PrepareResult();
+ } else {
+ nsRect dest(ToReferenceFrame(), size);
+ result = imageRenderer.DrawLayer(
+ frame->PresContext(), *aCtx, dest, dest, dest.TopLeft(),
+ GetPaintRect(aBuilder, aCtx), dest.Size(), /* aOpacity = */ 1.0f);
+ }
+ Unused << result;
+}
+
+bool nsDisplayGradient::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ auto* frame = static_cast<nsImageFrame*>(Frame());
+ nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
+ aDisplayListBuilder->GetImageRendererFlags());
+ nsSize size = frame->GetSize();
+ imageRenderer.SetPreferredSize({}, size);
+
+ ImgDrawResult result;
+ if (!imageRenderer.PrepareImage()) {
+ result = imageRenderer.PrepareResult();
+ } else {
+ nsRect dest(ToReferenceFrame(), size);
+ result = imageRenderer.BuildWebRenderDisplayItemsForLayer(
+ frame->PresContext(), aBuilder, aResources, aSc, aManager, this, dest,
+ dest, dest.TopLeft(), dest, dest.Size(),
+ /* aOpacity = */ 1.0f);
+ if (result == ImgDrawResult::NOT_SUPPORTED) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// sizes (pixels) for image icon, padding and border frame
+#define ICON_SIZE (16)
+#define ICON_PADDING (3)
+#define ALT_BORDER_WIDTH (1)
+
+// Default alignment value (so we can tell an unset value from a set value)
+#define ALIGN_UNSET uint8_t(-1)
+
+class BrokenImageIcon final : public imgINotificationObserver {
+ // private class that wraps the data and logic needed for
+ // broken image and loading image icons
+ public:
+ explicit BrokenImageIcon(const nsImageFrame& aFrame);
+ static void Shutdown();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ static imgRequestProxy* GetImage(nsImageFrame* aFrame) {
+ return Get(*aFrame).mImage.get();
+ }
+
+ static void AddObserver(nsImageFrame* aFrame) {
+ auto& instance = Get(*aFrame);
+ MOZ_ASSERT(!instance.mObservers.Contains(aFrame),
+ "Observer shouldn't aleady be in array");
+ instance.mObservers.AppendElement(aFrame);
+ }
+
+ static void RemoveObserver(nsImageFrame* aFrame) {
+ auto& instance = Get(*aFrame);
+ DebugOnly<bool> didRemove = instance.mObservers.RemoveElement(aFrame);
+ MOZ_ASSERT(didRemove, "Observer not in array");
+ }
+
+ private:
+ static BrokenImageIcon& Get(const nsImageFrame& aFrame) {
+ if (!gSingleton) {
+ gSingleton = new BrokenImageIcon(aFrame);
+ }
+ return *gSingleton;
+ }
+
+ ~BrokenImageIcon() = default;
+
+ nsTObserverArray<nsImageFrame*> mObservers;
+ RefPtr<imgRequestProxy> mImage;
+
+ static StaticRefPtr<BrokenImageIcon> gSingleton;
+};
+
+StaticRefPtr<BrokenImageIcon> BrokenImageIcon::gSingleton;
+
+NS_IMPL_ISUPPORTS(BrokenImageIcon, imgINotificationObserver)
+
+BrokenImageIcon::BrokenImageIcon(const nsImageFrame& aFrame) {
+ constexpr auto brokenSrc = u"resource://gre-resources/broken-image.png"_ns;
+ nsCOMPtr<nsIURI> realURI;
+ NS_NewURI(getter_AddRefs(realURI), brokenSrc);
+
+ MOZ_ASSERT(realURI, "how?");
+ if (NS_WARN_IF(!realURI)) {
+ return;
+ }
+
+ nsPresContext* pc = aFrame.PresContext();
+ Document* doc = pc->Document();
+ RefPtr<imgLoader> il = nsContentUtils::GetImgLoaderForDocument(doc);
+
+ nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
+
+ // For icon loads, we don't need to merge with the loadgroup flags
+ const nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ const nsContentPolicyType contentPolicyType =
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+
+ nsresult rv =
+ il->LoadImage(realURI, /* icon URI */
+ nullptr, /* initial document URI; this is only
+ relevant for cookies, so does not
+ apply to icons. */
+ nullptr, /* referrer (not relevant for icons) */
+ nullptr, /* principal (not relevant for icons) */
+ 0, loadGroup, this, nullptr, /* No context */
+ nullptr, /* Not associated with any particular document */
+ loadFlags, nullptr, contentPolicyType, u""_ns,
+ false, /* aUseUrgentStartForChannel */
+ false, /* aLinkPreload */
+ 0, getter_AddRefs(mImage));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+void BrokenImageIcon::Shutdown() {
+ if (!gSingleton) {
+ return;
+ }
+ if (gSingleton->mImage) {
+ gSingleton->mImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
+ gSingleton->mImage = nullptr;
+ }
+ gSingleton = nullptr;
+}
+
+void BrokenImageIcon::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ MOZ_ASSERT(aRequest);
+
+ if (aType != imgINotificationObserver::LOAD_COMPLETE &&
+ aType != imgINotificationObserver::FRAME_UPDATE) {
+ return;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ if (!image) {
+ return;
+ }
+
+ // Retrieve the image's intrinsic size.
+ int32_t width = 0;
+ int32_t height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+
+ // Request a decode at that size.
+ image->RequestDecodeForSize(IntSize(width, height),
+ imgIContainer::DECODE_FLAGS_DEFAULT |
+ imgIContainer::FLAG_HIGH_QUALITY_SCALING);
+ }
+
+ for (nsImageFrame* frame : mObservers.ForwardRange()) {
+ frame->InvalidateFrame();
+ }
+}
+
+// test if the width and height are fixed, looking at the style data
+// This is used by nsImageFrame::ImageFrameTypeFor and should not be used for
+// layout decisions.
+static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition) {
+ // check the width and height values in the reflow input's style struct
+ // - if width and height are specified as either coord or percentage, then
+ // the size of the image frame is constrained
+ return aStylePosition->mWidth.IsLengthPercentage() &&
+ aStylePosition->mHeight.IsLengthPercentage();
+}
+
+template <typename SizeOrMaxSize>
+static bool DependsOnIntrinsicSize(const SizeOrMaxSize& aMinOrMaxSize) {
+ auto length = nsIFrame::ToExtremumLength(aMinOrMaxSize);
+ if (!length) {
+ return false;
+ }
+ switch (*length) {
+ case nsIFrame::ExtremumLength::MinContent:
+ case nsIFrame::ExtremumLength::MaxContent:
+ case nsIFrame::ExtremumLength::FitContent:
+ case nsIFrame::ExtremumLength::FitContentFunction:
+ return true;
+ case nsIFrame::ExtremumLength::MozAvailable:
+ return false;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown sizing keyword?");
+ return false;
+}
+
+// Decide whether we can optimize away reflows that result from the
+// image's intrinsic size changing.
+static bool SizeDependsOnIntrinsicSize(const ReflowInput& aReflowInput) {
+ const auto& position = *aReflowInput.mStylePosition;
+ WritingMode wm = aReflowInput.GetWritingMode();
+ // Don't try to make this optimization when an image has percentages
+ // in its 'width' or 'height'. The percentages might be treated like
+ // auto (especially for intrinsic width calculations and for heights).
+ //
+ // min-width: min-content and such can also affect our intrinsic size.
+ // but note that those keywords on the block axis behave like auto, so we
+ // don't need to check them.
+ //
+ // Flex item's min-[width|height]:auto resolution depends on intrinsic size.
+ return !position.mHeight.ConvertsToLength() ||
+ !position.mWidth.ConvertsToLength() ||
+ DependsOnIntrinsicSize(position.MinISize(wm)) ||
+ DependsOnIntrinsicSize(position.MaxISize(wm)) ||
+ aReflowInput.mFrame->IsFlexItem();
+}
+
+nsIFrame* NS_NewImageFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ImageLoadingContent);
+}
+
+nsIFrame* NS_NewXULImageFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::XULImage);
+}
+
+nsIFrame* NS_NewImageFrameForContentProperty(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ContentProperty);
+}
+
+nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ContentPropertyAtIndex);
+}
+
+nsIFrame* NS_NewImageFrameForListStyleImage(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ListStyleImage);
+}
+
+bool nsImageFrame::ShouldShowBrokenImageIcon() const {
+ // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and
+ // Blink behave differently here for content: url(..), for now adapt to
+ // Blink's behavior.
+ if (mKind != Kind::ImageLoadingContent) {
+ return false;
+ }
+
+ if (!StaticPrefs::browser_display_show_image_placeholders()) {
+ return false;
+ }
+
+ // <img alt=""> is special, and it shouldn't draw the broken image icon,
+ // unlike the no-alt attribute or non-empty-alt-attribute case.
+ if (auto* image = HTMLImageElement::FromNode(mContent)) {
+ const nsAttrValue* alt = image->GetParsedAttr(nsGkAtoms::alt);
+ if (alt && alt->IsEmptyString()) {
+ return false;
+ }
+ }
+
+ // check for broken images. valid null images (eg. img src="") are
+ // not considered broken because they have no image requests
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ uint32_t imageStatus;
+ return NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
+ (imageStatus & imgIRequest::STATUS_ERROR);
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+ // Show the broken image icon only if we've tried to perform a load at all
+ // (that is, if we have a current uri).
+ nsCOMPtr<nsIURI> currentURI = imageLoader->GetCurrentURI();
+ return !!currentURI;
+}
+
+nsImageFrame* nsImageFrame::CreateContinuingFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle) const {
+ return new (aPresShell)
+ nsImageFrame(aStyle, aPresShell->GetPresContext(), mKind);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
+
+nsImageFrame::nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID, Kind aKind)
+ : nsAtomicContainerFrame(aStyle, aPresContext, aID),
+ mIntrinsicSize(0, 0),
+ mKind(aKind) {
+ EnableVisibilityTracking();
+}
+
+nsImageFrame::~nsImageFrame() = default;
+
+NS_QUERYFRAME_HEAD(nsImageFrame)
+ NS_QUERYFRAME_ENTRY(nsImageFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsImageFrame::AccessibleType() {
+ if (mKind == Kind::ListStyleImage) {
+ // This is an HTMLListBulletAccessible.
+ return a11y::eNoType;
+ }
+
+ // Don't use GetImageMap() to avoid reentrancy into accessibility.
+ if (HasImageMap()) {
+ return a11y::eHTMLImageMapType;
+ }
+
+ return a11y::eImageType;
+}
+#endif
+
+void nsImageFrame::DisconnectMap() {
+ if (!mImageMap) {
+ return;
+ }
+
+ mImageMap->Destroy();
+ mImageMap = nullptr;
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->RecreateAccessible(PresShell(), mContent);
+ }
+#endif
+}
+
+void nsImageFrame::Destroy(DestroyContext& aContext) {
+ MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing());
+
+ if (mReflowCallbackPosted) {
+ PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ }
+
+ // Tell our image map, if there is one, to clean up
+ // This causes the nsImageMap to unregister itself as
+ // a DOM listener.
+ DisconnectMap();
+
+ MOZ_ASSERT(mListener);
+
+ if (mKind == Kind::ImageLoadingContent) {
+ MOZ_ASSERT(!mOwnedRequest);
+ MOZ_ASSERT(!mOwnedRequestRegistered);
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+
+ // Notify our image loading content that we are going away so it can
+ // deregister with our refresh driver.
+ imageLoader->FrameDestroyed(this);
+ imageLoader->RemoveNativeObserver(mListener);
+ } else {
+ DeinitOwnedRequest();
+ }
+
+ // set the frame to null so we don't send messages to a dead object.
+ mListener->SetFrame(nullptr);
+ mListener = nullptr;
+
+ // If we were displaying an icon, take ourselves off the list
+ if (mDisplayingIcon) {
+ BrokenImageIcon::RemoveObserver(this);
+ }
+
+ nsAtomicContainerFrame::Destroy(aContext);
+}
+
+void nsImageFrame::DeinitOwnedRequest() {
+ MOZ_ASSERT(mKind != Kind::ImageLoadingContent);
+ if (!mOwnedRequest) {
+ return;
+ }
+ PresContext()->Document()->ImageTracker()->Remove(mOwnedRequest);
+ nsLayoutUtils::DeregisterImageRequest(PresContext(), mOwnedRequest,
+ &mOwnedRequestRegistered);
+ mOwnedRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mOwnedRequest = nullptr;
+}
+
+void nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ nsAtomicContainerFrame::DidSetComputedStyle(aOldStyle);
+
+ // A ::marker's default size is calculated from the font's em-size.
+ if (IsForMarkerPseudo()) {
+ mIntrinsicSize = IntrinsicSize(0, 0);
+ UpdateIntrinsicSize();
+ }
+
+ // Normal "owned" images reframe when `content` or `list-style-image` change,
+ // but XUL images don't (and we don't really need to). So deal with the
+ // dynamic list-style-image change in that case.
+ //
+ // TODO(emilio): We might want to do the same for regular list-style-image or
+ // even simple content: url() changes.
+ if (mKind == Kind::XULImage) {
+ if (!mContent->AsElement()->HasNonEmptyAttr(nsGkAtoms::src) && aOldStyle &&
+ aOldStyle->StyleList()->mListStyleImage !=
+ StyleList()->mListStyleImage) {
+ UpdateXULImage();
+ }
+ if (!mOwnedRequest && aOldStyle &&
+ aOldStyle->StyleDisplay()->EffectiveAppearance() !=
+ StyleDisplay()->EffectiveAppearance()) {
+ UpdateIntrinsicSize();
+ }
+ }
+
+ // We need to update our orientation either if we had no ComputedStyle before
+ // because this is the first time it's been set, or if the image-orientation
+ // property changed from its previous value.
+ bool shouldUpdateOrientation = false;
+ nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+ const auto newOrientation =
+ StyleVisibility()->UsedImageOrientation(currentRequest);
+ if (mImage) {
+ if (aOldStyle) {
+ auto oldOrientation =
+ aOldStyle->StyleVisibility()->UsedImageOrientation(currentRequest);
+ shouldUpdateOrientation = oldOrientation != newOrientation;
+ } else {
+ shouldUpdateOrientation = true;
+ }
+ }
+
+ if (shouldUpdateOrientation) {
+ nsCOMPtr<imgIContainer> image(mImage->Unwrap());
+ mImage = nsLayoutUtils::OrientImage(image, newOrientation);
+
+ UpdateIntrinsicSize();
+ UpdateIntrinsicRatio();
+ } else if (!aOldStyle || aOldStyle->StylePosition()->mAspectRatio !=
+ StylePosition()->mAspectRatio) {
+ UpdateIntrinsicRatio();
+ }
+}
+
+static bool SizeIsAvailable(imgIRequest* aRequest) {
+ if (!aRequest) {
+ return false;
+ }
+
+ uint32_t imageStatus = 0;
+ nsresult rv = aRequest->GetImageStatus(&imageStatus);
+ return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE);
+}
+
+const StyleImage* nsImageFrame::GetImageFromStyle() const {
+ switch (mKind) {
+ case Kind::ImageLoadingContent:
+ break;
+ case Kind::ListStyleImage:
+ MOZ_ASSERT(
+ GetParent()->GetContent()->IsGeneratedContentContainerForMarker());
+ MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
+ return &StyleList()->mListStyleImage;
+ case Kind::XULImage:
+ MOZ_ASSERT(!mContent->AsElement()->HasNonEmptyAttr(nsGkAtoms::src));
+ return &StyleList()->mListStyleImage;
+ case Kind::ContentProperty:
+ case Kind::ContentPropertyAtIndex: {
+ uint32_t contentIndex = 0;
+ const nsStyleContent* styleContent = StyleContent();
+ if (mKind == Kind::ContentPropertyAtIndex) {
+ MOZ_RELEASE_ASSERT(
+ mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
+ contentIndex =
+ static_cast<GeneratedImageContent*>(mContent.get())->Index();
+
+ // TODO(emilio): Consider inheriting the `content` property instead of
+ // doing this parent traversal?
+ nsIFrame* parent = GetParent();
+ MOZ_DIAGNOSTIC_ASSERT(
+ parent->GetContent()->IsGeneratedContentContainerForMarker() ||
+ parent->GetContent()->IsGeneratedContentContainerForAfter() ||
+ parent->GetContent()->IsGeneratedContentContainerForBefore());
+ nsIFrame* nonAnonymousParent = parent;
+ while (nonAnonymousParent->Style()->IsAnonBox()) {
+ nonAnonymousParent = nonAnonymousParent->GetParent();
+ }
+ MOZ_DIAGNOSTIC_ASSERT(parent->GetContent() ==
+ nonAnonymousParent->GetContent());
+ styleContent = nonAnonymousParent->StyleContent();
+ }
+ MOZ_RELEASE_ASSERT(contentIndex < styleContent->ContentCount());
+ auto& contentItem = styleContent->ContentAt(contentIndex);
+ MOZ_RELEASE_ASSERT(contentItem.IsImage());
+ return &contentItem.AsImage();
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Don't call me");
+ return nullptr;
+}
+
+void nsImageFrame::UpdateXULImage() {
+ MOZ_ASSERT(mKind == Kind::XULImage);
+ DeinitOwnedRequest();
+
+ nsAutoString src;
+ nsPresContext* pc = PresContext();
+ if (mContent->AsElement()->GetAttr(nsGkAtoms::src, src) && !src.IsEmpty()) {
+ nsContentPolicyType contentPolicyType;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ uint64_t requestContextID = 0;
+ nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ mContent, getter_AddRefs(triggeringPrincipal), contentPolicyType,
+ &requestContextID);
+ nsCOMPtr<nsIURI> uri;
+ nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(uri), src, pc->Document(), mContent->GetBaseURI());
+ if (uri) {
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mContent->AsElement());
+ nsContentUtils::LoadImage(
+ uri, mContent, pc->Document(), triggeringPrincipal, requestContextID,
+ referrerInfo, mListener, nsIRequest::LOAD_NORMAL, u""_ns,
+ getter_AddRefs(mOwnedRequest), contentPolicyType);
+ SetupOwnedRequest();
+ }
+ } else {
+ const auto* image = GetImageFromStyle();
+ if (image->IsImageRequestType()) {
+ if (imgRequestProxy* proxy = image->GetImageRequest()) {
+ proxy->Clone(mListener, pc->Document(), getter_AddRefs(mOwnedRequest));
+ SetupOwnedRequest();
+ }
+ }
+ }
+
+ if (!mOwnedRequest) {
+ UpdateImage(nullptr, nullptr);
+ }
+}
+
+void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT_IF(aPrevInFlow,
+ aPrevInFlow->Type() == Type() &&
+ static_cast<nsImageFrame*>(aPrevInFlow)->mKind == mKind);
+
+ nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mListener = new nsImageListener(this);
+
+ GetImageMap(); // Ensure to init the image map asap. This is important to
+ // make <area> elements focusable.
+
+ if (StaticPrefs::layout_image_eager_broken_image_icon()) {
+ Unused << BrokenImageIcon::GetImage(this);
+ }
+
+ nsPresContext* pc = PresContext();
+ if (mKind == Kind::ImageLoadingContent) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
+ MOZ_ASSERT(imageLoader);
+ imageLoader->AddNativeObserver(mListener);
+ // We have a PresContext now, so we need to notify the image content node
+ // that it can register images.
+ imageLoader->FrameCreated(this);
+ AssertSyncDecodingHintIsInSync();
+ if (nsIDocShell* docShell = pc->GetDocShell()) {
+ RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
+ mIsInObjectOrEmbed = bc->IsEmbedderTypeObjectOrEmbed() &&
+ pc->Document()->IsImageDocument();
+ }
+ } else if (mKind == Kind::XULImage) {
+ UpdateXULImage();
+ } else {
+ const StyleImage* image = GetImageFromStyle();
+ if (image->IsImageRequestType()) {
+ if (imgRequestProxy* proxy = image->GetImageRequest()) {
+ proxy->Clone(mListener, pc->Document(), getter_AddRefs(mOwnedRequest));
+ SetupOwnedRequest();
+ }
+ }
+ }
+
+ // Give image loads associated with an image frame a small priority boost.
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT;
+
+ // Increase load priority further if intrinsic size might be important for
+ // layout.
+ if (!HaveSpecifiedSize(StylePosition())) {
+ categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY;
+ }
+
+ currentRequest->BoostPriority(categoryToBoostPriority);
+ }
+
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+}
+
+void nsImageFrame::SetupOwnedRequest() {
+ MOZ_ASSERT(mKind != Kind::ImageLoadingContent);
+ if (!mOwnedRequest) {
+ return;
+ }
+
+ // We're not using AssociateRequestToFrame for the content property, so we
+ // need to add it to the image tracker manually.
+ PresContext()->Document()->ImageTracker()->Add(mOwnedRequest);
+
+ uint32_t status = 0;
+ nsresult rv = mOwnedRequest->GetImageStatus(&status);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ mOwnedRequest->GetImage(getter_AddRefs(image));
+ OnSizeAvailable(mOwnedRequest, image);
+ }
+
+ if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
+ mFirstFrameComplete = true;
+ }
+
+ if (status & imgIRequest::STATUS_IS_ANIMATED) {
+ nsLayoutUtils::RegisterImageRequest(PresContext(), mOwnedRequest,
+ &mOwnedRequestRegistered);
+ }
+}
+
+static void ScaleIntrinsicSizeForDensity(IntrinsicSize& aSize,
+ const ImageResolution& aResolution) {
+ if (aSize.width) {
+ aResolution.ApplyXTo(aSize.width.ref());
+ }
+ if (aSize.height) {
+ aResolution.ApplyYTo(aSize.height.ref());
+ }
+}
+
+static void ScaleIntrinsicSizeForDensity(imgIContainer* aImage,
+ nsIContent& aContent,
+ IntrinsicSize& aSize) {
+ ImageResolution resolution = aImage->GetResolution();
+ if (auto* image = HTMLImageElement::FromNode(aContent)) {
+ if (auto* selector = image->GetResponsiveImageSelector()) {
+ resolution.ScaleBy(selector->GetSelectedImageDensity());
+ }
+ }
+ ScaleIntrinsicSizeForDensity(aSize, resolution);
+}
+
+static nscoord ListImageDefaultLength(const nsImageFrame& aFrame) {
+ // https://drafts.csswg.org/css-lists-3/#image-markers
+ // The spec says we should use 1em x 1em, but that seems too large.
+ // See disussion in https://github.com/w3c/csswg-drafts/issues/4207
+ auto* pc = aFrame.PresContext();
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForComputedStyle(aFrame.Style(), pc);
+ RefPtr<gfxFont> font = fm->GetThebesFontGroup()->GetFirstValidFont();
+ auto emAU =
+ font->GetMetrics(fm->Orientation()).emHeight * pc->AppUnitsPerDevPixel();
+ return std::max(NSToCoordRound(0.4f * emAU),
+ nsPresContext::CSSPixelsToAppUnits(1));
+}
+
+static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage,
+ bool aUseMappedRatio,
+ nsImageFrame::Kind aKind,
+ const nsImageFrame& aFrame) {
+ const auto containAxes = aFrame.GetContainSizeAxes();
+ if (containAxes.IsBoth()) {
+ return aFrame.FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0));
+ }
+
+ nsSize size;
+ if (aImage && NS_SUCCEEDED(aImage->GetIntrinsicSize(&size))) {
+ IntrinsicSize intrinsicSize;
+ intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width);
+ intrinsicSize.height = size.height == -1 ? Nothing() : Some(size.height);
+ if (aKind == nsImageFrame::Kind::ListStyleImage) {
+ if (intrinsicSize.width.isNothing() || intrinsicSize.height.isNothing()) {
+ nscoord defaultLength = ListImageDefaultLength(aFrame);
+ if (intrinsicSize.width.isNothing()) {
+ intrinsicSize.width = Some(defaultLength);
+ }
+ if (intrinsicSize.height.isNothing()) {
+ intrinsicSize.height = Some(defaultLength);
+ }
+ }
+ }
+ if (aKind == nsImageFrame::Kind::ImageLoadingContent ||
+ (aKind == nsImageFrame::Kind::XULImage &&
+ aFrame.GetContent()->AsElement()->HasNonEmptyAttr(nsGkAtoms::src))) {
+ ScaleIntrinsicSizeForDensity(aImage, *aFrame.GetContent(), intrinsicSize);
+ } else {
+ ScaleIntrinsicSizeForDensity(
+ intrinsicSize,
+ aFrame.GetImageFromStyle()->GetResolution(*aFrame.Style()));
+ }
+ return aFrame.FinishIntrinsicSize(containAxes, intrinsicSize);
+ }
+
+ if (aKind == nsImageFrame::Kind::ListStyleImage) {
+ // Note: images are handled above, this handles gradients etc.
+ const nscoord defaultLength = ListImageDefaultLength(aFrame);
+ return aFrame.FinishIntrinsicSize(
+ containAxes, IntrinsicSize(defaultLength, defaultLength));
+ }
+
+ if (aKind == nsImageFrame::Kind::XULImage && aFrame.IsThemed()) {
+ nsPresContext* pc = aFrame.PresContext();
+ // FIXME: const_cast here is a bit evil but IsThemed and so does the same.
+ const auto widgetSize = pc->Theme()->GetMinimumWidgetSize(
+ pc, const_cast<nsImageFrame*>(&aFrame),
+ aFrame.StyleDisplay()->EffectiveAppearance());
+ const IntrinsicSize intrinsicSize(
+ LayoutDeviceIntSize::ToAppUnits(widgetSize, pc->AppUnitsPerDevPixel()));
+ return aFrame.FinishIntrinsicSize(containAxes, intrinsicSize);
+ }
+
+ if (aFrame.ShouldShowBrokenImageIcon()) {
+ nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
+ return aFrame.FinishIntrinsicSize(
+ containAxes, IntrinsicSize(edgeLengthToUse, edgeLengthToUse));
+ }
+
+ if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio.HasRatio()) {
+ return IntrinsicSize();
+ }
+
+ // XXX: No FinishIntrinsicSize?
+ return IntrinsicSize(0, 0);
+}
+
+// For compat reasons, see bug 1602047, we don't use the intrinsic ratio from
+// width="" and height="" for images with no src attribute (no request).
+//
+// But we shouldn't get fooled by <img loading=lazy>. We do want to apply the
+// ratio then...
+bool nsImageFrame::ShouldUseMappedAspectRatio() const {
+ if (mKind != Kind::ImageLoadingContent) {
+ return true;
+ }
+ nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+ if (currentRequest) {
+ return true;
+ }
+ // TODO(emilio): Investigate the compat situation of the above check, maybe we
+ // can just check for empty src attribute or something...
+ auto* image = HTMLImageElement::FromNode(mContent);
+ return image && image->IsAwaitingLoadOrLazyLoading();
+}
+
+bool nsImageFrame::UpdateIntrinsicSize() {
+ IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
+ mIntrinsicSize =
+ ComputeIntrinsicSize(mImage, ShouldUseMappedAspectRatio(), mKind, *this);
+ return mIntrinsicSize != oldIntrinsicSize;
+}
+
+static AspectRatio ComputeIntrinsicRatio(imgIContainer* aImage,
+ bool aUseMappedRatio,
+ const nsImageFrame& aFrame) {
+ if (aFrame.GetContainSizeAxes().IsAny()) {
+ return AspectRatio();
+ }
+
+ if (aImage) {
+ if (Maybe<AspectRatio> fromImage = aImage->GetIntrinsicRatio()) {
+ return *fromImage;
+ }
+ }
+ if (aUseMappedRatio) {
+ const StyleAspectRatio& ratio = aFrame.StylePosition()->mAspectRatio;
+ if (ratio.auto_ && ratio.HasRatio()) {
+ // Return the mapped intrinsic aspect ratio stored in
+ // nsStylePosition::mAspectRatio.
+ return ratio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes);
+ }
+ }
+ if (aFrame.ShouldShowBrokenImageIcon()) {
+ return AspectRatio(1.0f);
+ }
+ return AspectRatio();
+}
+
+bool nsImageFrame::UpdateIntrinsicRatio() {
+ AspectRatio oldIntrinsicRatio = mIntrinsicRatio;
+ mIntrinsicRatio =
+ ComputeIntrinsicRatio(mImage, ShouldUseMappedAspectRatio(), *this);
+ return mIntrinsicRatio != oldIntrinsicRatio;
+}
+
+bool nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) {
+ // First, figure out destRect (the rect we're rendering into).
+ // NOTE: We use mComputedSize instead of just GetContentRectRelativeToSelf()'s
+ // own size here, because GetContentRectRelativeToSelf() might be smaller if
+ // we're fragmented, whereas mComputedSize has our full content-box size
+ // (which we need for ComputeObjectDestRect to work correctly).
+ nsRect constraintRect(GetContentRectRelativeToSelf().TopLeft(),
+ mComputedSize);
+ constraintRect.y -= GetContinuationOffset();
+
+ nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
+ constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition());
+ // Set the translation components, based on destRect
+ // XXXbz does this introduce rounding errors because of the cast to
+ // float? Should we just manually add that stuff in every time
+ // instead?
+ aTransform.SetToTranslate(float(destRect.x), float(destRect.y));
+
+ // NOTE(emilio): This intrinsicSize is not the same as the layout intrinsic
+ // size (mIntrinsicSize), which can be scaled due to ResponsiveImageSelector,
+ // see ScaleIntrinsicSizeForDensity.
+ nsSize intrinsicSize;
+ if (!mImage || !NS_SUCCEEDED(mImage->GetIntrinsicSize(&intrinsicSize)) ||
+ intrinsicSize.IsEmpty()) {
+ return false;
+ }
+
+ aTransform.SetScale(float(destRect.width) / float(intrinsicSize.width),
+ float(destRect.height) / float(intrinsicSize.height));
+ return true;
+}
+
+// This function checks whether the given request is the current request for our
+// mContent.
+bool nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const {
+ // Default to pending load in case of errors
+ if (mKind != Kind::ImageLoadingContent) {
+ MOZ_ASSERT(aRequest == mOwnedRequest);
+ return false;
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
+ MOZ_ASSERT(imageLoader);
+
+ int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
+ imageLoader->GetRequestType(aRequest, &requestType);
+
+ return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
+}
+
+nsRect nsImageFrame::SourceRectToDest(const nsIntRect& aRect) {
+ // When scaling the image, row N of the source image may (depending on
+ // the scaling function) be used to draw any row in the destination image
+ // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the
+ // floating-point scaling factor. The same holds true for columns.
+ // So, we start by computing that bound without the floor and ceiling.
+
+ nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1),
+ nsPresContext::CSSPixelsToAppUnits(aRect.y - 1),
+ nsPresContext::CSSPixelsToAppUnits(aRect.width + 2),
+ nsPresContext::CSSPixelsToAppUnits(aRect.height + 2));
+
+ nsTransform2D sourceToDest;
+ if (!GetSourceToDestTransform(sourceToDest)) {
+ // Failed to generate transform matrix. Return our whole content area,
+ // to be on the safe side (since this method is used for generating
+ // invalidation rects).
+ return GetContentRectRelativeToSelf();
+ }
+
+ sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height);
+
+ // Now, round the edges out to the pixel boundary.
+ nscoord scale = nsPresContext::CSSPixelsToAppUnits(1);
+ nscoord right = r.x + r.width;
+ nscoord bottom = r.y + r.height;
+
+ r.x -= (scale + (r.x % scale)) % scale;
+ r.y -= (scale + (r.y % scale)) % scale;
+ r.width = right + ((scale - (right % scale)) % scale) - r.x;
+ r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y;
+
+ return r;
+}
+
+static bool ImageOk(ElementState aState) {
+ return !aState.HasState(ElementState::BROKEN);
+}
+
+static bool HasAltText(const Element& aElement) {
+ // We always return some alternate text for <input>, see
+ // nsCSSFrameConstructor::GetAlternateTextFor.
+ if (aElement.IsHTMLElement(nsGkAtoms::input)) {
+ return true;
+ }
+
+ MOZ_ASSERT(aElement.IsHTMLElement(nsGkAtoms::img));
+ return aElement.HasNonEmptyAttr(nsGkAtoms::alt);
+}
+
+bool nsImageFrame::ShouldCreateImageFrameForContentProperty(
+ const Element& aElement, const ComputedStyle& aStyle) {
+ if (aElement.IsRootOfNativeAnonymousSubtree()) {
+ return false;
+ }
+ const auto& content = aStyle.StyleContent()->mContent;
+ if (!content.IsItems()) {
+ return false;
+ }
+ Span<const StyleContentItem> items = content.AsItems().AsSpan();
+ return items.Length() == 1 && items[0].IsImage();
+}
+
+// Check if we want to use an image frame or just let the frame constructor make
+// us into an inline, and if so, which kind of image frame should we create.
+/* static */
+auto nsImageFrame::ImageFrameTypeFor(const Element& aElement,
+ const ComputedStyle& aStyle)
+ -> ImageFrameType {
+ if (ShouldCreateImageFrameForContentProperty(aElement, aStyle)) {
+ // Prefer the content property, for compat reasons, see bug 1484928.
+ return ImageFrameType::ForContentProperty;
+ }
+
+ if (ImageOk(aElement.State())) {
+ // Image is fine or loading; do the image frame thing
+ return ImageFrameType::ForElementRequest;
+ }
+
+ if (aStyle.StyleUIReset()->mMozForceBrokenImageIcon) {
+ return ImageFrameType::ForElementRequest;
+ }
+
+ if (!HasAltText(aElement)) {
+ return ImageFrameType::ForElementRequest;
+ }
+
+ // FIXME(emilio, bug 1788767): We definitely don't reframe when
+ // HaveSpecifiedSize changes...
+ if (aElement.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks &&
+ HaveSpecifiedSize(aStyle.StylePosition())) {
+ return ImageFrameType::ForElementRequest;
+ }
+
+ return ImageFrameType::None;
+}
+
+void nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aRect) {
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ return OnSizeAvailable(aRequest, image);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ return OnFrameUpdate(aRequest, aRect);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ mFirstFrameComplete = true;
+ }
+
+ if (aType == imgINotificationObserver::IS_ANIMATED &&
+ mKind != Kind::ImageLoadingContent) {
+ nsLayoutUtils::RegisterImageRequest(PresContext(), mOwnedRequest,
+ &mOwnedRequestRegistered);
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ LargestContentfulPaint::MaybeProcessImageForElementTiming(
+ static_cast<imgRequestProxy*>(aRequest), GetContent()->AsElement());
+ uint32_t imgStatus;
+ aRequest->GetImageStatus(&imgStatus);
+ nsresult status =
+ imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+}
+
+void nsImageFrame::OnSizeAvailable(imgIRequest* aRequest,
+ imgIContainer* aImage) {
+ if (!aImage) {
+ return;
+ }
+
+ /* Get requested animation policy from the pres context:
+ * normal = 0
+ * one frame = 1
+ * one loop = 2
+ */
+ aImage->SetAnimationMode(PresContext()->ImageAnimationMode());
+
+ if (IsPendingLoad(aRequest)) {
+ // We don't care
+ return;
+ }
+
+ UpdateImage(aRequest, aImage);
+}
+
+void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) {
+ if (SizeIsAvailable(aRequest)) {
+ StyleImageOrientation orientation =
+ StyleVisibility()->UsedImageOrientation(aRequest);
+ // This is valid and for the current request, so update our stored image
+ // container, orienting according to our style.
+ mImage = nsLayoutUtils::OrientImage(aImage, orientation);
+ MOZ_ASSERT(mImage);
+ } else {
+ // We no longer have a valid image, so release our stored image container.
+ mImage = mPrevImage = nullptr;
+ if (mKind == Kind::ListStyleImage) {
+ auto* genContent = static_cast<GeneratedImageContent*>(GetContent());
+ genContent->NotifyLoadFailed();
+ // No need to continue below since the above state change will destroy
+ // this frame.
+ return;
+ }
+ }
+
+ UpdateIntrinsicSizeAndRatio();
+
+ if (!GotInitialReflow()) {
+ return;
+ }
+
+ // We're going to need to repaint now either way.
+ InvalidateFrame();
+}
+
+void nsImageFrame::OnFrameUpdate(imgIRequest* aRequest,
+ const nsIntRect* aRect) {
+ if (NS_WARN_IF(!aRect)) {
+ return;
+ }
+
+ if (!GotInitialReflow()) {
+ // Don't bother to do anything; we have a reflow coming up!
+ return;
+ }
+
+ if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ if (IsPendingLoad(aRequest)) {
+ // We don't care
+ return;
+ }
+
+ nsIntRect layerInvalidRect =
+ mImage ? mImage->GetImageSpaceInvalidationRect(*aRect) : *aRect;
+
+ if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) {
+ // Invalidate our entire area.
+ InvalidateSelf(nullptr, nullptr);
+ return;
+ }
+
+ nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect);
+ InvalidateSelf(&layerInvalidRect, &frameInvalidRect);
+}
+
+void nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
+ const nsRect* aFrameInvalidRect) {
+ // Check if WebRender has interacted with this frame. If it has
+ // we need to let it know that things have changed.
+ const auto type = DisplayItemType::TYPE_IMAGE;
+ const auto providerId = mImage ? mImage->GetProviderId() : 0;
+ if (WebRenderUserData::ProcessInvalidateForImage(this, type, providerId)) {
+ return;
+ }
+
+ InvalidateLayer(type, aLayerInvalidRect, aFrameInvalidRect);
+
+ if (!mFirstFrameComplete) {
+ InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, aLayerInvalidRect,
+ aFrameInvalidRect);
+ }
+}
+
+void nsImageFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder() {
+ MaybeSendIntrinsicSizeAndRatioToEmbedder(Some(GetIntrinsicSize()),
+ Some(GetAspectRatio()));
+}
+
+void nsImageFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder(
+ Maybe<IntrinsicSize> aIntrinsicSize, Maybe<AspectRatio> aIntrinsicRatio) {
+ if (!mIsInObjectOrEmbed || !mImage) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ BrowsingContext* bc = docShell->GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+ MOZ_ASSERT(bc->IsContentSubframe());
+
+ if (bc->GetParent()->IsInProcess()) {
+ if (Element* embedder = bc->GetEmbedderElement()) {
+ if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(embedder)) {
+ static_cast<nsObjectLoadingContent*>(olc.get())
+ ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize,
+ aIntrinsicRatio);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Got out of sync?");
+ }
+ return;
+ }
+ }
+
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
+ Unused << browserChild->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize,
+ aIntrinsicRatio);
+ }
+}
+
+void nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) {
+ NotifyNewCurrentRequest(aRequest, aStatus);
+}
+
+void nsImageFrame::ElementStateChanged(ElementState aStates) {
+ if (!(aStates & ElementState::BROKEN)) {
+ return;
+ }
+ if (mKind != Kind::ImageLoadingContent) {
+ return;
+ }
+ if (!ImageOk(mContent->AsElement()->State())) {
+ UpdateImage(nullptr, nullptr);
+ }
+}
+
+void nsImageFrame::ResponsiveContentDensityChanged() {
+ UpdateIntrinsicSizeAndRatio();
+}
+
+void nsImageFrame::UpdateIntrinsicSizeAndRatio() {
+ bool intrinsicSizeOrRatioChanged = [&] {
+ // NOTE(emilio): We intentionally want to call both functions and avoid
+ // short-circuiting.
+ bool intrinsicSizeChanged = UpdateIntrinsicSize();
+ bool intrinsicRatioChanged = UpdateIntrinsicRatio();
+ return intrinsicSizeChanged || intrinsicRatioChanged;
+ }();
+
+ if (!intrinsicSizeOrRatioChanged) {
+ return;
+ }
+
+ // Our aspect-ratio property value changed, and an embedding <object> or
+ // <embed> might care about that.
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+
+ if (!GotInitialReflow()) {
+ return;
+ }
+
+ // Now we need to reflow if we have an unconstrained size and have
+ // already gotten the initial reflow.
+ if (!HasAnyStateBits(IMAGE_SIZECONSTRAINED)) {
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ } else if (PresShell()->IsActive()) {
+ // We've already gotten the initial reflow, and our size hasn't changed,
+ // so we're ready to request a decode.
+ MaybeDecodeForPredictedSize();
+ }
+}
+
+void nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest,
+ nsresult aStatus) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ NS_ASSERTION(image || NS_FAILED(aStatus),
+ "Successful load with no container?");
+ UpdateImage(aRequest, image);
+}
+
+void nsImageFrame::MaybeDecodeForPredictedSize() {
+ // Check that we're ready to decode.
+ if (!mImage) {
+ return; // Nothing to do yet.
+ }
+
+ if (mComputedSize.IsEmpty()) {
+ return; // We won't draw anything, so no point in decoding.
+ }
+
+ if (GetVisibility() != Visibility::ApproximatelyVisible) {
+ return; // We're not visible, so don't decode.
+ }
+
+ // OK, we're ready to decode. Compute the scale to the screen...
+ mozilla::PresShell* presShell = PresShell();
+ MatrixScales scale =
+ ScaleFactor<UnknownUnits, UnknownUnits>(
+ presShell->GetCumulativeResolution()) *
+ nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this);
+ auto resolutionToScreen = ViewAs<LayoutDeviceToScreenScale2D>(scale);
+
+ // If we are in a remote browser, then apply scaling from ancestor browsers
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
+ resolutionToScreen =
+ resolutionToScreen * ViewAs<ScreenToScreenScale2D>(
+ browserChild->GetEffectsInfo().mRasterScale);
+ }
+
+ // ...and this frame's content box...
+ const nsPoint offset =
+ GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this));
+ const nsRect frameContentBox = GetContentRectRelativeToSelf() + offset;
+
+ // ...and our predicted dest rect...
+ const int32_t factor = PresContext()->AppUnitsPerDevPixel();
+ const LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(
+ PredictedDestRect(frameContentBox), factor);
+
+ // ...and use them to compute our predicted size in screen pixels.
+ const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen;
+ const ScreenIntSize predictedScreenIntSize =
+ RoundedToInt(predictedScreenSize);
+ if (predictedScreenIntSize.IsEmpty()) {
+ return;
+ }
+
+ // Determine the optimal image size to use.
+ uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
+ imgIContainer::FLAG_ASYNC_NOTIFY;
+ SamplingFilter samplingFilter =
+ nsLayoutUtils::GetSamplingFilterForFrame(this);
+ gfxSize gfxPredictedScreenSize =
+ gfxSize(predictedScreenIntSize.width, predictedScreenIntSize.height);
+ nsIntSize predictedImageSize = mImage->OptimalImageSizeForDest(
+ gfxPredictedScreenSize, imgIContainer::FRAME_CURRENT, samplingFilter,
+ flags);
+
+ // Request a decode.
+ mImage->RequestDecodeForSize(predictedImageSize, flags);
+}
+
+nsRect nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) {
+ // Note: To get the "dest rect", we have to provide the "constraint rect"
+ // (which is the content-box, with the effects of fragmentation undone).
+ nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize);
+ constraintRect.y -= GetContinuationOffset();
+
+ return nsLayoutUtils::ComputeObjectDestRect(constraintRect, mIntrinsicSize,
+ mIntrinsicRatio, StylePosition());
+}
+
+bool nsImageFrame::IsForMarkerPseudo() const {
+ if (mKind == Kind::ImageLoadingContent) {
+ return false;
+ }
+ auto* subtreeRoot = GetContent()->GetClosestNativeAnonymousSubtreeRoot();
+ return subtreeRoot && subtreeRoot->IsGeneratedContentContainerForMarker();
+}
+
+void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
+ const auto containAxes = GetContainSizeAxes();
+ if (containAxes.IsBoth()) {
+ // If we have 'contain:size', then we have no intrinsic aspect ratio,
+ // and the intrinsic size is determined by contain-intrinsic-size,
+ // regardless of what our underlying image may think.
+ mIntrinsicSize = FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0));
+ mIntrinsicRatio = AspectRatio();
+ return;
+ }
+
+ // If mIntrinsicSize.width and height are 0, then we need to update from the
+ // image container. Note that we handle ::marker intrinsic size/ratio in
+ // DidSetComputedStyle.
+ if (mIntrinsicSize != IntrinsicSize(0, 0) && !IsForMarkerPseudo()) {
+ return;
+ }
+
+ bool intrinsicSizeOrRatioChanged = UpdateIntrinsicSize();
+ intrinsicSizeOrRatioChanged =
+ UpdateIntrinsicRatio() || intrinsicSizeOrRatioChanged;
+
+ if (intrinsicSizeOrRatioChanged) {
+ // Our aspect-ratio property value changed, and an embedding <object> or
+ // <embed> might care about that.
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+ }
+}
+
+nsIFrame::SizeComputationResult nsImageFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ EnsureIntrinsicSizeAndRatio();
+ return {ComputeSizeWithIntrinsicDimensions(
+ aRenderingContext, aWM, mIntrinsicSize, GetAspectRatio(), aCBSize,
+ aMargin, aBorderPadding, aSizeOverrides, aFlags),
+ AspectRatioUsage::None};
+}
+
+Element* nsImageFrame::GetMapElement() const {
+ return IsForImageLoadingContent()
+ ? nsImageLoadingContent::FindImageMap(mContent->AsElement())
+ : nullptr;
+}
+
+// get the offset into the content area of the image where aImg starts if it is
+// a continuation.
+nscoord nsImageFrame::GetContinuationOffset() const {
+ nscoord offset = 0;
+ for (nsIFrame* f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {
+ offset += f->GetContentRect().height;
+ }
+ NS_ASSERTION(offset >= 0, "bogus GetContentRect");
+ return offset;
+}
+
+nscoord nsImageFrame::GetMinISize(gfxContext* aRenderingContext) {
+ // XXX The caller doesn't account for constraints of the block-size,
+ // min-block-size, and max-block-size properties.
+ DebugOnly<nscoord> result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ EnsureIntrinsicSizeAndRatio();
+ const auto& iSize = GetWritingMode().IsVertical() ? mIntrinsicSize.height
+ : mIntrinsicSize.width;
+ return iSize.valueOr(0);
+}
+
+nscoord nsImageFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ // XXX The caller doesn't account for constraints of the block-size,
+ // min-block-size, and max-block-size properties.
+ DebugOnly<nscoord> result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ EnsureIntrinsicSizeAndRatio();
+ const auto& iSize = GetWritingMode().IsVertical() ? mIntrinsicSize.height
+ : mIntrinsicSize.width;
+ // convert from normal twips to scaled twips (printing...)
+ return iSize.valueOr(0);
+}
+
+void nsImageFrame::ReflowChildren(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ const LogicalSize& aImageSize) {
+ for (nsIFrame* child : mFrames) {
+ ReflowOutput childDesiredSize(aReflowInput);
+ WritingMode wm = GetWritingMode();
+ // Shouldn't be hard to support if we want, but why bother.
+ MOZ_ASSERT(
+ wm == child->GetWritingMode(),
+ "We don't expect mismatched writing-modes in content we control");
+ nsReflowStatus childStatus;
+
+ LogicalPoint childOffset(wm);
+ ReflowInput childReflowInput(aPresContext, aReflowInput, child, aImageSize);
+ const nsSize containerSize = aImageSize.GetPhysicalSize(wm);
+ ReflowChild(child, aPresContext, childDesiredSize, childReflowInput, wm,
+ childOffset, containerSize, ReflowChildFlags::Default,
+ childStatus);
+
+ FinishReflowChild(child, aPresContext, childDesiredSize, &childReflowInput,
+ wm, childOffset, containerSize,
+ ReflowChildFlags::Default);
+ }
+}
+
+void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE(
+ NS_FRAME_TRACE_CALLS,
+ ("enter nsImageFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow");
+
+ // see if we have a frozen size (i.e. a fixed width and height)
+ if (!SizeDependsOnIntrinsicSize(aReflowInput)) {
+ AddStateBits(IMAGE_SIZECONSTRAINED);
+ } else {
+ RemoveStateBits(IMAGE_SIZECONSTRAINED);
+ }
+
+ mComputedSize = aReflowInput.ComputedPhysicalSize();
+
+ const auto wm = GetWritingMode();
+ aMetrics.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm));
+
+ if (GetPrevInFlow()) {
+ aMetrics.Width() = GetPrevInFlow()->GetSize().width;
+ nscoord y = GetContinuationOffset();
+ aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top;
+ aMetrics.Height() = std::max(0, aMetrics.Height());
+ }
+
+ // we have to split images if we are:
+ // in Paginated mode, we need to have a constrained height, and have a height
+ // larger than our available height
+ uint32_t loadStatus = imgIRequest::STATUS_NONE;
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ currentRequest->GetImageStatus(&loadStatus);
+ }
+
+ if (aPresContext->IsPaginated() &&
+ ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) ||
+ HasAnyStateBits(IMAGE_SIZECONSTRAINED)) &&
+ NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() &&
+ aMetrics.Height() > aReflowInput.AvailableHeight()) {
+ // our desired height was greater than 0, so to avoid infinite
+ // splitting, use 1 pixel as the min
+ aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1),
+ aReflowInput.AvailableHeight());
+ aStatus.SetIncomplete();
+ }
+
+ aMetrics.SetOverflowAreasToDesiredBounds();
+ bool imageOK = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ // Determine if the size is available
+ bool haveSize = false;
+ if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) {
+ haveSize = true;
+ }
+
+ if (!imageOK || !haveSize) {
+ nsRect altFeedbackSize(
+ 0, 0,
+ nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)),
+ nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
+ // We include the altFeedbackSize in our ink overflow, but not in our
+ // scrollable overflow, since it doesn't really need to be scrolled to
+ // outside the image.
+ nsRect& inkOverflow = aMetrics.InkOverflow();
+ inkOverflow.UnionRect(inkOverflow, altFeedbackSize);
+ } else if (PresShell()->IsActive()) {
+ // We've just reflowed and we should have an accurate size, so we're ready
+ // to request a decode.
+ MaybeDecodeForPredictedSize();
+ }
+ FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
+
+ // Reflow the child frames. Our children can't affect our size in any way.
+ ReflowChildren(aPresContext, aReflowInput, aMetrics.Size(GetWritingMode()));
+
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresShell()->PostReflowCallback(this);
+ }
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsImageFrame::Reflow: size=%d,%d",
+ aMetrics.Width(), aMetrics.Height()));
+}
+
+bool nsImageFrame::ReflowFinished() {
+ mReflowCallbackPosted = false;
+
+ // XXX(seth): We don't need this. The purpose of updating visibility
+ // synchronously is to ensure that animated images start animating
+ // immediately. In the short term, however,
+ // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
+ // animations start as soon as the image is painted for the first time, and in
+ // the long term we want to update visibility information from the display
+ // list whenever we paint, so we don't actually need to do this. However, to
+ // avoid behavior changes during the transition from the old image visibility
+ // code, we'll leave it in for now.
+ UpdateVisibilitySynchronously();
+
+ return false;
+}
+
+void nsImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
+
+// Computes the width of the specified string. aMaxWidth specifies the maximum
+// width available. Once this limit is reached no more characters are measured.
+// The number of characters that fit within the maximum width are returned in
+// aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
+// into the rendering context before this is called (for performance). MMP
+nscoord nsImageFrame::MeasureString(const char16_t* aString, int32_t aLength,
+ nscoord aMaxWidth, uint32_t& aMaxFit,
+ gfxContext& aContext,
+ nsFontMetrics& aFontMetrics) {
+ nscoord totalWidth = 0;
+ aFontMetrics.SetTextRunRTL(false);
+ nscoord spaceWidth = aFontMetrics.SpaceWidth();
+
+ aMaxFit = 0;
+ while (aLength > 0) {
+ // Find the next place we can line break
+ uint32_t len = aLength;
+ bool trailingSpace = false;
+ for (int32_t i = 0; i < aLength; i++) {
+ if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) {
+ len = i; // don't include the space when measuring
+ trailingSpace = true;
+ break;
+ }
+ }
+
+ // Measure this chunk of text, and see if it fits
+ nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
+ aString, len, this, aFontMetrics, aContext);
+ bool fits = (totalWidth + width) <= aMaxWidth;
+
+ // If it fits on the line, or it's the first word we've processed then
+ // include it
+ if (fits || (0 == totalWidth)) {
+ // New piece fits
+ totalWidth += width;
+
+ // If there's a trailing space then see if it fits as well
+ if (trailingSpace) {
+ if ((totalWidth + spaceWidth) <= aMaxWidth) {
+ totalWidth += spaceWidth;
+ } else {
+ // Space won't fit. Leave it at the end but don't include it in
+ // the width
+ fits = false;
+ }
+
+ len++;
+ }
+
+ aMaxFit += len;
+ aString += len;
+ aLength -= len;
+ }
+
+ if (!fits) {
+ break;
+ }
+ }
+ return totalWidth;
+}
+
+// Formats the alt-text to fit within the specified rectangle. Breaks lines
+// between words if a word would extend past the edge of the rectangle
+void nsImageFrame::DisplayAltText(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsString& aAltText,
+ const nsRect& aRect) {
+ // Set font and color
+ aRenderingContext.SetColor(
+ sRGBColor::FromABGR(StyleText()->mColor.ToColor()));
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+
+ // Format the text to display within the formatting rect
+
+ nscoord maxAscent = fm->MaxAscent();
+ nscoord maxDescent = fm->MaxDescent();
+ nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate
+ // length if writing mode is vertical
+
+ WritingMode wm = GetWritingMode();
+ bool isVertical = wm.IsVertical();
+
+ fm->SetVertical(isVertical);
+ fm->SetTextOrientation(StyleVisibility()->mTextOrientation);
+
+ // XXX It would be nice if there was a way to have the font metrics tell
+ // use where to break the text given a maximum width. At a minimum we need
+ // to be able to get the break character...
+ const char16_t* str = aAltText.get();
+ int32_t strLen = aAltText.Length();
+ nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0)
+ : aRect.TopLeft();
+ nscoord iSize = isVertical ? aRect.height : aRect.width;
+
+ if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) {
+ aPresContext->SetBidiEnabled();
+ }
+
+ // Always show the first line, even if we have to clip it below
+ bool firstLine = true;
+ while (strLen > 0) {
+ if (!firstLine) {
+ // If we've run out of space, break out of the loop
+ if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) ||
+ (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) ||
+ (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) {
+ break;
+ }
+ }
+
+ // Determine how much of the text to display on this line
+ uint32_t maxFit; // number of characters that fit
+ nscoord strWidth =
+ MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm);
+
+ // Display the text
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aPresContext->BidiEnabled()) {
+ mozilla::intl::BidiEmbeddingLevel level;
+ nscoord x, y;
+
+ if (isVertical) {
+ x = pt.x + maxDescent;
+ if (wm.IsBidiLTR()) {
+ y = aRect.y;
+ level = mozilla::intl::BidiEmbeddingLevel::LTR();
+ } else {
+ y = aRect.YMost() - strWidth;
+ level = mozilla::intl::BidiEmbeddingLevel::RTL();
+ }
+ } else {
+ y = pt.y + maxAscent;
+ if (wm.IsBidiLTR()) {
+ x = aRect.x;
+ level = mozilla::intl::BidiEmbeddingLevel::LTR();
+ } else {
+ x = aRect.XMost() - strWidth;
+ level = mozilla::intl::BidiEmbeddingLevel::RTL();
+ }
+ }
+
+ rv = nsBidiPresUtils::RenderText(
+ str, maxFit, level, aPresContext, aRenderingContext,
+ aRenderingContext.GetDrawTarget(), *fm, x, y);
+ }
+ if (NS_FAILED(rv)) {
+ nsLayoutUtils::DrawUniDirString(str, maxFit,
+ isVertical
+ ? nsPoint(pt.x + maxDescent, pt.y)
+ : nsPoint(pt.x, pt.y + maxAscent),
+ *fm, aRenderingContext);
+ }
+
+ // Move to the next line
+ str += maxFit;
+ strLen -= maxFit;
+ if (wm.IsVerticalRL()) {
+ pt.x -= lineHeight;
+ } else if (wm.IsVerticalLR()) {
+ pt.x += lineHeight;
+ } else {
+ pt.y += lineHeight;
+ }
+
+ firstLine = false;
+ }
+}
+
+struct nsRecessedBorder : public nsStyleBorder {
+ explicit nsRecessedBorder(nscoord aBorderWidth) {
+ for (const auto side : AllPhysicalSides()) {
+ BorderColorFor(side) = StyleColor::Black();
+ mBorder.Side(side) = aBorderWidth;
+ // Note: use SetBorderStyle here because we want to affect
+ // mComputedBorder
+ SetBorderStyle(side, StyleBorderStyle::Inset);
+ }
+ }
+};
+
+class nsDisplayAltFeedback final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {}
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final {
+ *aSnap = false;
+ return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final {
+ // Always sync decode, because these icons are UI, and since they're not
+ // discardable we'll pay the price of sync decoding at most once.
+ uint32_t flags = imgIContainer::FLAG_SYNC_DECODE;
+
+ nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
+ Unused << f->DisplayAltFeedback(*aCtx, GetPaintRect(aBuilder, aCtx),
+ ToReferenceFrame(), flags);
+ }
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) final {
+ // Always sync decode, because these icons are UI, and since they're not
+ // discardable we'll pay the price of sync decoding at most once.
+ uint32_t flags =
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
+ nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
+ ImgDrawResult result = f->DisplayAltFeedbackWithoutLayer(
+ this, aBuilder, aResources, aSc, aManager, aDisplayListBuilder,
+ ToReferenceFrame(), flags);
+
+ return result == ImgDrawResult::SUCCESS;
+ }
+
+ NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK)
+};
+
+ImgDrawResult nsImageFrame::DisplayAltFeedback(gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt, uint32_t aFlags) {
+ // Whether we draw the broken or loading icon.
+ bool isLoading = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ // Calculate the content area.
+ nsRect inner = GetContentRectRelativeToSelf() + aPt;
+
+ // Display a recessed one pixel border
+ nscoord borderEdgeWidth =
+ nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
+
+ // if inner area is empty, then make it big enough for at least the icon
+ if (inner.IsEmpty()) {
+ inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)),
+ 2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)));
+ }
+
+ // Make sure we have enough room to actually render the border within
+ // our frame bounds
+ if ((inner.width < 2 * borderEdgeWidth) ||
+ (inner.height < 2 * borderEdgeWidth)) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // Paint the border
+ if (!isLoading) {
+ nsRecessedBorder recessedBorder(borderEdgeWidth);
+
+ // Assert that we're not drawing a border-image here; if we were, we
+ // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
+ // returns.
+ MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone());
+
+ Unused << nsCSSRendering::PaintBorderWithStyleBorder(
+ PresContext(), aRenderingContext, this, inner, inner, recessedBorder,
+ mComputedStyle, PaintBorderFlags::SyncDecodeImages);
+ }
+
+ // Adjust the inner rect to account for the one pixel recessed border,
+ // and a six pixel padding on each edge
+ inner.Deflate(
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH),
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH));
+ if (inner.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // Clip so we don't render outside the inner rect
+ aRenderingContext.Save();
+ aRenderingContext.Clip(NSRectToSnappedRect(
+ inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ // Check if we should display image placeholders
+ if (ShouldShowBrokenImageIcon()) {
+ result = ImgDrawResult::NOT_READY;
+ nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
+ imgIRequest* request = BrokenImageIcon::GetImage(this);
+
+ // If we weren't previously displaying an icon, register ourselves
+ // as an observer for load and animation updates and flag that we're
+ // doing so now.
+ if (request && !mDisplayingIcon) {
+ BrokenImageIcon::AddObserver(this);
+ mDisplayingIcon = true;
+ }
+
+ WritingMode wm = GetWritingMode();
+ bool flushRight = wm.IsPhysicalRTL();
+
+ // If the icon in question is loaded, draw it.
+ uint32_t imageStatus = 0;
+ if (request) request->GetImageStatus(&imageStatus);
+ if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
+ !(imageStatus & imgIRequest::STATUS_ERROR)) {
+ nsCOMPtr<imgIContainer> imgCon;
+ request->GetImage(getter_AddRefs(imgCon));
+ MOZ_ASSERT(imgCon, "Load complete, but no image container?");
+ nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size,
+ size);
+ result = nsLayoutUtils::DrawSingleImage(
+ aRenderingContext, PresContext(), imgCon,
+ nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
+ SVGImageContext(), aFlags);
+ }
+
+ // If we could not draw the icon, just draw some graffiti in the mean time.
+ if (result == ImgDrawResult::NOT_READY) {
+ ColorPattern color(ToDeviceColor(sRGBColor(1.f, 0.f, 0.f, 1.f)));
+
+ nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
+
+ // stroked rect:
+ nsRect rect(iconXPos, inner.y, size, size);
+ Rect devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
+ rect, PresContext()->AppUnitsPerDevPixel()));
+ drawTarget->StrokeRect(devPxRect, color);
+
+ // filled circle in bottom right quadrant of stroked rect:
+ nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
+ rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
+ size / 2 - twoPX);
+ devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
+ rect, PresContext()->AppUnitsPerDevPixel()));
+ RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
+ AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
+ RefPtr<Path> ellipse = builder->Finish();
+ drawTarget->Fill(ellipse, color);
+ }
+
+ // Reduce the inner rect by the width of the icon, and leave an
+ // additional ICON_PADDING pixels for padding
+ int32_t paddedIconSize =
+ nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
+ if (wm.IsVertical()) {
+ inner.y += paddedIconSize;
+ inner.height -= paddedIconSize;
+ } else {
+ if (!flushRight) {
+ inner.x += paddedIconSize;
+ }
+ inner.width -= paddedIconSize;
+ }
+ }
+
+ // If there's still room, display the alt-text
+ if (!inner.IsEmpty()) {
+ nsAutoString altText;
+ nsCSSFrameConstructor::GetAlternateTextFor(*mContent->AsElement(), altText);
+ DisplayAltText(PresContext(), aRenderingContext, altText, inner);
+ }
+
+ aRenderingContext.Restore();
+
+ return result;
+}
+
+ImgDrawResult nsImageFrame::DisplayAltFeedbackWithoutLayer(
+ nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, nsPoint aPt, uint32_t aFlags) {
+ // Whether we draw the broken or loading icon.
+ bool isLoading = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ // Calculate the content area.
+ nsRect inner = GetContentRectRelativeToSelf() + aPt;
+
+ // Display a recessed one pixel border
+ nscoord borderEdgeWidth =
+ nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
+
+ // if inner area is empty, then make it big enough for at least the icon
+ if (inner.IsEmpty()) {
+ inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)),
+ 2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)));
+ }
+
+ // Make sure we have enough room to actually render the border within
+ // our frame bounds
+ if ((inner.width < 2 * borderEdgeWidth) ||
+ (inner.height < 2 * borderEdgeWidth)) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // If the TextDrawTarget requires fallback we need to rollback everything we
+ // may have added to the display list, but we don't find that out until the
+ // end.
+ bool textDrawResult = true;
+ class AutoSaveRestore {
+ public:
+ explicit AutoSaveRestore(mozilla::wr::DisplayListBuilder& aBuilder,
+ bool& aTextDrawResult)
+ : mBuilder(aBuilder), mTextDrawResult(aTextDrawResult) {
+ mBuilder.Save();
+ }
+ ~AutoSaveRestore() {
+ // If we have to use fallback for the text restore the builder and remove
+ // anything else we added to the display list, we need to use fallback.
+ if (mTextDrawResult) {
+ mBuilder.ClearSave();
+ } else {
+ mBuilder.Restore();
+ }
+ }
+
+ private:
+ mozilla::wr::DisplayListBuilder& mBuilder;
+ bool& mTextDrawResult;
+ };
+
+ AutoSaveRestore autoSaveRestore(aBuilder, textDrawResult);
+
+ // Paint the border
+ if (!isLoading) {
+ nsRecessedBorder recessedBorder(borderEdgeWidth);
+ // Assert that we're not drawing a border-image here; if we were, we
+ // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
+ // returns.
+ MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone());
+
+ nsRect rect = nsRect(aPt, GetSize());
+ Unused << nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
+ aItem, this, rect, aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder, recessedBorder);
+ }
+
+ // Adjust the inner rect to account for the one pixel recessed border,
+ // and a six pixel padding on each edge
+ inner.Deflate(
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH),
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH));
+ if (inner.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // Clip to this rect so we don't render outside the inner rect
+ const auto bounds = LayoutDeviceRect::FromAppUnits(
+ inner, PresContext()->AppUnitsPerDevPixel());
+ auto wrBounds = wr::ToLayoutRect(bounds);
+
+ // Check if we should display image placeholders
+ if (ShouldShowBrokenImageIcon()) {
+ ImgDrawResult result = ImgDrawResult::NOT_READY;
+ nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
+ imgIRequest* request = BrokenImageIcon::GetImage(this);
+
+ // If we weren't previously displaying an icon, register ourselves
+ // as an observer for load and animation updates and flag that we're
+ // doing so now.
+ if (request && !mDisplayingIcon) {
+ BrokenImageIcon::AddObserver(this);
+ mDisplayingIcon = true;
+ }
+
+ WritingMode wm = GetWritingMode();
+ const bool flushRight = wm.IsPhysicalRTL();
+
+ // If the icon in question is loaded, draw it.
+ uint32_t imageStatus = 0;
+ if (request) request->GetImageStatus(&imageStatus);
+ if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
+ !(imageStatus & imgIRequest::STATUS_ERROR)) {
+ nsCOMPtr<imgIContainer> imgCon;
+ request->GetImage(getter_AddRefs(imgCon));
+ MOZ_ASSERT(imgCon, "Load complete, but no image container?");
+
+ nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size,
+ size);
+
+ const int32_t factor = PresContext()->AppUnitsPerDevPixel();
+ const auto destRect = LayoutDeviceRect::FromAppUnits(dest, factor);
+
+ SVGImageContext svgContext;
+ Maybe<ImageIntRegion> region;
+ IntSize decodeSize =
+ nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ imgCon, this, destRect, destRect, aSc, aFlags, svgContext,
+ region);
+ RefPtr<image::WebRenderImageProvider> provider;
+ result = imgCon->GetImageProvider(aManager->LayerManager(), decodeSize,
+ svgContext, region, aFlags,
+ getter_AddRefs(provider));
+ if (provider) {
+ bool wrResult = aManager->CommandBuilder().PushImageProvider(
+ aItem, provider, result, aBuilder, aResources, destRect, bounds);
+ result &= wrResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
+ } else {
+ // We don't use &= here because we want the result to be NOT_READY so
+ // the next block executes.
+ result = ImgDrawResult::NOT_READY;
+ }
+ }
+
+ // If we could not draw the icon, just draw some graffiti in the mean time.
+ if (result == ImgDrawResult::NOT_READY) {
+ auto color = wr::ColorF{1.0f, 0.0f, 0.0f, 1.0f};
+ bool isBackfaceVisible = !aItem->BackfaceIsHidden();
+
+ nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
+
+ // stroked rect:
+ nsRect rect(iconXPos, inner.y, size, size);
+ auto devPxRect = LayoutDeviceRect::FromAppUnits(
+ rect, PresContext()->AppUnitsPerDevPixel());
+ auto dest = wr::ToLayoutRect(devPxRect);
+
+ auto borderWidths = wr::ToBorderWidths(1.0, 1.0, 1.0, 1.0);
+ wr::BorderSide side = {color, wr::BorderStyle::Solid};
+ wr::BorderSide sides[4] = {side, side, side, side};
+ Range<const wr::BorderSide> sidesRange(sides, 4);
+ aBuilder.PushBorder(dest, wrBounds, isBackfaceVisible, borderWidths,
+ sidesRange, wr::EmptyBorderRadius());
+
+ // filled circle in bottom right quadrant of stroked rect:
+ nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
+ rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
+ size / 2 - twoPX);
+ devPxRect = LayoutDeviceRect::FromAppUnits(
+ rect, PresContext()->AppUnitsPerDevPixel());
+ dest = wr::ToLayoutRect(devPxRect);
+
+ aBuilder.PushRoundedRect(dest, wrBounds, isBackfaceVisible, color);
+ }
+
+ // Reduce the inner rect by the width of the icon, and leave an
+ // additional ICON_PADDING pixels for padding
+ int32_t paddedIconSize =
+ nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
+ if (wm.IsVertical()) {
+ inner.y += paddedIconSize;
+ inner.height -= paddedIconSize;
+ } else {
+ if (!flushRight) {
+ inner.x += paddedIconSize;
+ }
+ inner.width -= paddedIconSize;
+ }
+ }
+
+ // Draw text
+ if (!inner.IsEmpty()) {
+ RefPtr<TextDrawTarget> textDrawer =
+ new TextDrawTarget(aBuilder, aResources, aSc, aManager, aItem, inner,
+ /* aCallerDoesSaveRestore = */ true);
+ MOZ_ASSERT(textDrawer->IsValid());
+ if (textDrawer->IsValid()) {
+ gfxContext captureCtx(textDrawer);
+
+ nsAutoString altText;
+ nsCSSFrameConstructor::GetAlternateTextFor(*mContent->AsElement(),
+ altText);
+ DisplayAltText(PresContext(), captureCtx, altText, inner);
+
+ textDrawer->TerminateShadows();
+ textDrawResult = !textDrawer->CheckHasUnsupportedFeatures();
+ }
+ }
+
+ // Purposely ignore local DrawResult because we handled it not being success
+ // already.
+ return textDrawResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
+}
+
+// We want to sync-decode in this case, as otherwise we either need to flash
+// white while waiting to decode the new image, or paint the old image with a
+// different aspect-ratio, which would be bad as it'd be stretched.
+//
+// See bug 1589955.
+static bool OldImageHasDifferentRatio(const nsImageFrame& aFrame,
+ imgIContainer& aImage,
+ imgIContainer* aPrevImage) {
+ if (!aPrevImage || aPrevImage == &aImage) {
+ return false;
+ }
+
+ // If we don't depend on our intrinsic image size / ratio, we're good.
+ //
+ // FIXME(emilio): There's the case of the old image being painted
+ // intrinsically, and src and styles changing at the same time... Maybe we
+ // should keep track of the old GetPaintRect()'s ratio and the image's ratio,
+ // instead of checking this bit?
+ if (aFrame.HasAnyStateBits(IMAGE_SIZECONSTRAINED)) {
+ return false;
+ }
+
+ auto currentRatio = aFrame.GetIntrinsicRatio();
+ // If we have an image, we need to have a current request.
+ // Same if we had an image.
+ const bool hasRequest = true;
+#ifdef DEBUG
+ auto currentRatioRecomputed =
+ ComputeIntrinsicRatio(&aImage, hasRequest, aFrame);
+ // If the image encounters an error after decoding the size (and we run
+ // UpdateIntrinsicRatio) then the image will return the empty AspectRatio and
+ // the aspect ratio we compute here will be different from what was computed
+ // and stored before the image went into error state. It would be better to
+ // check that the image has an error here but we need an imgIRequest for that,
+ // not an imgIContainer. In lieu of that we check that
+ // aImage.GetIntrinsicRatio() returns Nothing() as it does when the image is
+ // in the error state and that the recomputed ratio is the zero ratio.
+ MOZ_ASSERT(
+ (!currentRatioRecomputed && aImage.GetIntrinsicRatio() == Nothing()) ||
+ currentRatio == currentRatioRecomputed,
+ "aspect-ratio got out of sync during paint? How?");
+#endif
+ auto oldRatio = ComputeIntrinsicRatio(aPrevImage, hasRequest, aFrame);
+ return oldRatio != currentRatio;
+}
+
+#ifdef DEBUG
+void nsImageFrame::AssertSyncDecodingHintIsInSync() const {
+ if (!IsForImageLoadingContent()) {
+ return;
+ }
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+
+ // The opposite is not true, we might have some other heuristics which force
+ // sync-decoding of images.
+ MOZ_ASSERT_IF(imageLoader->GetSyncDecodingHint(), mForceSyncDecoding);
+}
+#endif
+
+void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ MOZ_ASSERT(mImage);
+ auto* frame = static_cast<nsImageFrame*>(mFrame);
+ frame->AssertSyncDecodingHintIsInSync();
+
+ const bool oldImageIsDifferent =
+ OldImageHasDifferentRatio(*frame, *mImage, mPrevImage);
+
+ uint32_t flags = aBuilder->GetImageDecodeFlags();
+ if (aBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent ||
+ frame->mForceSyncDecoding) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ ImgDrawResult result = frame->PaintImage(
+ *aCtx, ToReferenceFrame(), GetPaintRect(aBuilder, aCtx), mImage, flags);
+
+ if (result == ImgDrawResult::NOT_READY ||
+ result == ImgDrawResult::INCOMPLETE ||
+ result == ImgDrawResult::TEMPORARY_ERROR) {
+ // If the current image failed to paint because it's still loading or
+ // decoding, try painting the previous image.
+ if (mPrevImage) {
+ result =
+ frame->PaintImage(*aCtx, ToReferenceFrame(),
+ GetPaintRect(aBuilder, aCtx), mPrevImage, flags);
+ }
+ }
+}
+
+nsRect nsDisplayImage::GetDestRect() const {
+ bool snap = true;
+ const nsRect frameContentBox = GetBounds(&snap);
+
+ nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
+ return imageFrame->PredictedDestRect(frameContentBox);
+}
+
+nsRegion nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ if (mImage && mImage->WillDrawOpaqueNow()) {
+ const nsRect frameContentBox = GetBounds(aSnap);
+ return GetDestRect().Intersect(frameContentBox);
+ }
+ return nsRegion();
+}
+
+bool nsDisplayImage::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!mImage) {
+ return false;
+ }
+
+ MOZ_ASSERT(mFrame->IsImageFrame() || mFrame->IsImageControlFrame());
+ // Image layer doesn't support draw focus ring for image map.
+ auto* frame = static_cast<nsImageFrame*>(mFrame);
+ if (frame->HasImageMap()) {
+ return false;
+ }
+
+ frame->AssertSyncDecodingHintIsInSync();
+ const bool oldImageIsDifferent =
+ OldImageHasDifferentRatio(*frame, *mImage, mPrevImage);
+
+ uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
+ if (aDisplayListBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent ||
+ frame->mForceSyncDecoding) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+ if (StaticPrefs::image_svg_blob_image() &&
+ mImage->GetType() == imgIContainer::TYPE_VECTOR) {
+ flags |= imgIContainer::FLAG_RECORD_BLOB;
+ }
+
+ const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect destRect(
+ LayoutDeviceRect::FromAppUnits(GetDestRect(), factor));
+
+ SVGImageContext svgContext;
+ Maybe<ImageIntRegion> region;
+ IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ mImage, mFrame, destRect, destRect, aSc, flags, svgContext, region);
+
+ RefPtr<image::WebRenderImageProvider> provider;
+ ImgDrawResult drawResult =
+ mImage->GetImageProvider(aManager->LayerManager(), decodeSize, svgContext,
+ region, flags, getter_AddRefs(provider));
+
+ if (nsCOMPtr<imgIRequest> currentRequest = frame->GetCurrentRequest()) {
+ LCPHelpers::FinalizeLCPEntryForImage(
+ frame->GetContent()->AsElement(),
+ static_cast<imgRequestProxy*>(currentRequest.get()),
+ GetDestRect() - ToReferenceFrame());
+ }
+
+ // While we got a container, it may not contain a fully decoded surface. If
+ // that is the case, and we have an image we were previously displaying which
+ // has a fully decoded surface, then we should prefer the previous image.
+ bool updatePrevImage = false;
+ switch (drawResult) {
+ case ImgDrawResult::NOT_READY:
+ case ImgDrawResult::INCOMPLETE:
+ case ImgDrawResult::TEMPORARY_ERROR:
+ if (mPrevImage && mPrevImage != mImage) {
+ // The current image and the previous image might be switching between
+ // rasterized surfaces and blob recordings, so we need to update the
+ // flags appropriately.
+ uint32_t prevFlags = flags;
+ if (StaticPrefs::image_svg_blob_image() &&
+ mPrevImage->GetType() == imgIContainer::TYPE_VECTOR) {
+ prevFlags |= imgIContainer::FLAG_RECORD_BLOB;
+ } else {
+ prevFlags &= ~imgIContainer::FLAG_RECORD_BLOB;
+ }
+
+ RefPtr<image::WebRenderImageProvider> prevProvider;
+ ImgDrawResult prevDrawResult = mPrevImage->GetImageProvider(
+ aManager->LayerManager(), decodeSize, svgContext, region, prevFlags,
+ getter_AddRefs(prevProvider));
+ if (prevProvider && (prevDrawResult == ImgDrawResult::SUCCESS ||
+ prevDrawResult == ImgDrawResult::WRONG_SIZE)) {
+ // We use WRONG_SIZE here to ensure that when the frame next tries to
+ // invalidate due to a frame update from the current image, we don't
+ // consider the result from the previous image to be a valid result to
+ // avoid redrawing.
+ drawResult = ImgDrawResult::WRONG_SIZE;
+ provider = std::move(prevProvider);
+ flags = prevFlags;
+ break;
+ }
+
+ // Previous image was unusable; we can forget about it.
+ updatePrevImage = true;
+ }
+ break;
+ case ImgDrawResult::NOT_SUPPORTED:
+ return false;
+ default:
+ updatePrevImage = mPrevImage != mImage;
+ break;
+ }
+
+ // The previous image was not used, and is different from the current image.
+ // We should forget about it. We need to update the frame as well because the
+ // display item may get recreated.
+ if (updatePrevImage) {
+ mPrevImage = mImage;
+ frame->mPrevImage = frame->mImage;
+ }
+
+ // If the image provider is null, we don't want to fallback. Any other
+ // failure will be due to resource constraints and fallback is unlikely to
+ // help us. Hence we can ignore the return value from PushImage.
+ aManager->CommandBuilder().PushImageProvider(
+ this, provider, drawResult, aBuilder, aResources, destRect, destRect);
+ return true;
+}
+
+ImgDrawResult nsImageFrame::PaintImage(gfxContext& aRenderingContext,
+ nsPoint aPt, const nsRect& aDirtyRect,
+ imgIContainer* aImage, uint32_t aFlags) {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // Render the image into our content area (the area inside
+ // the borders and padding)
+ NS_ASSERTION(GetContentRectRelativeToSelf().width == mComputedSize.width,
+ "bad width");
+
+ // NOTE: We use mComputedSize instead of just GetContentRectRelativeToSelf()'s
+ // own size here, because GetContentRectRelativeToSelf() might be smaller if
+ // we're fragmented, whereas mComputedSize has our full content-box size
+ // (which we need for ComputeObjectDestRect to work correctly).
+ nsRect constraintRect(aPt + GetContentRectRelativeToSelf().TopLeft(),
+ mComputedSize);
+ constraintRect.y -= GetContinuationOffset();
+
+ nsPoint anchorPoint;
+ nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
+ constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition(),
+ &anchorPoint);
+
+ SVGImageContext svgContext;
+ SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage);
+
+ ImgDrawResult result = nsLayoutUtils::DrawSingleImage(
+ aRenderingContext, PresContext(), aImage,
+ nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
+ svgContext, aFlags, &anchorPoint);
+
+ if (nsImageMap* map = GetImageMap()) {
+ gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
+ dest.TopLeft(), PresContext()->AppUnitsPerDevPixel());
+ AutoRestoreTransform autoRestoreTransform(drawTarget);
+ drawTarget->SetTransform(
+ drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
+
+ // solid white stroke:
+ ColorPattern white(ToDeviceColor(sRGBColor::OpaqueWhite()));
+ map->Draw(this, *drawTarget, white);
+
+ // then dashed black stroke over the top:
+ ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack()));
+ StrokeOptions strokeOptions;
+ nsLayoutUtils::InitDashPattern(strokeOptions, StyleBorderStyle::Dotted);
+ map->Draw(this, *drawTarget, black, strokeOptions);
+ }
+
+ if (result == ImgDrawResult::SUCCESS) {
+ mPrevImage = aImage;
+ } else if (result == ImgDrawResult::BAD_IMAGE) {
+ mPrevImage = nullptr;
+ }
+
+ return result;
+}
+
+already_AddRefed<imgIRequest> nsImageFrame::GetCurrentRequest() const {
+ if (mKind != Kind::ImageLoadingContent) {
+ return do_AddRef(mOwnedRequest);
+ }
+
+ MOZ_ASSERT(!mOwnedRequest);
+
+ nsCOMPtr<imgIRequest> request;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ return request.forget();
+}
+
+void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) {
+ return;
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ if (HidesContent()) {
+ DisplaySelectionOverlay(aBuilder, aLists.Content(),
+ nsISelectionDisplay::DISPLAY_IMAGES);
+ return;
+ }
+
+ uint32_t clipFlags =
+ nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())
+ ? 0
+ : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
+
+ DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
+ aBuilder, this, clipFlags);
+
+ if (mComputedSize.width != 0 && mComputedSize.height != 0) {
+ const bool imageOK = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+
+ const bool isImageFromStyle =
+ mKind != Kind::ImageLoadingContent && mKind != Kind::XULImage;
+ const bool drawAltFeedback = [&] {
+ if (!imageOK) {
+ return true;
+ }
+ // If we're a gradient, we don't need to draw alt feedback.
+ if (isImageFromStyle && !GetImageFromStyle()->IsImageRequestType()) {
+ return false;
+ }
+ // XXX(seth): The SizeIsAvailable check here should not be necessary - the
+ // intention is that a non-null mImage means we have a size, but there is
+ // currently some code that violates this invariant.
+ return !mImage || !SizeIsAvailable(currentRequest);
+ }();
+
+ if (drawAltFeedback) {
+ // No image yet, or image load failed. Draw the alt-text and an icon
+ // indicating the status
+ aLists.Content()->AppendNewToTop<nsDisplayAltFeedback>(aBuilder, this);
+
+ // This image is visible (we are being asked to paint it) but it's not
+ // decoded yet. And we are not going to ask the image to draw, so this
+ // may be the only chance to tell it that it should decode.
+ if (currentRequest) {
+ uint32_t status = 0;
+ currentRequest->GetImageStatus(&status);
+ if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) {
+ MaybeDecodeForPredictedSize();
+ }
+ // Increase loading priority if the image is ready to be displayed.
+ if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
+ }
+ }
+ } else {
+ if (mImage) {
+ aLists.Content()->AppendNewToTop<nsDisplayImage>(aBuilder, this, mImage,
+ mPrevImage);
+ } else if (isImageFromStyle) {
+ aLists.Content()->AppendNewToTop<nsDisplayGradient>(aBuilder, this);
+ }
+
+ // If we were previously displaying an icon, we're not anymore
+ if (mDisplayingIcon) {
+ BrokenImageIcon::RemoveObserver(this);
+ mDisplayingIcon = false;
+ }
+ }
+ }
+
+ if (ShouldDisplaySelection()) {
+ DisplaySelectionOverlay(aBuilder, aLists.Content(),
+ nsISelectionDisplay::DISPLAY_IMAGES);
+ }
+
+ BuildDisplayListForNonBlockChildren(aBuilder, aLists);
+}
+
+bool nsImageFrame::ShouldDisplaySelection() {
+ int16_t displaySelection = PresShell()->GetSelectionFlags();
+ if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES)) {
+ // no need to check the blue border, we cannot be drawn selected.
+ return false;
+ }
+
+ if (displaySelection != nsISelectionDisplay::DISPLAY_ALL) {
+ return true;
+ }
+
+ // If the image is currently resize target of the editor, don't draw the
+ // selection overlay.
+ HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(PresContext());
+ if (!htmlEditor) {
+ return true;
+ }
+
+ return htmlEditor->GetResizerTarget() != mContent;
+}
+
+nsImageMap* nsImageFrame::GetImageMap() {
+ if (!mImageMap) {
+ if (nsIContent* map = GetMapElement()) {
+ mImageMap = new nsImageMap();
+ mImageMap->Init(this, map);
+ }
+ }
+
+ return mImageMap;
+}
+
+bool nsImageFrame::IsServerImageMap() {
+ return mContent->AsElement()->HasAttr(nsGkAtoms::ismap);
+}
+
+CSSIntPoint nsImageFrame::TranslateEventCoords(const nsPoint& aPoint) {
+ const nsRect contentRect = GetContentRectRelativeToSelf();
+ // Subtract out border and padding here so that the coordinates are
+ // now relative to the content area of this frame.
+ return CSSPixel::FromAppUnitsRounded(aPoint - contentRect.TopLeft());
+}
+
+bool nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
+ nsIContent** aNode) {
+ aTarget.Truncate();
+ *aHref = nullptr;
+ *aNode = nullptr;
+
+ // Walk up the content tree, looking for an nsIDOMAnchorElement
+ for (nsIContent* content = mContent->GetParent(); content;
+ content = content->GetParent()) {
+ nsCOMPtr<dom::Link> link = do_QueryInterface(content);
+ if (!link) {
+ continue;
+ }
+ if (nsCOMPtr<nsIURI> href = link->GetURI()) {
+ href.forget(aHref);
+ }
+
+ if (auto* anchor = HTMLAnchorElement::FromNode(content)) {
+ anchor->GetTarget(aTarget);
+ }
+ NS_ADDREF(*aNode = content);
+ return *aHref != nullptr;
+ }
+ return false;
+}
+
+bool nsImageFrame::IsLeafDynamic() const {
+ if (mKind != Kind::ImageLoadingContent) {
+ // Image frames created for "content: url()" could have an author-controlled
+ // shadow root, we want to be a regular leaf for those.
+ return true;
+ }
+ // For elements that create image frames, calling attachShadow() will throw,
+ // so the only ShadowRoot we could ever have is a UA widget.
+ const auto* shadow = mContent->AsElement()->GetShadowRoot();
+ MOZ_ASSERT_IF(shadow, shadow->IsUAWidget());
+ return !shadow;
+}
+
+nsresult nsImageFrame::GetContentForEvent(const WidgetEvent* aEvent,
+ nsIContent** aContent) {
+ NS_ENSURE_ARG_POINTER(aContent);
+
+ nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
+ if (f != this) {
+ return f->GetContentForEvent(aEvent, aContent);
+ }
+
+ // XXX We need to make this special check for area element's capturing the
+ // mouse due to bug 135040. Remove it once that's fixed.
+ nsIContent* capturingContent = aEvent->HasMouseEventMessage()
+ ? PresShell::GetCapturingContent()
+ : nullptr;
+ if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
+ *aContent = capturingContent;
+ NS_IF_ADDREF(*aContent);
+ return NS_OK;
+ }
+
+ if (nsImageMap* map = GetImageMap()) {
+ const CSSIntPoint p = TranslateEventCoords(
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this}));
+ nsCOMPtr<nsIContent> area = map->GetArea(p);
+ if (area) {
+ area.forget(aContent);
+ return NS_OK;
+ }
+ }
+
+ *aContent = GetContent();
+ NS_IF_ADDREF(*aContent);
+ return NS_OK;
+}
+
+// XXX what should clicks on transparent pixels do?
+nsresult nsImageFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ if ((aEvent->mMessage == eMouseClick &&
+ aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) ||
+ aEvent->mMessage == eMouseMove) {
+ nsImageMap* map = GetImageMap();
+ bool isServerMap = IsServerImageMap();
+ if (map || isServerMap) {
+ CSSIntPoint p =
+ TranslateEventCoords(nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aEvent, RelativeTo{this}));
+
+ // Even though client-side image map triggering happens
+ // through content, we need to make sure we're not inside
+ // (in case we deal with a case of both client-side and
+ // sever-side on the same image - it happens!)
+ const bool inside = map && map->GetArea(p);
+
+ if (!inside && isServerMap) {
+ // Server side image maps use the href in a containing anchor
+ // element to provide the basis for the destination url.
+ nsCOMPtr<nsIURI> uri;
+ nsAutoString target;
+ nsCOMPtr<nsIContent> anchorNode;
+ if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target,
+ getter_AddRefs(anchorNode))) {
+ // XXX if the mouse is over/clicked in the border/padding area
+ // we should probably just pretend nothing happened. Nav4
+ // keeps the x,y coordinates positive as we do; IE doesn't
+ // bother. Both of them send the click through even when the
+ // mouse is over the border.
+ if (p.x < 0) p.x = 0;
+ if (p.y < 0) p.y = 0;
+
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ spec += nsPrintfCString("?%d,%d", p.x.value, p.y.value);
+ rv = NS_MutateURI(uri).SetSpec(spec).Finalize(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool clicked = false;
+ if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) {
+ *aEventStatus = nsEventStatus_eConsumeDoDefault;
+ clicked = true;
+ }
+ nsContentUtils::TriggerLink(anchorNode, uri, target, clicked,
+ /* isTrusted */ true);
+ }
+ }
+ }
+ }
+
+ return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent,
+ aEventStatus);
+}
+
+nsIFrame::Cursor nsImageFrame::GetCursor(const nsPoint& aPoint) {
+ nsImageMap* map = GetImageMap();
+ if (!map) {
+ return nsIFrame::GetCursor(aPoint);
+ }
+ const CSSIntPoint p = TranslateEventCoords(aPoint);
+ HTMLAreaElement* area = map->GetArea(p);
+ if (!area) {
+ return nsIFrame::GetCursor(aPoint);
+ }
+
+ // Use the cursor from the style of the *area* element.
+ RefPtr<ComputedStyle> areaStyle =
+ PresShell()->StyleSet()->ResolveStyleLazily(*area);
+
+ // This is one of the cases, like the <xul:tree> pseudo-style stuff, where we
+ // get styles out of the blue and expect to trigger image loads for those.
+ areaStyle->StartImageLoads(*PresContext()->Document());
+
+ StyleCursorKind kind = areaStyle->StyleUI()->Cursor().keyword;
+ if (kind == StyleCursorKind::Auto) {
+ kind = StyleCursorKind::Default;
+ }
+ return Cursor{kind, AllowCustomCursorImage::Yes, std::move(areaStyle)};
+}
+
+nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (nsGkAtoms::alt == aAttribute) {
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ }
+ if (mKind == Kind::XULImage && aAttribute == nsGkAtoms::src &&
+ aNameSpaceID == kNameSpaceID_None) {
+ UpdateXULImage();
+ }
+ return NS_OK;
+}
+
+void nsImageFrame::OnVisibilityChange(
+ Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ if (mKind == Kind::ImageLoadingContent) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ }
+
+ if (aNewVisibility == Visibility::ApproximatelyVisible &&
+ PresShell()->IsActive()) {
+ MaybeDecodeForPredictedSize();
+ }
+
+ nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsImageFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"ImageFrame"_ns, aResult);
+}
+
+void nsImageFrame::List(FILE* out, const char* aPrefix,
+ ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+
+ // output the img src url
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ nsCOMPtr<nsIURI> uri = currentRequest->GetURI();
+ nsAutoCString uristr;
+ uri->GetAsciiSpec(uristr);
+ str += nsPrintfCString(" [src=%s]", uristr.get());
+ }
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+LogicalSides nsImageFrame::GetLogicalSkipSides() const {
+ LogicalSides skip(mWritingMode);
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return skip;
+ }
+ if (GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver)
+
+nsImageListener::nsImageListener(nsImageFrame* aFrame) : mFrame(aFrame) {}
+
+nsImageListener::~nsImageListener() = default;
+
+void nsImageListener::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ if (!mFrame) {
+ return;
+ }
+
+ return mFrame->Notify(aRequest, aType, aData);
+}
+
+static bool IsInAutoWidthTableCellForQuirk(nsIFrame* aFrame) {
+ if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
+ return false;
+ // Check if the parent of the closest nsBlockFrame has auto width.
+ nsBlockFrame* ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame);
+ if (ancestor->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
+ // Assume direct parent is a table cell frame.
+ nsIFrame* grandAncestor = static_cast<nsIFrame*>(ancestor->GetParent());
+ return grandAncestor && grandAncestor->StylePosition()->mWidth.IsAuto();
+ }
+ return false;
+}
+
+void nsImageFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ nscoord isize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, this, IntrinsicISizeType::MinISize);
+ bool canBreak = !IsInAutoWidthTableCellForQuirk(this);
+ aData->DefaultAddInlineMinISize(this, isize, canBreak);
+}
+
+void nsImageFrame::ReleaseGlobals() { BrokenImageIcon::Shutdown(); }
diff --git a/layout/generic/nsImageFrame.h b/layout/generic/nsImageFrame.h
new file mode 100644
index 0000000000..d9e2610e81
--- /dev/null
+++ b/layout/generic/nsImageFrame.h
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for replaced elements with image data */
+
+#ifndef nsImageFrame_h___
+#define nsImageFrame_h___
+
+#include "nsAtomicContainerFrame.h"
+#include "nsIObserver.h"
+
+#include "imgINotificationObserver.h"
+
+#include "nsDisplayList.h"
+#include "imgIContainer.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIReflowCallback.h"
+#include "nsTObserverArray.h"
+
+class nsFontMetrics;
+class nsImageMap;
+class nsIURI;
+class nsILoadGroup;
+class nsPresContext;
+class nsImageFrame;
+class nsTransform2D;
+class nsImageLoadingContent;
+
+namespace mozilla {
+class nsDisplayImage;
+class PresShell;
+namespace layers {
+class ImageContainer;
+class LayerManager;
+} // namespace layers
+} // namespace mozilla
+
+class nsImageListener final : public imgINotificationObserver {
+ protected:
+ virtual ~nsImageListener();
+
+ public:
+ explicit nsImageListener(nsImageFrame* aFrame);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ void SetFrame(nsImageFrame* frame) { mFrame = frame; }
+
+ private:
+ nsImageFrame* mFrame;
+};
+
+class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
+ public:
+ template <typename T>
+ using Maybe = mozilla::Maybe<T>;
+ using Nothing = mozilla::Nothing;
+ using Visibility = mozilla::Visibility;
+
+ typedef mozilla::image::ImgDrawResult ImgDrawResult;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+ typedef mozilla::layers::LayerManager LayerManager;
+
+ NS_DECL_FRAMEARENA_HELPERS(nsImageFrame)
+ NS_DECL_QUERYFRAME
+
+ void Destroy(DestroyContext&) override;
+ void DidSetComputedStyle(ComputedStyle* aOldStyle) final;
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ void BuildDisplayList(nsDisplayListBuilder*, const nsDisplayListSet&) final;
+ nscoord GetMinISize(gfxContext* aRenderingContext) final;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) final;
+ mozilla::IntrinsicSize GetIntrinsicSize() final { return mIntrinsicSize; }
+ mozilla::AspectRatio GetIntrinsicRatio() const final {
+ return mIntrinsicRatio;
+ }
+ void Reflow(nsPresContext*, ReflowOutput&, const ReflowInput&,
+ nsReflowStatus&) override;
+ bool IsLeafDynamic() const override;
+
+ nsresult GetContentForEvent(const mozilla::WidgetEvent*,
+ nsIContent** aContent) final;
+ nsresult HandleEvent(nsPresContext*, mozilla::WidgetGUIEvent*,
+ nsEventStatus*) override;
+ Cursor GetCursor(const nsPoint&) override;
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) final;
+
+ void OnVisibilityChange(
+ Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) final;
+
+ void ResponsiveContentDensityChanged();
+ void ElementStateChanged(mozilla::dom::ElementState) override;
+ void SetupOwnedRequest();
+ void DeinitOwnedRequest();
+ bool ShouldShowBrokenImageIcon() const;
+
+ bool IsForImageLoadingContent() const {
+ return mKind == Kind::ImageLoadingContent;
+ }
+
+ void UpdateXULImage();
+ const mozilla::StyleImage* GetImageFromStyle() const;
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+ void List(FILE* out = stderr, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const final;
+#endif
+
+ LogicalSides GetLogicalSkipSides() const final;
+
+ static void ReleaseGlobals();
+
+ already_AddRefed<imgIRequest> GetCurrentRequest() const;
+ void Notify(imgIRequest*, int32_t aType, const nsIntRect* aData);
+
+ /**
+ * Returns whether we should replace an element with an image corresponding to
+ * its 'content' CSS property.
+ */
+ static bool ShouldCreateImageFrameForContentProperty(
+ const mozilla::dom::Element&, const ComputedStyle&);
+
+ /**
+ * Function to test whether given an element and its style, that element
+ * should get an image frame, and if so, which kind of image frame (for
+ * `content`, or for the element itself).
+ */
+ enum class ImageFrameType {
+ ForContentProperty,
+ ForElementRequest,
+ None,
+ };
+ static ImageFrameType ImageFrameTypeFor(const mozilla::dom::Element&,
+ const ComputedStyle&);
+
+ ImgDrawResult DisplayAltFeedback(gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt,
+ uint32_t aFlags);
+
+ ImgDrawResult DisplayAltFeedbackWithoutLayer(
+ nsDisplayItem*, mozilla::wr::DisplayListBuilder&,
+ mozilla::wr::IpcResourceUpdateQueue&,
+ const mozilla::layers::StackingContextHelper&,
+ mozilla::layers::RenderRootStateManager*, nsDisplayListBuilder*,
+ nsPoint aPt, uint32_t aFlags);
+
+ /**
+ * Return a map element associated with this image.
+ */
+ mozilla::dom::Element* GetMapElement() const;
+
+ /**
+ * Return true if the image has associated image map.
+ */
+ bool HasImageMap() const { return mImageMap || GetMapElement(); }
+
+ nsImageMap* GetImageMap();
+ nsImageMap* GetExistingImageMap() const { return mImageMap; }
+
+ void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) final;
+
+ void DisconnectMap();
+
+ // nsIReflowCallback
+ bool ReflowFinished() final;
+ void ReflowCallbackCanceled() final;
+
+ // The kind of image frame we are.
+ enum class Kind : uint8_t {
+ // For an nsImageLoadingContent.
+ ImageLoadingContent,
+ // For a <xul:image> element.
+ XULImage,
+ // For css 'content: url(..)' on non-generated content.
+ ContentProperty,
+ // For a child of a ::before / ::after pseudo-element that had an url() item
+ // for the content property.
+ ContentPropertyAtIndex,
+ // For a list-style-image ::marker.
+ ListStyleImage,
+ };
+
+ // Creates a suitable continuing frame for this frame.
+ nsImageFrame* CreateContinuingFrame(mozilla::PresShell*,
+ ComputedStyle*) const;
+
+ private:
+ friend nsIFrame* NS_NewImageFrame(mozilla::PresShell*, ComputedStyle*);
+ friend nsIFrame* NS_NewXULImageFrame(mozilla::PresShell*, ComputedStyle*);
+ friend nsIFrame* NS_NewImageFrameForContentProperty(mozilla::PresShell*,
+ ComputedStyle*);
+ friend nsIFrame* NS_NewImageFrameForGeneratedContentIndex(mozilla::PresShell*,
+ ComputedStyle*);
+ friend nsIFrame* NS_NewImageFrameForListStyleImage(mozilla::PresShell*,
+ ComputedStyle*);
+
+ nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, Kind aKind)
+ : nsImageFrame(aStyle, aPresContext, kClassID, aKind) {}
+
+ nsImageFrame(ComputedStyle*, nsPresContext* aPresContext, ClassID, Kind);
+
+ void ReflowChildren(nsPresContext*, const ReflowInput&,
+ const mozilla::LogicalSize& aImageSize);
+
+ void UpdateIntrinsicSizeAndRatio();
+
+ protected:
+ nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, ClassID aID)
+ : nsImageFrame(aStyle, aPresContext, aID, Kind::ImageLoadingContent) {}
+
+ ~nsImageFrame() override;
+
+ void EnsureIntrinsicSizeAndRatio();
+
+ bool GotInitialReflow() const {
+ return !HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+ }
+
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) final;
+
+ bool IsServerImageMap();
+
+ // Translate a point that is relative to our frame into a localized CSS pixel
+ // coordinate that is relative to the content area of this frame (inside the
+ // border+padding).
+ mozilla::CSSIntPoint TranslateEventCoords(const nsPoint& aPoint);
+
+ bool GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
+ nsIContent** aNode);
+ /**
+ * Computes the width of the string that fits into the available space
+ *
+ * @param in aLength total length of the string in PRUnichars
+ * @param in aMaxWidth width not to be exceeded
+ * @param out aMaxFit length of the string that fits within aMaxWidth
+ * in PRUnichars
+ * @return width of the string that fits within aMaxWidth
+ */
+ nscoord MeasureString(const char16_t* aString, int32_t aLength,
+ nscoord aMaxWidth, uint32_t& aMaxFit,
+ gfxContext& aContext, nsFontMetrics& aFontMetrics);
+
+ void DisplayAltText(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext, const nsString& aAltText,
+ const nsRect& aRect);
+
+ ImgDrawResult PaintImage(gfxContext& aRenderingContext, nsPoint aPt,
+ const nsRect& aDirtyRect, imgIContainer* aImage,
+ uint32_t aFlags);
+
+ /**
+ * If we're ready to decode - that is, if our current request's image is
+ * available and our decoding heuristics are satisfied - then trigger a decode
+ * for our image at the size we predict it will be drawn next time it's
+ * painted.
+ */
+ void MaybeDecodeForPredictedSize();
+
+ /**
+ * Is this frame part of a ::marker pseudo?
+ */
+ bool IsForMarkerPseudo() const;
+
+ protected:
+ friend class nsImageListener;
+ friend class nsImageLoadingContent;
+ friend class mozilla::PresShell;
+
+ void OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
+ void OnFrameUpdate(imgIRequest* aRequest, const nsIntRect* aRect);
+ void OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
+
+ /**
+ * Notification that aRequest will now be the current request.
+ */
+ void NotifyNewCurrentRequest(imgIRequest* aRequest, nsresult aStatus);
+
+ /// Always sync decode our image when painting if @aForce is true.
+ void SetForceSyncDecoding(bool aForce) { mForceSyncDecoding = aForce; }
+
+ void AssertSyncDecodingHintIsInSync() const
+#ifndef DEBUG
+ {}
+#else
+ ;
+#endif
+
+ /**
+ * Computes the predicted dest rect that we'll draw into, in app units, based
+ * upon the provided frame content box. (The content box is what
+ * nsDisplayImage::GetBounds() returns.)
+ * The result is not necessarily contained in the frame content box.
+ */
+ nsRect PredictedDestRect(const nsRect& aFrameContentBox);
+
+ private:
+ nscoord GetContinuationOffset() const;
+ bool ShouldDisplaySelection();
+
+ // Whether the image frame should use the mapped aspect ratio from width=""
+ // and height="".
+ bool ShouldUseMappedAspectRatio() const;
+
+ // Recalculate mIntrinsicSize from the image.
+ bool UpdateIntrinsicSize();
+
+ // Recalculate mIntrinsicRatio from the image.
+ bool UpdateIntrinsicRatio();
+
+ /**
+ * This function calculates the transform for converting between
+ * source space & destination space. May fail if our image has a
+ * percent-valued or zero-valued height or width.
+ *
+ * @param aTransform The transform object to populate.
+ *
+ * @return whether we succeeded in creating the transform.
+ */
+ bool GetSourceToDestTransform(nsTransform2D& aTransform);
+
+ /**
+ * Helper function to check whether the request corresponds to a load we don't
+ * care about. Most of the decoder observer methods will bail early if this
+ * returns true.
+ */
+ bool IsPendingLoad(imgIRequest*) const;
+
+ /**
+ * Updates mImage based on the current image request, and the image passed in
+ * (both can be null), and invalidate layout and paint as needed.
+ */
+ void UpdateImage(imgIRequest*, imgIContainer*);
+
+ /**
+ * Function to convert a dirty rect in the source image to a dirty
+ * rect for the image frame.
+ */
+ nsRect SourceRectToDest(const nsIntRect& aRect);
+
+ /**
+ * Triggers invalidation for both our image display item and, if appropriate,
+ * our alt-feedback display item.
+ *
+ * @param aLayerInvalidRect The area to invalidate in layer space. If null,
+ * the entire layer will be invalidated.
+ * @param aFrameInvalidRect The area to invalidate in frame space. If null,
+ * the entire frame will be invalidated.
+ */
+ void InvalidateSelf(const nsIntRect* aLayerInvalidRect,
+ const nsRect* aFrameInvalidRect);
+
+ void MaybeSendIntrinsicSizeAndRatioToEmbedder();
+ void MaybeSendIntrinsicSizeAndRatioToEmbedder(Maybe<mozilla::IntrinsicSize>,
+ Maybe<mozilla::AspectRatio>);
+
+ RefPtr<nsImageMap> mImageMap;
+
+ RefPtr<nsImageListener> mListener;
+
+ // An image request created for content: url(..), list-style-image, or
+ // <xul:image>.
+ RefPtr<imgRequestProxy> mOwnedRequest;
+
+ nsCOMPtr<imgIContainer> mImage;
+ nsCOMPtr<imgIContainer> mPrevImage;
+
+ // The content-box size as if we are not fragmented, cached in the most recent
+ // reflow.
+ nsSize mComputedSize;
+
+ mozilla::IntrinsicSize mIntrinsicSize;
+
+ // Stores mImage's intrinsic ratio, or a default AspectRatio if there's no
+ // intrinsic ratio.
+ mozilla::AspectRatio mIntrinsicRatio;
+
+ const Kind mKind;
+ bool mOwnedRequestRegistered = false;
+ bool mDisplayingIcon = false;
+ bool mFirstFrameComplete = false;
+ bool mReflowCallbackPosted = false;
+ bool mForceSyncDecoding = false;
+ bool mIsInObjectOrEmbed = false;
+
+ public:
+ friend class mozilla::nsDisplayImage;
+ friend class nsDisplayGradient;
+};
+
+namespace mozilla {
+/**
+ * Note that nsDisplayImage does not receive events. However, an image element
+ * is replaced content so its background will be z-adjacent to the
+ * image itself, and hence receive events just as if the image itself
+ * received events.
+ */
+class nsDisplayImage final : public nsPaintedDisplayItem {
+ public:
+ typedef mozilla::layers::LayerManager LayerManager;
+
+ nsDisplayImage(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame,
+ imgIContainer* aImage, imgIContainer* aPrevImage)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mImage(aImage),
+ mPrevImage(aPrevImage) {
+ MOZ_COUNT_CTOR(nsDisplayImage);
+ }
+ ~nsDisplayImage() final { MOZ_COUNT_DTOR(nsDisplayImage); }
+
+ void Paint(nsDisplayListBuilder*, gfxContext* aCtx) final;
+
+ /**
+ * @return The dest rect we'll use when drawing this image, in app units.
+ * Not necessarily contained in this item's bounds.
+ */
+ nsRect GetDestRect() const;
+
+ nsRect GetBounds(bool* aSnap) const {
+ *aSnap = true;
+ return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final {
+ return GetBounds(aSnap);
+ }
+
+ nsRegion GetOpaqueRegion(nsDisplayListBuilder*, bool* aSnap) const final;
+
+ bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder&,
+ mozilla::wr::IpcResourceUpdateQueue&,
+ const StackingContextHelper&,
+ mozilla::layers::RenderRootStateManager*,
+ nsDisplayListBuilder*) final;
+
+ NS_DISPLAY_DECL_NAME("Image", TYPE_IMAGE)
+ private:
+ nsCOMPtr<imgIContainer> mImage;
+ nsCOMPtr<imgIContainer> mPrevImage;
+};
+
+} // namespace mozilla
+
+#endif /* nsImageFrame_h___ */
diff --git a/layout/generic/nsImageMap.cpp b/layout/generic/nsImageMap.cpp
new file mode 100644
index 0000000000..54b861c093
--- /dev/null
+++ b/layout/generic/nsImageMap.cpp
@@ -0,0 +1,879 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* code for HTML client-side image maps */
+
+#include "nsImageMap.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/UniquePtr.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPresContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsImageFrame.h"
+#include "nsCoord.h"
+#include "nsIContentInlines.h"
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::dom;
+
+class Area {
+ public:
+ explicit Area(HTMLAreaElement* aArea);
+ virtual ~Area();
+
+ virtual void ParseCoords(const nsAString& aSpec);
+
+ virtual bool IsInside(nscoord x, nscoord y) const = 0;
+ virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) = 0;
+ virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) = 0;
+
+ void HasFocus(bool aHasFocus);
+
+ RefPtr<HTMLAreaElement> mArea;
+ UniquePtr<nscoord[]> mCoords;
+ int32_t mNumCoords;
+ bool mHasFocus;
+};
+
+Area::Area(HTMLAreaElement* aArea) : mArea(aArea) {
+ MOZ_COUNT_CTOR(Area);
+ MOZ_ASSERT(mArea, "How did that happen?");
+ mNumCoords = 0;
+ mHasFocus = false;
+}
+
+Area::~Area() { MOZ_COUNT_DTOR(Area); }
+
+#include <stdlib.h>
+
+inline bool is_space(char c) {
+ return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' ||
+ c == '\v');
+}
+
+static void logMessage(nsIContent* aContent, const nsAString& aCoordsSpec,
+ int32_t aFlags, const char* aMessageName) {
+ nsContentUtils::ReportToConsole(
+ aFlags, "Layout: ImageMap"_ns, aContent->OwnerDoc(),
+ nsContentUtils::eLAYOUT_PROPERTIES, aMessageName,
+ nsTArray<nsString>(), /* params */
+ nullptr,
+ PromiseFlatString(u"coords=\""_ns + aCoordsSpec +
+ u"\""_ns)); /* source line */
+}
+
+void Area::ParseCoords(const nsAString& aSpec) {
+ char* cp = ToNewUTF8String(aSpec);
+ if (cp) {
+ char* tptr;
+ char* n_str;
+ int32_t i, cnt;
+
+ /*
+ * Nothing in an empty list
+ */
+ mNumCoords = 0;
+ mCoords = nullptr;
+ if (*cp == '\0') {
+ free(cp);
+ return;
+ }
+
+ /*
+ * Skip beginning whitespace, all whitespace is empty list.
+ */
+ n_str = cp;
+ while (is_space(*n_str)) {
+ n_str++;
+ }
+ if (*n_str == '\0') {
+ free(cp);
+ return;
+ }
+
+ /*
+ * Make a pass where any two numbers separated by just whitespace
+ * are given a comma separator. Count entries while passing.
+ */
+ cnt = 0;
+ while (*n_str != '\0') {
+ bool has_comma;
+
+ /*
+ * Skip to a separator
+ */
+ tptr = n_str;
+ while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0') {
+ tptr++;
+ }
+ n_str = tptr;
+
+ /*
+ * If no more entries, break out here
+ */
+ if (*n_str == '\0') {
+ break;
+ }
+
+ /*
+ * Skip to the end of the separator, noting if we have a
+ * comma.
+ */
+ has_comma = false;
+ while (is_space(*tptr) || *tptr == ',') {
+ if (*tptr == ',') {
+ if (!has_comma) {
+ has_comma = true;
+ } else {
+ break;
+ }
+ }
+ tptr++;
+ }
+ /*
+ * If this was trailing whitespace we skipped, we are done.
+ */
+ if ((*tptr == '\0') && !has_comma) {
+ break;
+ }
+ /*
+ * Else if the separator is all whitespace, and this is not the
+ * end of the string, add a comma to the separator.
+ */
+ else if (!has_comma) {
+ *n_str = ',';
+ }
+
+ /*
+ * count the entry skipped.
+ */
+ cnt++;
+
+ n_str = tptr;
+ }
+ /*
+ * count the last entry in the list.
+ */
+ cnt++;
+
+ /*
+ * Allocate space for the coordinate array.
+ */
+ UniquePtr<nscoord[]> value_list = MakeUnique<nscoord[]>(cnt);
+ if (!value_list) {
+ free(cp);
+ return;
+ }
+
+ /*
+ * Second pass to copy integer values into list.
+ */
+ tptr = cp;
+ for (i = 0; i < cnt; i++) {
+ char* ptr;
+
+ ptr = strchr(tptr, ',');
+ if (ptr) {
+ *ptr = '\0';
+ }
+ /*
+ * Strip whitespace in front of number because I don't
+ * trust atoi to do it on all platforms.
+ */
+ while (is_space(*tptr)) {
+ tptr++;
+ }
+ if (*tptr == '\0') {
+ value_list[i] = 0;
+ } else {
+ value_list[i] = (nscoord)::atoi(tptr);
+ }
+ if (ptr) {
+ *ptr = ',';
+ tptr = ptr + 1;
+ }
+ }
+
+ mNumCoords = cnt;
+ mCoords = std::move(value_list);
+
+ free(cp);
+ }
+}
+
+void Area::HasFocus(bool aHasFocus) { mHasFocus = aHasFocus; }
+
+//----------------------------------------------------------------------
+
+class DefaultArea final : public Area {
+ public:
+ explicit DefaultArea(HTMLAreaElement* aArea);
+
+ virtual bool IsInside(nscoord x, nscoord y) const override;
+ virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) override;
+ virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
+};
+
+DefaultArea::DefaultArea(HTMLAreaElement* aArea) : Area(aArea) {}
+
+bool DefaultArea::IsInside(nscoord x, nscoord y) const { return true; }
+
+void DefaultArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) {
+ if (mHasFocus) {
+ nsRect r(nsPoint(0, 0), aFrame->GetSize());
+ const nscoord kOnePixel = nsPresContext::CSSPixelsToAppUnits(1);
+ r.width -= kOnePixel;
+ r.height -= kOnePixel;
+ Rect rect = ToRect(nsLayoutUtils::RectToGfxRect(
+ r, aFrame->PresContext()->AppUnitsPerDevPixel()));
+ StrokeSnappedEdgesOfRect(rect, aDrawTarget, aColor, aStrokeOptions);
+ }
+}
+
+void DefaultArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
+ aRect = aFrame->GetRect();
+ aRect.MoveTo(0, 0);
+}
+
+//----------------------------------------------------------------------
+
+class RectArea final : public Area {
+ public:
+ explicit RectArea(HTMLAreaElement* aArea);
+
+ virtual void ParseCoords(const nsAString& aSpec) override;
+ virtual bool IsInside(nscoord x, nscoord y) const override;
+ virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) override;
+ virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
+};
+
+RectArea::RectArea(HTMLAreaElement* aArea) : Area(aArea) {}
+
+void RectArea::ParseCoords(const nsAString& aSpec) {
+ Area::ParseCoords(aSpec);
+
+ bool saneRect = true;
+ int32_t flag = nsIScriptError::warningFlag;
+ if (mNumCoords >= 4) {
+ if (mCoords[0] > mCoords[2]) {
+ // x-coords in reversed order
+ nscoord x = mCoords[2];
+ mCoords[2] = mCoords[0];
+ mCoords[0] = x;
+ saneRect = false;
+ }
+
+ if (mCoords[1] > mCoords[3]) {
+ // y-coords in reversed order
+ nscoord y = mCoords[3];
+ mCoords[3] = mCoords[1];
+ mCoords[1] = y;
+ saneRect = false;
+ }
+
+ if (mNumCoords > 4) {
+ // Someone missed the concept of a rect here
+ saneRect = false;
+ }
+ } else {
+ saneRect = false;
+ flag = nsIScriptError::errorFlag;
+ }
+
+ if (!saneRect) {
+ logMessage(mArea, aSpec, flag, "ImageMapRectBoundsError");
+ }
+}
+
+bool RectArea::IsInside(nscoord x, nscoord y) const {
+ if (mNumCoords >= 4) { // Note: > is for nav compatibility
+ nscoord x1 = mCoords[0];
+ nscoord y1 = mCoords[1];
+ nscoord x2 = mCoords[2];
+ nscoord y2 = mCoords[3];
+ NS_ASSERTION(x1 <= x2 && y1 <= y2,
+ "Someone screwed up RectArea::ParseCoords");
+ if ((x >= x1) && (x <= x2) && (y >= y1) && (y <= y2)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void RectArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) {
+ if (mHasFocus) {
+ if (mNumCoords >= 4) {
+ nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
+ nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
+ nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
+ nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
+ NS_ASSERTION(x1 <= x2 && y1 <= y2,
+ "Someone screwed up RectArea::ParseCoords");
+ nsRect r(x1, y1, x2 - x1, y2 - y1);
+ Rect rect = ToRect(nsLayoutUtils::RectToGfxRect(
+ r, aFrame->PresContext()->AppUnitsPerDevPixel()));
+ StrokeSnappedEdgesOfRect(rect, aDrawTarget, aColor, aStrokeOptions);
+ }
+ }
+}
+
+void RectArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
+ if (mNumCoords >= 4) {
+ nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
+ nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
+ nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
+ nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
+ NS_ASSERTION(x1 <= x2 && y1 <= y2,
+ "Someone screwed up RectArea::ParseCoords");
+
+ aRect.SetRect(x1, y1, x2, y2);
+ }
+}
+
+//----------------------------------------------------------------------
+
+class PolyArea final : public Area {
+ public:
+ explicit PolyArea(HTMLAreaElement* aArea);
+
+ virtual void ParseCoords(const nsAString& aSpec) override;
+ virtual bool IsInside(nscoord x, nscoord y) const override;
+ virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) override;
+ virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
+};
+
+PolyArea::PolyArea(HTMLAreaElement* aArea) : Area(aArea) {}
+
+void PolyArea::ParseCoords(const nsAString& aSpec) {
+ Area::ParseCoords(aSpec);
+
+ if (mNumCoords >= 2) {
+ if (mNumCoords & 1U) {
+ logMessage(mArea, aSpec, nsIScriptError::warningFlag,
+ "ImageMapPolyOddNumberOfCoords");
+ }
+ } else {
+ logMessage(mArea, aSpec, nsIScriptError::errorFlag,
+ "ImageMapPolyWrongNumberOfCoords");
+ }
+}
+
+bool PolyArea::IsInside(nscoord x, nscoord y) const {
+ if (mNumCoords >= 6) {
+ int32_t intersects = 0;
+ nscoord wherex = x;
+ nscoord wherey = y;
+ int32_t totalv = mNumCoords / 2;
+ int32_t totalc = totalv * 2;
+ nscoord xval = mCoords[totalc - 2];
+ nscoord yval = mCoords[totalc - 1];
+ int32_t end = totalc;
+ int32_t pointer = 1;
+
+ if ((yval >= wherey) != (mCoords[pointer] >= wherey)) {
+ if ((xval >= wherex) == (mCoords[0] >= wherex)) {
+ intersects += (xval >= wherex) ? 1 : 0;
+ } else {
+ intersects += ((xval - (yval - wherey) * (mCoords[0] - xval) /
+ (mCoords[pointer] - yval)) >= wherex)
+ ? 1
+ : 0;
+ }
+ }
+
+ // XXX I wonder what this is doing; this is a translation of ptinpoly.c
+ while (pointer < end) {
+ yval = mCoords[pointer];
+ pointer += 2;
+ if (yval >= wherey) {
+ while ((pointer < end) && (mCoords[pointer] >= wherey)) pointer += 2;
+ if (pointer >= end) break;
+ if ((mCoords[pointer - 3] >= wherex) ==
+ (mCoords[pointer - 1] >= wherex)) {
+ intersects += (mCoords[pointer - 3] >= wherex) ? 1 : 0;
+ } else {
+ intersects +=
+ ((mCoords[pointer - 3] -
+ (mCoords[pointer - 2] - wherey) *
+ (mCoords[pointer - 1] - mCoords[pointer - 3]) /
+ (mCoords[pointer] - mCoords[pointer - 2])) >= wherex)
+ ? 1
+ : 0;
+ }
+ } else {
+ while ((pointer < end) && (mCoords[pointer] < wherey)) pointer += 2;
+ if (pointer >= end) break;
+ if ((mCoords[pointer - 3] >= wherex) ==
+ (mCoords[pointer - 1] >= wherex)) {
+ intersects += (mCoords[pointer - 3] >= wherex) ? 1 : 0;
+ } else {
+ intersects +=
+ ((mCoords[pointer - 3] -
+ (mCoords[pointer - 2] - wherey) *
+ (mCoords[pointer - 1] - mCoords[pointer - 3]) /
+ (mCoords[pointer] - mCoords[pointer - 2])) >= wherex)
+ ? 1
+ : 0;
+ }
+ }
+ }
+ if ((intersects & 1) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void PolyArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) {
+ if (mHasFocus) {
+ if (mNumCoords >= 6) {
+ // Where possible, we want all horizontal and vertical lines to align on
+ // pixel rows or columns, and to start at pixel boundaries so that one
+ // pixel dashing neatly sits on pixels to give us neat lines. To achieve
+ // that we draw each line segment as a separate path, snapping it to
+ // device pixels if applicable.
+ nsPresContext* pc = aFrame->PresContext();
+ Point p1(pc->CSSPixelsToDevPixels(mCoords[0]),
+ pc->CSSPixelsToDevPixels(mCoords[1]));
+ Point p2, p1snapped, p2snapped;
+ for (int32_t i = 2; i < mNumCoords - 1; i += 2) {
+ p2.x = pc->CSSPixelsToDevPixels(mCoords[i]);
+ p2.y = pc->CSSPixelsToDevPixels(mCoords[i + 1]);
+ p1snapped = p1;
+ p2snapped = p2;
+ SnapLineToDevicePixelsForStroking(p1snapped, p2snapped, aDrawTarget,
+ aStrokeOptions.mLineWidth);
+ aDrawTarget.StrokeLine(p1snapped, p2snapped, aColor, aStrokeOptions);
+ p1 = p2;
+ }
+ p2.x = pc->CSSPixelsToDevPixels(mCoords[0]);
+ p2.y = pc->CSSPixelsToDevPixels(mCoords[1]);
+ p1snapped = p1;
+ p2snapped = p2;
+ SnapLineToDevicePixelsForStroking(p1snapped, p2snapped, aDrawTarget,
+ aStrokeOptions.mLineWidth);
+ aDrawTarget.StrokeLine(p1snapped, p2snapped, aColor, aStrokeOptions);
+ }
+ }
+}
+
+void PolyArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
+ if (mNumCoords >= 6) {
+ nscoord x1, x2, y1, y2, xtmp, ytmp;
+ x1 = x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
+ y1 = y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
+ for (int32_t i = 2; i < mNumCoords - 1; i += 2) {
+ xtmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i]);
+ ytmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i + 1]);
+ x1 = x1 < xtmp ? x1 : xtmp;
+ y1 = y1 < ytmp ? y1 : ytmp;
+ x2 = x2 > xtmp ? x2 : xtmp;
+ y2 = y2 > ytmp ? y2 : ytmp;
+ }
+
+ aRect.SetRect(x1, y1, x2, y2);
+ }
+}
+
+//----------------------------------------------------------------------
+
+class CircleArea final : public Area {
+ public:
+ explicit CircleArea(HTMLAreaElement* aArea);
+
+ virtual void ParseCoords(const nsAString& aSpec) override;
+ virtual bool IsInside(nscoord x, nscoord y) const override;
+ virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) override;
+ virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
+};
+
+CircleArea::CircleArea(HTMLAreaElement* aArea) : Area(aArea) {}
+
+void CircleArea::ParseCoords(const nsAString& aSpec) {
+ Area::ParseCoords(aSpec);
+
+ bool wrongNumberOfCoords = false;
+ int32_t flag = nsIScriptError::warningFlag;
+ if (mNumCoords >= 3) {
+ if (mCoords[2] < 0) {
+ logMessage(mArea, aSpec, nsIScriptError::errorFlag,
+ "ImageMapCircleNegativeRadius");
+ }
+
+ if (mNumCoords > 3) {
+ wrongNumberOfCoords = true;
+ }
+ } else {
+ wrongNumberOfCoords = true;
+ flag = nsIScriptError::errorFlag;
+ }
+
+ if (wrongNumberOfCoords) {
+ logMessage(mArea, aSpec, flag, "ImageMapCircleWrongNumberOfCoords");
+ }
+}
+
+bool CircleArea::IsInside(nscoord x, nscoord y) const {
+ // Note: > is for nav compatibility
+ if (mNumCoords >= 3) {
+ nscoord x1 = mCoords[0];
+ nscoord y1 = mCoords[1];
+ nscoord radius = mCoords[2];
+ if (radius < 0) {
+ return false;
+ }
+ nscoord dx = x1 - x;
+ nscoord dy = y1 - y;
+ nscoord dist = (dx * dx) + (dy * dy);
+ if (dist <= (radius * radius)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void CircleArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) {
+ if (mHasFocus) {
+ if (mNumCoords >= 3) {
+ Point center(aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[0]),
+ aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[1]));
+ Float diameter =
+ 2 * aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[2]);
+ if (diameter <= 0) {
+ return;
+ }
+ RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
+ AppendEllipseToPath(builder, center, Size(diameter, diameter));
+ RefPtr<Path> circle = builder->Finish();
+ aDrawTarget.Stroke(circle, aColor, aStrokeOptions);
+ }
+ }
+}
+
+void CircleArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
+ if (mNumCoords >= 3) {
+ nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
+ nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
+ nscoord radius = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
+ if (radius < 0) {
+ return;
+ }
+
+ aRect.SetRect(x1 - radius, y1 - radius, x1 + radius, y1 + radius);
+ }
+}
+
+//----------------------------------------------------------------------
+
+nsImageMap::nsImageMap() : mImageFrame(nullptr), mConsiderWholeSubtree(false) {}
+
+nsImageMap::~nsImageMap() {
+ NS_ASSERTION(mAreas.Length() == 0, "Destroy was not called");
+}
+
+NS_IMPL_ISUPPORTS(nsImageMap, nsIMutationObserver, nsIDOMEventListener)
+
+nsresult nsImageMap::GetBoundsForAreaContent(nsIContent* aContent,
+ nsRect& aBounds) {
+ NS_ENSURE_TRUE(aContent && mImageFrame, NS_ERROR_INVALID_ARG);
+
+ // Find the Area struct associated with this content node, and return bounds
+ for (auto& area : mAreas) {
+ if (area->mArea == aContent) {
+ aBounds = nsRect();
+ area->GetRect(mImageFrame, aBounds);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+void nsImageMap::AreaRemoved(HTMLAreaElement* aArea) {
+ if (aArea->GetPrimaryFrame() == mImageFrame) {
+ aArea->SetPrimaryFrame(nullptr);
+ }
+
+ aArea->RemoveSystemEventListener(u"focus"_ns, this, false);
+ aArea->RemoveSystemEventListener(u"blur"_ns, this, false);
+}
+
+void nsImageMap::FreeAreas() {
+ for (UniquePtr<Area>& area : mAreas) {
+ AreaRemoved(area->mArea);
+ }
+
+ mAreas.Clear();
+}
+
+void nsImageMap::Init(nsImageFrame* aImageFrame, nsIContent* aMap) {
+ MOZ_ASSERT(aMap);
+ MOZ_ASSERT(aImageFrame);
+
+ mImageFrame = aImageFrame;
+ mMap = aMap;
+ mMap->AddMutationObserver(this);
+
+ // "Compile" the areas in the map into faster access versions
+ UpdateAreas();
+}
+
+void nsImageMap::SearchForAreas(nsIContent* aParent) {
+ // Look for <area> elements.
+ for (nsIContent* child = aParent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (auto* area = HTMLAreaElement::FromNode(child)) {
+ AddArea(area);
+
+ // Continue to next child. This stops mConsiderWholeSubtree from
+ // getting set. It also makes us ignore children of <area>s which
+ // is consistent with how we react to dynamic insertion of such
+ // children.
+ continue;
+ }
+
+ if (child->IsElement()) {
+ mConsiderWholeSubtree = true;
+ SearchForAreas(child);
+ }
+ }
+}
+
+void nsImageMap::UpdateAreas() {
+ // Get rid of old area data
+ FreeAreas();
+
+ mConsiderWholeSubtree = false;
+ SearchForAreas(mMap);
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->UpdateImageMap(mImageFrame);
+ }
+#endif
+}
+
+void nsImageMap::AddArea(HTMLAreaElement* aArea) {
+ static AttrArray::AttrValuesArray strings[] = {
+ nsGkAtoms::rect, nsGkAtoms::rectangle,
+ nsGkAtoms::circle, nsGkAtoms::circ,
+ nsGkAtoms::_default, nsGkAtoms::poly,
+ nsGkAtoms::polygon, nullptr};
+
+ UniquePtr<Area> area;
+ switch (aArea->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::shape, strings,
+ eIgnoreCase)) {
+ case AttrArray::ATTR_VALUE_NO_MATCH:
+ case AttrArray::ATTR_MISSING:
+ case 0:
+ case 1:
+ area = MakeUnique<RectArea>(aArea);
+ break;
+ case 2:
+ case 3:
+ area = MakeUnique<CircleArea>(aArea);
+ break;
+ case 4:
+ area = MakeUnique<DefaultArea>(aArea);
+ break;
+ case 5:
+ case 6:
+ area = MakeUnique<PolyArea>(aArea);
+ break;
+ default:
+ area = nullptr;
+ MOZ_ASSERT_UNREACHABLE("FindAttrValueIn returned an unexpected value.");
+ break;
+ }
+
+ // Add focus listener to track area focus changes
+ aArea->AddSystemEventListener(u"focus"_ns, this, false, false);
+ aArea->AddSystemEventListener(u"blur"_ns, this, false, false);
+
+ // This is a nasty hack. It needs to go away: see bug 135040. Once this is
+ // removed, the code added to RestyleManager::RestyleElement,
+ // nsCSSFrameConstructor::ContentRemoved (both hacks there), and
+ // RestyleManager::ProcessRestyledFrames to work around this issue can
+ // be removed.
+ aArea->SetPrimaryFrame(mImageFrame);
+
+ nsAutoString coords;
+ aArea->GetAttr(nsGkAtoms::coords, coords);
+ area->ParseCoords(coords);
+ mAreas.AppendElement(std::move(area));
+}
+
+HTMLAreaElement* nsImageMap::GetArea(const CSSIntPoint& aPt) const {
+ NS_ASSERTION(mMap, "Not initialized");
+ for (const auto& area : mAreas) {
+ if (area->IsInside(aPt.x, aPt.y)) {
+ return area->mArea;
+ }
+ }
+
+ return nullptr;
+}
+
+HTMLAreaElement* nsImageMap::GetAreaAt(uint32_t aIndex) const {
+ return mAreas.ElementAt(aIndex)->mArea;
+}
+
+void nsImageMap::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions) {
+ for (auto& area : mAreas) {
+ area->Draw(aFrame, aDrawTarget, aColor, aStrokeOptions);
+ }
+}
+
+void nsImageMap::MaybeUpdateAreas(nsIContent* aContent) {
+ if (aContent == mMap || mConsiderWholeSubtree) {
+ UpdateAreas();
+ }
+}
+
+void nsImageMap::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ // If the parent of the changing content node is our map then update
+ // the map. But only do this if the node is an HTML <area> or <a>
+ // and the attribute that's changing is "shape" or "coords" -- those
+ // are the only cases we care about.
+ if ((aElement->NodeInfo()->Equals(nsGkAtoms::area) ||
+ aElement->NodeInfo()->Equals(nsGkAtoms::a)) &&
+ aElement->IsHTMLElement() && aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::shape || aAttribute == nsGkAtoms::coords)) {
+ MaybeUpdateAreas(aElement->GetParent());
+ } else if (aElement == mMap && aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::name || aAttribute == nsGkAtoms::id) &&
+ mImageFrame) {
+ // ID or name has changed. Let ImageFrame recreate ImageMap.
+ mImageFrame->DisconnectMap();
+ }
+}
+
+void nsImageMap::ContentAppended(nsIContent* aFirstNewContent) {
+ MaybeUpdateAreas(aFirstNewContent->GetParent());
+}
+
+void nsImageMap::ContentInserted(nsIContent* aChild) {
+ MaybeUpdateAreas(aChild->GetParent());
+}
+
+static UniquePtr<Area> TakeArea(nsImageMap::AreaList& aAreas,
+ HTMLAreaElement* aArea) {
+ UniquePtr<Area> result;
+ size_t index = 0;
+ for (UniquePtr<Area>& area : aAreas) {
+ if (area->mArea == aArea) {
+ result = std::move(area);
+ break;
+ }
+ index++;
+ }
+
+ if (result) {
+ aAreas.RemoveElementAt(index);
+ }
+
+ return result;
+}
+
+void nsImageMap::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ if (aChild->GetParent() != mMap && !mConsiderWholeSubtree) {
+ return;
+ }
+
+ auto* areaElement = HTMLAreaElement::FromNode(aChild);
+ if (!areaElement) {
+ return;
+ }
+
+ UniquePtr<Area> area = TakeArea(mAreas, areaElement);
+ if (!area) {
+ return;
+ }
+
+ AreaRemoved(area->mArea);
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->UpdateImageMap(mImageFrame);
+ }
+#endif
+}
+
+void nsImageMap::ParentChainChanged(nsIContent* aContent) {
+ NS_ASSERTION(aContent == mMap, "Unexpected ParentChainChanged notification!");
+ if (mImageFrame) {
+ mImageFrame->DisconnectMap();
+ }
+}
+
+nsresult nsImageMap::HandleEvent(Event* aEvent) {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ bool focus = eventType.EqualsLiteral("focus");
+ MOZ_ASSERT(focus == !eventType.EqualsLiteral("blur"),
+ "Unexpected event type");
+
+ // Set which one of our areas changed focus
+ nsCOMPtr<nsIContent> targetContent = do_QueryInterface(aEvent->GetTarget());
+ if (!targetContent) {
+ return NS_OK;
+ }
+
+ for (auto& area : mAreas) {
+ if (area->mArea == targetContent) {
+ // Set or Remove internal focus
+ area->HasFocus(focus);
+ // Now invalidate the rect
+ if (mImageFrame) {
+ mImageFrame->InvalidateFrame();
+ }
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+void nsImageMap::Destroy() {
+ FreeAreas();
+ mImageFrame = nullptr;
+ mMap->RemoveMutationObserver(this);
+}
diff --git a/layout/generic/nsImageMap.h b/layout/generic/nsImageMap.h
new file mode 100644
index 0000000000..aa9ac2bec8
--- /dev/null
+++ b/layout/generic/nsImageMap.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* code for HTML client-side image maps */
+
+#ifndef nsImageMap_h
+#define nsImageMap_h
+
+#include "mozilla/gfx/2D.h"
+#include "nsCOMPtr.h"
+#include "nsCoord.h"
+#include "nsTArray.h"
+#include "nsStubMutationObserver.h"
+#include "nsIDOMEventListener.h"
+#include "Units.h"
+
+class Area;
+class nsImageFrame;
+class nsIFrame;
+class nsIContent;
+struct nsRect;
+
+namespace mozilla {
+namespace dom {
+class HTMLAreaElement;
+}
+} // namespace mozilla
+
+class nsImageMap final : public nsStubMutationObserver,
+ public nsIDOMEventListener {
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::ColorPattern ColorPattern;
+ typedef mozilla::gfx::StrokeOptions StrokeOptions;
+
+ public:
+ nsImageMap();
+
+ void Init(nsImageFrame* aImageFrame, nsIContent* aMap);
+
+ /**
+ * Return the first area element (in content order) for the given point in
+ * CSS pixel coordinate or nullptr if the coordinate is outside all areas.
+ */
+ mozilla::dom::HTMLAreaElement* GetArea(const mozilla::CSSIntPoint& aPt) const;
+
+ /**
+ * Return area elements count associated with the image map.
+ */
+ uint32_t AreaCount() const { return mAreas.Length(); }
+
+ /**
+ * Return area element at the given index.
+ */
+ mozilla::dom::HTMLAreaElement* GetAreaAt(uint32_t aIndex) const;
+
+ void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
+ const ColorPattern& aColor,
+ const StrokeOptions& aStrokeOptions = StrokeOptions());
+
+ /**
+ * Called just before the nsImageFrame releases us.
+ * Used to break the cycle caused by the DOM listener.
+ */
+ void Destroy();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
+
+ // nsIDOMEventListener
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ nsresult GetBoundsForAreaContent(nsIContent* aContent, nsRect& aBounds);
+
+ using AreaList = AutoTArray<mozilla::UniquePtr<Area>, 8>;
+
+ protected:
+ virtual ~nsImageMap();
+
+ void FreeAreas();
+
+ void UpdateAreas();
+
+ void SearchForAreas(nsIContent* aParent);
+
+ void AddArea(mozilla::dom::HTMLAreaElement* aArea);
+ void AreaRemoved(mozilla::dom::HTMLAreaElement* aArea);
+
+ void MaybeUpdateAreas(nsIContent* aContent);
+
+ nsImageFrame* mImageFrame; // the frame that owns us
+ nsCOMPtr<nsIContent> mMap;
+
+ // almost always has some entries
+ AreaList mAreas;
+
+ // This is set when we search for all area children and tells us whether we
+ // should consider the whole subtree or just direct children when we get
+ // content notifications about changes inside the map subtree.
+ bool mConsiderWholeSubtree;
+};
+
+#endif /* nsImageMap_h */
diff --git a/layout/generic/nsInlineFrame.cpp b/layout/generic/nsInlineFrame.cpp
new file mode 100644
index 0000000000..7a99ecbe5d
--- /dev/null
+++ b/layout/generic/nsInlineFrame.cpp
@@ -0,0 +1,1083 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS display:inline objects */
+
+#include "nsInlineFrame.h"
+
+#include "gfxContext.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/SVGTextFrame.h"
+#include "nsLineLayout.h"
+#include "nsBlockFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsDisplayList.h"
+#include "nsStyleChangeList.h"
+
+#ifdef DEBUG
+# undef NOISY_PUSHING
+#endif
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+//////////////////////////////////////////////////////////////////////
+
+// Basic nsInlineFrame methods
+
+nsInlineFrame* NS_NewInlineFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsInlineFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsInlineFrame)
+
+NS_QUERYFRAME_HEAD(nsInlineFrame)
+ NS_QUERYFRAME_ENTRY(nsInlineFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsInlineFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Inline"_ns, aResult);
+}
+#endif
+
+void nsInlineFrame::InvalidateFrame(uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ if (IsInSVGTextSubtree()) {
+ nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
+ GetParent(), LayoutFrameType::SVGText);
+ svgTextFrame->InvalidateFrame();
+ return;
+ }
+ nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
+}
+
+void nsInlineFrame::InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ if (IsInSVGTextSubtree()) {
+ nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
+ GetParent(), LayoutFrameType::SVGText);
+ svgTextFrame->InvalidateFrame();
+ return;
+ }
+ nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
+ aRebuildDisplayItems);
+}
+
+static inline bool IsMarginZero(const LengthPercentageOrAuto& aLength) {
+ return aLength.IsAuto() ||
+ nsLayoutUtils::IsMarginZero(aLength.AsLengthPercentage());
+}
+
+/* virtual */
+bool nsInlineFrame::IsSelfEmpty() {
+#if 0
+ // I used to think inline frames worked this way, but it seems they
+ // don't. At least not in our codebase.
+ if (GetPresContext()->CompatibilityMode() == eCompatibility_FullStandards) {
+ return false;
+ }
+#endif
+ const nsStyleMargin* margin = StyleMargin();
+ const nsStyleBorder* border = StyleBorder();
+ const nsStylePadding* padding = StylePadding();
+ // Block-start and -end ignored, since they shouldn't affect things, but this
+ // doesn't really match with nsLineLayout.cpp's setting of
+ // ZeroEffectiveSpanBox, anymore, so what should this really be?
+ WritingMode wm = GetWritingMode();
+ bool haveStart, haveEnd;
+
+ auto HaveSide = [&](mozilla::Side aSide) -> bool {
+ return border->GetComputedBorderWidth(aSide) != 0 ||
+ !nsLayoutUtils::IsPaddingZero(padding->mPadding.Get(aSide)) ||
+ !IsMarginZero(margin->mMargin.Get(aSide));
+ };
+ // Initially set up haveStart and haveEnd in terms of visual (LTR/TTB)
+ // coordinates; we'll exchange them later if bidi-RTL is in effect to
+ // get logical start and end flags.
+ if (wm.IsVertical()) {
+ haveStart = HaveSide(eSideTop);
+ haveEnd = HaveSide(eSideBottom);
+ } else {
+ haveStart = HaveSide(eSideLeft);
+ haveEnd = HaveSide(eSideRight);
+ }
+ if (haveStart || haveEnd) {
+ // We skip this block and return false for box-decoration-break:clone since
+ // in that case all the continuations will have the border/padding/margin.
+ if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
+ StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Slice) {
+ // When direction=rtl, we need to consider logical rather than visual
+ // start and end, so swap the flags.
+ if (wm.IsBidiRTL()) {
+ std::swap(haveStart, haveEnd);
+ }
+ // For ib-split frames, ignore things we know we'll skip in GetSkipSides.
+ // XXXbz should we be doing this for non-ib-split frames too, in a more
+ // general way?
+
+ // Get the first continuation eagerly, as a performance optimization, to
+ // avoid having to get it twice..
+ nsIFrame* firstCont = FirstContinuation();
+ return (!haveStart || firstCont->FrameIsNonFirstInIBSplit()) &&
+ (!haveEnd || firstCont->FrameIsNonLastInIBSplit());
+ }
+ return false;
+ }
+ return true;
+}
+
+bool nsInlineFrame::IsEmpty() {
+ if (!IsSelfEmpty()) {
+ return false;
+ }
+
+ for (nsIFrame* kid : mFrames) {
+ if (!kid->IsEmpty()) return false;
+ }
+
+ return true;
+}
+
+nsIFrame::FrameSearchResult nsInlineFrame::PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
+ // Override the implementation in nsFrame, to skip empty inline frames
+ NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range");
+ int32_t startOffset = *aOffset;
+ if (startOffset < 0) startOffset = 1;
+ if (aForward == (startOffset == 0)) {
+ // We're before the frame and moving forward, or after it and moving
+ // backwards: skip to the other side, but keep going.
+ *aOffset = 1 - startOffset;
+ }
+ return CONTINUE;
+}
+
+void nsInlineFrame::Destroy(DestroyContext& aContext) {
+ nsFrameList* overflowFrames = GetOverflowFrames();
+ if (overflowFrames) {
+ // Fixup the parent pointers for any child frames on the OverflowList.
+ // nsIFrame::DestroyFrom depends on that to find the sticky scroll
+ // container (an ancestor).
+ overflowFrames->ApplySetParent(this);
+ }
+ nsContainerFrame::Destroy(aContext);
+}
+
+void nsInlineFrame::StealFrame(nsIFrame* aChild) {
+ if (MaybeStealOverflowContainerFrame(aChild)) {
+ return;
+ }
+
+ nsInlineFrame* parent = this;
+ do {
+ if (parent->mFrames.StartRemoveFrame(aChild)) {
+ return;
+ }
+
+ // We didn't find the child in our principal child list.
+ // Maybe it's on the overflow list?
+ nsFrameList* frameList = parent->GetOverflowFrames();
+ if (frameList && frameList->ContinueRemoveFrame(aChild)) {
+ if (frameList->IsEmpty()) {
+ parent->DestroyOverflowList();
+ }
+ return;
+ }
+
+ // Due to our "lazy reparenting" optimization 'aChild' might not actually
+ // be on any of our child lists, but instead in one of our next-in-flows.
+ parent = static_cast<nsInlineFrame*>(parent->GetNextInFlow());
+ } while (parent);
+
+ MOZ_ASSERT_UNREACHABLE("nsInlineFrame::StealFrame: can't find aChild");
+}
+
+void nsInlineFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ BuildDisplayListForInline(aBuilder, aLists);
+
+ // The sole purpose of this is to trigger display of the selection
+ // window for Named Anchors, which don't have any children and
+ // normally don't have any size, but in Editor we use CSS to display
+ // an image to represent this "hidden" element.
+ if (!mFrames.FirstChild()) {
+ DisplaySelectionOverlay(aBuilder, aLists.Content());
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+// Reflow methods
+
+/* virtual */
+void nsInlineFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ DoInlineMinISize(aRenderingContext, aData);
+}
+
+/* virtual */
+void nsInlineFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
+ nsIFrame::InlinePrefISizeData* aData) {
+ DoInlinePrefISize(aRenderingContext, aData);
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult nsInlineFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ // Inlines and text don't compute size before reflow.
+ return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
+ AspectRatioUsage::None};
+}
+
+nsRect nsInlineFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
+ // be conservative
+ if (Style()->HasTextDecorationLines()) {
+ return InkOverflowRect();
+ }
+ return ComputeSimpleTightBounds(aDrawTarget);
+}
+
+static void ReparentChildListStyle(nsPresContext* aPresContext,
+ const nsFrameList::Slice& aFrames,
+ nsIFrame* aParentFrame) {
+ RestyleManager* restyleManager = aPresContext->RestyleManager();
+
+ for (nsIFrame* f : aFrames) {
+ NS_ASSERTION(f->GetParent() == aParentFrame, "Bogus parentage");
+ restyleManager->ReparentComputedStyleForFirstLine(f);
+ nsLayoutUtils::MarkDescendantsDirty(f);
+ }
+}
+
+void nsInlineFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsInlineFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ if (!aReflowInput.mLineLayout) {
+ NS_ERROR("must have non-null aReflowInput.mLineLayout");
+ return;
+ }
+ if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) {
+ return;
+ }
+
+ bool lazilySetParentPointer = false;
+
+ // Check for an overflow list with our prev-in-flow
+ nsInlineFrame* prevInFlow = (nsInlineFrame*)GetPrevInFlow();
+ if (prevInFlow) {
+ AutoFrameListPtr prevOverflowFrames(aPresContext,
+ prevInFlow->StealOverflowFrames());
+ if (prevOverflowFrames) {
+ // When pushing and pulling frames we need to check for whether any
+ // views need to be reparented.
+ nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow,
+ this);
+
+ // Check if we should do the lazilySetParentPointer optimization.
+ // Only do it in simple cases where we're being reflowed for the
+ // first time, nothing (e.g. bidi resolution) has already given
+ // us children, and there's no next-in-flow, so all our frames
+ // will be taken from prevOverflowFrames.
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && mFrames.IsEmpty() &&
+ !GetNextInFlow()) {
+ // If our child list is empty, just put the new frames into it.
+ // Note that we don't set the parent pointer for the new frames. Instead
+ // wait to do this until we actually reflow the frame. If the overflow
+ // list contains thousands of frames this is a big performance issue
+ // (see bug #5588)
+ mFrames = std::move(*prevOverflowFrames);
+ lazilySetParentPointer = true;
+ } else {
+ // Insert the new frames at the beginning of the child list
+ // and set their parent pointer
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(this, nullptr, std::move(*prevOverflowFrames));
+ // If our prev in flow was under the first continuation of a first-line
+ // frame then we need to reparent the ComputedStyles to remove the
+ // the special first-line styling. In the lazilySetParentPointer case
+ // we reparent the ComputedStyles when we set their parents in
+ // nsInlineFrame::ReflowFrames and nsInlineFrame::ReflowInlineFrame.
+ if (aReflowInput.mLineLayout->GetInFirstLine()) {
+ ReparentChildListStyle(aPresContext, newFrames, this);
+ }
+ }
+ }
+ }
+
+ // It's also possible that we have an overflow list for ourselves
+#ifdef DEBUG
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // If it's our initial reflow, then we should not have an overflow list.
+ // However, add an assertion in case we get reflowed more than once with
+ // the initial reflow reason
+ nsFrameList* overflowFrames = GetOverflowFrames();
+ NS_ASSERTION(!overflowFrames || overflowFrames->IsEmpty(),
+ "overflow list is not empty for initial reflow");
+ }
+#endif
+ if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ DrainSelfOverflowListInternal(aReflowInput.mLineLayout->GetInFirstLine());
+ }
+
+ // Set our own reflow input (additional state above and beyond aReflowInput).
+ InlineReflowInput irs;
+ irs.mPrevFrame = nullptr;
+ irs.mLineContainer = aReflowInput.mLineLayout->LineContainerFrame();
+ irs.mLineLayout = aReflowInput.mLineLayout;
+ irs.mNextInFlow = (nsInlineFrame*)GetNextInFlow();
+ irs.mSetParentPointer = lazilySetParentPointer;
+
+ if (mFrames.IsEmpty()) {
+ // Try to pull over one frame before starting so that we know
+ // whether we have an anonymous block or not.
+ Unused << PullOneFrame(aPresContext, irs);
+ }
+
+ ReflowFrames(aPresContext, aReflowInput, irs, aMetrics, aStatus);
+
+ ReflowAbsoluteFrames(aPresContext, aMetrics, aReflowInput, aStatus);
+
+ // Note: the line layout code will properly compute our
+ // overflow-rect state for us.
+}
+
+nsresult nsInlineFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ nsresult rv =
+ nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (IsInSVGTextSubtree()) {
+ SVGTextFrame* f = static_cast<SVGTextFrame*>(
+ nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText));
+ f->HandleAttributeChangeInDescendant(mContent->AsElement(), aNameSpaceID,
+ aAttribute);
+ }
+
+ return NS_OK;
+}
+
+bool nsInlineFrame::DrainSelfOverflowListInternal(bool aInFirstLine) {
+ AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
+ if (!overflowFrames || overflowFrames->IsEmpty()) {
+ return false;
+ }
+
+ // The frames on our own overflowlist may have been pushed by a
+ // previous lazilySetParentPointer Reflow so we need to ensure the
+ // correct parent pointer. This is sometimes skipped by Reflow.
+ nsIFrame* firstChild = overflowFrames->FirstChild();
+ RestyleManager* restyleManager = PresContext()->RestyleManager();
+ for (nsIFrame* f = firstChild; f; f = f->GetNextSibling()) {
+ f->SetParent(this);
+ if (MOZ_UNLIKELY(aInFirstLine)) {
+ restyleManager->ReparentComputedStyleForFirstLine(f);
+ nsLayoutUtils::MarkDescendantsDirty(f);
+ }
+ }
+ mFrames.AppendFrames(nullptr, std::move(*overflowFrames));
+ return true;
+}
+
+/* virtual */
+bool nsInlineFrame::DrainSelfOverflowList() {
+ nsIFrame* lineContainer = nsLayoutUtils::FindNearestBlockAncestor(this);
+ // Add the eInFirstLine flag if we have a ::first-line ancestor frame.
+ // No need to look further than the nearest line container though.
+ bool inFirstLine = false;
+ for (nsIFrame* p = GetParent(); p != lineContainer; p = p->GetParent()) {
+ if (p->IsLineFrame()) {
+ inFirstLine = true;
+ break;
+ }
+ }
+ return DrainSelfOverflowListInternal(inFirstLine);
+}
+
+/* virtual */
+bool nsInlineFrame::CanContinueTextRun() const {
+ // We can continue a text run through an inline frame
+ return true;
+}
+
+/* virtual */
+void nsInlineFrame::PullOverflowsFromPrevInFlow() {
+ nsInlineFrame* prevInFlow = static_cast<nsInlineFrame*>(GetPrevInFlow());
+ if (prevInFlow) {
+ nsPresContext* presContext = PresContext();
+ AutoFrameListPtr prevOverflowFrames(presContext,
+ prevInFlow->StealOverflowFrames());
+ if (prevOverflowFrames) {
+ // Assume that our prev-in-flow has the same line container that we do.
+ nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow,
+ this);
+ mFrames.InsertFrames(this, nullptr, std::move(*prevOverflowFrames));
+ }
+ }
+}
+
+void nsInlineFrame::ReflowFrames(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ InlineReflowInput& irs, ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ nsLineLayout* lineLayout = aReflowInput.mLineLayout;
+ bool inFirstLine = aReflowInput.mLineLayout->GetInFirstLine();
+ RestyleManager* restyleManager = aPresContext->RestyleManager();
+ WritingMode frameWM = aReflowInput.GetWritingMode();
+ WritingMode lineWM = aReflowInput.mLineLayout->mRootSpan->mWritingMode;
+ LogicalMargin framePadding =
+ aReflowInput.ComputedLogicalBorderPadding(frameWM);
+ nscoord startEdge = 0;
+ const bool boxDecorationBreakClone = MOZ_UNLIKELY(
+ StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone);
+ // Don't offset by our start borderpadding if we have a prev continuation or
+ // if we're in a part of an {ib} split other than the first one. For
+ // box-decoration-break:clone we always offset our start since all
+ // continuations have border/padding.
+ if ((!GetPrevContinuation() && !FrameIsNonFirstInIBSplit()) ||
+ boxDecorationBreakClone) {
+ startEdge = framePadding.IStart(frameWM);
+ }
+ nscoord availableISize = aReflowInput.AvailableISize();
+ NS_ASSERTION(availableISize != NS_UNCONSTRAINEDSIZE,
+ "should no longer use available widths");
+ // Subtract off inline axis border+padding from availableISize
+ availableISize -= startEdge;
+ availableISize -= framePadding.IEnd(frameWM);
+ lineLayout->BeginSpan(this, &aReflowInput, startEdge,
+ startEdge + availableISize, &mBaseline);
+
+ // First reflow our principal children.
+ nsIFrame* frame = mFrames.FirstChild();
+ bool done = false;
+ while (frame) {
+ // Check if we should lazily set the child frame's parent pointer.
+ if (irs.mSetParentPointer) {
+ nsIFrame* child = frame;
+ do {
+ child->SetParent(this);
+ if (inFirstLine) {
+ restyleManager->ReparentComputedStyleForFirstLine(child);
+ nsLayoutUtils::MarkDescendantsDirty(child);
+ }
+ // We also need to do the same for |frame|'s next-in-flows that are in
+ // the sibling list. Otherwise, if we reflow |frame| and it's complete
+ // we'll crash when trying to delete its next-in-flow.
+ // This scenario doesn't happen often, but it can happen.
+ nsIFrame* nextSibling = child->GetNextSibling();
+ child = child->GetNextInFlow();
+ if (MOZ_UNLIKELY(child)) {
+ while (child != nextSibling && nextSibling) {
+ nextSibling = nextSibling->GetNextSibling();
+ }
+ if (!nextSibling) {
+ child = nullptr;
+ }
+ }
+ MOZ_ASSERT(!child || mFrames.ContainsFrame(child));
+ } while (child);
+
+ // Fix the parent pointer for ::first-letter child frame next-in-flows,
+ // so nsFirstLetterFrame::Reflow can destroy them safely (bug 401042).
+ nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(frame);
+ if (realFrame->IsLetterFrame()) {
+ nsIFrame* child = realFrame->PrincipalChildList().FirstChild();
+ if (child) {
+ NS_ASSERTION(child->IsTextFrame(), "unexpected frame type");
+ nsIFrame* nextInFlow = child->GetNextInFlow();
+ for (; nextInFlow; nextInFlow = nextInFlow->GetNextInFlow()) {
+ NS_ASSERTION(nextInFlow->IsTextFrame(), "unexpected frame type");
+ if (mFrames.ContainsFrame(nextInFlow)) {
+ nextInFlow->SetParent(this);
+ if (inFirstLine) {
+ restyleManager->ReparentComputedStyleForFirstLine(nextInFlow);
+ nsLayoutUtils::MarkDescendantsDirty(nextInFlow);
+ }
+ } else {
+#ifdef DEBUG
+ // Once we find a next-in-flow that isn't ours none of the
+ // remaining next-in-flows should be either.
+ for (; nextInFlow; nextInFlow = nextInFlow->GetNextInFlow()) {
+ NS_ASSERTION(!mFrames.ContainsFrame(nextInFlow),
+ "unexpected letter frame flow");
+ }
+#endif
+ break;
+ }
+ }
+ }
+ }
+ }
+ MOZ_ASSERT(frame->GetParent() == this);
+
+ if (!done) {
+ bool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK();
+ ReflowInlineFrame(aPresContext, aReflowInput, irs, frame, aStatus);
+ done = aStatus.IsInlineBreak() ||
+ (!reflowingFirstLetter && aStatus.IsIncomplete());
+ if (done) {
+ if (!irs.mSetParentPointer) {
+ break;
+ }
+ // Keep reparenting the remaining siblings, but don't reflow them.
+ nsFrameList* pushedFrames = GetOverflowFrames();
+ if (pushedFrames && pushedFrames->FirstChild() == frame) {
+ // Don't bother if |frame| was pushed to our overflow list.
+ break;
+ }
+ } else {
+ irs.mPrevFrame = frame;
+ }
+ }
+ frame = frame->GetNextSibling();
+ }
+
+ // Attempt to pull frames from our next-in-flow until we can't
+ if (!done && GetNextInFlow()) {
+ while (true) {
+ bool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK();
+ if (!frame) { // Could be non-null if we pulled a first-letter frame and
+ // it created a continuation, since we don't push those.
+ frame = PullOneFrame(aPresContext, irs);
+ }
+#ifdef NOISY_PUSHING
+ printf("%p pulled up %p\n", this, frame);
+#endif
+ if (!frame) {
+ break;
+ }
+ ReflowInlineFrame(aPresContext, aReflowInput, irs, frame, aStatus);
+ if (aStatus.IsInlineBreak() ||
+ (!reflowingFirstLetter && aStatus.IsIncomplete())) {
+ break;
+ }
+ irs.mPrevFrame = frame;
+ frame = frame->GetNextSibling();
+ }
+ }
+
+ NS_ASSERTION(!aStatus.IsComplete() || !GetOverflowFrames(),
+ "We can't be complete AND have overflow frames!");
+
+ // If after reflowing our children they take up no area then make
+ // sure that we don't either.
+ //
+ // Note: CSS demands that empty inline elements still affect the
+ // line-height calculations. However, continuations of an inline
+ // that are empty we force to empty so that things like collapsed
+ // whitespace in an inline element don't affect the line-height.
+ aMetrics.ISize(lineWM) = lineLayout->EndSpan(this);
+
+ // Compute final width.
+
+ // XXX Note that that the padding start and end are in the frame's
+ // writing mode, but the metrics' inline-size is in the line's
+ // writing mode. This makes sense if the line and frame are both
+ // vertical or both horizontal, but what should happen with
+ // orthogonal inlines?
+
+ // Make sure to not include our start border and padding if we have a prev
+ // continuation or if we're in a part of an {ib} split other than the first
+ // one. For box-decoration-break:clone we always include our start border
+ // and padding since all continuations have them.
+ if ((!GetPrevContinuation() && !FrameIsNonFirstInIBSplit()) ||
+ boxDecorationBreakClone) {
+ aMetrics.ISize(lineWM) += framePadding.IStart(frameWM);
+ }
+
+ /*
+ * We want to only apply the end border and padding if we're the last
+ * continuation and either not in an {ib} split or the last part of it. To
+ * be the last continuation we have to be complete (so that we won't get a
+ * next-in-flow) and have no non-fluid continuations on our continuation
+ * chain. For box-decoration-break:clone we always apply the end border and
+ * padding since all continuations have them.
+ */
+ if ((aStatus.IsComplete() && !LastInFlow()->GetNextContinuation() &&
+ !FrameIsNonLastInIBSplit()) ||
+ boxDecorationBreakClone) {
+ aMetrics.ISize(lineWM) += framePadding.IEnd(frameWM);
+ }
+
+ nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, framePadding, lineWM,
+ frameWM);
+
+ // For now our overflow area is zero. The real value will be
+ // computed in |nsLineLayout::RelativePositionFrames|.
+ aMetrics.mOverflowAreas.Clear();
+
+#ifdef NOISY_FINAL_SIZE
+ ListTag(stdout);
+ printf(": metrics=%d,%d ascent=%d\n", aMetrics.Width(), aMetrics.Height(),
+ aMetrics.BlockStartAscent());
+#endif
+}
+
+// Returns whether there's any remaining frame to pull.
+/* static */
+bool nsInlineFrame::HasFramesToPull(nsInlineFrame* aNextInFlow) {
+ while (aNextInFlow) {
+ if (!aNextInFlow->mFrames.IsEmpty()) {
+ return true;
+ }
+ if (const nsFrameList* overflow = aNextInFlow->GetOverflowFrames()) {
+ if (!overflow->IsEmpty()) {
+ return true;
+ }
+ }
+ aNextInFlow = static_cast<nsInlineFrame*>(aNextInFlow->GetNextInFlow());
+ }
+ return false;
+}
+
+void nsInlineFrame::ReflowInlineFrame(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ InlineReflowInput& irs, nsIFrame* aFrame,
+ nsReflowStatus& aStatus) {
+ nsLineLayout* lineLayout = aReflowInput.mLineLayout;
+ bool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK();
+ bool pushedFrame;
+ aStatus.Reset();
+ lineLayout->ReflowFrame(aFrame, aStatus, nullptr, pushedFrame);
+
+ if (aStatus.IsInlineBreakBefore()) {
+ if (aFrame != mFrames.FirstChild()) {
+ // Change break-before status into break-after since we have
+ // already placed at least one child frame. This preserves the
+ // break-type so that it can be propagated upward.
+ StyleClear oldClearType = aStatus.FloatClearType();
+ aStatus.Reset();
+ aStatus.SetIncomplete();
+ aStatus.SetInlineLineBreakAfter(oldClearType);
+ PushFrames(aPresContext, aFrame, irs.mPrevFrame, irs);
+ } else {
+ // Preserve reflow status when breaking-before our first child
+ // and propagate it upward without modification.
+ }
+ return;
+ }
+
+ // Create a next-in-flow if needed.
+ if (!aStatus.IsFullyComplete()) {
+ CreateNextInFlow(aFrame);
+ }
+
+ if (aStatus.IsInlineBreakAfter()) {
+ nsIFrame* nextFrame = aFrame->GetNextSibling();
+ if (nextFrame) {
+ aStatus.SetIncomplete();
+ PushFrames(aPresContext, nextFrame, aFrame, irs);
+ } else {
+ // We must return an incomplete status if there are more child
+ // frames remaining in a next-in-flow that follows this frame.
+ if (HasFramesToPull(static_cast<nsInlineFrame*>(GetNextInFlow()))) {
+ aStatus.SetIncomplete();
+ }
+ }
+ return;
+ }
+
+ if (!aStatus.IsFullyComplete() && !reflowingFirstLetter) {
+ nsIFrame* nextFrame = aFrame->GetNextSibling();
+ if (nextFrame) {
+ PushFrames(aPresContext, nextFrame, aFrame, irs);
+ }
+ }
+}
+
+nsIFrame* nsInlineFrame::PullOneFrame(nsPresContext* aPresContext,
+ InlineReflowInput& irs) {
+ nsIFrame* frame = nullptr;
+ nsInlineFrame* nextInFlow = irs.mNextInFlow;
+
+#ifdef DEBUG
+ bool willPull = HasFramesToPull(nextInFlow);
+#endif
+
+ while (nextInFlow) {
+ frame = nextInFlow->mFrames.FirstChild();
+ if (!frame) {
+ // The nextInFlow's principal list has no frames, try its overflow list.
+ nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames();
+ if (overflowFrames) {
+ frame = overflowFrames->RemoveFirstChild();
+ if (overflowFrames->IsEmpty()) {
+ // We're stealing the only frame - delete the overflow list.
+ nextInFlow->DestroyOverflowList();
+ } else {
+ // We leave the remaining frames on the overflow list (rather than
+ // putting them on nextInFlow's principal list) so we don't have to
+ // set up the parent for them.
+ }
+ // ReparentFloatsForInlineChild needs it to be on a child list -
+ // we remove it again below.
+ nextInFlow->mFrames = nsFrameList(frame, frame);
+ }
+ }
+
+ if (frame) {
+ // If our block has no next continuation, then any floats belonging to
+ // the pulled frame must belong to our block already. This check ensures
+ // we do no extra work in the common non-vertical-breaking case.
+ if (irs.mLineContainer && irs.mLineContainer->GetNextContinuation()) {
+ // The blockChildren.ContainsFrame check performed by
+ // ReparentFloatsForInlineChild will be fast because frame's ancestor
+ // will be the first child of its containing block.
+ ReparentFloatsForInlineChild(irs.mLineContainer, frame, false);
+ }
+ nextInFlow->mFrames.RemoveFirstChild();
+ // nsFirstLineFrame::PullOneFrame calls ReparentComputedStyle.
+
+ mFrames.InsertFrame(this, irs.mPrevFrame, frame);
+ if (irs.mLineLayout) {
+ irs.mLineLayout->SetDirtyNextLine();
+ }
+ nsContainerFrame::ReparentFrameView(frame, nextInFlow, this);
+ break;
+ }
+ nextInFlow = static_cast<nsInlineFrame*>(nextInFlow->GetNextInFlow());
+ irs.mNextInFlow = nextInFlow;
+ }
+
+ MOZ_ASSERT(!!frame == willPull);
+ return frame;
+}
+
+void nsInlineFrame::PushFrames(nsPresContext* aPresContext,
+ nsIFrame* aFromChild, nsIFrame* aPrevSibling,
+ InlineReflowInput& aState) {
+#ifdef NOISY_PUSHING
+ printf("%p pushing aFromChild %p, disconnecting from prev sib %p\n", this,
+ aFromChild, aPrevSibling);
+#endif
+
+ PushChildrenToOverflow(aFromChild, aPrevSibling);
+ if (aState.mLineLayout) {
+ aState.mLineLayout->SetDirtyNextLine();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+LogicalSides nsInlineFrame::GetLogicalSkipSides() const {
+ LogicalSides skip(mWritingMode);
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return skip;
+ }
+
+ if (!IsFirst()) {
+ nsInlineFrame* prev = (nsInlineFrame*)GetPrevContinuation();
+ if (HasAnyStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) ||
+ (prev && (prev->mRect.height || prev->mRect.width))) {
+ // Prev continuation is not empty therefore we don't render our start
+ // border edge.
+ skip |= eLogicalSideBitsIStart;
+ } else {
+ // If the prev continuation is empty, then go ahead and let our start
+ // edge border render.
+ }
+ }
+ if (!IsLast()) {
+ nsInlineFrame* next = (nsInlineFrame*)GetNextContinuation();
+ if (HasAnyStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) ||
+ (next && (next->mRect.height || next->mRect.width))) {
+ // Next continuation is not empty therefore we don't render our end
+ // border edge.
+ skip |= eLogicalSideBitsIEnd;
+ } else {
+ // If the next continuation is empty, then go ahead and let our end
+ // edge border render.
+ }
+ }
+
+ if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // All but the last part of an {ib} split should skip the "end" side (as
+ // determined by this frame's direction) and all but the first part of such
+ // a split should skip the "start" side. But figuring out which part of
+ // the split we are involves getting our first continuation, which might be
+ // expensive. So don't bother if we already have the relevant bits set.
+ if (skip != LogicalSides(mWritingMode, eLogicalSideBitsIBoth)) {
+ // We're missing one of the skip bits, so check whether we need to set it.
+ // Only get the first continuation once, as an optimization.
+ nsIFrame* firstContinuation = FirstContinuation();
+ if (firstContinuation->FrameIsNonLastInIBSplit()) {
+ skip |= eLogicalSideBitsIEnd;
+ }
+ if (firstContinuation->FrameIsNonFirstInIBSplit()) {
+ skip |= eLogicalSideBitsIStart;
+ }
+ }
+ }
+
+ return skip;
+}
+
+Maybe<nscoord> nsInlineFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const {
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return Nothing{};
+ }
+ return Some(mBaseline);
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsInlineFrame::AccessibleType() {
+ // FIXME(emilio): This is broken, if the image has its default `display` value
+ // overridden. Should be somewhere else.
+ if (mContent->IsHTMLElement(
+ nsGkAtoms::img)) // Create accessible for broken <img>
+ return a11y::eHyperTextType;
+
+ return a11y::eNoType;
+}
+#endif
+
+void nsInlineFrame::UpdateStyleOfOwnedAnonBoxesForIBSplit(
+ ServoRestyleState& aRestyleState) {
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES),
+ "Why did we get called?");
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
+ "Why did we have the NS_FRAME_OWNS_ANON_BOXES bit set?");
+ // Note: this assert _looks_ expensive, but it's cheap in all the cases when
+ // it passes!
+ MOZ_ASSERT(nsLayoutUtils::FirstContinuationOrIBSplitSibling(this) == this,
+ "Only the primary frame of the inline in a block-inside-inline "
+ "split should have NS_FRAME_OWNS_ANON_BOXES");
+ MOZ_ASSERT(mContent->GetPrimaryFrame() == this,
+ "We should be the primary frame for our element");
+
+ nsIFrame* blockFrame = GetProperty(nsIFrame::IBSplitSibling());
+ MOZ_ASSERT(blockFrame, "Why did we have an IB split?");
+
+ // The later inlines need to get our style.
+ ComputedStyle* ourStyle = Style();
+
+ // The anonymous block's style inherits from ours, and we already have our new
+ // ComputedStyle.
+ RefPtr<ComputedStyle> newContext =
+ aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::mozBlockInsideInlineWrapper, ourStyle);
+
+ // We're guaranteed that newContext only differs from the old ComputedStyle on
+ // the block in things they might inherit from us. And changehint processing
+ // guarantees walking the continuation and ib-sibling chains, so our existing
+ // changehint being in aChangeList is good enough. So we don't need to touch
+ // aChangeList at all here.
+
+ while (blockFrame) {
+ MOZ_ASSERT(!blockFrame->GetPrevContinuation(),
+ "Must be first continuation");
+
+ MOZ_ASSERT(blockFrame->Style()->GetPseudoType() ==
+ PseudoStyleType::mozBlockInsideInlineWrapper,
+ "Unexpected kind of ComputedStyle");
+
+ for (nsIFrame* cont = blockFrame; cont;
+ cont = cont->GetNextContinuation()) {
+ cont->SetComputedStyle(newContext);
+ }
+
+ nsIFrame* nextInline = blockFrame->GetProperty(nsIFrame::IBSplitSibling());
+
+ // This check is here due to bug 1431232. Please remove it once
+ // that bug is fixed.
+ if (MOZ_UNLIKELY(!nextInline)) {
+ break;
+ }
+
+ MOZ_ASSERT(nextInline, "There is always a trailing inline in an IB split");
+
+ for (nsIFrame* cont = nextInline; cont;
+ cont = cont->GetNextContinuation()) {
+ cont->SetComputedStyle(ourStyle);
+ }
+ blockFrame = nextInline->GetProperty(nsIFrame::IBSplitSibling());
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+// nsLineFrame implementation
+
+nsFirstLineFrame* NS_NewFirstLineFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsFirstLineFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsFirstLineFrame)
+
+void nsFirstLineFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsInlineFrame::Init(aContent, aParent, aPrevInFlow);
+ if (!aPrevInFlow) {
+ MOZ_ASSERT(Style()->GetPseudoType() == PseudoStyleType::firstLine);
+ return;
+ }
+
+ // This frame is a continuation - fixup the computed style if aPrevInFlow
+ // is the first-in-flow (the only one with a ::first-line pseudo).
+ if (aPrevInFlow->Style()->GetPseudoType() == PseudoStyleType::firstLine) {
+ MOZ_ASSERT(FirstInFlow() == aPrevInFlow);
+ // Create a new ComputedStyle that is a child of the parent
+ // ComputedStyle thus removing the ::first-line style. This way
+ // we behave as if an anonymous (unstyled) span was the child
+ // of the parent frame.
+ ComputedStyle* parentContext = aParent->Style();
+ RefPtr<ComputedStyle> newSC =
+ PresContext()->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::mozLineFrame, parentContext);
+ SetComputedStyle(newSC);
+ } else {
+ MOZ_ASSERT(FirstInFlow() != aPrevInFlow);
+ MOZ_ASSERT(aPrevInFlow->Style()->GetPseudoType() ==
+ PseudoStyleType::mozLineFrame);
+ }
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsFirstLineFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Line"_ns, aResult);
+}
+#endif
+
+nsIFrame* nsFirstLineFrame::PullOneFrame(nsPresContext* aPresContext,
+ InlineReflowInput& irs) {
+ nsIFrame* frame = nsInlineFrame::PullOneFrame(aPresContext, irs);
+ if (frame && !GetPrevInFlow()) {
+ // We are a first-line frame. Fixup the child frames
+ // style-context that we just pulled.
+ NS_ASSERTION(frame->GetParent() == this, "Incorrect parent?");
+ aPresContext->RestyleManager()->ReparentComputedStyleForFirstLine(frame);
+ nsLayoutUtils::MarkDescendantsDirty(frame);
+ }
+ return frame;
+}
+
+void nsFirstLineFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ if (nullptr == aReflowInput.mLineLayout) {
+ return; // XXX does this happen? why?
+ }
+
+ // Check for an overflow list with our prev-in-flow
+ nsFirstLineFrame* prevInFlow = (nsFirstLineFrame*)GetPrevInFlow();
+ if (prevInFlow) {
+ AutoFrameListPtr prevOverflowFrames(aPresContext,
+ prevInFlow->StealOverflowFrames());
+ if (prevOverflowFrames) {
+ // Reparent the new frames and their ComputedStyles.
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(this, nullptr, std::move(*prevOverflowFrames));
+ ReparentChildListStyle(aPresContext, newFrames, this);
+ }
+ }
+
+ // It's also possible that we have an overflow list for ourselves.
+ DrainSelfOverflowList();
+
+ // Set our own reflow input (additional state above and beyond aReflowInput).
+ InlineReflowInput irs;
+ irs.mPrevFrame = nullptr;
+ irs.mLineContainer = aReflowInput.mLineLayout->LineContainerFrame();
+ irs.mLineLayout = aReflowInput.mLineLayout;
+ irs.mNextInFlow = (nsInlineFrame*)GetNextInFlow();
+
+ bool wasEmpty = mFrames.IsEmpty();
+ if (wasEmpty) {
+ // Try to pull over one frame before starting so that we know
+ // whether we have an anonymous block or not.
+ PullOneFrame(aPresContext, irs);
+ }
+
+ if (nullptr == GetPrevInFlow()) {
+ // XXX This is pretty sick, but what we do here is to pull-up, in
+ // advance, all of the next-in-flows children. We re-resolve their
+ // style while we are at at it so that when we reflow they have
+ // the right style.
+ //
+ // All of this is so that text-runs reflow properly.
+ irs.mPrevFrame = mFrames.LastChild();
+ for (;;) {
+ nsIFrame* frame = PullOneFrame(aPresContext, irs);
+ if (!frame) {
+ break;
+ }
+ irs.mPrevFrame = frame;
+ }
+ irs.mPrevFrame = nullptr;
+ }
+
+ NS_ASSERTION(!aReflowInput.mLineLayout->GetInFirstLine(),
+ "Nested first-line frames? BOGUS");
+ aReflowInput.mLineLayout->SetInFirstLine(true);
+ ReflowFrames(aPresContext, aReflowInput, irs, aMetrics, aStatus);
+ aReflowInput.mLineLayout->SetInFirstLine(false);
+
+ ReflowAbsoluteFrames(aPresContext, aMetrics, aReflowInput, aStatus);
+
+ // Note: the line layout code will properly compute our overflow state for us
+}
+
+/* virtual */
+void nsFirstLineFrame::PullOverflowsFromPrevInFlow() {
+ nsFirstLineFrame* prevInFlow =
+ static_cast<nsFirstLineFrame*>(GetPrevInFlow());
+ if (prevInFlow) {
+ nsPresContext* presContext = PresContext();
+ AutoFrameListPtr prevOverflowFrames(presContext,
+ prevInFlow->StealOverflowFrames());
+ if (prevOverflowFrames) {
+ // Assume that our prev-in-flow has the same line container that we do.
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(this, nullptr, std::move(*prevOverflowFrames));
+ ReparentChildListStyle(presContext, newFrames, this);
+ }
+ }
+}
+
+/* virtual */
+bool nsFirstLineFrame::DrainSelfOverflowList() {
+ AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
+ if (overflowFrames) {
+ bool result = !overflowFrames->IsEmpty();
+ const nsFrameList::Slice& newFrames =
+ mFrames.AppendFrames(nullptr, std::move(*overflowFrames));
+ ReparentChildListStyle(PresContext(), newFrames, this);
+ return result;
+ }
+ return false;
+}
diff --git a/layout/generic/nsInlineFrame.h b/layout/generic/nsInlineFrame.h
new file mode 100644
index 0000000000..524ce91d9f
--- /dev/null
+++ b/layout/generic/nsInlineFrame.h
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS display:inline objects */
+
+#ifndef nsInlineFrame_h___
+#define nsInlineFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+
+class nsLineLayout;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Inline frame class.
+ *
+ * This class manages a list of child frames that are inline frames. Working
+ * with nsLineLayout, the class will reflow and place inline frames on a line.
+ */
+class nsInlineFrame : public nsContainerFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsInlineFrame)
+
+ friend nsInlineFrame* NS_NewInlineFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ // nsIFrame overrides
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true) override;
+ virtual void InvalidateFrameWithRect(
+ const nsRect& aRect, uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true) override;
+
+ virtual bool IsEmpty() override;
+ virtual bool IsSelfEmpty() override;
+
+ virtual FrameSearchResult PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset,
+ PeekOffsetCharacterOptions aOptions =
+ PeekOffsetCharacterOptions()) override;
+
+ void Destroy(DestroyContext&) override;
+
+ void StealFrame(nsIFrame* aChild) override;
+
+ // nsIHTMLReflow overrides
+ virtual void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) override;
+ virtual void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) override;
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+ virtual nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const override;
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool CanContinueTextRun() const override;
+
+ virtual void PullOverflowsFromPrevInFlow() override;
+
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const override;
+ virtual bool DrainSelfOverflowList() override;
+
+ /**
+ * Return true if the frame is first visual frame or first continuation
+ */
+ bool IsFirst() const {
+ // If the frame's bidi visual state is set, return is-first state
+ // else return true if it's the first continuation.
+ return HasAnyStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET)
+ ? HasAnyStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST)
+ : !GetPrevInFlow();
+ }
+
+ /**
+ * Return true if the frame is last visual frame or last continuation.
+ */
+ bool IsLast() const {
+ // If the frame's bidi visual state is set, return is-last state
+ // else return true if it's the last continuation.
+ return HasAnyStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET)
+ ? HasAnyStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST)
+ : !GetNextInFlow();
+ }
+
+ // Restyles the block wrappers around our non-inline-outside kids.
+ // This will only be called when such wrappers in fact exist.
+ void UpdateStyleOfOwnedAnonBoxesForIBSplit(
+ mozilla::ServoRestyleState& aRestyleState);
+
+ protected:
+ // Additional reflow input used during our reflow methods
+ struct InlineReflowInput {
+ nsIFrame* mPrevFrame;
+ nsInlineFrame* mNextInFlow;
+ nsIFrame* mLineContainer;
+ nsLineLayout* mLineLayout;
+ bool mSetParentPointer; // when reflowing child frame first set its
+ // parent frame pointer
+
+ InlineReflowInput() {
+ mPrevFrame = nullptr;
+ mNextInFlow = nullptr;
+ mLineContainer = nullptr;
+ mLineLayout = nullptr;
+ mSetParentPointer = false;
+ }
+ };
+
+ nsInlineFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, ClassID aID)
+ : nsContainerFrame(aStyle, aPresContext, aID),
+ mBaseline(NS_INTRINSIC_ISIZE_UNKNOWN) {}
+
+ LogicalSides GetLogicalSkipSides() const override;
+
+ void ReflowFrames(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput, InlineReflowInput& rs,
+ ReflowOutput& aMetrics, nsReflowStatus& aStatus);
+
+ void ReflowInlineFrame(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput, InlineReflowInput& rs,
+ nsIFrame* aFrame, nsReflowStatus& aStatus);
+
+ // Returns whether there's any frame that PullOneFrame would pull from
+ // aNextInFlow or any of aNextInFlow's next-in-flows.
+ static bool HasFramesToPull(nsInlineFrame* aNextInFlow);
+
+ virtual nsIFrame* PullOneFrame(nsPresContext*, InlineReflowInput&);
+
+ virtual void PushFrames(nsPresContext* aPresContext, nsIFrame* aFromChild,
+ nsIFrame* aPrevSibling, InlineReflowInput& aState);
+
+ private:
+ explicit nsInlineFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsInlineFrame(aStyle, aPresContext, kClassID) {}
+
+ /**
+ * Move any frames on our overflow list to the end of our principal list.
+ * @param aInFirstLine whether we're in a first-line frame.
+ * @return true if there were any overflow frames
+ */
+ bool DrainSelfOverflowListInternal(bool aInFirstLine);
+
+ protected:
+ nscoord mBaseline;
+};
+
+//----------------------------------------------------------------------
+
+/**
+ * Variation on inline-frame used to manage lines for line layout in
+ * special situations (:first-line style in particular).
+ */
+class nsFirstLineFrame final : public nsInlineFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsFirstLineFrame)
+
+ friend nsFirstLineFrame* NS_NewFirstLineFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void PullOverflowsFromPrevInFlow() override;
+ virtual bool DrainSelfOverflowList() override;
+
+ protected:
+ explicit nsFirstLineFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsInlineFrame(aStyle, aPresContext, kClassID) {}
+
+ nsIFrame* PullOneFrame(nsPresContext*, InlineReflowInput&) override;
+};
+
+#endif /* nsInlineFrame_h___ */
diff --git a/layout/generic/nsIntervalSet.cpp b/layout/generic/nsIntervalSet.cpp
new file mode 100644
index 0000000000..9cb61253a7
--- /dev/null
+++ b/layout/generic/nsIntervalSet.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=8:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a set of ranges on a number-line */
+
+#include "nsIntervalSet.h"
+#include <new>
+#include <algorithm>
+#include "mozilla/PresShell.h" // for allocation
+
+using namespace mozilla;
+
+nsIntervalSet::nsIntervalSet(PresShell* aPresShell)
+ : mList(nullptr), mPresShell(aPresShell) {}
+
+nsIntervalSet::~nsIntervalSet() {
+ Interval* current = mList;
+ while (current) {
+ Interval* trash = current;
+ current = current->mNext;
+ FreeInterval(trash);
+ }
+}
+
+void* nsIntervalSet::AllocateInterval() {
+ return mPresShell->AllocateByObjectID(eArenaObjectID_nsIntervalSet_Interval,
+ sizeof(Interval));
+}
+
+void nsIntervalSet::FreeInterval(nsIntervalSet::Interval* aInterval) {
+ NS_ASSERTION(aInterval, "null interval");
+
+ aInterval->Interval::~Interval();
+ mPresShell->FreeByObjectID(eArenaObjectID_nsIntervalSet_Interval, aInterval);
+}
+
+void nsIntervalSet::IncludeInterval(coord_type aBegin, coord_type aEnd) {
+ auto newInterval = static_cast<Interval*>(AllocateInterval());
+ new (newInterval) Interval(aBegin, aEnd);
+
+ Interval** current = &mList;
+ while (*current && (*current)->mEnd < aBegin) current = &(*current)->mNext;
+
+ newInterval->mNext = *current;
+ *current = newInterval;
+
+ Interval* subsumed = newInterval->mNext;
+ while (subsumed && subsumed->mBegin <= aEnd) {
+ newInterval->mBegin = std::min(newInterval->mBegin, subsumed->mBegin);
+ newInterval->mEnd = std::max(newInterval->mEnd, subsumed->mEnd);
+ newInterval->mNext = subsumed->mNext;
+ FreeInterval(subsumed);
+ subsumed = newInterval->mNext;
+ }
+}
+
+bool nsIntervalSet::Intersects(coord_type aBegin, coord_type aEnd) const {
+ Interval* current = mList;
+ while (current && current->mBegin <= aEnd) {
+ if (current->mEnd >= aBegin) return true;
+ current = current->mNext;
+ }
+ return false;
+}
+
+bool nsIntervalSet::Contains(coord_type aBegin, coord_type aEnd) const {
+ Interval* current = mList;
+ while (current && current->mBegin <= aBegin) {
+ if (current->mEnd >= aEnd) return true;
+ current = current->mNext;
+ }
+ return false;
+}
diff --git a/layout/generic/nsIntervalSet.h b/layout/generic/nsIntervalSet.h
new file mode 100644
index 0000000000..0d89357bd7
--- /dev/null
+++ b/layout/generic/nsIntervalSet.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=8:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* a set of ranges on a number-line */
+
+#ifndef nsIntervalSet_h___
+#define nsIntervalSet_h___
+
+#include "nsCoord.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/*
+ * A list-based class (hopefully tree-based when I get around to it)
+ * for representing a set of ranges on a number-line.
+ */
+class nsIntervalSet {
+ public:
+ typedef nscoord coord_type;
+
+ explicit nsIntervalSet(mozilla::PresShell* aPresShell);
+ ~nsIntervalSet();
+
+ /*
+ * Include the interval [aBegin, aEnd] in the set.
+ *
+ * Removal of intervals added is not supported because that would
+ * require keeping track of the individual intervals that were
+ * added (nsIntervalMap should do that). It would be simple to
+ * implement ExcludeInterval if anyone wants it, though.
+ */
+ void IncludeInterval(coord_type aBegin, coord_type aEnd);
+
+ /*
+ * Are _some_ points in [aBegin, aEnd] contained within the set
+ * of intervals?
+ */
+ bool Intersects(coord_type aBegin, coord_type aEnd) const;
+
+ /*
+ * Are _all_ points in [aBegin, aEnd] contained within the set
+ * of intervals?
+ */
+ bool Contains(coord_type aBegin, coord_type aEnd) const;
+
+ bool IsEmpty() const { return !mList; }
+
+ private:
+ class Interval {
+ public:
+ Interval(coord_type aBegin, coord_type aEnd)
+ : mBegin(aBegin), mEnd(aEnd), mPrev(nullptr), mNext(nullptr) {}
+
+ coord_type mBegin;
+ coord_type mEnd;
+ Interval* mPrev;
+ Interval* mNext;
+ };
+
+ void* AllocateInterval();
+ void FreeInterval(Interval* aInterval);
+
+ Interval* mList;
+ mozilla::PresShell* mPresShell;
+};
+
+#endif // !defined(nsIntervalSet_h___)
diff --git a/layout/generic/nsLeafFrame.cpp b/layout/generic/nsLeafFrame.cpp
new file mode 100644
index 0000000000..2688da3d4b
--- /dev/null
+++ b/layout/generic/nsLeafFrame.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for rendering objects that do not have child lists */
+
+#include "nsLeafFrame.h"
+
+#include "mozilla/PresShell.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+
+nsLeafFrame::~nsLeafFrame() = default;
+
+/* virtual */
+void nsLeafFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsLeafFrame");
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+}
+
+/* virtual */
+nscoord nsLeafFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ result = GetIntrinsicISize();
+ return result;
+}
+
+/* virtual */
+nscoord nsLeafFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ result = GetIntrinsicISize();
+ return result;
+}
+
+/* virtual */
+LogicalSize nsLeafFrame::ComputeAutoSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ const WritingMode wm = GetWritingMode();
+ LogicalSize result(wm, GetIntrinsicISize(), GetIntrinsicBSize());
+ return result.ConvertTo(aWM, wm);
+}
+
+nscoord nsLeafFrame::GetIntrinsicBSize() {
+ MOZ_ASSERT_UNREACHABLE("Someone didn't override Reflow or ComputeAutoSize");
+ return 0;
+}
+
+void nsLeafFrame::SizeToAvailSize(const ReflowInput& aReflowInput,
+ ReflowOutput& aDesiredSize) {
+ aDesiredSize.SetSize(aReflowInput.GetWritingMode(),
+ aReflowInput.AvailableSize());
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
+}
diff --git a/layout/generic/nsLeafFrame.h b/layout/generic/nsLeafFrame.h
new file mode 100644
index 0000000000..c8275f0eef
--- /dev/null
+++ b/layout/generic/nsLeafFrame.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for rendering objects that do not have child lists */
+
+#ifndef nsLeafFrame_h___
+#define nsLeafFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIFrame.h"
+#include "nsDisplayList.h"
+
+/**
+ * Abstract class that provides simple fixed-size layout for leaf objects
+ * (e.g. images, form elements, etc.). Deriviations provide the implementation
+ * of the GetDesiredSize method. The rendering method knows how to render
+ * borders and backgrounds.
+ */
+class nsLeafFrame : public nsIFrame {
+ public:
+ NS_DECL_ABSTRACT_FRAME(nsLeafFrame)
+
+ // nsIFrame replacements
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ /**
+ * Both GetMinISize and GetPrefISize will return whatever GetIntrinsicISize
+ * returns.
+ */
+ virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ /**
+ * Our auto size is just intrinsic width and intrinsic height.
+ */
+ mozilla::LogicalSize ComputeAutoSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+
+ /**
+ * Each of our subclasses should provide its own Reflow impl:
+ */
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override = 0;
+
+ protected:
+ nsLeafFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, ClassID aID)
+ : nsIFrame(aStyle, aPresContext, aID) {}
+
+ virtual ~nsLeafFrame();
+
+ /**
+ * Return the intrinsic isize of the frame's content area. Note that this
+ * should not include borders or padding and should not depend on the applied
+ * styles.
+ */
+ virtual nscoord GetIntrinsicISize() = 0;
+
+ /**
+ * Return the intrinsic bsize of the frame's content area. This should not
+ * include border or padding. This will only matter if the specified bsize
+ * is auto. Note that subclasses must either implement this or override
+ * Reflow and ComputeAutoSize; the default Reflow and ComputeAutoSize impls
+ * call this method.
+ */
+ virtual nscoord GetIntrinsicBSize();
+
+ /**
+ * Set aDesiredSize to be the available size
+ */
+ void SizeToAvailSize(const ReflowInput& aReflowInput,
+ ReflowOutput& aDesiredSize);
+};
+
+#endif /* nsLeafFrame_h___ */
diff --git a/layout/generic/nsLineBox.cpp b/layout/generic/nsLineBox.cpp
new file mode 100644
index 0000000000..18051aa69a
--- /dev/null
+++ b/layout/generic/nsLineBox.cpp
@@ -0,0 +1,625 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of one line within a block frame, a CSS line box */
+
+#include "nsLineBox.h"
+
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/ToString.h"
+#include "nsBidiPresUtils.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsPresArena.h"
+#include "nsPrintfCString.h"
+#include "nsWindowSizes.h"
+
+#ifdef DEBUG
+static int32_t ctorCount;
+int32_t nsLineBox::GetCtorCount() { return ctorCount; }
+#endif
+
+#ifndef _MSC_VER
+// static nsLineBox constant; initialized in the header file.
+const uint32_t nsLineBox::kMinChildCountForHashtable;
+#endif
+
+using namespace mozilla;
+
+nsLineBox::nsLineBox(nsIFrame* aFrame, int32_t aCount, bool aIsBlock)
+ : mFirstChild(aFrame),
+ mContainerSize(-1, -1),
+ mBounds(WritingMode()), // mBounds will be initialized with the correct
+ // writing mode when it is set
+ mFrames(),
+ mAscent(),
+ mAllFlags(0),
+ mData(nullptr) {
+ // Assert that the union elements chosen for initialisation are at
+ // least as large as all other elements in their respective unions, so
+ // as to ensure that no parts are missed.
+ static_assert(sizeof(mFrames) >= sizeof(mChildCount), "nsLineBox init #1");
+ static_assert(sizeof(mAllFlags) >= sizeof(mFlags), "nsLineBox init #2");
+ static_assert(sizeof(mData) >= sizeof(mBlockData), "nsLineBox init #3");
+ static_assert(sizeof(mData) >= sizeof(mInlineData), "nsLineBox init #4");
+
+ MOZ_COUNT_CTOR(nsLineBox);
+#ifdef DEBUG
+ ++ctorCount;
+ NS_ASSERTION(!aIsBlock || aCount == 1, "Blocks must have exactly one child");
+ nsIFrame* f = aFrame;
+ for (int32_t n = aCount; n > 0; f = f->GetNextSibling(), --n) {
+ NS_ASSERTION(aIsBlock == f->IsBlockOutside(), "wrong kind of child frame");
+ }
+#endif
+ mChildCount = aCount;
+ MarkDirty();
+ mFlags.mBlock = aIsBlock;
+}
+
+nsLineBox::~nsLineBox() {
+ MOZ_COUNT_DTOR(nsLineBox);
+ if (MOZ_UNLIKELY(mFlags.mHasHashedFrames)) {
+ delete mFrames;
+ }
+ Cleanup();
+}
+
+nsLineBox* NS_NewLineBox(PresShell* aPresShell, nsIFrame* aFrame,
+ bool aIsBlock) {
+ return new (aPresShell) nsLineBox(aFrame, 1, aIsBlock);
+}
+
+nsLineBox* NS_NewLineBox(PresShell* aPresShell, nsLineBox* aFromLine,
+ nsIFrame* aFrame, int32_t aCount) {
+ nsLineBox* newLine = new (aPresShell) nsLineBox(aFrame, aCount, false);
+ newLine->NoteFramesMovedFrom(aFromLine);
+ newLine->mContainerSize = aFromLine->mContainerSize;
+ return newLine;
+}
+
+void nsLineBox::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
+ if (mFlags.mHasHashedFrames) {
+ aSizes.mLayoutFramePropertiesSize +=
+ mFrames->ShallowSizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
+ }
+}
+
+void nsLineBox::StealHashTableFrom(nsLineBox* aFromLine,
+ uint32_t aFromLineNewCount) {
+ MOZ_ASSERT(!mFlags.mHasHashedFrames);
+ MOZ_ASSERT(GetChildCount() >= int32_t(aFromLineNewCount));
+ mFrames = aFromLine->mFrames;
+ mFlags.mHasHashedFrames = 1;
+ aFromLine->mFlags.mHasHashedFrames = 0;
+ aFromLine->mChildCount = aFromLineNewCount;
+ // remove aFromLine's frames that aren't on this line
+ nsIFrame* f = aFromLine->mFirstChild;
+ for (uint32_t i = 0; i < aFromLineNewCount; f = f->GetNextSibling(), ++i) {
+ mFrames->Remove(f);
+ }
+}
+
+void nsLineBox::NoteFramesMovedFrom(nsLineBox* aFromLine) {
+ uint32_t fromCount = aFromLine->GetChildCount();
+ uint32_t toCount = GetChildCount();
+ MOZ_ASSERT(toCount <= fromCount, "moved more frames than aFromLine has");
+ uint32_t fromNewCount = fromCount - toCount;
+ if (MOZ_LIKELY(!aFromLine->mFlags.mHasHashedFrames)) {
+ aFromLine->mChildCount = fromNewCount;
+ MOZ_ASSERT(toCount < kMinChildCountForHashtable);
+ } else if (fromNewCount < kMinChildCountForHashtable) {
+ // aFromLine has a hash table but will not have it after moving the frames
+ // so this line can steal the hash table if it needs it.
+ if (toCount >= kMinChildCountForHashtable) {
+ StealHashTableFrom(aFromLine, fromNewCount);
+ } else {
+ delete aFromLine->mFrames;
+ aFromLine->mFlags.mHasHashedFrames = 0;
+ aFromLine->mChildCount = fromNewCount;
+ }
+ } else {
+ // aFromLine still needs a hash table.
+ if (toCount < kMinChildCountForHashtable) {
+ // remove the moved frames from it
+ nsIFrame* f = mFirstChild;
+ for (uint32_t i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
+ aFromLine->mFrames->Remove(f);
+ }
+ } else if (toCount <= fromNewCount) {
+ // This line needs a hash table, allocate a hash table for it since that
+ // means fewer hash ops.
+ nsIFrame* f = mFirstChild;
+ for (uint32_t i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
+ aFromLine->mFrames->Remove(f); // toCount RemoveEntry
+ }
+ SwitchToHashtable(); // toCount PutEntry
+ } else {
+ // This line needs a hash table, but it's fewer hash ops to steal
+ // aFromLine's hash table and allocate a new hash table for that line.
+ StealHashTableFrom(aFromLine, fromNewCount); // fromNewCount RemoveEntry
+ aFromLine->SwitchToHashtable(); // fromNewCount PutEntry
+ }
+ }
+}
+
+void* nsLineBox::operator new(size_t sz, PresShell* aPresShell) {
+ return aPresShell->AllocateByObjectID(eArenaObjectID_nsLineBox, sz);
+}
+
+void nsLineBox::Destroy(PresShell* aPresShell) {
+ this->nsLineBox::~nsLineBox();
+ aPresShell->FreeByObjectID(eArenaObjectID_nsLineBox, this);
+}
+
+void nsLineBox::Cleanup() {
+ if (mData) {
+ if (IsBlock()) {
+ delete mBlockData;
+ } else {
+ delete mInlineData;
+ }
+ mData = nullptr;
+ }
+}
+
+#ifdef DEBUG_FRAME_DUMP
+static void ListFloats(FILE* out, const char* aPrefix,
+ const nsTArray<nsIFrame*>& aFloats) {
+ for (nsIFrame* f : aFloats) {
+ nsCString str(aPrefix);
+ str += nsPrintfCString("floatframe@%p ", static_cast<void*>(f));
+ nsAutoString frameName;
+ f->GetFrameName(frameName);
+ str += NS_ConvertUTF16toUTF8(frameName).get();
+ fprintf_stderr(out, "%s\n", str.get());
+ }
+}
+
+/* static */ const char* nsLineBox::StyleClearToString(StyleClear aClearType) {
+ switch (aClearType) {
+ case StyleClear::None:
+ return "none";
+ case StyleClear::Left:
+ return "left";
+ case StyleClear::Right:
+ return "right";
+ case StyleClear::Both:
+ return "both";
+ }
+ return "unknown";
+}
+
+void nsLineBox::List(FILE* out, int32_t aIndent,
+ nsIFrame::ListFlags aFlags) const {
+ nsCString str;
+ while (aIndent-- > 0) {
+ str += " ";
+ }
+ List(out, str.get(), aFlags);
+}
+
+void nsLineBox::List(FILE* out, const char* aPrefix,
+ nsIFrame::ListFlags aFlags) const {
+ nsCString str(aPrefix);
+ str += nsPrintfCString(
+ "line@%p count=%d state=%s,%s,%s,%s,%s,%s,clear-before:%s,clear-after:%s",
+ this, GetChildCount(), IsBlock() ? "block" : "inline",
+ IsDirty() ? "dirty" : "clean",
+ IsPreviousMarginDirty() ? "prevmargindirty" : "prevmarginclean",
+ IsImpactedByFloat() ? "impacted" : "not-impacted",
+ IsLineWrapped() ? "wrapped" : "not-wrapped",
+ HasForcedLineBreak() ? "forced-break" : "no-break",
+ StyleClearToString(FloatClearTypeBefore()),
+ StyleClearToString(FloatClearTypeAfter()));
+
+ if (IsBlock() && !GetCarriedOutBEndMargin().IsZero()) {
+ const nscoord bm = GetCarriedOutBEndMargin().get();
+ str += nsPrintfCString("bm=%s ",
+ nsIFrame::ConvertToString(bm, aFlags).c_str());
+ }
+ nsRect bounds = GetPhysicalBounds();
+ str +=
+ nsPrintfCString("%s ", nsIFrame::ConvertToString(bounds, aFlags).c_str());
+ if (mWritingMode.IsVertical() || mWritingMode.IsBidiRTL()) {
+ str += nsPrintfCString(
+ "wm=%s cs=(%s) logical-rect=%s ", ToString(mWritingMode).c_str(),
+ nsIFrame::ConvertToString(mContainerSize, aFlags).c_str(),
+ nsIFrame::ConvertToString(mBounds, mWritingMode, aFlags).c_str());
+ }
+ if (mData) {
+ const nsRect vo = mData->mOverflowAreas.InkOverflow();
+ const nsRect so = mData->mOverflowAreas.ScrollableOverflow();
+ if (!vo.IsEqualEdges(bounds) || !so.IsEqualEdges(bounds)) {
+ str += nsPrintfCString("ink-overflow=%s scr-overflow=%s ",
+ nsIFrame::ConvertToString(vo, aFlags).c_str(),
+ nsIFrame::ConvertToString(so, aFlags).c_str());
+ }
+ }
+ fprintf_stderr(out, "%s<\n", str.get());
+
+ nsIFrame* frame = mFirstChild;
+ int32_t n = GetChildCount();
+ nsCString pfx(aPrefix);
+ pfx += " ";
+ while (--n >= 0) {
+ frame->List(out, pfx.get(), aFlags);
+ frame = frame->GetNextSibling();
+ }
+
+ if (HasFloats()) {
+ fprintf_stderr(out, "%s> floats <\n", aPrefix);
+ ListFloats(out, pfx.get(), mInlineData->mFloats);
+ }
+ fprintf_stderr(out, "%s>\n", aPrefix);
+}
+
+nsIFrame* nsLineBox::LastChild() const {
+ nsIFrame* frame = mFirstChild;
+ int32_t n = GetChildCount() - 1;
+ while (--n >= 0) {
+ frame = frame->GetNextSibling();
+ }
+ return frame;
+}
+#endif
+
+int32_t nsLineBox::IndexOf(nsIFrame* aFrame) const {
+ int32_t i, n = GetChildCount();
+ nsIFrame* frame = mFirstChild;
+ for (i = 0; i < n; i++) {
+ if (frame == aFrame) {
+ return i;
+ }
+ frame = frame->GetNextSibling();
+ }
+ return -1;
+}
+
+int32_t nsLineBox::RIndexOf(nsIFrame* aFrame,
+ nsIFrame* aLastFrameInLine) const {
+ nsIFrame* frame = aLastFrameInLine;
+ for (int32_t i = GetChildCount() - 1; i >= 0; --i) {
+ MOZ_ASSERT(i != 0 || frame == mFirstChild,
+ "caller provided incorrect last frame");
+ if (frame == aFrame) {
+ return i;
+ }
+ frame = frame->GetPrevSibling();
+ }
+ return -1;
+}
+
+bool nsLineBox::IsEmpty() const {
+ if (IsBlock()) return mFirstChild->IsEmpty();
+
+ int32_t n;
+ nsIFrame* kid;
+ for (n = GetChildCount(), kid = mFirstChild; n > 0;
+ --n, kid = kid->GetNextSibling()) {
+ if (!kid->IsEmpty()) return false;
+ }
+ if (HasMarker()) {
+ return false;
+ }
+ return true;
+}
+
+bool nsLineBox::CachedIsEmpty() {
+ if (mFlags.mDirty) {
+ return IsEmpty();
+ }
+
+ if (mFlags.mEmptyCacheValid) {
+ return mFlags.mEmptyCacheState;
+ }
+
+ bool result;
+ if (IsBlock()) {
+ result = mFirstChild->CachedIsEmpty();
+ } else {
+ int32_t n;
+ nsIFrame* kid;
+ result = true;
+ for (n = GetChildCount(), kid = mFirstChild; n > 0;
+ --n, kid = kid->GetNextSibling()) {
+ if (!kid->CachedIsEmpty()) {
+ result = false;
+ break;
+ }
+ }
+ if (HasMarker()) {
+ result = false;
+ }
+ }
+
+ mFlags.mEmptyCacheValid = true;
+ mFlags.mEmptyCacheState = result;
+ return result;
+}
+
+void nsLineBox::DeleteLineList(nsPresContext* aPresContext, nsLineList& aLines,
+ nsFrameList* aFrames, DestroyContext& aContext) {
+ PresShell* presShell = aPresContext->PresShell();
+
+ // Keep our line list and frame list up to date as we
+ // remove frames, in case something wants to traverse the
+ // frame tree while we're destroying.
+ while (!aLines.empty()) {
+ nsLineBox* line = aLines.front();
+ if (MOZ_UNLIKELY(line->mFlags.mHasHashedFrames)) {
+ line->SwitchToCounter(); // Avoid expensive has table removals.
+ }
+ while (line->GetChildCount() > 0) {
+ nsIFrame* child = aFrames->RemoveFirstChild();
+ MOZ_DIAGNOSTIC_ASSERT(child->PresContext() == aPresContext);
+ MOZ_DIAGNOSTIC_ASSERT(child == line->mFirstChild, "Lines out of sync");
+ line->mFirstChild = aFrames->FirstChild();
+ line->NoteFrameRemoved(child);
+ child->Destroy(aContext);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(line == aLines.front(),
+ "destroying child frames messed up our lines!");
+ aLines.pop_front();
+ line->Destroy(presShell);
+ }
+}
+
+bool nsLineBox::RFindLineContaining(nsIFrame* aFrame,
+ const nsLineList::iterator& aBegin,
+ nsLineList::iterator& aEnd,
+ nsIFrame* aLastFrameBeforeEnd,
+ int32_t* aFrameIndexInLine) {
+ MOZ_ASSERT(aFrame, "null ptr");
+
+ nsIFrame* curFrame = aLastFrameBeforeEnd;
+ while (aBegin != aEnd) {
+ --aEnd;
+ NS_ASSERTION(aEnd->LastChild() == curFrame, "Unexpected curFrame");
+ if (MOZ_UNLIKELY(aEnd->mFlags.mHasHashedFrames) &&
+ !aEnd->Contains(aFrame)) {
+ if (aEnd->mFirstChild) {
+ curFrame = aEnd->mFirstChild->GetPrevSibling();
+ }
+ continue;
+ }
+ // i is the index of curFrame in aEnd
+ int32_t i = aEnd->GetChildCount() - 1;
+ while (i >= 0) {
+ if (curFrame == aFrame) {
+ *aFrameIndexInLine = i;
+ return true;
+ }
+ --i;
+ curFrame = curFrame->GetPrevSibling();
+ }
+ MOZ_ASSERT(!aEnd->mFlags.mHasHashedFrames, "Contains lied to us!");
+ }
+ *aFrameIndexInLine = -1;
+ return false;
+}
+
+nsCollapsingMargin nsLineBox::GetCarriedOutBEndMargin() const {
+ NS_ASSERTION(IsBlock(), "GetCarriedOutBEndMargin called on non-block line.");
+ return (IsBlock() && mBlockData) ? mBlockData->mCarriedOutBEndMargin
+ : nsCollapsingMargin();
+}
+
+bool nsLineBox::SetCarriedOutBEndMargin(nsCollapsingMargin aValue) {
+ bool changed = false;
+ if (IsBlock()) {
+ if (!aValue.IsZero()) {
+ if (!mBlockData) {
+ mBlockData = new ExtraBlockData(GetPhysicalBounds());
+ }
+ changed = aValue != mBlockData->mCarriedOutBEndMargin;
+ mBlockData->mCarriedOutBEndMargin = aValue;
+ } else if (mBlockData) {
+ changed = aValue != mBlockData->mCarriedOutBEndMargin;
+ mBlockData->mCarriedOutBEndMargin = aValue;
+ MaybeFreeData();
+ }
+ }
+ return changed;
+}
+
+void nsLineBox::MaybeFreeData() {
+ nsRect bounds = GetPhysicalBounds();
+ if (mData && mData->mOverflowAreas == OverflowAreas(bounds, bounds)) {
+ if (IsInline()) {
+ if (mInlineData->mFloats.IsEmpty()) {
+ delete mInlineData;
+ mInlineData = nullptr;
+ }
+ } else if (mBlockData->mCarriedOutBEndMargin.IsZero()) {
+ delete mBlockData;
+ mBlockData = nullptr;
+ }
+ }
+}
+
+void nsLineBox::ClearFloats() {
+ MOZ_ASSERT(IsInline(), "block line can't have floats");
+ if (IsInline() && mInlineData) {
+ mInlineData->mFloats.Clear();
+ MaybeFreeData();
+ }
+}
+
+void nsLineBox::AppendFloats(nsTArray<nsIFrame*>&& aFloats) {
+ MOZ_ASSERT(IsInline(), "block line can't have floats");
+ if (MOZ_UNLIKELY(!IsInline())) {
+ return;
+ }
+ if (!aFloats.IsEmpty()) {
+ if (mInlineData) {
+ mInlineData->mFloats.AppendElements(std::move(aFloats));
+ } else {
+ mInlineData = new ExtraInlineData(GetPhysicalBounds());
+ mInlineData->mFloats = std::move(aFloats);
+ }
+ }
+}
+
+bool nsLineBox::RemoveFloat(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsInline(), "block line can't have floats");
+ MOZ_ASSERT(aFrame);
+ if (IsInline() && mInlineData) {
+ if (mInlineData->mFloats.RemoveElement(aFrame)) {
+ // Note: the placeholder is part of the line's child list
+ // and will be removed later.
+ MaybeFreeData();
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsLineBox::SetFloatEdges(nscoord aStart, nscoord aEnd) {
+ MOZ_ASSERT(IsInline(), "block line can't have float edges");
+ if (!mInlineData) {
+ mInlineData = new ExtraInlineData(GetPhysicalBounds());
+ }
+ mInlineData->mFloatEdgeIStart = aStart;
+ mInlineData->mFloatEdgeIEnd = aEnd;
+}
+
+void nsLineBox::ClearFloatEdges() {
+ MOZ_ASSERT(IsInline(), "block line can't have float edges");
+ if (mInlineData) {
+ mInlineData->mFloatEdgeIStart = nscoord_MIN;
+ mInlineData->mFloatEdgeIEnd = nscoord_MIN;
+ }
+}
+
+void nsLineBox::SetOverflowAreas(const OverflowAreas& aOverflowAreas) {
+#ifdef DEBUG
+ for (const auto otype : mozilla::AllOverflowTypes()) {
+ NS_ASSERTION(aOverflowAreas.Overflow(otype).width >= 0,
+ "Illegal width for an overflow area!");
+ NS_ASSERTION(aOverflowAreas.Overflow(otype).height >= 0,
+ "Illegal height for an overflow area!");
+ }
+#endif
+
+ nsRect bounds = GetPhysicalBounds();
+ if (!aOverflowAreas.InkOverflow().IsEqualInterior(bounds) ||
+ !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
+ if (!mData) {
+ if (IsInline()) {
+ mInlineData = new ExtraInlineData(bounds);
+ } else {
+ mBlockData = new ExtraBlockData(bounds);
+ }
+ }
+ mData->mOverflowAreas = aOverflowAreas;
+ } else if (mData) {
+ // Store away new value so that MaybeFreeData compares against
+ // the right value.
+ mData->mOverflowAreas = aOverflowAreas;
+ MaybeFreeData();
+ }
+}
+
+//----------------------------------------------------------------------
+
+Result<nsILineIterator::LineInfo, nsresult> nsLineIterator::GetLine(
+ int32_t aLineNumber) {
+ const nsLineBox* line = GetLineAt(aLineNumber);
+ if (!line) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ LineInfo structure;
+ structure.mFirstFrameOnLine = line->mFirstChild;
+ structure.mNumFramesOnLine = line->GetChildCount();
+ structure.mLineBounds = line->GetPhysicalBounds();
+ structure.mIsWrapped = line->IsLineWrapped();
+ return structure;
+}
+
+int32_t nsLineIterator::FindLineContaining(nsIFrame* aFrame,
+ int32_t aStartLine) {
+ const nsLineBox* line = GetLineAt(aStartLine);
+ MOZ_ASSERT(line, "aStartLine out of range");
+ while (line) {
+ if (line->Contains(aFrame)) {
+ return mIndex;
+ }
+ line = GetNextLine();
+ }
+ return -1;
+}
+
+NS_IMETHODIMP
+nsLineIterator::CheckLineOrder(int32_t aLine, bool* aIsReordered,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) {
+ const nsLineBox* line = GetLineAt(aLine);
+ MOZ_ASSERT(line, "aLine out of range!");
+
+ if (!line || !line->mFirstChild) { // empty line
+ *aIsReordered = false;
+ *aFirstVisual = nullptr;
+ *aLastVisual = nullptr;
+ return NS_OK;
+ }
+
+ nsIFrame* leftmostFrame;
+ nsIFrame* rightmostFrame;
+ *aIsReordered =
+ nsBidiPresUtils::CheckLineOrder(line->mFirstChild, line->GetChildCount(),
+ &leftmostFrame, &rightmostFrame);
+
+ // map leftmost/rightmost to first/last according to paragraph direction
+ *aFirstVisual = mRightToLeft ? rightmostFrame : leftmostFrame;
+ *aLastVisual = mRightToLeft ? leftmostFrame : rightmostFrame;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLineIterator::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
+ nsIFrame** aFrameFound,
+ bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) {
+ MOZ_ASSERT(aFrameFound && aPosIsBeforeFirstFrame && aPosIsAfterLastFrame,
+ "null OUT ptr");
+
+ if (!aFrameFound || !aPosIsBeforeFirstFrame || !aPosIsAfterLastFrame) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ const nsLineBox* line = GetLineAt(aLineNumber);
+ if (!line) {
+ *aFrameFound = nullptr;
+ *aPosIsBeforeFirstFrame = true;
+ *aPosIsAfterLastFrame = false;
+ return NS_OK;
+ }
+
+ if (line->ISize() == 0 && line->BSize() == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LineFrameFinder finder(aPos, line->mContainerSize, line->mWritingMode,
+ mRightToLeft);
+ int32_t n = line->GetChildCount();
+ nsIFrame* frame = line->mFirstChild;
+ while (n--) {
+ finder.Scan(frame);
+ if (finder.IsDone()) {
+ break;
+ }
+ frame = frame->GetNextSibling();
+ }
+ finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
+ return NS_OK;
+}
diff --git a/layout/generic/nsLineBox.h b/layout/generic/nsLineBox.h
new file mode 100644
index 0000000000..d46cf9604a
--- /dev/null
+++ b/layout/generic/nsLineBox.h
@@ -0,0 +1,1569 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of one line within a block frame, a CSS line box */
+
+#ifndef nsLineBox_h___
+#define nsLineBox_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "nsILineIterator.h"
+#include "nsIFrame.h"
+#include "nsStyleConsts.h"
+#include "nsTHashSet.h"
+
+#include <algorithm>
+
+class nsLineBox;
+class nsWindowSizes;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Function to create a line box and initialize it with a single frame.
+ * The allocation is infallible.
+ * If the frame was moved from another line then you're responsible
+ * for notifying that line using NoteFrameRemoved(). Alternatively,
+ * it's better to use the next function that does that for you in an
+ * optimal way.
+ */
+nsLineBox* NS_NewLineBox(mozilla::PresShell* aPresShell, nsIFrame* aFrame,
+ bool aIsBlock);
+/**
+ * Function to create a line box and initialize it with aCount frames
+ * that are currently on aFromLine. The allocation is infallible.
+ */
+nsLineBox* NS_NewLineBox(mozilla::PresShell* aPresShell, nsLineBox* aFromLine,
+ nsIFrame* aFrame, int32_t aCount);
+
+class nsLineList;
+
+// don't use the following names outside of this file. Instead, use
+// nsLineList::iterator, etc. These are just here to allow them to
+// be specified as parameters to methods of nsLineBox.
+class nsLineList_iterator;
+class nsLineList_const_iterator;
+class nsLineList_reverse_iterator;
+class nsLineList_const_reverse_iterator;
+
+/**
+ * Users must have the class that is to be part of the list inherit
+ * from nsLineLink. If they want to be efficient, it should be the
+ * first base class. (This was originally nsCLink in a templatized
+ * nsCList, but it's still useful separately.)
+ */
+
+class nsLineLink {
+ public:
+ friend class nsLineList;
+ friend class nsLineList_iterator;
+ friend class nsLineList_reverse_iterator;
+ friend class nsLineList_const_iterator;
+ friend class nsLineList_const_reverse_iterator;
+
+ private:
+ nsLineLink* _mNext; // or head
+ nsLineLink* _mPrev; // or tail
+};
+
+/**
+ * The nsLineBox class represents a horizontal line of frames. It contains
+ * enough state to support incremental reflow of the frames, event handling
+ * for the frames, and rendering of the frames.
+ */
+class nsLineBox final : public nsLineLink {
+ private:
+ nsLineBox(nsIFrame* aFrame, int32_t aCount, bool aIsBlock);
+ ~nsLineBox();
+
+ // Infallible overloaded new operator. Uses an arena (which comes from the
+ // presShell) to perform the allocation.
+ void* operator new(size_t sz, mozilla::PresShell* aPresShell);
+ void operator delete(void* aPtr, size_t sz) = delete;
+
+ public:
+ // Use these functions to allocate and destroy line boxes
+ friend nsLineBox* NS_NewLineBox(mozilla::PresShell* aPresShell,
+ nsIFrame* aFrame, bool aIsBlock);
+ friend nsLineBox* NS_NewLineBox(mozilla::PresShell* aPresShell,
+ nsLineBox* aFromLine, nsIFrame* aFrame,
+ int32_t aCount);
+ void Destroy(mozilla::PresShell* aPresShell);
+
+ // mBlock bit
+ bool IsBlock() const { return mFlags.mBlock; }
+ bool IsInline() const { return !mFlags.mBlock; }
+
+ // mDirty bit
+ void MarkDirty() { mFlags.mDirty = 1; }
+ void ClearDirty() { mFlags.mDirty = 0; }
+ bool IsDirty() const { return mFlags.mDirty; }
+
+ // mPreviousMarginDirty bit
+ void MarkPreviousMarginDirty() { mFlags.mPreviousMarginDirty = 1; }
+ void ClearPreviousMarginDirty() { mFlags.mPreviousMarginDirty = 0; }
+ bool IsPreviousMarginDirty() const { return mFlags.mPreviousMarginDirty; }
+
+ // mHasClearance bit
+ void SetHasClearance() { mFlags.mHasClearance = 1; }
+ void ClearHasClearance() { mFlags.mHasClearance = 0; }
+ bool HasClearance() const { return mFlags.mHasClearance; }
+
+ // mImpactedByFloat bit
+ void SetLineIsImpactedByFloat(bool aValue) {
+ mFlags.mImpactedByFloat = aValue;
+ }
+ bool IsImpactedByFloat() const { return mFlags.mImpactedByFloat; }
+
+ // mLineWrapped bit
+ void SetLineWrapped(bool aOn) { mFlags.mLineWrapped = aOn; }
+ bool IsLineWrapped() const { return mFlags.mLineWrapped; }
+
+ // mInvalidateTextRuns bit
+ void SetInvalidateTextRuns(bool aOn) { mFlags.mInvalidateTextRuns = aOn; }
+ bool GetInvalidateTextRuns() const { return mFlags.mInvalidateTextRuns; }
+
+ // mResizeReflowOptimizationDisabled bit
+ void DisableResizeReflowOptimization() {
+ mFlags.mResizeReflowOptimizationDisabled = true;
+ }
+ void EnableResizeReflowOptimization() {
+ mFlags.mResizeReflowOptimizationDisabled = false;
+ }
+ bool ResizeReflowOptimizationDisabled() const {
+ return mFlags.mResizeReflowOptimizationDisabled;
+ }
+
+ // mHasMarker bit
+ void SetHasMarker() {
+ mFlags.mHasMarker = true;
+ InvalidateCachedIsEmpty();
+ }
+ void ClearHasMarker() {
+ mFlags.mHasMarker = false;
+ InvalidateCachedIsEmpty();
+ }
+ bool HasMarker() const { return mFlags.mHasMarker; }
+
+ // mHadFloatPushed bit
+ void SetHadFloatPushed() { mFlags.mHadFloatPushed = true; }
+ void ClearHadFloatPushed() { mFlags.mHadFloatPushed = false; }
+ bool HadFloatPushed() const { return mFlags.mHadFloatPushed; }
+
+ // mHasLineClampEllipsis bit
+ void SetHasLineClampEllipsis() { mFlags.mHasLineClampEllipsis = true; }
+ void ClearHasLineClampEllipsis() { mFlags.mHasLineClampEllipsis = false; }
+ bool HasLineClampEllipsis() const { return mFlags.mHasLineClampEllipsis; }
+
+ // mMovedFragments bit
+ void SetMovedFragments() { mFlags.mMovedFragments = true; }
+ void ClearMovedFragments() { mFlags.mMovedFragments = false; }
+ bool MovedFragments() const { return mFlags.mMovedFragments; }
+
+ private:
+ // Add a hash table for fast lookup when the line has more frames than this.
+ static const uint32_t kMinChildCountForHashtable = 200;
+
+ /**
+ * Take ownership of aFromLine's hash table and remove the frames that
+ * stay on aFromLine from it, i.e. aFromLineNewCount frames starting with
+ * mFirstChild. This method is used to optimize moving a large number
+ * of frames from one line to the next.
+ */
+ void StealHashTableFrom(nsLineBox* aFromLine, uint32_t aFromLineNewCount);
+
+ /**
+ * Does the equivalent of this->NoteFrameAdded and aFromLine->NoteFrameRemoved
+ * for each frame on this line, but in a optimized way.
+ */
+ void NoteFramesMovedFrom(nsLineBox* aFromLine);
+
+ void SwitchToHashtable() {
+ MOZ_ASSERT(!mFlags.mHasHashedFrames);
+ uint32_t count = GetChildCount();
+ mFlags.mHasHashedFrames = 1;
+ uint32_t minLength =
+ std::max(kMinChildCountForHashtable,
+ uint32_t(PLDHashTable::kDefaultInitialLength));
+ mFrames = new nsTHashSet<nsIFrame*>(std::max(count, minLength));
+ for (nsIFrame* f = mFirstChild; count-- > 0; f = f->GetNextSibling()) {
+ mFrames->Insert(f);
+ }
+ }
+ void SwitchToCounter() {
+ MOZ_ASSERT(mFlags.mHasHashedFrames);
+ uint32_t count = GetChildCount();
+ delete mFrames;
+ mFlags.mHasHashedFrames = 0;
+ mChildCount = count;
+ }
+
+ public:
+ int32_t GetChildCount() const {
+ return MOZ_UNLIKELY(mFlags.mHasHashedFrames) ? mFrames->Count()
+ : mChildCount;
+ }
+
+ /**
+ * Register that aFrame is now on this line.
+ */
+ void NoteFrameAdded(nsIFrame* aFrame) {
+ if (MOZ_UNLIKELY(mFlags.mHasHashedFrames)) {
+ mFrames->Insert(aFrame);
+ } else {
+ if (++mChildCount >= kMinChildCountForHashtable) {
+ SwitchToHashtable();
+ }
+ }
+ }
+
+ /**
+ * Register that aFrame is not on this line anymore.
+ */
+ void NoteFrameRemoved(nsIFrame* aFrame) {
+ MOZ_ASSERT(GetChildCount() > 0);
+ if (MOZ_UNLIKELY(mFlags.mHasHashedFrames)) {
+ mFrames->Remove(aFrame);
+ if (mFrames->Count() < kMinChildCountForHashtable) {
+ SwitchToCounter();
+ }
+ } else {
+ --mChildCount;
+ }
+ }
+
+ // mHasForcedLineBreak bit & mFloatClearType value
+ // Break information is applied *before* the line if the line is a block,
+ // or *after* the line if the line is an inline.
+ bool HasForcedLineBreak() const { return mFlags.mHasForcedLineBreak; }
+ void ClearForcedLineBreak() {
+ mFlags.mHasForcedLineBreak = false;
+ mFlags.mFloatClearType = mozilla::StyleClear::None;
+ }
+
+ bool HasForcedLineBreakBefore() const {
+ return IsBlock() && HasForcedLineBreak();
+ }
+ void SetForcedLineBreakBefore(mozilla::StyleClear aClearType) {
+ MOZ_ASSERT(IsBlock(), "Only blocks have break-before");
+ MOZ_ASSERT(aClearType != mozilla::StyleClear::None,
+ "Only StyleClear:Left/Right/Both are allowed before a line");
+ mFlags.mHasForcedLineBreak = true;
+ mFlags.mFloatClearType = aClearType;
+ }
+ mozilla::StyleClear FloatClearTypeBefore() const {
+ return IsBlock() ? FloatClearType() : mozilla::StyleClear::None;
+ }
+
+ bool HasForcedLineBreakAfter() const {
+ return IsInline() && HasForcedLineBreak();
+ }
+ void SetForcedLineBreakAfter(mozilla::StyleClear aClearType) {
+ MOZ_ASSERT(IsInline(), "Only inlines have break-after");
+ mFlags.mHasForcedLineBreak = true;
+ mFlags.mFloatClearType = aClearType;
+ }
+ bool HasFloatClearTypeAfter() const {
+ return IsInline() && FloatClearType() != mozilla::StyleClear::None;
+ }
+ mozilla::StyleClear FloatClearTypeAfter() const {
+ return IsInline() ? FloatClearType() : mozilla::StyleClear::None;
+ }
+
+ // mCarriedOutBEndMargin value
+ nsCollapsingMargin GetCarriedOutBEndMargin() const;
+ // Returns true if the margin changed
+ bool SetCarriedOutBEndMargin(nsCollapsingMargin aValue);
+
+ // mFloats
+ bool HasFloats() const {
+ return (IsInline() && mInlineData) && !mInlineData->mFloats.IsEmpty();
+ }
+ const nsTArray<nsIFrame*>& Floats() const {
+ MOZ_ASSERT(HasFloats());
+ return mInlineData->mFloats;
+ }
+ // Append aFloats to mFloat. aFloats will be empty.
+ void AppendFloats(nsTArray<nsIFrame*>&& aFloats);
+ void ClearFloats();
+ bool RemoveFloat(nsIFrame* aFrame);
+
+ // The ink overflow area should never be used for things that affect layout.
+ // The scrollable overflow area are permitted to affect layout for handling of
+ // overflow and scrollbars.
+ void SetOverflowAreas(const mozilla::OverflowAreas& aOverflowAreas);
+ mozilla::LogicalRect GetOverflowArea(mozilla::OverflowType aType,
+ mozilla::WritingMode aWM,
+ const nsSize& aContainerSize) {
+ return mozilla::LogicalRect(aWM, GetOverflowArea(aType), aContainerSize);
+ }
+ nsRect GetOverflowArea(mozilla::OverflowType aType) const {
+ return mData ? mData->mOverflowAreas.Overflow(aType) : GetPhysicalBounds();
+ }
+ mozilla::OverflowAreas GetOverflowAreas() const {
+ if (mData) {
+ return mData->mOverflowAreas;
+ }
+ nsRect bounds = GetPhysicalBounds();
+ return mozilla::OverflowAreas(bounds, bounds);
+ }
+ nsRect InkOverflowRect() const {
+ return GetOverflowArea(mozilla::OverflowType::Ink);
+ }
+ nsRect ScrollableOverflowRect() {
+ return GetOverflowArea(mozilla::OverflowType::Scrollable);
+ }
+
+ void SlideBy(nscoord aDBCoord, const nsSize& aContainerSize) {
+ NS_ASSERTION(
+ aContainerSize == mContainerSize || mContainerSize == nsSize(-1, -1),
+ "container size doesn't match");
+ mContainerSize = aContainerSize;
+ mBounds.BStart(mWritingMode) += aDBCoord;
+ if (mData) {
+ // Use a null containerSize to convert vector from logical to physical.
+ const nsSize nullContainerSize;
+ nsPoint physicalDelta =
+ mozilla::LogicalPoint(mWritingMode, 0, aDBCoord)
+ .GetPhysicalPoint(mWritingMode, nullContainerSize);
+ for (const auto otype : mozilla::AllOverflowTypes()) {
+ mData->mOverflowAreas.Overflow(otype) += physicalDelta;
+ }
+ }
+ }
+
+ // Container-size for the line is changing (and therefore if writing mode
+ // was vertical-rl, the line will move physically; this is like SlideBy,
+ // but it is the container size instead of the line's own logical coord
+ // that is changing.
+ nsSize UpdateContainerSize(const nsSize aNewContainerSize) {
+ NS_ASSERTION(mContainerSize != nsSize(-1, -1), "container size not set");
+ nsSize delta = mContainerSize - aNewContainerSize;
+ mContainerSize = aNewContainerSize;
+ // this has a physical-coordinate effect only in vertical-rl mode
+ if (mWritingMode.IsVerticalRL() && mData) {
+ nsPoint physicalDelta(-delta.width, 0);
+ for (const auto otype : mozilla::AllOverflowTypes()) {
+ mData->mOverflowAreas.Overflow(otype) += physicalDelta;
+ }
+ }
+ return delta;
+ }
+
+ void IndentBy(nscoord aDICoord, const nsSize& aContainerSize) {
+ NS_ASSERTION(
+ aContainerSize == mContainerSize || mContainerSize == nsSize(-1, -1),
+ "container size doesn't match");
+ mContainerSize = aContainerSize;
+ mBounds.IStart(mWritingMode) += aDICoord;
+ }
+
+ void ExpandBy(nscoord aDISize, const nsSize& aContainerSize) {
+ NS_ASSERTION(
+ aContainerSize == mContainerSize || mContainerSize == nsSize(-1, -1),
+ "container size doesn't match");
+ mContainerSize = aContainerSize;
+ mBounds.ISize(mWritingMode) += aDISize;
+ }
+
+ /**
+ * The logical ascent (distance from block-start to baseline) of the
+ * linebox is the logical ascent of the anonymous inline box (for
+ * which we don't actually create a frame) that wraps all the
+ * consecutive inline children of a block.
+ *
+ * This is currently unused for block lines.
+ */
+ nscoord GetLogicalAscent() const { return mAscent; }
+ void SetLogicalAscent(nscoord aAscent) { mAscent = aAscent; }
+
+ nscoord BStart() const { return mBounds.BStart(mWritingMode); }
+ nscoord BSize() const { return mBounds.BSize(mWritingMode); }
+ nscoord BEnd() const { return mBounds.BEnd(mWritingMode); }
+ nscoord IStart() const { return mBounds.IStart(mWritingMode); }
+ nscoord ISize() const { return mBounds.ISize(mWritingMode); }
+ nscoord IEnd() const { return mBounds.IEnd(mWritingMode); }
+ void SetBoundsEmpty() {
+ mBounds.IStart(mWritingMode) = 0;
+ mBounds.ISize(mWritingMode) = 0;
+ mBounds.BStart(mWritingMode) = 0;
+ mBounds.BSize(mWritingMode) = 0;
+ }
+
+ using DestroyContext = nsIFrame::DestroyContext;
+ static void DeleteLineList(nsPresContext* aPresContext, nsLineList& aLines,
+ nsFrameList* aFrames, DestroyContext&);
+
+ // search from end to beginning of [aBegin, aEnd)
+ // Returns true if it found the line and false if not.
+ // Moves aEnd as it searches so that aEnd points to the resulting line.
+ // aLastFrameBeforeEnd is the last frame before aEnd (so if aEnd is
+ // the end of the line list, it's just the last frame in the frame
+ // list).
+ static bool RFindLineContaining(nsIFrame* aFrame,
+ const nsLineList_iterator& aBegin,
+ nsLineList_iterator& aEnd,
+ nsIFrame* aLastFrameBeforeEnd,
+ int32_t* aFrameIndexInLine);
+
+#ifdef DEBUG_FRAME_DUMP
+ static const char* StyleClearToString(mozilla::StyleClear aClearType);
+
+ void List(FILE* out, int32_t aIndent,
+ nsIFrame::ListFlags aFlags = nsIFrame::ListFlags()) const;
+ void List(FILE* out = stderr, const char* aPrefix = "",
+ nsIFrame::ListFlags aFlags = nsIFrame::ListFlags()) const;
+ nsIFrame* LastChild() const;
+#endif
+
+ void AddSizeOfExcludingThis(nsWindowSizes& aSizes) const;
+
+ // Find the index of aFrame within the line, starting search at the start.
+ int32_t IndexOf(nsIFrame* aFrame) const;
+
+ // Find the index of aFrame within the line, starting search at the end.
+ // (Produces the same result as IndexOf, but with different performance
+ // characteristics.) The caller must provide the last frame in the line.
+ int32_t RIndexOf(nsIFrame* aFrame, nsIFrame* aLastFrameInLine) const;
+
+ bool Contains(nsIFrame* aFrame) const {
+ return MOZ_UNLIKELY(mFlags.mHasHashedFrames) ? mFrames->Contains(aFrame)
+ : IndexOf(aFrame) >= 0;
+ }
+
+ // whether the line box is "logically" empty (just like nsIFrame::IsEmpty)
+ bool IsEmpty() const;
+
+ // Call this only while in Reflow() for the block the line belongs
+ // to, only between reflowing the line (or sliding it, if we skip
+ // reflowing it) and the end of reflowing the block.
+ bool CachedIsEmpty();
+
+ void InvalidateCachedIsEmpty() { mFlags.mEmptyCacheValid = false; }
+
+ // For debugging purposes
+ bool IsValidCachedIsEmpty() { return mFlags.mEmptyCacheValid; }
+
+#ifdef DEBUG
+ static int32_t GetCtorCount();
+#endif
+
+ nsIFrame* mFirstChild;
+
+ mozilla::WritingMode mWritingMode;
+
+ // Physical size. Use only for physical <-> logical coordinate conversion.
+ nsSize mContainerSize;
+
+ private:
+ mozilla::LogicalRect mBounds;
+
+ public:
+ const mozilla::LogicalRect& GetBounds() { return mBounds; }
+ nsRect GetPhysicalBounds() const {
+ if (mBounds.IsAllZero()) {
+ return nsRect(0, 0, 0, 0);
+ }
+
+ NS_ASSERTION(mContainerSize != nsSize(-1, -1),
+ "mContainerSize not initialized");
+ return mBounds.GetPhysicalRect(mWritingMode, mContainerSize);
+ }
+ void SetBounds(mozilla::WritingMode aWritingMode, nscoord aIStart,
+ nscoord aBStart, nscoord aISize, nscoord aBSize,
+ const nsSize& aContainerSize) {
+ mWritingMode = aWritingMode;
+ mContainerSize = aContainerSize;
+ mBounds =
+ mozilla::LogicalRect(aWritingMode, aIStart, aBStart, aISize, aBSize);
+ }
+
+ // mFlags.mHasHashedFrames says which one to use
+ union {
+ nsTHashSet<nsIFrame*>* mFrames;
+ uint32_t mChildCount;
+ };
+
+ struct FlagBits {
+ bool mDirty : 1;
+ bool mPreviousMarginDirty : 1;
+ bool mHasClearance : 1;
+ bool mBlock : 1;
+ bool mImpactedByFloat : 1;
+ bool mLineWrapped : 1;
+ bool mInvalidateTextRuns : 1;
+ // default 0 = means that the opt potentially applies to this line.
+ // 1 = never skip reflowing this line for a resize reflow
+ bool mResizeReflowOptimizationDisabled : 1;
+ bool mEmptyCacheValid : 1;
+ bool mEmptyCacheState : 1;
+ // mHasMarker indicates that this is an inline line whose block's
+ // ::marker is adjacent to this line and non-empty.
+ bool mHasMarker : 1;
+ // Indicates that this line *may* have a placeholder for a float
+ // that was pushed to a later column or page.
+ bool mHadFloatPushed : 1;
+ bool mHasHashedFrames : 1;
+ // Indicates that this line is the one identified by an ancestor block
+ // with -webkit-line-clamp on its legacy flex container, and that subsequent
+ // lines under that block are "clamped" away, and therefore we need to
+ // render a 'text-overflow: ellipsis'-like marker in this line. At most one
+ // line in the set of lines found by LineClampLineIterator for a given
+ // block will have this flag set.
+ bool mHasLineClampEllipsis : 1;
+ // Has this line moved to a different fragment of the block since
+ // the last time it was reflowed?
+ bool mMovedFragments : 1;
+ // mHasForcedLineBreak indicates that this line has either a break-before or
+ // a break-after.
+ bool mHasForcedLineBreak : 1;
+ // mFloatClearType indicates that there's a float clearance before or after
+ // this line.
+ mozilla::StyleClear mFloatClearType;
+ };
+
+ struct ExtraData {
+ explicit ExtraData(const nsRect& aBounds)
+ : mOverflowAreas(aBounds, aBounds) {}
+ mozilla::OverflowAreas mOverflowAreas;
+ };
+
+ struct ExtraBlockData : public ExtraData {
+ explicit ExtraBlockData(const nsRect& aBounds) : ExtraData(aBounds) {}
+ nsCollapsingMargin mCarriedOutBEndMargin;
+ };
+
+ struct ExtraInlineData : public ExtraData {
+ explicit ExtraInlineData(const nsRect& aBounds)
+ : ExtraData(aBounds),
+ mFloatEdgeIStart(nscoord_MIN),
+ mFloatEdgeIEnd(nscoord_MIN) {}
+ nscoord mFloatEdgeIStart;
+ nscoord mFloatEdgeIEnd;
+ nsTArray<nsIFrame*> mFloats;
+ };
+
+ bool GetFloatEdges(nscoord* aStart, nscoord* aEnd) const {
+ MOZ_ASSERT(IsInline(), "block line can't have float edges");
+ if (mInlineData && mInlineData->mFloatEdgeIStart != nscoord_MIN) {
+ *aStart = mInlineData->mFloatEdgeIStart;
+ *aEnd = mInlineData->mFloatEdgeIEnd;
+ return true;
+ }
+ return false;
+ }
+ void SetFloatEdges(nscoord aStart, nscoord aEnd);
+ void ClearFloatEdges();
+
+ protected:
+ nscoord mAscent; // see |SetAscent| / |GetAscent|
+ static_assert(sizeof(FlagBits) <= sizeof(uint32_t),
+ "size of FlagBits should not be larger than size of uint32_t");
+ union {
+ uint32_t mAllFlags;
+ FlagBits mFlags;
+ };
+
+ mozilla::StyleClear FloatClearType() const { return mFlags.mFloatClearType; };
+
+ union {
+ ExtraData* mData;
+ ExtraBlockData* mBlockData;
+ ExtraInlineData* mInlineData;
+ };
+
+ void Cleanup();
+ void MaybeFreeData();
+};
+
+/**
+ * A linked list type where the items in the list must inherit from
+ * a link type to fuse allocations.
+ *
+ * API heavily based on the |list| class in the C++ standard.
+ */
+
+class nsLineList_iterator {
+ public:
+ friend class nsLineList;
+ friend class nsLineList_reverse_iterator;
+ friend class nsLineList_const_iterator;
+ friend class nsLineList_const_reverse_iterator;
+
+ typedef nsLineList_iterator iterator_self_type;
+ typedef nsLineList_reverse_iterator iterator_reverse_type;
+
+ typedef nsLineBox& reference;
+ typedef const nsLineBox& const_reference;
+
+ typedef nsLineBox* pointer;
+ typedef const nsLineBox* const_pointer;
+
+ typedef uint32_t size_type;
+ typedef int32_t difference_type;
+
+ typedef nsLineLink link_type;
+
+#ifdef DEBUG
+ nsLineList_iterator() : mListLink(nullptr) {
+ memset(&mCurrent, 0xcd, sizeof(mCurrent));
+ }
+#else
+ // Auto generated default constructor OK.
+#endif
+ // Auto generated copy-constructor OK.
+
+ inline iterator_self_type& operator=(const iterator_self_type& aOther);
+ inline iterator_self_type& operator=(const iterator_reverse_type& aOther);
+
+ iterator_self_type& operator++() {
+ mCurrent = mCurrent->_mNext;
+ return *this;
+ }
+
+ iterator_self_type operator++(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mNext;
+ return rv;
+ }
+
+ iterator_self_type& operator--() {
+ mCurrent = mCurrent->_mPrev;
+ return *this;
+ }
+
+ iterator_self_type operator--(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mPrev;
+ return rv;
+ }
+
+ reference operator*() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return *static_cast<pointer>(mCurrent);
+ }
+
+ pointer operator->() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<pointer>(mCurrent);
+ }
+
+ pointer get() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<pointer>(mCurrent);
+ }
+
+ operator pointer() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<pointer>(mCurrent);
+ }
+
+ const_reference operator*() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return *static_cast<const_pointer>(mCurrent);
+ }
+
+ const_pointer operator->() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+
+#ifndef __MWERKS__
+ operator const_pointer() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+#endif /* !__MWERKS__ */
+
+ iterator_self_type next() {
+ iterator_self_type copy(*this);
+ return ++copy;
+ }
+
+ const iterator_self_type next() const {
+ iterator_self_type copy(*this);
+ return ++copy;
+ }
+
+ iterator_self_type prev() {
+ iterator_self_type copy(*this);
+ return --copy;
+ }
+
+ const iterator_self_type prev() const {
+ iterator_self_type copy(*this);
+ return --copy;
+ }
+
+ bool operator==(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent == aOther.mCurrent;
+ }
+ bool operator!=(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent != aOther.mCurrent;
+ }
+
+#ifdef DEBUG
+ bool IsInSameList(const iterator_self_type& aOther) const {
+ return mListLink == aOther.mListLink;
+ }
+#endif
+
+ private:
+ link_type* mCurrent;
+#ifdef DEBUG
+ link_type* mListLink; // the list's link, i.e., the end
+#endif
+};
+
+class nsLineList_reverse_iterator {
+ public:
+ friend class nsLineList;
+ friend class nsLineList_iterator;
+ friend class nsLineList_const_iterator;
+ friend class nsLineList_const_reverse_iterator;
+
+ typedef nsLineList_reverse_iterator iterator_self_type;
+ typedef nsLineList_iterator iterator_reverse_type;
+
+ typedef nsLineBox& reference;
+ typedef const nsLineBox& const_reference;
+
+ typedef nsLineBox* pointer;
+ typedef const nsLineBox* const_pointer;
+
+ typedef uint32_t size_type;
+ typedef int32_t difference_type;
+
+ typedef nsLineLink link_type;
+
+#ifdef DEBUG
+ nsLineList_reverse_iterator() : mListLink(nullptr) {
+ memset(&mCurrent, 0xcd, sizeof(mCurrent));
+ }
+#else
+ // Auto generated default constructor OK.
+#endif
+ // Auto generated copy-constructor OK.
+
+ inline iterator_self_type& operator=(const iterator_reverse_type& aOther);
+ inline iterator_self_type& operator=(const iterator_self_type& aOther);
+
+ iterator_self_type& operator++() {
+ mCurrent = mCurrent->_mPrev;
+ return *this;
+ }
+
+ iterator_self_type operator++(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mPrev;
+ return rv;
+ }
+
+ iterator_self_type& operator--() {
+ mCurrent = mCurrent->_mNext;
+ return *this;
+ }
+
+ iterator_self_type operator--(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mNext;
+ return rv;
+ }
+
+ reference operator*() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return *static_cast<pointer>(mCurrent);
+ }
+
+ pointer operator->() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<pointer>(mCurrent);
+ }
+
+ pointer get() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<pointer>(mCurrent);
+ }
+
+ operator pointer() {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<pointer>(mCurrent);
+ }
+
+ const_reference operator*() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return *static_cast<const_pointer>(mCurrent);
+ }
+
+ const_pointer operator->() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+
+#ifndef __MWERKS__
+ operator const_pointer() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+#endif /* !__MWERKS__ */
+
+ bool operator==(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ NS_ASSERTION(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent == aOther.mCurrent;
+ }
+ bool operator!=(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ NS_ASSERTION(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent != aOther.mCurrent;
+ }
+
+#ifdef DEBUG
+ bool IsInSameList(const iterator_self_type& aOther) const {
+ return mListLink == aOther.mListLink;
+ }
+#endif
+
+ private:
+ link_type* mCurrent;
+#ifdef DEBUG
+ link_type* mListLink; // the list's link, i.e., the end
+#endif
+};
+
+class nsLineList_const_iterator {
+ public:
+ friend class nsLineList;
+ friend class nsLineList_iterator;
+ friend class nsLineList_reverse_iterator;
+ friend class nsLineList_const_reverse_iterator;
+
+ typedef nsLineList_const_iterator iterator_self_type;
+ typedef nsLineList_const_reverse_iterator iterator_reverse_type;
+ typedef nsLineList_iterator iterator_nonconst_type;
+ typedef nsLineList_reverse_iterator iterator_nonconst_reverse_type;
+
+ typedef nsLineBox& reference;
+ typedef const nsLineBox& const_reference;
+
+ typedef nsLineBox* pointer;
+ typedef const nsLineBox* const_pointer;
+
+ typedef uint32_t size_type;
+ typedef int32_t difference_type;
+
+ typedef nsLineLink link_type;
+
+#ifdef DEBUG
+ nsLineList_const_iterator() : mListLink(nullptr) {
+ memset(&mCurrent, 0xcd, sizeof(mCurrent));
+ }
+#else
+ // Auto generated default constructor OK.
+#endif
+ // Auto generated copy-constructor OK.
+
+ inline iterator_self_type& operator=(const iterator_nonconst_type& aOther);
+ inline iterator_self_type& operator=(
+ const iterator_nonconst_reverse_type& aOther);
+ inline iterator_self_type& operator=(const iterator_self_type& aOther);
+ inline iterator_self_type& operator=(const iterator_reverse_type& aOther);
+
+ iterator_self_type& operator++() {
+ mCurrent = mCurrent->_mNext;
+ return *this;
+ }
+
+ iterator_self_type operator++(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mNext;
+ return rv;
+ }
+
+ iterator_self_type& operator--() {
+ mCurrent = mCurrent->_mPrev;
+ return *this;
+ }
+
+ iterator_self_type operator--(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mPrev;
+ return rv;
+ }
+
+ const_reference operator*() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return *static_cast<const_pointer>(mCurrent);
+ }
+
+ const_pointer operator->() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+
+ const_pointer get() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+
+#ifndef __MWERKS__
+ operator const_pointer() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+#endif /* !__MWERKS__ */
+
+ const iterator_self_type next() const {
+ iterator_self_type copy(*this);
+ return ++copy;
+ }
+
+ const iterator_self_type prev() const {
+ iterator_self_type copy(*this);
+ return --copy;
+ }
+
+ bool operator==(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ NS_ASSERTION(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent == aOther.mCurrent;
+ }
+ bool operator!=(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ NS_ASSERTION(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent != aOther.mCurrent;
+ }
+
+#ifdef DEBUG
+ bool IsInSameList(const iterator_self_type& aOther) const {
+ return mListLink == aOther.mListLink;
+ }
+#endif
+
+ private:
+ const link_type* mCurrent;
+#ifdef DEBUG
+ const link_type* mListLink; // the list's link, i.e., the end
+#endif
+};
+
+class nsLineList_const_reverse_iterator {
+ public:
+ friend class nsLineList;
+ friend class nsLineList_iterator;
+ friend class nsLineList_reverse_iterator;
+ friend class nsLineList_const_iterator;
+
+ typedef nsLineList_const_reverse_iterator iterator_self_type;
+ typedef nsLineList_const_iterator iterator_reverse_type;
+ typedef nsLineList_iterator iterator_nonconst_reverse_type;
+ typedef nsLineList_reverse_iterator iterator_nonconst_type;
+
+ typedef nsLineBox& reference;
+ typedef const nsLineBox& const_reference;
+
+ typedef nsLineBox* pointer;
+ typedef const nsLineBox* const_pointer;
+
+ typedef uint32_t size_type;
+ typedef int32_t difference_type;
+
+ typedef nsLineLink link_type;
+
+#ifdef DEBUG
+ nsLineList_const_reverse_iterator() : mListLink(nullptr) {
+ memset(&mCurrent, 0xcd, sizeof(mCurrent));
+ }
+#else
+ // Auto generated default constructor OK.
+#endif
+ // Auto generated copy-constructor OK.
+
+ inline iterator_self_type& operator=(const iterator_nonconst_type& aOther);
+ inline iterator_self_type& operator=(
+ const iterator_nonconst_reverse_type& aOther);
+ inline iterator_self_type& operator=(const iterator_self_type& aOther);
+ inline iterator_self_type& operator=(const iterator_reverse_type& aOther);
+
+ iterator_self_type& operator++() {
+ mCurrent = mCurrent->_mPrev;
+ return *this;
+ }
+
+ iterator_self_type operator++(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mPrev;
+ return rv;
+ }
+
+ iterator_self_type& operator--() {
+ mCurrent = mCurrent->_mNext;
+ return *this;
+ }
+
+ iterator_self_type operator--(int) {
+ iterator_self_type rv(*this);
+ mCurrent = mCurrent->_mNext;
+ return rv;
+ }
+
+ const_reference operator*() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return *static_cast<const_pointer>(mCurrent);
+ }
+
+ const_pointer operator->() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+
+ const_pointer get() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+
+#ifndef __MWERKS__
+ operator const_pointer() const {
+ MOZ_ASSERT(mListLink);
+ MOZ_ASSERT(mCurrent != mListLink, "running past end");
+ return static_cast<const_pointer>(mCurrent);
+ }
+#endif /* !__MWERKS__ */
+
+ bool operator==(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ NS_ASSERTION(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent == aOther.mCurrent;
+ }
+ bool operator!=(const iterator_self_type& aOther) const {
+ MOZ_ASSERT(mListLink);
+ NS_ASSERTION(mListLink == aOther.mListLink,
+ "comparing iterators over different lists");
+ return mCurrent != aOther.mCurrent;
+ }
+
+#ifdef DEBUG
+ bool IsInSameList(const iterator_self_type& aOther) const {
+ return mListLink == aOther.mListLink;
+ }
+#endif
+
+ // private:
+ const link_type* mCurrent;
+#ifdef DEBUG
+ const link_type* mListLink; // the list's link, i.e., the end
+#endif
+};
+
+class nsLineList {
+ public:
+ friend class nsLineList_iterator;
+ friend class nsLineList_reverse_iterator;
+ friend class nsLineList_const_iterator;
+ friend class nsLineList_const_reverse_iterator;
+
+ typedef uint32_t size_type;
+ typedef int32_t difference_type;
+
+ typedef nsLineLink link_type;
+
+ private:
+ link_type mLink;
+
+ public:
+ typedef nsLineList self_type;
+
+ typedef nsLineBox& reference;
+ typedef const nsLineBox& const_reference;
+
+ typedef nsLineBox* pointer;
+ typedef const nsLineBox* const_pointer;
+
+ typedef nsLineList_iterator iterator;
+ typedef nsLineList_reverse_iterator reverse_iterator;
+ typedef nsLineList_const_iterator const_iterator;
+ typedef nsLineList_const_reverse_iterator const_reverse_iterator;
+
+ nsLineList() {
+ MOZ_COUNT_CTOR(nsLineList);
+ clear();
+ }
+
+ MOZ_COUNTED_DTOR(nsLineList)
+
+ const_iterator begin() const {
+ const_iterator rv;
+ rv.mCurrent = mLink._mNext;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ iterator begin() {
+ iterator rv;
+ rv.mCurrent = mLink._mNext;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ iterator begin(nsLineBox* aLine) {
+ iterator rv;
+ rv.mCurrent = aLine;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ const_iterator end() const {
+ const_iterator rv;
+ rv.mCurrent = &mLink;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ iterator end() {
+ iterator rv;
+ rv.mCurrent = &mLink;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ const_reverse_iterator rbegin() const {
+ const_reverse_iterator rv;
+ rv.mCurrent = mLink._mPrev;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ reverse_iterator rbegin() {
+ reverse_iterator rv;
+ rv.mCurrent = mLink._mPrev;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ reverse_iterator rbegin(nsLineBox* aLine) {
+ reverse_iterator rv;
+ rv.mCurrent = aLine;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ const_reverse_iterator rend() const {
+ const_reverse_iterator rv;
+ rv.mCurrent = &mLink;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ reverse_iterator rend() {
+ reverse_iterator rv;
+ rv.mCurrent = &mLink;
+#ifdef DEBUG
+ rv.mListLink = &mLink;
+#endif
+ return rv;
+ }
+
+ bool empty() const { return mLink._mNext == &mLink; }
+
+ // NOTE: O(N).
+ size_type size() const {
+ size_type count = 0;
+ for (const link_type* cur = mLink._mNext; cur != &mLink;
+ cur = cur->_mNext) {
+ ++count;
+ }
+ return count;
+ }
+
+ pointer front() {
+ NS_ASSERTION(!empty(), "no element to return");
+ return static_cast<pointer>(mLink._mNext);
+ }
+
+ const_pointer front() const {
+ NS_ASSERTION(!empty(), "no element to return");
+ return static_cast<const_pointer>(mLink._mNext);
+ }
+
+ pointer back() {
+ NS_ASSERTION(!empty(), "no element to return");
+ return static_cast<pointer>(mLink._mPrev);
+ }
+
+ const_pointer back() const {
+ NS_ASSERTION(!empty(), "no element to return");
+ return static_cast<const_pointer>(mLink._mPrev);
+ }
+
+ void push_front(pointer aNew) {
+ aNew->_mNext = mLink._mNext;
+ mLink._mNext->_mPrev = aNew;
+ aNew->_mPrev = &mLink;
+ mLink._mNext = aNew;
+ }
+
+ void pop_front()
+ // NOTE: leaves dangling next/prev pointers
+ {
+ NS_ASSERTION(!empty(), "no element to pop");
+ link_type* newFirst = mLink._mNext->_mNext;
+ newFirst->_mPrev = &mLink;
+ // mLink._mNext->_mNext = nullptr;
+ // mLink._mNext->_mPrev = nullptr;
+ mLink._mNext = newFirst;
+ }
+
+ void push_back(pointer aNew) {
+ aNew->_mPrev = mLink._mPrev;
+ mLink._mPrev->_mNext = aNew;
+ aNew->_mNext = &mLink;
+ mLink._mPrev = aNew;
+ }
+
+ void pop_back()
+ // NOTE: leaves dangling next/prev pointers
+ {
+ NS_ASSERTION(!empty(), "no element to pop");
+ link_type* newLast = mLink._mPrev->_mPrev;
+ newLast->_mNext = &mLink;
+ // mLink._mPrev->_mPrev = nullptr;
+ // mLink._mPrev->_mNext = nullptr;
+ mLink._mPrev = newLast;
+ }
+
+ // inserts x before position
+ iterator before_insert(iterator position, pointer x) {
+ // use |mCurrent| to prevent DEBUG_PASS_END assertions
+ x->_mPrev = position.mCurrent->_mPrev;
+ x->_mNext = position.mCurrent;
+ position.mCurrent->_mPrev->_mNext = x;
+ position.mCurrent->_mPrev = x;
+ return --position;
+ }
+
+ // inserts x after position
+ iterator after_insert(iterator position, pointer x) {
+ // use |mCurrent| to prevent DEBUG_PASS_END assertions
+ x->_mNext = position.mCurrent->_mNext;
+ x->_mPrev = position.mCurrent;
+ position.mCurrent->_mNext->_mPrev = x;
+ position.mCurrent->_mNext = x;
+ return ++position;
+ }
+
+ // returns iterator pointing to after the element
+ iterator erase(iterator position)
+ // NOTE: leaves dangling next/prev pointers
+ {
+ position->_mPrev->_mNext = position->_mNext;
+ position->_mNext->_mPrev = position->_mPrev;
+ return ++position;
+ }
+
+ void swap(self_type& y) {
+ link_type tmp(y.mLink);
+ y.mLink = mLink;
+ mLink = tmp;
+
+ if (!empty()) {
+ mLink._mNext->_mPrev = &mLink;
+ mLink._mPrev->_mNext = &mLink;
+ }
+
+ if (!y.empty()) {
+ y.mLink._mNext->_mPrev = &y.mLink;
+ y.mLink._mPrev->_mNext = &y.mLink;
+ }
+ }
+
+ void clear()
+ // NOTE: leaves dangling next/prev pointers
+ {
+ mLink._mNext = &mLink;
+ mLink._mPrev = &mLink;
+ }
+
+ // inserts the conts of x before position and makes x empty
+ void splice(iterator position, self_type& x) {
+ // use |mCurrent| to prevent DEBUG_PASS_END assertions
+ position.mCurrent->_mPrev->_mNext = x.mLink._mNext;
+ x.mLink._mNext->_mPrev = position.mCurrent->_mPrev;
+ x.mLink._mPrev->_mNext = position.mCurrent;
+ position.mCurrent->_mPrev = x.mLink._mPrev;
+ x.clear();
+ }
+
+ // Inserts element *i from list x before position and removes
+ // it from x.
+ void splice(iterator position, self_type& x, iterator i) {
+ NS_ASSERTION(!x.empty(), "Can't insert from empty list.");
+ NS_ASSERTION(position != i && position.mCurrent != i->_mNext,
+ "We don't check for this case.");
+
+ // remove from |x|
+ i->_mPrev->_mNext = i->_mNext;
+ i->_mNext->_mPrev = i->_mPrev;
+
+ // use |mCurrent| to prevent DEBUG_PASS_END assertions
+ // link into |this|, before-side
+ i->_mPrev = position.mCurrent->_mPrev;
+ position.mCurrent->_mPrev->_mNext = i.get();
+
+ // link into |this|, after-side
+ i->_mNext = position.mCurrent;
+ position.mCurrent->_mPrev = i.get();
+ }
+
+ // Inserts elements in [|first|, |last|), which are in |x|,
+ // into |this| before |position| and removes them from |x|.
+ void splice(iterator position, self_type& x, iterator first, iterator last) {
+ NS_ASSERTION(!x.empty(), "Can't insert from empty list.");
+
+ if (first == last) return;
+
+ --last; // so we now want to move [first, last]
+ // remove from |x|
+ first->_mPrev->_mNext = last->_mNext;
+ last->_mNext->_mPrev = first->_mPrev;
+
+ // use |mCurrent| to prevent DEBUG_PASS_END assertions
+ // link into |this|, before-side
+ first->_mPrev = position.mCurrent->_mPrev;
+ position.mCurrent->_mPrev->_mNext = first.get();
+
+ // link into |this|, after-side
+ last->_mNext = position.mCurrent;
+ position.mCurrent->_mPrev = last.get();
+ }
+};
+
+// Many of these implementations of operator= don't work yet. I don't
+// know why.
+
+#ifdef DEBUG
+
+// NOTE: ASSIGN_FROM is meant to be used *only* as the entire body
+// of a function and therefore lacks PR_{BEGIN,END}_MACRO
+# define ASSIGN_FROM(other_) \
+ mCurrent = other_.mCurrent; \
+ mListLink = other_.mListLink; \
+ return *this;
+
+#else /* !NS_LINELIST_DEBUG_PASS_END */
+
+# define ASSIGN_FROM(other_) \
+ mCurrent = other_.mCurrent; \
+ return *this;
+
+#endif /* !NS_LINELIST_DEBUG_PASS_END */
+
+inline nsLineList_iterator& nsLineList_iterator::operator=(
+ const nsLineList_iterator& aOther) = default;
+
+inline nsLineList_iterator& nsLineList_iterator::operator=(
+ const nsLineList_reverse_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_reverse_iterator& nsLineList_reverse_iterator::operator=(
+ const nsLineList_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_reverse_iterator& nsLineList_reverse_iterator::operator=(
+ const nsLineList_reverse_iterator& aOther) = default;
+
+inline nsLineList_const_iterator& nsLineList_const_iterator::operator=(
+ const nsLineList_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_const_iterator& nsLineList_const_iterator::operator=(
+ const nsLineList_reverse_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_const_iterator& nsLineList_const_iterator::operator=(
+ const nsLineList_const_iterator& aOther) = default;
+
+inline nsLineList_const_iterator& nsLineList_const_iterator::operator=(
+ const nsLineList_const_reverse_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_const_reverse_iterator&
+nsLineList_const_reverse_iterator::operator=(
+ const nsLineList_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_const_reverse_iterator&
+nsLineList_const_reverse_iterator::operator=(
+ const nsLineList_reverse_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_const_reverse_iterator&
+nsLineList_const_reverse_iterator::operator=(
+ const nsLineList_const_iterator& aOther) {
+ ASSIGN_FROM(aOther)
+}
+
+inline nsLineList_const_reverse_iterator&
+nsLineList_const_reverse_iterator::operator=(
+ const nsLineList_const_reverse_iterator& aOther) = default;
+
+//----------------------------------------------------------------------
+
+class nsLineIterator final : public nsILineIterator {
+ public:
+ nsLineIterator(const nsLineList& aLines, bool aRightToLeft)
+ : mLines(aLines), mRightToLeft(aRightToLeft) {
+ mIter = mLines.begin();
+ if (mIter != mLines.end()) {
+ mIndex = 0;
+ }
+ }
+
+ int32_t GetNumLines() const final {
+ if (mNumLines < 0) {
+ mNumLines = int32_t(mLines.size()); // This is O(N) in number of lines!
+ }
+ return mNumLines;
+ }
+
+ bool IsLineIteratorFlowRTL() final { return mRightToLeft; }
+
+ // Note that this updates the iterator's current position!
+ mozilla::Result<LineInfo, nsresult> GetLine(int32_t aLineNumber) final;
+
+ int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) final;
+
+ NS_IMETHOD FindFrameAt(int32_t aLineNumber, nsPoint aPos,
+ nsIFrame** aFrameFound, bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) final;
+
+ NS_IMETHOD CheckLineOrder(int32_t aLine, bool* aIsReordered,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) final;
+
+ private:
+ nsLineIterator() = delete;
+ nsLineIterator(const nsLineIterator& aOther) = delete;
+
+ const nsLineBox* GetNextLine() {
+ MOZ_ASSERT(mIter != mLines.end(), "Already at end!");
+ ++mIndex;
+ ++mIter;
+ if (mIter == mLines.end()) {
+ MOZ_ASSERT(mNumLines < 0 || mNumLines == mIndex);
+ mNumLines = mIndex;
+ return nullptr;
+ }
+ return mIter.get();
+ }
+
+ // Note that this updates the iterator's current position to the given line.
+ const nsLineBox* GetLineAt(int32_t aIndex) {
+ MOZ_ASSERT(mIndex >= 0);
+ if (aIndex < 0 || (mNumLines >= 0 && aIndex >= mNumLines)) {
+ return nullptr;
+ }
+ // Check if we should start counting lines from mIndex, or reset to the
+ // start or end of the list and count from there (if the requested index is
+ // closer to an end than to the current position).
+ if (aIndex < mIndex / 2) {
+ // Reset to the beginning and search from there.
+ mIter = mLines.begin();
+ mIndex = 0;
+ } else if (mNumLines > 0 && aIndex > (mNumLines + mIndex) / 2) {
+ // Jump to the end and search back from there.
+ mIter = mLines.end();
+ --mIter;
+ mIndex = mNumLines - 1;
+ }
+ while (mIndex > aIndex) {
+ // This cannot run past the start of the list, because we checked that
+ // aIndex is non-negative.
+ --mIter;
+ --mIndex;
+ }
+ while (mIndex < aIndex) {
+ // Here we have to check for reaching the end, as aIndex could be out of
+ // range (if mNumLines was not initialized, so we couldn't range-check
+ // aIndex on entry).
+ if (mIter == mLines.end()) {
+ MOZ_ASSERT(mNumLines < 0 || mNumLines == mIndex);
+ mNumLines = mIndex;
+ return nullptr;
+ }
+ ++mIter;
+ ++mIndex;
+ }
+ return mIter.get();
+ }
+
+ const nsLineList& mLines;
+ nsLineList_const_iterator mIter;
+ int32_t mIndex = -1;
+ mutable int32_t mNumLines = -1;
+ const bool mRightToLeft;
+};
+
+#endif /* nsLineBox_h___ */
diff --git a/layout/generic/nsLineLayout.cpp b/layout/generic/nsLineLayout.cpp
new file mode 100644
index 0000000000..03aa6e87b3
--- /dev/null
+++ b/layout/generic/nsLineLayout.cpp
@@ -0,0 +1,3495 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* state and methods used while laying out a single line of a block frame */
+
+#include "nsLineLayout.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/SVGTextFrame.h"
+
+#include "LayoutLogging.h"
+#include "nsBlockFrame.h"
+#include "nsFontMetrics.h"
+#include "nsStyleConsts.h"
+#include "nsContainerFrame.h"
+#include "nsFloatManager.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsLayoutUtils.h"
+#include "nsTextFrame.h"
+#include "nsStyleStructInlines.h"
+#include "nsBidiPresUtils.h"
+#include "nsRubyFrame.h"
+#include "nsRubyTextFrame.h"
+#include "RubyUtils.h"
+#include <algorithm>
+
+#ifdef DEBUG
+# undef NOISY_INLINEDIR_ALIGN
+# undef NOISY_BLOCKDIR_ALIGN
+# undef NOISY_REFLOW
+# undef REALLY_NOISY_REFLOW
+# undef NOISY_PUSHING
+# undef REALLY_NOISY_PUSHING
+# undef NOISY_CAN_PLACE_FRAME
+# undef NOISY_TRIM
+# undef REALLY_NOISY_TRIM
+#endif
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+nsLineLayout::nsLineLayout(nsPresContext* aPresContext,
+ nsFloatManager* aFloatManager,
+ const ReflowInput& aLineContainerRI,
+ const nsLineList::iterator* aLine,
+ nsLineLayout* aBaseLineLayout)
+ : mPresContext(aPresContext),
+ mFloatManager(aFloatManager),
+ mLineContainerRI(aLineContainerRI),
+ mBaseLineLayout(aBaseLineLayout),
+ mLastOptionalBreakFrame(nullptr),
+ mForceBreakFrame(nullptr),
+ mLastOptionalBreakPriority(gfxBreakPriority::eNoBreak),
+ mLastOptionalBreakFrameOffset(-1),
+ mForceBreakFrameOffset(-1),
+ mMinLineBSize(0),
+ mTextIndent(0),
+ mMaxStartBoxBSize(0),
+ mMaxEndBoxBSize(0),
+ mFinalLineBSize(0),
+ mFirstLetterStyleOK(false),
+ mIsTopOfPage(false),
+ mImpactedByFloats(false),
+ mLastFloatWasLetterFrame(false),
+ mLineIsEmpty(false),
+ mLineEndsInBR(false),
+ mNeedBackup(false),
+ mInFirstLine(false),
+ mGotLineBox(false),
+ mInFirstLetter(false),
+ mHasMarker(false),
+ mDirtyNextLine(false),
+ mLineAtStart(false),
+ mHasRuby(false),
+ mSuppressLineWrap(LineContainerFrame()->IsInSVGTextSubtree()),
+ mUsedOverflowWrap(false)
+#ifdef DEBUG
+ ,
+ mSpansAllocated(0),
+ mSpansFreed(0),
+ mFramesAllocated(0),
+ mFramesFreed(0)
+#endif
+{
+ NS_ASSERTION(aFloatManager || LineContainerFrame()->IsLetterFrame(),
+ "float manager should be present");
+ MOZ_ASSERT(
+ !!mBaseLineLayout == LineContainerFrame()->IsRubyTextContainerFrame(),
+ "Only ruby text container frames have a different base line layout");
+ MOZ_COUNT_CTOR(nsLineLayout);
+
+ // Stash away some style data that we need
+ nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame());
+ mStyleText = blockFrame ? blockFrame->StyleTextForLineLayout()
+ : LineContainerFrame()->StyleText();
+
+ mLineNumber = 0;
+ mTotalPlacedFrames = 0;
+ mBStartEdge = 0;
+ mTrimmableISize = 0;
+
+ mInflationMinFontSize =
+ nsLayoutUtils::InflationMinFontSizeFor(LineContainerFrame());
+
+ // Instead of always pre-initializing the free-lists for frames and
+ // spans, we do it on demand so that situations that only use a few
+ // frames and spans won't waste a lot of time in unneeded
+ // initialization.
+ mFrameFreeList = nullptr;
+ mSpanFreeList = nullptr;
+
+ mCurrentSpan = mRootSpan = nullptr;
+ mSpanDepth = 0;
+
+ if (aLine) {
+ mGotLineBox = true;
+ mLineBox = *aLine;
+ }
+}
+
+nsLineLayout::~nsLineLayout() {
+ MOZ_COUNT_DTOR(nsLineLayout);
+
+ NS_ASSERTION(nullptr == mRootSpan, "bad line-layout user");
+}
+
+// Find out if the frame has a non-null prev-in-flow, i.e., whether it
+// is a continuation.
+inline bool HasPrevInFlow(nsIFrame* aFrame) {
+ nsIFrame* prevInFlow = aFrame->GetPrevInFlow();
+ return prevInFlow != nullptr;
+}
+
+void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
+ nscoord aISize, nscoord aBSize,
+ bool aImpactedByFloats, bool aIsTopOfPage,
+ WritingMode aWritingMode,
+ const nsSize& aContainerSize,
+ nscoord aInset) {
+ NS_ASSERTION(nullptr == mRootSpan, "bad linelayout user");
+ LAYOUT_WARN_IF_FALSE(aISize != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained width; this should only result from "
+ "very large sizes, not attempts at intrinsic width "
+ "calculation");
+#ifdef DEBUG
+ if ((aISize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aISize) &&
+ !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ LineContainerFrame()->ListTag(stdout);
+ printf(": Init: bad caller: width WAS %d(0x%x)\n", aISize, aISize);
+ }
+ if ((aBSize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aBSize) &&
+ !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ LineContainerFrame()->ListTag(stdout);
+ printf(": Init: bad caller: height WAS %d(0x%x)\n", aBSize, aBSize);
+ }
+#endif
+#ifdef NOISY_REFLOW
+ LineContainerFrame()->ListTag(stdout);
+ printf(": BeginLineReflow: %d,%d,%d,%d impacted=%s %s\n", aICoord, aBCoord,
+ aISize, aBSize, aImpactedByFloats ? "true" : "false",
+ aIsTopOfPage ? "top-of-page" : "");
+#endif
+#ifdef DEBUG
+ mSpansAllocated = mSpansFreed = mFramesAllocated = mFramesFreed = 0;
+#endif
+
+ mFirstLetterStyleOK = false;
+ mIsTopOfPage = aIsTopOfPage;
+ mImpactedByFloats = aImpactedByFloats;
+ mTotalPlacedFrames = 0;
+ if (!mBaseLineLayout) {
+ mLineIsEmpty = true;
+ mLineAtStart = true;
+ } else {
+ mLineIsEmpty = false;
+ mLineAtStart = false;
+ }
+ mLineEndsInBR = false;
+ mSpanDepth = 0;
+ mMaxStartBoxBSize = mMaxEndBoxBSize = 0;
+
+ if (mGotLineBox) {
+ mLineBox->ClearHasMarker();
+ }
+
+ PerSpanData* psd = NewPerSpanData();
+ mCurrentSpan = mRootSpan = psd;
+ psd->mReflowInput = &mLineContainerRI;
+ psd->mIStart = aICoord;
+ psd->mICoord = aICoord;
+ psd->mIEnd = aICoord + aISize;
+ // Set up inset to be used for text-wrap:balance implementation, but only if
+ // the available size is greater than inset.
+ psd->mInset = aISize > aInset ? aInset : 0;
+ mContainerSize = aContainerSize;
+
+ mBStartEdge = aBCoord;
+
+ psd->mNoWrap = !mStyleText->WhiteSpaceCanWrapStyle() || mSuppressLineWrap;
+ psd->mWritingMode = aWritingMode;
+
+ // Determine if this is the first line of the block (or first after a hard
+ // line-break, if `each-line` is in effect).
+ nsIFrame* containerFrame = LineContainerFrame();
+ if (!containerFrame->IsRubyTextContainerFrame()) {
+ bool isFirstLineOrAfterHardBreak = [&] {
+ if (mLineNumber > 0) {
+ return mStyleText->mTextIndent.each_line && GetLine() &&
+ !GetLine()->prev()->IsLineWrapped();
+ }
+ if (nsBlockFrame* prevBlock =
+ do_QueryFrame(containerFrame->GetPrevInFlow())) {
+ return mStyleText->mTextIndent.each_line &&
+ (prevBlock->Lines().empty() ||
+ !prevBlock->LinesEnd().prev()->IsLineWrapped());
+ }
+ return true;
+ }();
+
+ // Resolve and apply the text-indent value if this line requires it.
+ // The `hanging` option inverts which lines are to be indented.
+ if (isFirstLineOrAfterHardBreak != mStyleText->mTextIndent.hanging) {
+ nscoord pctBasis = mLineContainerRI.ComputedISize();
+ mTextIndent = mStyleText->mTextIndent.length.Resolve(pctBasis);
+ psd->mICoord += mTextIndent;
+ }
+ }
+
+ PerFrameData* pfd = NewPerFrameData(containerFrame);
+ pfd->mAscent = 0;
+ pfd->mSpan = psd;
+ psd->mFrame = pfd;
+ if (containerFrame->IsRubyTextContainerFrame()) {
+ // Ruby text container won't be reflowed via ReflowFrame, hence the
+ // relative positioning information should be recorded here.
+ MOZ_ASSERT(mBaseLineLayout != this);
+ pfd->mIsRelativelyOrStickyPos =
+ mLineContainerRI.mStyleDisplay->IsRelativelyOrStickyPositionedStyle();
+ if (pfd->mIsRelativelyOrStickyPos) {
+ MOZ_ASSERT(mLineContainerRI.GetWritingMode() == pfd->mWritingMode,
+ "mLineContainerRI.frame == frame, "
+ "hence they should have identical writing mode");
+ pfd->mOffsets =
+ mLineContainerRI.ComputedLogicalOffsets(pfd->mWritingMode);
+ }
+ }
+}
+
+bool nsLineLayout::EndLineReflow() {
+#ifdef NOISY_REFLOW
+ LineContainerFrame()->ListTag(stdout);
+ printf(": EndLineReflow: width=%d\n",
+ mRootSpan->mICoord - mRootSpan->mIStart);
+#endif
+
+ NS_ASSERTION(!mBaseLineLayout ||
+ (!mSpansAllocated && !mSpansFreed && !mSpanFreeList &&
+ !mFramesAllocated && !mFramesFreed && !mFrameFreeList),
+ "Allocated frames or spans on non-base line layout?");
+ MOZ_ASSERT(mRootSpan == mCurrentSpan);
+
+ UnlinkFrame(mRootSpan->mFrame);
+ mCurrentSpan = mRootSpan = nullptr;
+
+ NS_ASSERTION(mSpansAllocated == mSpansFreed, "leak");
+ NS_ASSERTION(mFramesAllocated == mFramesFreed, "leak");
+
+#if 0
+ static int32_t maxSpansAllocated = NS_LINELAYOUT_NUM_SPANS;
+ static int32_t maxFramesAllocated = NS_LINELAYOUT_NUM_FRAMES;
+ if (mSpansAllocated > maxSpansAllocated) {
+ printf("XXX: saw a line with %d spans\n", mSpansAllocated);
+ maxSpansAllocated = mSpansAllocated;
+ }
+ if (mFramesAllocated > maxFramesAllocated) {
+ printf("XXX: saw a line with %d frames\n", mFramesAllocated);
+ maxFramesAllocated = mFramesAllocated;
+ }
+#endif
+
+ return mUsedOverflowWrap;
+}
+
+// XXX swtich to a single mAvailLineWidth that we adjust as each frame
+// on the line is placed. Each span can still have a per-span mICoord that
+// tracks where a child frame is going in its span; they don't need a
+// per-span mIStart?
+
+void nsLineLayout::UpdateBand(WritingMode aWM,
+ const LogicalRect& aNewAvailSpace,
+ nsIFrame* aFloatFrame) {
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ // need to convert to our writing mode, because we might have a different
+ // mode from the caller due to dir: auto
+ LogicalRect availSpace =
+ aNewAvailSpace.ConvertTo(lineWM, aWM, ContainerSize());
+#ifdef REALLY_NOISY_REFLOW
+ printf(
+ "nsLL::UpdateBand %d, %d, %d, %d, (converted to %d, %d, %d, %d); "
+ "frame=%p\n will set mImpacted to true\n",
+ aNewAvailSpace.IStart(aWM), aNewAvailSpace.BStart(aWM),
+ aNewAvailSpace.ISize(aWM), aNewAvailSpace.BSize(aWM),
+ availSpace.IStart(lineWM), availSpace.BStart(lineWM),
+ availSpace.ISize(lineWM), availSpace.BSize(lineWM), aFloatFrame);
+#endif
+#ifdef DEBUG
+ if ((availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
+ ABSURD_SIZE(availSpace.ISize(lineWM)) &&
+ !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ LineContainerFrame()->ListTag(stdout);
+ printf(": UpdateBand: bad caller: ISize WAS %d(0x%x)\n",
+ availSpace.ISize(lineWM), availSpace.ISize(lineWM));
+ }
+ if ((availSpace.BSize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
+ ABSURD_SIZE(availSpace.BSize(lineWM)) &&
+ !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ LineContainerFrame()->ListTag(stdout);
+ printf(": UpdateBand: bad caller: BSize WAS %d(0x%x)\n",
+ availSpace.BSize(lineWM), availSpace.BSize(lineWM));
+ }
+#endif
+
+ // Compute the difference between last times width and the new width
+ NS_WARNING_ASSERTION(
+ mRootSpan->mIEnd != NS_UNCONSTRAINEDSIZE &&
+ availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained inline size; this should only result from very large "
+ "sizes, not attempts at intrinsic width calculation");
+ // The root span's mIStart moves to aICoord
+ nscoord deltaICoord = availSpace.IStart(lineWM) - mRootSpan->mIStart;
+ // The inline size of all spans changes by this much (the root span's
+ // mIEnd moves to aICoord + aISize, its new inline size is aISize)
+ nscoord deltaISize =
+ availSpace.ISize(lineWM) - (mRootSpan->mIEnd - mRootSpan->mIStart);
+#ifdef NOISY_REFLOW
+ LineContainerFrame()->ListTag(stdout);
+ printf(": UpdateBand: %d,%d,%d,%d deltaISize=%d deltaICoord=%d\n",
+ availSpace.IStart(lineWM), availSpace.BStart(lineWM),
+ availSpace.ISize(lineWM), availSpace.BSize(lineWM), deltaISize,
+ deltaICoord);
+#endif
+
+ // Update the root span position
+ mRootSpan->mIStart += deltaICoord;
+ mRootSpan->mIEnd += deltaICoord;
+ mRootSpan->mICoord += deltaICoord;
+
+ // Now update the right edges of the open spans to account for any
+ // change in available space width
+ for (PerSpanData* psd = mCurrentSpan; psd; psd = psd->mParent) {
+ psd->mIEnd += deltaISize;
+ psd->mContainsFloat = true;
+#ifdef NOISY_REFLOW
+ printf(" span %p: oldIEnd=%d newIEnd=%d\n", psd, psd->mIEnd - deltaISize,
+ psd->mIEnd);
+#endif
+ }
+ NS_ASSERTION(mRootSpan->mContainsFloat &&
+ mRootSpan->mIStart == availSpace.IStart(lineWM) &&
+ mRootSpan->mIEnd == availSpace.IEnd(lineWM),
+ "root span was updated incorrectly?");
+
+ // Update frame bounds
+ // Note: Only adjust the outermost frames (the ones that are direct
+ // children of the block), not the ones in the child spans. The reason
+ // is simple: the frames in the spans have coordinates local to their
+ // parent therefore they are moved when their parent span is moved.
+ if (deltaICoord != 0) {
+ for (PerFrameData* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
+ pfd->mBounds.IStart(lineWM) += deltaICoord;
+ }
+ }
+
+ mBStartEdge = availSpace.BStart(lineWM);
+ mImpactedByFloats = true;
+
+ mLastFloatWasLetterFrame = aFloatFrame->IsLetterFrame();
+}
+
+nsLineLayout::PerSpanData* nsLineLayout::NewPerSpanData() {
+ nsLineLayout* outerLineLayout = GetOutermostLineLayout();
+ PerSpanData* psd = outerLineLayout->mSpanFreeList;
+ if (!psd) {
+ void* mem = outerLineLayout->mArena.Allocate(sizeof(PerSpanData));
+ psd = reinterpret_cast<PerSpanData*>(mem);
+ } else {
+ outerLineLayout->mSpanFreeList = psd->mNextFreeSpan;
+ }
+ psd->mParent = nullptr;
+ psd->mFrame = nullptr;
+ psd->mFirstFrame = nullptr;
+ psd->mLastFrame = nullptr;
+ psd->mContainsFloat = false;
+ psd->mHasNonemptyContent = false;
+
+#ifdef DEBUG
+ outerLineLayout->mSpansAllocated++;
+#endif
+ return psd;
+}
+
+void nsLineLayout::BeginSpan(nsIFrame* aFrame,
+ const ReflowInput* aSpanReflowInput,
+ nscoord aIStart, nscoord aIEnd,
+ nscoord* aBaseline) {
+ NS_ASSERTION(aIEnd != NS_UNCONSTRAINEDSIZE,
+ "should no longer be using unconstrained sizes");
+#ifdef NOISY_REFLOW
+ nsIFrame::IndentBy(stdout, mSpanDepth + 1);
+ aFrame->ListTag(stdout);
+ printf(": BeginSpan leftEdge=%d rightEdge=%d\n", aIStart, aIEnd);
+#endif
+
+ PerSpanData* psd = NewPerSpanData();
+ // Link up span frame's pfd to point to its child span data
+ PerFrameData* pfd = mCurrentSpan->mLastFrame;
+ NS_ASSERTION(pfd->mFrame == aFrame, "huh?");
+ pfd->mSpan = psd;
+
+ // Init new span
+ psd->mFrame = pfd;
+ psd->mParent = mCurrentSpan;
+ psd->mReflowInput = aSpanReflowInput;
+ psd->mIStart = aIStart;
+ psd->mICoord = aIStart;
+ psd->mIEnd = aIEnd;
+ psd->mInset = mCurrentSpan->mInset;
+ psd->mBaseline = aBaseline;
+
+ nsIFrame* frame = aSpanReflowInput->mFrame;
+ psd->mNoWrap = !frame->StyleText()->WhiteSpaceCanWrap(frame) ||
+ mSuppressLineWrap || frame->Style()->ShouldSuppressLineBreak();
+ psd->mWritingMode = aSpanReflowInput->GetWritingMode();
+
+ // Switch to new span
+ mCurrentSpan = psd;
+ mSpanDepth++;
+}
+
+nscoord nsLineLayout::EndSpan(nsIFrame* aFrame) {
+ NS_ASSERTION(mSpanDepth > 0, "end-span without begin-span");
+#ifdef NOISY_REFLOW
+ nsIFrame::IndentBy(stdout, mSpanDepth);
+ aFrame->ListTag(stdout);
+ printf(": EndSpan width=%d\n", mCurrentSpan->mICoord - mCurrentSpan->mIStart);
+#endif
+ PerSpanData* psd = mCurrentSpan;
+ MOZ_ASSERT(psd->mParent, "We never call this on the root");
+
+ if (psd->mNoWrap && !psd->mParent->mNoWrap) {
+ FlushNoWrapFloats();
+ }
+
+ nscoord iSizeResult = psd->mLastFrame ? (psd->mICoord - psd->mIStart) : 0;
+
+ mSpanDepth--;
+ mCurrentSpan->mReflowInput = nullptr; // no longer valid so null it out!
+ mCurrentSpan = mCurrentSpan->mParent;
+ return iSizeResult;
+}
+
+void nsLineLayout::AttachFrameToBaseLineLayout(PerFrameData* aFrame) {
+ MOZ_ASSERT(mBaseLineLayout,
+ "This method must not be called in a base line layout.");
+
+ PerFrameData* baseFrame = mBaseLineLayout->LastFrame();
+ MOZ_ASSERT(aFrame && baseFrame);
+ MOZ_ASSERT(!aFrame->mIsLinkedToBase,
+ "The frame must not have been linked with the base");
+#ifdef DEBUG
+ LayoutFrameType baseType = baseFrame->mFrame->Type();
+ LayoutFrameType annotationType = aFrame->mFrame->Type();
+ MOZ_ASSERT((baseType == LayoutFrameType::RubyBaseContainer &&
+ annotationType == LayoutFrameType::RubyTextContainer) ||
+ (baseType == LayoutFrameType::RubyBase &&
+ annotationType == LayoutFrameType::RubyText));
+#endif
+
+ aFrame->mNextAnnotation = baseFrame->mNextAnnotation;
+ baseFrame->mNextAnnotation = aFrame;
+ aFrame->mIsLinkedToBase = true;
+}
+
+int32_t nsLineLayout::GetCurrentSpanCount() const {
+ NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
+ int32_t count = 0;
+ PerFrameData* pfd = mRootSpan->mFirstFrame;
+ while (nullptr != pfd) {
+ count++;
+ pfd = pfd->mNext;
+ }
+ return count;
+}
+
+void nsLineLayout::SplitLineTo(int32_t aNewCount) {
+ NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
+
+#ifdef REALLY_NOISY_PUSHING
+ printf("SplitLineTo %d (current count=%d); before:\n", aNewCount,
+ GetCurrentSpanCount());
+ DumpPerSpanData(mRootSpan, 1);
+#endif
+ PerSpanData* psd = mRootSpan;
+ PerFrameData* pfd = psd->mFirstFrame;
+ while (nullptr != pfd) {
+ if (--aNewCount == 0) {
+ // Truncate list at pfd (we keep pfd, but anything following is freed)
+ PerFrameData* next = pfd->mNext;
+ pfd->mNext = nullptr;
+ psd->mLastFrame = pfd;
+
+ // Now unlink all of the frames following pfd
+ UnlinkFrame(next);
+ break;
+ }
+ pfd = pfd->mNext;
+ }
+#ifdef NOISY_PUSHING
+ printf("SplitLineTo %d (current count=%d); after:\n", aNewCount,
+ GetCurrentSpanCount());
+ DumpPerSpanData(mRootSpan, 1);
+#endif
+}
+
+void nsLineLayout::PushFrame(nsIFrame* aFrame) {
+ PerSpanData* psd = mCurrentSpan;
+ NS_ASSERTION(psd->mLastFrame->mFrame == aFrame, "pushing non-last frame");
+
+#ifdef REALLY_NOISY_PUSHING
+ nsIFrame::IndentBy(stdout, mSpanDepth);
+ printf("PushFrame %p, before:\n", psd);
+ DumpPerSpanData(psd, 1);
+#endif
+
+ // Take the last frame off of the span's frame list
+ PerFrameData* pfd = psd->mLastFrame;
+ if (pfd == psd->mFirstFrame) {
+ // We are pushing away the only frame...empty the list
+ psd->mFirstFrame = nullptr;
+ psd->mLastFrame = nullptr;
+ } else {
+ PerFrameData* prevFrame = pfd->mPrev;
+ prevFrame->mNext = nullptr;
+ psd->mLastFrame = prevFrame;
+ }
+
+ // Now unlink the frame
+ MOZ_ASSERT(!pfd->mNext);
+ UnlinkFrame(pfd);
+#ifdef NOISY_PUSHING
+ nsIFrame::IndentBy(stdout, mSpanDepth);
+ printf("PushFrame: %p after:\n", psd);
+ DumpPerSpanData(psd, 1);
+#endif
+}
+
+void nsLineLayout::UnlinkFrame(PerFrameData* pfd) {
+ while (nullptr != pfd) {
+ PerFrameData* next = pfd->mNext;
+ if (pfd->mIsLinkedToBase) {
+ // This frame is linked to a ruby base, and should not be freed
+ // now. Just unlink it from the span. It will be freed when its
+ // base frame gets unlinked.
+ pfd->mNext = pfd->mPrev = nullptr;
+ pfd = next;
+ continue;
+ }
+
+ // It is a ruby base frame. If there are any annotations
+ // linked to this frame, free them first.
+ PerFrameData* annotationPFD = pfd->mNextAnnotation;
+ while (annotationPFD) {
+ PerFrameData* nextAnnotation = annotationPFD->mNextAnnotation;
+ MOZ_ASSERT(
+ annotationPFD->mNext == nullptr && annotationPFD->mPrev == nullptr,
+ "PFD in annotations should have been unlinked.");
+ FreeFrame(annotationPFD);
+ annotationPFD = nextAnnotation;
+ }
+
+ FreeFrame(pfd);
+ pfd = next;
+ }
+}
+
+void nsLineLayout::FreeFrame(PerFrameData* pfd) {
+ if (nullptr != pfd->mSpan) {
+ FreeSpan(pfd->mSpan);
+ }
+ nsLineLayout* outerLineLayout = GetOutermostLineLayout();
+ pfd->mNext = outerLineLayout->mFrameFreeList;
+ outerLineLayout->mFrameFreeList = pfd;
+#ifdef DEBUG
+ outerLineLayout->mFramesFreed++;
+#endif
+}
+
+void nsLineLayout::FreeSpan(PerSpanData* psd) {
+ // Unlink its frames
+ UnlinkFrame(psd->mFirstFrame);
+
+ nsLineLayout* outerLineLayout = GetOutermostLineLayout();
+ // Now put the span on the free list since it's free too
+ psd->mNextFreeSpan = outerLineLayout->mSpanFreeList;
+ outerLineLayout->mSpanFreeList = psd;
+#ifdef DEBUG
+ outerLineLayout->mSpansFreed++;
+#endif
+}
+
+bool nsLineLayout::IsZeroBSize() {
+ PerSpanData* psd = mCurrentSpan;
+ PerFrameData* pfd = psd->mFirstFrame;
+ while (nullptr != pfd) {
+ if (0 != pfd->mBounds.BSize(psd->mWritingMode)) {
+ return false;
+ }
+ pfd = pfd->mNext;
+ }
+ return true;
+}
+
+nsLineLayout::PerFrameData* nsLineLayout::NewPerFrameData(nsIFrame* aFrame) {
+ nsLineLayout* outerLineLayout = GetOutermostLineLayout();
+ PerFrameData* pfd = outerLineLayout->mFrameFreeList;
+ if (!pfd) {
+ void* mem = outerLineLayout->mArena.Allocate(sizeof(PerFrameData));
+ pfd = reinterpret_cast<PerFrameData*>(mem);
+ } else {
+ outerLineLayout->mFrameFreeList = pfd->mNext;
+ }
+ pfd->mSpan = nullptr;
+ pfd->mNext = nullptr;
+ pfd->mPrev = nullptr;
+ pfd->mNextAnnotation = nullptr;
+ pfd->mFrame = aFrame;
+
+ // all flags default to false
+ pfd->mIsRelativelyOrStickyPos = false;
+ pfd->mIsTextFrame = false;
+ pfd->mIsNonEmptyTextFrame = false;
+ pfd->mIsNonWhitespaceTextFrame = false;
+ pfd->mIsLetterFrame = false;
+ pfd->mRecomputeOverflow = false;
+ pfd->mIsMarker = false;
+ pfd->mSkipWhenTrimmingWhitespace = false;
+ pfd->mIsEmpty = false;
+ pfd->mIsPlaceholder = false;
+ pfd->mIsLinkedToBase = false;
+
+ pfd->mWritingMode = aFrame->GetWritingMode();
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ pfd->mBounds = LogicalRect(lineWM);
+ pfd->mOverflowAreas.Clear();
+ pfd->mMargin = LogicalMargin(lineWM);
+ pfd->mBorderPadding = LogicalMargin(lineWM);
+ pfd->mOffsets = LogicalMargin(pfd->mWritingMode);
+
+ pfd->mJustificationInfo = JustificationInfo();
+ pfd->mJustificationAssignment = JustificationAssignment();
+
+#ifdef DEBUG
+ pfd->mBlockDirAlign = 0xFF;
+ outerLineLayout->mFramesAllocated++;
+#endif
+ return pfd;
+}
+
+bool nsLineLayout::LineIsBreakable() const {
+ // XXX mTotalPlacedFrames should go away and we should just use
+ // mLineIsEmpty here instead
+ if ((0 != mTotalPlacedFrames) || mImpactedByFloats) {
+ return true;
+ }
+ return false;
+}
+
+// Checks all four sides for percentage units. This means it should
+// only be used for things (margin, padding) where percentages on top
+// and bottom depend on the *width* just like percentages on left and
+// right.
+template <typename T>
+static bool HasPercentageUnitSide(const StyleRect<T>& aSides) {
+ return aSides.Any([](const auto& aLength) { return aLength.HasPercent(); });
+}
+
+static bool IsPercentageAware(const nsIFrame* aFrame, WritingMode aWM) {
+ NS_ASSERTION(aFrame, "null frame is not allowed");
+
+ LayoutFrameType fType = aFrame->Type();
+ if (fType == LayoutFrameType::Text) {
+ // None of these things can ever be true for text frames.
+ return false;
+ }
+
+ // Some of these things don't apply to non-replaced inline frames
+ // (that is, fType == LayoutFrameType::Inline), but we won't bother making
+ // things unnecessarily complicated, since they'll probably be set
+ // quite rarely.
+
+ const nsStyleMargin* margin = aFrame->StyleMargin();
+ if (HasPercentageUnitSide(margin->mMargin)) {
+ return true;
+ }
+
+ const nsStylePadding* padding = aFrame->StylePadding();
+ if (HasPercentageUnitSide(padding->mPadding)) {
+ return true;
+ }
+
+ // Note that borders can't be aware of percentages
+
+ const nsStylePosition* pos = aFrame->StylePosition();
+
+ if ((pos->ISizeDependsOnContainer(aWM) && !pos->ISize(aWM).IsAuto()) ||
+ pos->MaxISizeDependsOnContainer(aWM) ||
+ pos->MinISizeDependsOnContainer(aWM) ||
+ pos->mOffset.GetIStart(aWM).HasPercent() ||
+ pos->mOffset.GetIEnd(aWM).HasPercent()) {
+ return true;
+ }
+
+ if (pos->ISize(aWM).IsAuto()) {
+ // We need to check for frames that shrink-wrap when they're auto
+ // width.
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ if ((disp->DisplayOutside() == StyleDisplayOutside::Inline &&
+ (disp->DisplayInside() == StyleDisplayInside::FlowRoot ||
+ disp->DisplayInside() == StyleDisplayInside::Table)) ||
+ fType == LayoutFrameType::HTMLButtonControl ||
+ fType == LayoutFrameType::GfxButtonControl ||
+ fType == LayoutFrameType::FieldSet ||
+ fType == LayoutFrameType::ComboboxDisplay) {
+ return true;
+ }
+
+ // Per CSS 2.1, section 10.3.2:
+ // If 'height' and 'width' both have computed values of 'auto' and
+ // the element has an intrinsic ratio but no intrinsic height or
+ // width and the containing block's width does not itself depend
+ // on the replaced element's width, then the used value of 'width'
+ // is calculated from the constraint equation used for
+ // block-level, non-replaced elements in normal flow.
+ nsIFrame* f = const_cast<nsIFrame*>(aFrame);
+ if (f->GetAspectRatio() &&
+ // Some percents are treated like 'auto', so check != coord
+ !pos->BSize(aWM).ConvertsToLength()) {
+ const IntrinsicSize& intrinsicSize = f->GetIntrinsicSize();
+ if (!intrinsicSize.width && !intrinsicSize.height) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void nsLineLayout::ReflowFrame(nsIFrame* aFrame, nsReflowStatus& aReflowStatus,
+ ReflowOutput* aMetrics, bool& aPushedFrame) {
+ // Initialize OUT parameter
+ aPushedFrame = false;
+
+ PerFrameData* pfd = NewPerFrameData(aFrame);
+ PerSpanData* psd = mCurrentSpan;
+ psd->AppendFrame(pfd);
+
+#ifdef REALLY_NOISY_REFLOW
+ nsIFrame::IndentBy(stdout, mSpanDepth);
+ printf("%p: Begin ReflowFrame pfd=%p ", psd, pfd);
+ aFrame->ListTag(stdout);
+ printf("\n");
+#endif
+
+ if (mCurrentSpan == mRootSpan) {
+ pfd->mFrame->RemoveProperty(nsIFrame::LineBaselineOffset());
+ } else {
+#ifdef DEBUG
+ bool hasLineOffset;
+ pfd->mFrame->GetProperty(nsIFrame::LineBaselineOffset(), &hasLineOffset);
+ NS_ASSERTION(!hasLineOffset,
+ "LineBaselineOffset was set but was not expected");
+#endif
+ }
+
+ mJustificationInfo = JustificationInfo();
+
+ // Stash copies of some of the computed state away for later
+ // (block-direction alignment, for example)
+ WritingMode frameWM = pfd->mWritingMode;
+ WritingMode lineWM = mRootSpan->mWritingMode;
+
+ // NOTE: While the inline direction coordinate remains relative to the
+ // parent span, the block direction coordinate is fixed at the top
+ // edge for the line. During VerticalAlignFrames we will repair this
+ // so that the block direction coordinate is properly set and relative
+ // to the appropriate span.
+ pfd->mBounds.IStart(lineWM) = psd->mICoord;
+ pfd->mBounds.BStart(lineWM) = mBStartEdge;
+
+ // We want to guarantee that we always make progress when
+ // formatting. Therefore, if the object being placed on the line is
+ // too big for the line, but it is the only thing on the line and is not
+ // impacted by a float, then we go ahead and place it anyway. (If the line
+ // is impacted by one or more floats, then it is safe to break because
+ // we can move the line down below float(s).)
+ //
+ // Capture this state *before* we reflow the frame in case it clears
+ // the state out. We need to know how to treat the current frame
+ // when breaking.
+ bool notSafeToBreak = LineIsEmpty() && !mImpactedByFloats;
+
+ // Figure out whether we're talking about a textframe here
+ LayoutFrameType frameType = aFrame->Type();
+ const bool isText = frameType == LayoutFrameType::Text;
+
+ // Inline-ish and text-ish things don't compute their width;
+ // everything else does. We need to give them an available width that
+ // reflects the space left on the line.
+ LAYOUT_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained width; this should only result from "
+ "very large sizes, not attempts at intrinsic width "
+ "calculation");
+ nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord - psd->mInset;
+
+ // Setup reflow input for reflowing the frame
+ Maybe<ReflowInput> reflowInputHolder;
+ if (!isText) {
+ // Compute the available size for the frame. This available width
+ // includes room for the side margins.
+ // For now, set the available block-size to unconstrained always.
+ LogicalSize availSize = mLineContainerRI.ComputedSize(frameWM);
+ availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
+ reflowInputHolder.emplace(mPresContext, *psd->mReflowInput, aFrame,
+ availSize);
+ ReflowInput& reflowInput = *reflowInputHolder;
+ reflowInput.mLineLayout = this;
+ reflowInput.mFlags.mIsTopOfPage = mIsTopOfPage;
+ if (reflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE) {
+ reflowInput.SetAvailableISize(availableSpaceOnLine);
+ }
+ pfd->mMargin = reflowInput.ComputedLogicalMargin(lineWM);
+ pfd->mBorderPadding = reflowInput.ComputedLogicalBorderPadding(lineWM);
+ pfd->mIsRelativelyOrStickyPos =
+ reflowInput.mStyleDisplay->IsRelativelyOrStickyPositionedStyle();
+ if (pfd->mIsRelativelyOrStickyPos) {
+ pfd->mOffsets = reflowInput.ComputedLogicalOffsets(frameWM);
+ }
+
+ // Calculate whether the the frame should have a start margin and
+ // subtract the margin from the available width if necessary.
+ // The margin will be applied to the starting inline coordinates of
+ // the frame in CanPlaceFrame() after reflowing the frame.
+ AllowForStartMargin(pfd, reflowInput);
+ }
+ // if isText(), no need to propagate NS_FRAME_IS_DIRTY from the parent,
+ // because reflow doesn't look at the dirty bits on the frame being reflowed.
+
+ // See if this frame depends on the inline-size of its containing block.
+ // If so, disable resize reflow optimizations for the line. (Note that,
+ // to be conservative, we do this if we *try* to fit a frame on a
+ // line, even if we don't succeed.) (Note also that we can only make
+ // this IsPercentageAware check *after* we've constructed our
+ // ReflowInput, because that construction may be what forces aFrame
+ // to lazily initialize its (possibly-percent-valued) intrinsic size.)
+ if (mGotLineBox && IsPercentageAware(aFrame, lineWM)) {
+ mLineBox->DisableResizeReflowOptimization();
+ }
+
+ // Note that we don't bother positioning the frame yet, because we're probably
+ // going to end up moving it when we do the block-direction alignment.
+
+ // Adjust float manager coordinate system for the frame.
+ ReflowOutput reflowOutput(lineWM);
+#ifdef DEBUG
+ reflowOutput.ISize(lineWM) = nscoord(0xdeadbeef);
+ reflowOutput.BSize(lineWM) = nscoord(0xdeadbeef);
+#endif
+ nscoord tI = pfd->mBounds.LineLeft(lineWM, ContainerSize());
+ nscoord tB = pfd->mBounds.BStart(lineWM);
+ mFloatManager->Translate(tI, tB);
+
+ int32_t savedOptionalBreakOffset;
+ gfxBreakPriority savedOptionalBreakPriority;
+ nsIFrame* savedOptionalBreakFrame = GetLastOptionalBreakPosition(
+ &savedOptionalBreakOffset, &savedOptionalBreakPriority);
+
+ if (!isText) {
+ aFrame->Reflow(mPresContext, reflowOutput, *reflowInputHolder,
+ aReflowStatus);
+ } else {
+ static_cast<nsTextFrame*>(aFrame)->ReflowText(
+ *this, availableSpaceOnLine,
+ psd->mReflowInput->mRenderingContext->GetDrawTarget(), reflowOutput,
+ aReflowStatus);
+ }
+
+ pfd->mJustificationInfo = mJustificationInfo;
+ mJustificationInfo = JustificationInfo();
+
+ // See if the frame is a placeholderFrame and if it is process
+ // the float. At the same time, check if the frame has any non-collapsed-away
+ // content.
+ bool placedFloat = false;
+ bool isEmpty;
+ if (frameType == LayoutFrameType::None) {
+ isEmpty = pfd->mFrame->IsEmpty();
+ } else if (LayoutFrameType::Placeholder == frameType) {
+ isEmpty = true;
+ pfd->mIsPlaceholder = true;
+ pfd->mSkipWhenTrimmingWhitespace = true;
+ nsIFrame* outOfFlowFrame = nsLayoutUtils::GetFloatFromPlaceholder(aFrame);
+ if (outOfFlowFrame) {
+ if (psd->mNoWrap &&
+ // We can always place floats in an empty line.
+ !LineIsEmpty() &&
+ // We always place floating letter frames. This kinda sucks. They'd
+ // usually fall into the LineIsEmpty() check anyway, except when
+ // there's something like a ::marker before or what not. We actually
+ // need to place them now, because they're pretty nasty and they
+ // create continuations that are in flow and not a kid of the
+ // previous continuation's parent. We don't want the deferred reflow
+ // of the letter frame to kill a continuation after we've stored it
+ // in the line layout data structures. See bug 1490281 to fix the
+ // underlying issue. When that's fixed this check should be removed.
+ !outOfFlowFrame->IsLetterFrame() &&
+ !GetOutermostLineLayout()->mBlockRS->mFlags.mCanHaveOverflowMarkers) {
+ // We'll do this at the next break opportunity.
+ RecordNoWrapFloat(outOfFlowFrame);
+ } else {
+ placedFloat = TryToPlaceFloat(outOfFlowFrame);
+ }
+ }
+ } else if (isText) {
+ // Note non-empty text-frames for inline frame compatibility hackery
+ pfd->mIsTextFrame = true;
+ auto* textFrame = static_cast<nsTextFrame*>(pfd->mFrame);
+ isEmpty = !textFrame->HasNoncollapsedCharacters();
+ if (!isEmpty) {
+ pfd->mIsNonEmptyTextFrame = true;
+ pfd->mIsNonWhitespaceTextFrame =
+ !textFrame->GetContent()->TextIsOnlyWhitespace();
+ }
+ } else if (LayoutFrameType::Br == frameType) {
+ pfd->mSkipWhenTrimmingWhitespace = true;
+ isEmpty = false;
+ } else {
+ if (LayoutFrameType::Letter == frameType) {
+ pfd->mIsLetterFrame = true;
+ }
+ if (pfd->mSpan) {
+ isEmpty = !pfd->mSpan->mHasNonemptyContent && pfd->mFrame->IsSelfEmpty();
+ } else {
+ isEmpty = pfd->mFrame->IsEmpty();
+ }
+ }
+ pfd->mIsEmpty = isEmpty;
+
+ mFloatManager->Translate(-tI, -tB);
+
+ NS_ASSERTION(reflowOutput.ISize(lineWM) >= 0, "bad inline size");
+ NS_ASSERTION(reflowOutput.BSize(lineWM) >= 0, "bad block size");
+ if (reflowOutput.ISize(lineWM) < 0) {
+ reflowOutput.ISize(lineWM) = 0;
+ }
+ if (reflowOutput.BSize(lineWM) < 0) {
+ reflowOutput.BSize(lineWM) = 0;
+ }
+
+#ifdef DEBUG
+ // Note: break-before means ignore the reflow metrics since the
+ // frame will be reflowed another time.
+ if (!aReflowStatus.IsInlineBreakBefore()) {
+ if ((ABSURD_SIZE(reflowOutput.ISize(lineWM)) ||
+ ABSURD_SIZE(reflowOutput.BSize(lineWM))) &&
+ !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
+ printf("nsLineLayout: ");
+ aFrame->ListTag(stdout);
+ printf(" metrics=%d,%d!\n", reflowOutput.Width(), reflowOutput.Height());
+ }
+ if ((reflowOutput.Width() == nscoord(0xdeadbeef)) ||
+ (reflowOutput.Height() == nscoord(0xdeadbeef))) {
+ printf("nsLineLayout: ");
+ aFrame->ListTag(stdout);
+ printf(" didn't set w/h %d,%d!\n", reflowOutput.Width(),
+ reflowOutput.Height());
+ }
+ }
+#endif
+
+ // Unlike with non-inline reflow, the overflow area here does *not*
+ // include the accumulation of the frame's bounds and its inline
+ // descendants' bounds. Nor does it include the outline area; it's
+ // just the union of the bounds of any absolute children. That is
+ // added in later by nsLineLayout::ReflowInlineFrames.
+ pfd->mOverflowAreas = reflowOutput.mOverflowAreas;
+
+ pfd->mBounds.ISize(lineWM) = reflowOutput.ISize(lineWM);
+ pfd->mBounds.BSize(lineWM) = reflowOutput.BSize(lineWM);
+
+ // Size the frame, but |RelativePositionFrames| will size the view.
+ aFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd));
+
+ // Tell the frame that we're done reflowing it
+ aFrame->DidReflow(mPresContext, isText ? nullptr : reflowInputHolder.ptr());
+
+ if (aMetrics) {
+ *aMetrics = reflowOutput;
+ }
+
+ if (!aReflowStatus.IsInlineBreakBefore()) {
+ // If frame is complete and has a next-in-flow, we need to delete
+ // them now. Do not do this when a break-before is signaled because
+ // the frame is going to get reflowed again (and may end up wanting
+ // a next-in-flow where it ends up).
+ if (aReflowStatus.IsComplete()) {
+ if (nsIFrame* kidNextInFlow = aFrame->GetNextInFlow()) {
+ // Remove all of the childs next-in-flows. Make sure that we ask
+ // the right parent to do the removal (it's possible that the
+ // parent is not this because we are executing pullup code)
+ FrameDestroyContext context(aFrame->PresShell());
+ kidNextInFlow->GetParent()->DeleteNextInFlowChild(context,
+ kidNextInFlow, true);
+ }
+ }
+
+ // Check whether this frame breaks up text runs. All frames break up text
+ // runs (hence return false here) except for text frames and inline
+ // containers.
+ bool continuingTextRun = aFrame->CanContinueTextRun();
+
+ // Clear any residual mTrimmableISize if this isn't a text frame
+ if (!continuingTextRun && !pfd->mSkipWhenTrimmingWhitespace) {
+ mTrimmableISize = 0;
+ }
+
+ // See if we can place the frame. If we can't fit it, then we
+ // return now.
+ bool optionalBreakAfterFits;
+ NS_ASSERTION(isText || !reflowInputHolder->mStyleDisplay->IsFloating(
+ reflowInputHolder->mFrame),
+ "How'd we get a floated inline frame? "
+ "The frame ctor should've dealt with this.");
+ if (CanPlaceFrame(pfd, notSafeToBreak, continuingTextRun,
+ savedOptionalBreakFrame != nullptr, reflowOutput,
+ aReflowStatus, &optionalBreakAfterFits)) {
+ if (!isEmpty) {
+ psd->mHasNonemptyContent = true;
+ mLineIsEmpty = false;
+ if (!pfd->mSpan) {
+ // nonempty leaf content has been placed
+ mLineAtStart = false;
+ }
+ if (LayoutFrameType::Ruby == frameType) {
+ mHasRuby = true;
+ SyncAnnotationBounds(pfd);
+ }
+ }
+
+ // Place the frame, updating aBounds with the final size and
+ // location. Then apply the bottom+right margins (as
+ // appropriate) to the frame.
+ PlaceFrame(pfd, reflowOutput);
+ PerSpanData* span = pfd->mSpan;
+ if (span) {
+ // The frame we just finished reflowing is an inline
+ // container. It needs its child frames aligned in the block direction,
+ // so do most of it now.
+ VerticalAlignFrames(span);
+ }
+
+ if (!continuingTextRun && !psd->mNoWrap) {
+ if (!LineIsEmpty() || placedFloat) {
+ // record soft break opportunity after this content that can't be
+ // part of a text run. This is not a text frame so we know
+ // that offset INT32_MAX means "after the content".
+ if ((!aFrame->IsPlaceholderFrame() || LineIsEmpty()) &&
+ NotifyOptionalBreakPosition(aFrame, INT32_MAX,
+ optionalBreakAfterFits,
+ gfxBreakPriority::eNormalBreak)) {
+ // If this returns true then we are being told to actually break
+ // here.
+ aReflowStatus.SetInlineLineBreakAfter();
+ }
+ }
+ }
+ } else {
+ PushFrame(aFrame);
+ aPushedFrame = true;
+ // Undo any saved break positions that the frame might have told us about,
+ // since we didn't end up placing it
+ RestoreSavedBreakPosition(savedOptionalBreakFrame,
+ savedOptionalBreakOffset,
+ savedOptionalBreakPriority);
+ }
+ } else {
+ PushFrame(aFrame);
+ aPushedFrame = true;
+ }
+
+#ifdef REALLY_NOISY_REFLOW
+ nsIFrame::IndentBy(stdout, mSpanDepth);
+ printf("End ReflowFrame ");
+ aFrame->ListTag(stdout);
+ printf(" status=%x\n", aReflowStatus);
+#endif
+}
+
+void nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
+ ReflowInput& aReflowInput) {
+ NS_ASSERTION(!aReflowInput.mStyleDisplay->IsFloating(aReflowInput.mFrame),
+ "How'd we get a floated inline frame? "
+ "The frame ctor should've dealt with this.");
+
+ WritingMode lineWM = mRootSpan->mWritingMode;
+
+ // Only apply start-margin on the first-in flow for inline frames,
+ // and make sure to not apply it to any inline other than the first
+ // in an ib split. Note that the ib sibling (block-in-inline
+ // sibling) annotations only live on the first continuation, but we
+ // don't want to apply the start margin for later continuations
+ // anyway. For box-decoration-break:clone we apply the start-margin
+ // on all continuations.
+ if ((pfd->mFrame->GetPrevContinuation() ||
+ pfd->mFrame->FrameIsNonFirstInIBSplit()) &&
+ aReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ // Zero this out so that when we compute the max-element-width of
+ // the frame we will properly avoid adding in the starting margin.
+ pfd->mMargin.IStart(lineWM) = 0;
+ } else if (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedISize()) {
+ NS_WARNING_ASSERTION(
+ NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(),
+ "have unconstrained inline-size; this should only result from very "
+ "large sizes, not attempts at intrinsic inline-size calculation");
+ // For inline-ish and text-ish things (which don't compute widths
+ // in the reflow input), adjust available inline-size to account
+ // for the start margin. The end margin will be accounted for when
+ // we finish flowing the frame.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ aReflowInput.SetAvailableISize(
+ aReflowInput.AvailableISize() -
+ pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm));
+ }
+}
+
+nscoord nsLineLayout::GetCurrentFrameInlineDistanceFromBlock() {
+ PerSpanData* psd;
+ nscoord x = 0;
+ for (psd = mCurrentSpan; psd; psd = psd->mParent) {
+ x += psd->mICoord;
+ }
+ return x;
+}
+
+/**
+ * This method syncs bounds of ruby annotations and ruby annotation
+ * containers from their rect. It is necessary because:
+ * Containers are not part of the line in their levels, which means
+ * their bounds are not set properly before.
+ * Ruby annotations' position may have been changed when reflowing
+ * their containers.
+ */
+void nsLineLayout::SyncAnnotationBounds(PerFrameData* aRubyFrame) {
+ MOZ_ASSERT(aRubyFrame->mFrame->IsRubyFrame());
+ MOZ_ASSERT(aRubyFrame->mSpan);
+
+ PerSpanData* span = aRubyFrame->mSpan;
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ for (PerFrameData* pfd = span->mFirstFrame; pfd; pfd = pfd->mNext) {
+ for (PerFrameData* rtc = pfd->mNextAnnotation; rtc;
+ rtc = rtc->mNextAnnotation) {
+ if (lineWM.IsOrthogonalTo(rtc->mFrame->GetWritingMode())) {
+ // Inter-character case: don't attempt to sync annotation bounds.
+ continue;
+ }
+ // When the annotation container is reflowed, the width of the
+ // ruby container is unknown so we use a dummy container size;
+ // in the case of RTL block direction, the final position will be
+ // fixed up later.
+ const nsSize dummyContainerSize;
+ LogicalRect rtcBounds(lineWM, rtc->mFrame->GetRect(), dummyContainerSize);
+ rtc->mBounds = rtcBounds;
+ nsSize rtcSize = rtcBounds.Size(lineWM).GetPhysicalSize(lineWM);
+ for (PerFrameData* rt = rtc->mSpan->mFirstFrame; rt; rt = rt->mNext) {
+ LogicalRect rtBounds = rt->mFrame->GetLogicalRect(lineWM, rtcSize);
+ MOZ_ASSERT(rt->mBounds.Size(lineWM) == rtBounds.Size(lineWM),
+ "Size of the annotation should not have been changed");
+ rt->mBounds.SetOrigin(lineWM, rtBounds.Origin(lineWM));
+ }
+ }
+ }
+}
+
+/**
+ * See if the frame can be placed now that we know it's desired size.
+ * We can always place the frame if the line is empty. Note that we
+ * know that the reflow-status is not a break-before because if it was
+ * ReflowFrame above would have returned false, preventing this method
+ * from being called. The logic in this method assumes that.
+ *
+ * Note that there is no check against the Y coordinate because we
+ * assume that the caller will take care of that.
+ */
+bool nsLineLayout::CanPlaceFrame(PerFrameData* pfd, bool aNotSafeToBreak,
+ bool aFrameCanContinueTextRun,
+ bool aCanRollBackBeforeFrame,
+ ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus,
+ bool* aOptionalBreakAfterFits) {
+ MOZ_ASSERT(pfd && pfd->mFrame, "bad args, null pointers for frame data");
+
+ *aOptionalBreakAfterFits = true;
+
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ /*
+ * We want to only apply the end margin if we're the last continuation and
+ * either not in an {ib} split or the last inline in it. In all other
+ * cases we want to zero it out. That means zeroing it out if any of these
+ * conditions hold:
+ * 1) The frame is not complete (in this case it will get a next-in-flow)
+ * 2) The frame is complete but has a non-fluid continuation on its
+ * continuation chain. Note that if it has a fluid continuation, that
+ * continuation will get destroyed later, so we don't want to drop the
+ * end-margin in that case.
+ * 3) The frame is in an {ib} split and is not the last part.
+ *
+ * However, none of that applies if this is a letter frame (XXXbz why?)
+ *
+ * For box-decoration-break:clone we apply the end margin on all
+ * continuations (that are not letter frames).
+ */
+ if ((aStatus.IsIncomplete() ||
+ pfd->mFrame->LastInFlow()->GetNextContinuation() ||
+ pfd->mFrame->FrameIsNonLastInIBSplit()) &&
+ !pfd->mIsLetterFrame &&
+ pfd->mFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ pfd->mMargin.IEnd(lineWM) = 0;
+ }
+
+ // Apply the start margin to the frame bounds.
+ nscoord startMargin = pfd->mMargin.IStart(lineWM);
+ nscoord endMargin = pfd->mMargin.IEnd(lineWM);
+
+ pfd->mBounds.IStart(lineWM) += startMargin;
+
+ PerSpanData* psd = mCurrentSpan;
+ if (psd->mNoWrap) {
+ // When wrapping is off, everything fits.
+ return true;
+ }
+
+#ifdef NOISY_CAN_PLACE_FRAME
+ if (psd->mFrame) {
+ psd->mFrame->mFrame->ListTag(stdout);
+ }
+ printf(": aNotSafeToBreak=%s frame=", aNotSafeToBreak ? "true" : "false");
+ pfd->mFrame->ListTag(stdout);
+ printf(" frameWidth=%d, margins=%d,%d\n",
+ pfd->mBounds.IEnd(lineWM) + endMargin - psd->mICoord, startMargin,
+ endMargin);
+#endif
+
+ // Set outside to true if the result of the reflow leads to the
+ // frame sticking outside of our available area.
+ bool outside =
+ pfd->mBounds.IEnd(lineWM) - mTrimmableISize + endMargin > psd->mIEnd;
+ if (!outside) {
+ // If it fits, it fits
+#ifdef NOISY_CAN_PLACE_FRAME
+ printf(" ==> inside\n");
+#endif
+ return true;
+ }
+ *aOptionalBreakAfterFits = false;
+
+ // When it doesn't fit, check for a few special conditions where we
+ // allow it to fit anyway.
+ if (0 == startMargin + pfd->mBounds.ISize(lineWM) + endMargin) {
+ // Empty frames always fit right where they are
+#ifdef NOISY_CAN_PLACE_FRAME
+ printf(" ==> empty frame fits\n");
+#endif
+ return true;
+ }
+
+ // another special case: always place a BR
+ if (pfd->mFrame->IsBrFrame()) {
+#ifdef NOISY_CAN_PLACE_FRAME
+ printf(" ==> BR frame fits\n");
+#endif
+ return true;
+ }
+
+ if (aNotSafeToBreak) {
+ // There are no frames on the line that take up width and the line is
+ // not impacted by floats, so we must allow the current frame to be
+ // placed on the line
+#ifdef NOISY_CAN_PLACE_FRAME
+ printf(" ==> not-safe and not-impacted fits: ");
+ while (nullptr != psd) {
+ printf("<psd=%p x=%d left=%d> ", psd, psd->mICoord, psd->mIStart);
+ psd = psd->mParent;
+ }
+ printf("\n");
+#endif
+ return true;
+ }
+
+ // Special check for span frames
+ if (pfd->mSpan && pfd->mSpan->mContainsFloat) {
+ // If the span either directly or indirectly contains a float then
+ // it fits. Why? It's kind of complicated, but here goes:
+ //
+ // 1. CanPlaceFrame is used for all frame placements on a line,
+ // and in a span. This includes recursively placement of frames
+ // inside of spans, and the span itself. Because the logic always
+ // checks for room before proceeding (the code above here), the
+ // only things on a line will be those things that "fit".
+ //
+ // 2. Before a float is placed on a line, the line has to be empty
+ // (otherwise it's a "below current line" float and will be placed
+ // after the line).
+ //
+ // Therefore, if the span directly or indirectly has a float
+ // then it means that at the time of the placement of the float
+ // the line was empty. Because of #1, only the frames that fit can
+ // be added after that point, therefore we can assume that the
+ // current span being placed has fit.
+ //
+ // So how do we get here and have a span that should already fit
+ // and yet doesn't: Simple: span's that have the no-wrap attribute
+ // set on them and contain a float and are placed where they
+ // don't naturally fit.
+ return true;
+ }
+
+ if (aFrameCanContinueTextRun) {
+ // Let it fit, but we reserve the right to roll back.
+ // Note that we usually won't get here because a text frame will break
+ // itself to avoid exceeding the available width.
+ // We'll only get here for text frames that couldn't break early enough.
+#ifdef NOISY_CAN_PLACE_FRAME
+ printf(" ==> placing overflowing textrun, requesting backup\n");
+#endif
+
+ // We will want to try backup.
+ mNeedBackup = true;
+ return true;
+ }
+
+#ifdef NOISY_CAN_PLACE_FRAME
+ printf(" ==> didn't fit\n");
+#endif
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return false;
+}
+
+/**
+ * Place the frame. Update running counters.
+ */
+void nsLineLayout::PlaceFrame(PerFrameData* pfd, ReflowOutput& aMetrics) {
+ WritingMode lineWM = mRootSpan->mWritingMode;
+
+ // If the frame's block direction does not match the line's, we can't use
+ // its ascent; instead, treat it as a block with baseline at the block-end
+ // edge (or block-begin in the case of an "inverted" line).
+ if (pfd->mWritingMode.GetBlockDir() != lineWM.GetBlockDir()) {
+ pfd->mAscent = lineWM.IsAlphabeticalBaseline()
+ ? lineWM.IsLineInverted() ? 0 : aMetrics.BSize(lineWM)
+ : aMetrics.BSize(lineWM) / 2;
+ } else {
+ // For inline reflow participants, baseline may get assigned as the frame is
+ // vertically aligned, which happens after this.
+ const auto baselineSource = pfd->mFrame->StyleDisplay()->mBaselineSource;
+ if (baselineSource == StyleBaselineSource::Auto ||
+ pfd->mFrame->IsLineParticipant()) {
+ if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
+ pfd->mAscent = pfd->mFrame->GetLogicalBaseline(lineWM);
+ } else {
+ pfd->mAscent = aMetrics.BlockStartAscent();
+ }
+ } else {
+ const auto sourceGroup = [baselineSource]() {
+ switch (baselineSource) {
+ case StyleBaselineSource::First:
+ return BaselineSharingGroup::First;
+ case StyleBaselineSource::Last:
+ return BaselineSharingGroup::Last;
+ case StyleBaselineSource::Auto:
+ break;
+ }
+ MOZ_ASSERT_UNREACHABLE("Auto should be already handled?");
+ return BaselineSharingGroup::First;
+ }();
+ // We ignore line-layout specific layout quirks by setting
+ // `BaselineExportContext::Other`.
+ // Note(dshin): For a lot of frames, the export context does not make a
+ // difference, and we may be wasting the value cached in
+ // `BlockStartAscent`.
+ pfd->mAscent = pfd->mFrame->GetLogicalBaseline(
+ lineWM, sourceGroup, BaselineExportContext::Other);
+ }
+ }
+
+ // Advance to next inline coordinate
+ mCurrentSpan->mICoord = pfd->mBounds.IEnd(lineWM) + pfd->mMargin.IEnd(lineWM);
+
+ // Count the number of non-placeholder frames on the line...
+ if (pfd->mFrame->IsPlaceholderFrame()) {
+ NS_ASSERTION(
+ pfd->mBounds.ISize(lineWM) == 0 && pfd->mBounds.BSize(lineWM) == 0,
+ "placeholders should have 0 width/height (checking "
+ "placeholders were never counted by the old code in "
+ "this function)");
+ } else {
+ mTotalPlacedFrames++;
+ }
+}
+
+void nsLineLayout::AddMarkerFrame(nsIFrame* aFrame,
+ const ReflowOutput& aMetrics) {
+ NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
+ NS_ASSERTION(mGotLineBox, "must have line box");
+
+ nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame());
+ MOZ_ASSERT(blockFrame, "must be for block");
+ if (!blockFrame->MarkerIsEmpty()) {
+ mHasMarker = true;
+ mLineBox->SetHasMarker();
+ }
+
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ PerFrameData* pfd = NewPerFrameData(aFrame);
+ PerSpanData* psd = mRootSpan;
+
+ MOZ_ASSERT(psd->mFirstFrame, "adding marker to an empty line?");
+ // Prepend the marker frame to the line.
+ psd->mFirstFrame->mPrev = pfd;
+ pfd->mNext = psd->mFirstFrame;
+ psd->mFirstFrame = pfd;
+
+ pfd->mIsMarker = true;
+ if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
+ pfd->mAscent = aFrame->GetLogicalBaseline(lineWM);
+ } else {
+ pfd->mAscent = aMetrics.BlockStartAscent();
+ }
+
+ // Note: block-coord value will be updated during block-direction alignment
+ pfd->mBounds = LogicalRect(lineWM, aFrame->GetRect(), ContainerSize());
+ pfd->mOverflowAreas = aMetrics.mOverflowAreas;
+}
+
+void nsLineLayout::RemoveMarkerFrame(nsIFrame* aFrame) {
+ PerSpanData* psd = mCurrentSpan;
+ MOZ_ASSERT(psd == mRootSpan, "::marker on non-root span?");
+ MOZ_ASSERT(psd->mFirstFrame->mFrame == aFrame,
+ "::marker is not the first frame?");
+ PerFrameData* pfd = psd->mFirstFrame;
+ MOZ_ASSERT(pfd != psd->mLastFrame, "::marker is the only frame?");
+ pfd->mNext->mPrev = nullptr;
+ psd->mFirstFrame = pfd->mNext;
+ FreeFrame(pfd);
+}
+#ifdef DEBUG
+void nsLineLayout::DumpPerSpanData(PerSpanData* psd, int32_t aIndent) {
+ nsIFrame::IndentBy(stdout, aIndent);
+ printf("%p: left=%d x=%d right=%d\n", static_cast<void*>(psd), psd->mIStart,
+ psd->mICoord, psd->mIEnd);
+ PerFrameData* pfd = psd->mFirstFrame;
+ while (nullptr != pfd) {
+ nsIFrame::IndentBy(stdout, aIndent + 1);
+ pfd->mFrame->ListTag(stdout);
+ nsRect rect =
+ pfd->mBounds.GetPhysicalRect(psd->mWritingMode, ContainerSize());
+ printf(" %d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height);
+ if (pfd->mSpan) {
+ DumpPerSpanData(pfd->mSpan, aIndent + 1);
+ }
+ pfd = pfd->mNext;
+ }
+}
+#endif
+
+void nsLineLayout::RecordNoWrapFloat(nsIFrame* aFloat) {
+ GetOutermostLineLayout()->mBlockRS->mNoWrapFloats.AppendElement(aFloat);
+}
+
+void nsLineLayout::FlushNoWrapFloats() {
+ auto& noWrapFloats = GetOutermostLineLayout()->mBlockRS->mNoWrapFloats;
+ for (nsIFrame* floatedFrame : noWrapFloats) {
+ TryToPlaceFloat(floatedFrame);
+ }
+ noWrapFloats.Clear();
+}
+
+bool nsLineLayout::TryToPlaceFloat(nsIFrame* aFloat) {
+ // Add mTrimmableISize to the available width since if the line ends here, the
+ // width of the inline content will be reduced by mTrimmableISize.
+ nscoord availableISize =
+ mCurrentSpan->mIEnd - (mCurrentSpan->mICoord - mTrimmableISize);
+ NS_ASSERTION(!(aFloat->IsLetterFrame() && GetFirstLetterStyleOK()),
+ "FirstLetterStyle set on line with floating first letter");
+ return GetOutermostLineLayout()->AddFloat(aFloat, availableISize);
+}
+
+bool nsLineLayout::NotifyOptionalBreakPosition(nsIFrame* aFrame,
+ int32_t aOffset, bool aFits,
+ gfxBreakPriority aPriority) {
+ NS_ASSERTION(!aFits || !mNeedBackup,
+ "Shouldn't be updating the break position with a break that fits"
+ " after we've already flagged an overrun");
+ MOZ_ASSERT(mCurrentSpan, "Should be doing line layout");
+ if (mCurrentSpan->mNoWrap) {
+ FlushNoWrapFloats();
+ }
+
+ // Remember the last break position that fits; if there was no break that fit,
+ // just remember the first break
+ if ((aFits && aPriority >= mLastOptionalBreakPriority) ||
+ !mLastOptionalBreakFrame) {
+ mLastOptionalBreakFrame = aFrame;
+ mLastOptionalBreakFrameOffset = aOffset;
+ mLastOptionalBreakPriority = aPriority;
+ }
+ return aFrame && mForceBreakFrame == aFrame &&
+ mForceBreakFrameOffset == aOffset;
+}
+
+#define VALIGN_OTHER 0
+#define VALIGN_TOP 1
+#define VALIGN_BOTTOM 2
+
+void nsLineLayout::VerticalAlignLine() {
+ // Partially place the children of the block frame. The baseline for
+ // this operation is set to zero so that the y coordinates for all
+ // of the placed children will be relative to there.
+ PerSpanData* psd = mRootSpan;
+ VerticalAlignFrames(psd);
+
+ // *** Note that comments here still use the anachronistic term
+ // "line-height" when we really mean "size of the line in the block
+ // direction", "vertical-align" when we really mean "alignment in
+ // the block direction", and "top" and "bottom" when we really mean
+ // "block start" and "block end". This is partly for brevity and
+ // partly to retain the association with the CSS line-height and
+ // vertical-align properties.
+ //
+ // Compute the line-height. The line-height will be the larger of:
+ //
+ // [1] maxBCoord - minBCoord (the distance between the first child's
+ // block-start edge and the last child's block-end edge)
+ //
+ // [2] the maximum logical box block size (since not every frame may have
+ // participated in #1; for example: "top" and "botttom" aligned frames)
+ //
+ // [3] the minimum line height ("line-height" property set on the
+ // block frame)
+ nscoord lineBSize = psd->mMaxBCoord - psd->mMinBCoord;
+
+ // Now that the line-height is computed, we need to know where the
+ // baseline is in the line. Position baseline so that mMinBCoord is just
+ // inside the start of the line box.
+ nscoord baselineBCoord;
+ if (psd->mMinBCoord < 0) {
+ baselineBCoord = mBStartEdge - psd->mMinBCoord;
+ } else {
+ baselineBCoord = mBStartEdge;
+ }
+
+ // It's also possible that the line block-size isn't tall enough because
+ // of "top" and "bottom" aligned elements that were not accounted for in
+ // min/max BCoord.
+ //
+ // The CSS2 spec doesn't really say what happens when to the
+ // baseline in this situations. What we do is if the largest start
+ // aligned box block size is greater than the line block-size then we leave
+ // the baseline alone. If the largest end aligned box is greater
+ // than the line block-size then we slide the baseline forward by the extra
+ // amount.
+ //
+ // Navigator 4 gives precedence to the first top/bottom aligned
+ // object. We just let block end aligned objects win.
+ if (lineBSize < mMaxEndBoxBSize) {
+ // When the line is shorter than the maximum block start aligned box
+ nscoord extra = mMaxEndBoxBSize - lineBSize;
+ baselineBCoord += extra;
+ lineBSize = mMaxEndBoxBSize;
+ }
+ if (lineBSize < mMaxStartBoxBSize) {
+ lineBSize = mMaxStartBoxBSize;
+ }
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" [line]==> lineBSize=%d baselineBCoord=%d\n", lineBSize,
+ baselineBCoord);
+#endif
+
+ // Now position all of the frames in the root span. We will also
+ // recurse over the child spans and place any frames we find with
+ // vertical-align: top or bottom.
+ // XXX PERFORMANCE: set a bit per-span to avoid the extra work
+ // (propagate it upward too)
+ WritingMode lineWM = psd->mWritingMode;
+ for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
+ if (pfd->mBlockDirAlign == VALIGN_OTHER) {
+ pfd->mBounds.BStart(lineWM) += baselineBCoord;
+ pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSize());
+ }
+ }
+ PlaceTopBottomFrames(psd, -mBStartEdge, lineBSize);
+
+ mFinalLineBSize = lineBSize;
+ if (mGotLineBox) {
+ // Fill in returned line-box and max-element-width data
+ mLineBox->SetBounds(lineWM, psd->mIStart, mBStartEdge,
+ psd->mICoord - psd->mIStart, lineBSize,
+ ContainerSize());
+
+ mLineBox->SetLogicalAscent(baselineBCoord - mBStartEdge);
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" [line]==> bounds{x,y,w,h}={%d,%d,%d,%d} lh=%d a=%d\n",
+ mLineBox->GetBounds().IStart(lineWM),
+ mLineBox->GetBounds().BStart(lineWM),
+ mLineBox->GetBounds().ISize(lineWM),
+ mLineBox->GetBounds().BSize(lineWM), mFinalLineBSize,
+ mLineBox->GetLogicalAscent());
+#endif
+ }
+}
+
+// Place frames with CSS property vertical-align: top or bottom.
+void nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
+ nscoord aDistanceFromStart,
+ nscoord aLineBSize) {
+ for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
+ PerSpanData* span = pfd->mSpan;
+#ifdef DEBUG
+ NS_ASSERTION(0xFF != pfd->mBlockDirAlign, "umr");
+#endif
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ nsSize containerSize = ContainerSizeForSpan(psd);
+ switch (pfd->mBlockDirAlign) {
+ case VALIGN_TOP:
+ if (span) {
+ pfd->mBounds.BStart(lineWM) = -aDistanceFromStart - span->mMinBCoord;
+ } else {
+ pfd->mBounds.BStart(lineWM) =
+ -aDistanceFromStart + pfd->mMargin.BStart(lineWM);
+ }
+ pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize);
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" ");
+ pfd->mFrame->ListTag(stdout);
+ printf(": y=%d dTop=%d [bp.top=%d topLeading=%d]\n",
+ pfd->mBounds.BStart(lineWM), aDistanceFromStart,
+ span ? pfd->mBorderPadding.BStart(lineWM) : 0,
+ span ? span->mBStartLeading : 0);
+#endif
+ break;
+ case VALIGN_BOTTOM:
+ if (span) {
+ // Compute bottom leading
+ pfd->mBounds.BStart(lineWM) =
+ -aDistanceFromStart + aLineBSize - span->mMaxBCoord;
+ } else {
+ pfd->mBounds.BStart(lineWM) = -aDistanceFromStart + aLineBSize -
+ pfd->mMargin.BEnd(lineWM) -
+ pfd->mBounds.BSize(lineWM);
+ }
+ pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize);
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" ");
+ pfd->mFrame->ListTag(stdout);
+ printf(": y=%d\n", pfd->mBounds.BStart(lineWM));
+#endif
+ break;
+ }
+ if (span) {
+ nscoord fromStart = aDistanceFromStart + pfd->mBounds.BStart(lineWM);
+ PlaceTopBottomFrames(span, fromStart, aLineBSize);
+ }
+ }
+}
+
+static nscoord GetBSizeOfEmphasisMarks(nsIFrame* aSpanFrame, float aInflation) {
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
+ aSpanFrame->Style(), aSpanFrame->PresContext(), aInflation);
+ return fm->MaxHeight();
+}
+
+void nsLineLayout::AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd,
+ const nsStyleText* aStyleText,
+ float aInflation,
+ bool* aZeroEffectiveSpanBox) {
+ MOZ_ASSERT(spanFrame == psd->mFrame->mFrame);
+ nscoord requiredStartLeading = 0;
+ nscoord requiredEndLeading = 0;
+ if (spanFrame->IsRubyFrame()) {
+ // We may need to extend leadings here for ruby annotations as
+ // required by section Line Spacing in the CSS Ruby spec.
+ // See http://dev.w3.org/csswg/css-ruby/#line-height
+ auto rubyFrame = static_cast<nsRubyFrame*>(spanFrame);
+ RubyBlockLeadings leadings = rubyFrame->GetBlockLeadings();
+ requiredStartLeading += leadings.mStart;
+ requiredEndLeading += leadings.mEnd;
+ }
+ if (aStyleText->HasEffectiveTextEmphasis()) {
+ nscoord bsize = GetBSizeOfEmphasisMarks(spanFrame, aInflation);
+ LogicalSide side = aStyleText->TextEmphasisSide(mRootSpan->mWritingMode);
+ if (side == eLogicalSideBStart) {
+ requiredStartLeading += bsize;
+ } else {
+ MOZ_ASSERT(side == eLogicalSideBEnd,
+ "emphasis marks must be in block axis");
+ requiredEndLeading += bsize;
+ }
+ }
+
+ nscoord requiredLeading = requiredStartLeading + requiredEndLeading;
+ // If we do not require any additional leadings, don't touch anything
+ // here even if it is greater than the original leading, because the
+ // latter could be negative.
+ if (requiredLeading != 0) {
+ nscoord leading = psd->mBStartLeading + psd->mBEndLeading;
+ nscoord deltaLeading = requiredLeading - leading;
+ if (deltaLeading > 0) {
+ // If the total leading is not wide enough for ruby annotations
+ // and/or emphasis marks, extend the side which is not enough. If
+ // both sides are not wide enough, replace the leadings with the
+ // requested values.
+ if (requiredStartLeading < psd->mBStartLeading) {
+ psd->mBEndLeading += deltaLeading;
+ } else if (requiredEndLeading < psd->mBEndLeading) {
+ psd->mBStartLeading += deltaLeading;
+ } else {
+ psd->mBStartLeading = requiredStartLeading;
+ psd->mBEndLeading = requiredEndLeading;
+ }
+ psd->mLogicalBSize += deltaLeading;
+ // We have adjusted the leadings, it is no longer a zero
+ // effective span box.
+ *aZeroEffectiveSpanBox = false;
+ }
+ }
+}
+
+static float GetInflationForBlockDirAlignment(nsIFrame* aFrame,
+ nscoord aInflationMinFontSize) {
+ if (aFrame->IsInSVGTextSubtree()) {
+ const nsIFrame* container =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
+ NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
+ return static_cast<const SVGTextFrame*>(container)
+ ->GetFontSizeScaleFactor();
+ }
+ return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
+}
+
+bool nsLineLayout::ShouldApplyLineHeightInPreserveWhiteSpace(
+ const PerSpanData* psd) {
+ if (psd->mFrame->mFrame->Style()->IsAnonBox()) {
+ // e.g. An empty `input[type=button]` should still be line-height sized.
+ return true;
+ }
+
+ for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
+ if (!pfd->mIsEmpty) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#define BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM nscoord_MAX
+#define BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM nscoord_MIN
+
+// Place frames in the block direction within a given span (CSS property
+// vertical-align) Note: this doesn't place frames with vertical-align:
+// top or bottom as those have to wait until the entire line box block
+// size is known. This is called after the span frame has finished being
+// reflowed so that we know its block size.
+void nsLineLayout::VerticalAlignFrames(PerSpanData* psd) {
+ // Get parent frame info
+ PerFrameData* spanFramePFD = psd->mFrame;
+ nsIFrame* spanFrame = spanFramePFD->mFrame;
+
+ // Get the parent frame's font for all of the frames in this span
+ float inflation =
+ GetInflationForBlockDirAlignment(spanFrame, mInflationMinFontSize);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(spanFrame, inflation);
+
+ bool preMode = mStyleText->WhiteSpaceIsSignificant();
+
+ // See if the span is an empty continuation. It's an empty continuation iff:
+ // - it has a prev-in-flow
+ // - it has no next in flow
+ // - it's zero sized
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ bool emptyContinuation = psd != mRootSpan && spanFrame->GetPrevInFlow() &&
+ !spanFrame->GetNextInFlow() &&
+ spanFramePFD->mBounds.IsZeroSize();
+
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf("[%sSpan]", (psd == mRootSpan) ? "Root" : "");
+ spanFrame->ListTag(stdout);
+ printf(": preMode=%s strictMode=%s w/h=%d,%d emptyContinuation=%s",
+ preMode ? "yes" : "no",
+ mPresContext->CompatibilityMode() != eCompatibility_NavQuirks ? "yes"
+ : "no",
+ spanFramePFD->mBounds.ISize(lineWM),
+ spanFramePFD->mBounds.BSize(lineWM), emptyContinuation ? "yes" : "no");
+ if (psd != mRootSpan) {
+ printf(" bp=%d,%d,%d,%d margin=%d,%d,%d,%d",
+ spanFramePFD->mBorderPadding.Top(lineWM),
+ spanFramePFD->mBorderPadding.Right(lineWM),
+ spanFramePFD->mBorderPadding.Bottom(lineWM),
+ spanFramePFD->mBorderPadding.Left(lineWM),
+ spanFramePFD->mMargin.Top(lineWM),
+ spanFramePFD->mMargin.Right(lineWM),
+ spanFramePFD->mMargin.Bottom(lineWM),
+ spanFramePFD->mMargin.Left(lineWM));
+ }
+ printf("\n");
+#endif
+
+ // Compute the span's zeroEffectiveSpanBox flag. What we are trying
+ // to determine is how we should treat the span: should it act
+ // "normally" according to css2 or should it effectively
+ // "disappear".
+ //
+ // In general, if the document being processed is in full standards
+ // mode then it should act normally (with one exception). The
+ // exception case is when a span is continued and yet the span is
+ // empty (e.g. compressed whitespace). For this kind of span we treat
+ // it as if it were not there so that it doesn't impact the
+ // line block-size.
+ //
+ // In almost standards mode or quirks mode, we should sometimes make
+ // it disappear. The cases that matter are those where the span
+ // contains no real text elements that would provide an ascent and
+ // descent and height. However, if css style elements have been
+ // applied to the span (border/padding/margin) so that it's clear the
+ // document author is intending css2 behavior then we act as if strict
+ // mode is set.
+ //
+ // This code works correctly for preMode, because a blank line
+ // in PRE mode is encoded as a text node with a LF in it, since
+ // text nodes with only whitespace are considered in preMode.
+ //
+ // Much of this logic is shared with the various implementations of
+ // nsIFrame::IsEmpty since they need to duplicate the way it makes
+ // some lines empty. However, nsIFrame::IsEmpty can't be reused here
+ // since this code sets zeroEffectiveSpanBox even when there are
+ // non-empty children.
+ bool zeroEffectiveSpanBox = false;
+ // XXXldb If we really have empty continuations, then all these other
+ // checks don't make sense for them.
+ // XXXldb This should probably just use nsIFrame::IsSelfEmpty, assuming that
+ // it agrees with this code. (If it doesn't agree, it probably should.)
+ if ((emptyContinuation ||
+ mPresContext->CompatibilityMode() != eCompatibility_FullStandards) &&
+ ((psd == mRootSpan) || (spanFramePFD->mBorderPadding.IsAllZero() &&
+ spanFramePFD->mMargin.IsAllZero()))) {
+ // This code handles an issue with compatibility with non-css
+ // conformant browsers. In particular, there are some cases
+ // where the font-size and line-height for a span must be
+ // ignored and instead the span must *act* as if it were zero
+ // sized. In general, if the span contains any non-compressed
+ // text then we don't use this logic.
+ // However, this is not propagated outwards, since (in compatibility
+ // mode) we don't want big line heights for things like
+ // <p><font size="-1">Text</font></p>
+
+ // We shouldn't include any whitespace that collapses, unless we're
+ // preformatted (in which case it shouldn't, but the width=0 test is
+ // perhaps incorrect). This includes whitespace at the beginning of
+ // a line and whitespace preceded (?) by other whitespace.
+ // See bug 134580 and bug 155333.
+ zeroEffectiveSpanBox = true;
+ for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
+ if (pfd->mIsTextFrame &&
+ (pfd->mIsNonWhitespaceTextFrame || preMode ||
+ pfd->mBounds.ISize(mRootSpan->mWritingMode) != 0)) {
+ zeroEffectiveSpanBox = false;
+ break;
+ }
+ }
+ }
+
+ // Setup baselineBCoord, minBCoord, and maxBCoord
+ nscoord baselineBCoord, minBCoord, maxBCoord;
+ if (psd == mRootSpan) {
+ // Use a zero baselineBCoord since we don't yet know where the baseline
+ // will be (until we know how tall the line is; then we will
+ // know). In addition, use extreme values for the minBCoord and maxBCoord
+ // values so that only the child frames will impact their values
+ // (since these are children of the block, there is no span box to
+ // provide initial values).
+ baselineBCoord = 0;
+ minBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM;
+ maxBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM;
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf("[RootSpan]");
+ spanFrame->ListTag(stdout);
+ printf(
+ ": pass1 valign frames: topEdge=%d minLineBSize=%d "
+ "zeroEffectiveSpanBox=%s\n",
+ mBStartEdge, mMinLineBSize, zeroEffectiveSpanBox ? "yes" : "no");
+#endif
+ } else {
+ // Compute the logical block size for this span. The logical block size
+ // is based on the "line-height" value, not the font-size. Also
+ // compute the top leading.
+ float inflation =
+ GetInflationForBlockDirAlignment(spanFrame, mInflationMinFontSize);
+ nscoord logicalBSize = ReflowInput::CalcLineHeight(
+ *spanFrame->Style(), spanFrame->PresContext(), spanFrame->GetContent(),
+ mLineContainerRI.ComputedHeight(), inflation);
+ nscoord contentBSize = spanFramePFD->mBounds.BSize(lineWM) -
+ spanFramePFD->mBorderPadding.BStartEnd(lineWM);
+
+ // Special-case for a ::first-letter frame, set the line height to
+ // the frame block size if the user has left line-height == normal
+ if (spanFramePFD->mIsLetterFrame && !spanFrame->GetPrevInFlow() &&
+ spanFrame->StyleFont()->mLineHeight.IsNormal()) {
+ logicalBSize = spanFramePFD->mBounds.BSize(lineWM);
+ }
+
+ nscoord leading = logicalBSize - contentBSize;
+ psd->mBStartLeading = leading / 2;
+ psd->mBEndLeading = leading - psd->mBStartLeading;
+ psd->mLogicalBSize = logicalBSize;
+ AdjustLeadings(spanFrame, psd, spanFrame->StyleText(), inflation,
+ &zeroEffectiveSpanBox);
+
+ if (zeroEffectiveSpanBox) {
+ // When the span-box is to be ignored, zero out the initial
+ // values so that the span doesn't impact the final line
+ // height. The contents of the span can impact the final line
+ // height.
+
+ // Note that things are readjusted for this span after its children
+ // are reflowed
+ minBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM;
+ maxBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM;
+ } else {
+ // The initial values for the min and max block coord values are in the
+ // span's coordinate space, and cover the logical block size of the span.
+ // If there are child frames in this span that stick out of this area
+ // then the minBCoord and maxBCoord are updated by the amount of logical
+ // blockSize that is outside this range.
+ minBCoord =
+ spanFramePFD->mBorderPadding.BStart(lineWM) - psd->mBStartLeading;
+ maxBCoord = minBCoord + psd->mLogicalBSize;
+ }
+
+ // This is the distance from the top edge of the parents visual
+ // box to the baseline. The span already computed this for us,
+ // so just use it.
+ *psd->mBaseline = baselineBCoord = spanFramePFD->mAscent;
+
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf("[%sSpan]", (psd == mRootSpan) ? "Root" : "");
+ spanFrame->ListTag(stdout);
+ printf(
+ ": baseLine=%d logicalBSize=%d topLeading=%d h=%d bp=%d,%d "
+ "zeroEffectiveSpanBox=%s\n",
+ baselineBCoord, psd->mLogicalBSize, psd->mBStartLeading,
+ spanFramePFD->mBounds.BSize(lineWM),
+ spanFramePFD->mBorderPadding.Top(lineWM),
+ spanFramePFD->mBorderPadding.Bottom(lineWM),
+ zeroEffectiveSpanBox ? "yes" : "no");
+#endif
+ }
+
+ nscoord maxStartBoxBSize = 0;
+ nscoord maxEndBoxBSize = 0;
+ PerFrameData* pfd = psd->mFirstFrame;
+ while (nullptr != pfd) {
+ nsIFrame* frame = pfd->mFrame;
+
+ // sanity check (see bug 105168, non-reproducible crashes from null frame)
+ NS_ASSERTION(frame,
+ "null frame in PerFrameData - something is very very bad");
+ if (!frame) {
+ return;
+ }
+
+ // Compute the logical block size of the frame
+ nscoord logicalBSize;
+ PerSpanData* frameSpan = pfd->mSpan;
+ if (frameSpan) {
+ // For span frames the logical-block-size and start-leading were
+ // pre-computed when the span was reflowed.
+ logicalBSize = frameSpan->mLogicalBSize;
+ } else {
+ // For other elements the logical block size is the same as the
+ // frame's block size plus its margins.
+ logicalBSize =
+ pfd->mBounds.BSize(lineWM) + pfd->mMargin.BStartEnd(lineWM);
+ if (logicalBSize < 0 &&
+ mPresContext->CompatibilityMode() != eCompatibility_FullStandards) {
+ pfd->mAscent -= logicalBSize;
+ logicalBSize = 0;
+ }
+ }
+
+ // Get vertical-align property ("vertical-align" is the CSS name for
+ // block-direction align)
+ const auto& verticalAlign = frame->StyleDisplay()->mVerticalAlign;
+ Maybe<StyleVerticalAlignKeyword> verticalAlignEnum =
+ frame->VerticalAlignEnum();
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" [frame]");
+ frame->ListTag(stdout);
+ printf(": verticalAlignIsKw=%d (enum == %d", verticalAlign.IsKeyword(),
+ verticalAlign.IsKeyword()
+ ? static_cast<int>(verticalAlign.AsKeyword())
+ : -1);
+ if (verticalAlignEnum) {
+ printf(", after SVG dominant-baseline conversion == %d",
+ static_cast<int>(*verticalAlignEnum));
+ }
+ printf(")\n");
+#endif
+
+ if (verticalAlignEnum) {
+ StyleVerticalAlignKeyword keyword = *verticalAlignEnum;
+ if (lineWM.IsVertical()) {
+ if (keyword == StyleVerticalAlignKeyword::Middle) {
+ // For vertical writing mode where the dominant baseline is centered
+ // (i.e. text-orientation is not sideways-*), we remap 'middle' to
+ // 'middle-with-baseline' so that images align sensibly with the
+ // center-baseline-aligned text.
+ if (!lineWM.IsSideways()) {
+ keyword = StyleVerticalAlignKeyword::MozMiddleWithBaseline;
+ }
+ } else if (lineWM.IsLineInverted()) {
+ // Swap the meanings of top and bottom when line is inverted
+ // relative to block direction.
+ switch (keyword) {
+ case StyleVerticalAlignKeyword::Top:
+ keyword = StyleVerticalAlignKeyword::Bottom;
+ break;
+ case StyleVerticalAlignKeyword::Bottom:
+ keyword = StyleVerticalAlignKeyword::Top;
+ break;
+ case StyleVerticalAlignKeyword::TextTop:
+ keyword = StyleVerticalAlignKeyword::TextBottom;
+ break;
+ case StyleVerticalAlignKeyword::TextBottom:
+ keyword = StyleVerticalAlignKeyword::TextTop;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // baseline coord that may be adjusted for script offset
+ nscoord revisedBaselineBCoord = baselineBCoord;
+
+ // For superscript and subscript, raise or lower the baseline of the box
+ // to the proper offset of the parent's box, then proceed as for BASELINE
+ if (keyword == StyleVerticalAlignKeyword::Sub ||
+ keyword == StyleVerticalAlignKeyword::Super) {
+ revisedBaselineBCoord += lineWM.FlowRelativeToLineRelativeFactor() *
+ (keyword == StyleVerticalAlignKeyword::Sub
+ ? fm->SubscriptOffset()
+ : -fm->SuperscriptOffset());
+ keyword = StyleVerticalAlignKeyword::Baseline;
+ }
+
+ switch (keyword) {
+ default:
+ case StyleVerticalAlignKeyword::Baseline:
+ pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord - pfd->mAscent;
+ pfd->mBlockDirAlign = VALIGN_OTHER;
+ break;
+
+ case StyleVerticalAlignKeyword::Top: {
+ pfd->mBlockDirAlign = VALIGN_TOP;
+ nscoord subtreeBSize = logicalBSize;
+ if (frameSpan) {
+ subtreeBSize = frameSpan->mMaxBCoord - frameSpan->mMinBCoord;
+ NS_ASSERTION(subtreeBSize >= logicalBSize,
+ "unexpected subtree block size");
+ }
+ if (subtreeBSize > maxStartBoxBSize) {
+ maxStartBoxBSize = subtreeBSize;
+ }
+ break;
+ }
+
+ case StyleVerticalAlignKeyword::Bottom: {
+ pfd->mBlockDirAlign = VALIGN_BOTTOM;
+ nscoord subtreeBSize = logicalBSize;
+ if (frameSpan) {
+ subtreeBSize = frameSpan->mMaxBCoord - frameSpan->mMinBCoord;
+ NS_ASSERTION(subtreeBSize >= logicalBSize,
+ "unexpected subtree block size");
+ }
+ if (subtreeBSize > maxEndBoxBSize) {
+ maxEndBoxBSize = subtreeBSize;
+ }
+ break;
+ }
+
+ case StyleVerticalAlignKeyword::Middle: {
+ // Align the midpoint of the frame with 1/2 the parents
+ // x-height above the baseline.
+ nscoord parentXHeight =
+ lineWM.FlowRelativeToLineRelativeFactor() * fm->XHeight();
+ if (frameSpan) {
+ pfd->mBounds.BStart(lineWM) =
+ baselineBCoord -
+ (parentXHeight + pfd->mBounds.BSize(lineWM)) / 2;
+ } else {
+ pfd->mBounds.BStart(lineWM) = baselineBCoord -
+ (parentXHeight + logicalBSize) / 2 +
+ pfd->mMargin.BStart(lineWM);
+ }
+ pfd->mBlockDirAlign = VALIGN_OTHER;
+ break;
+ }
+
+ case StyleVerticalAlignKeyword::TextTop: {
+ // The top of the logical box is aligned with the top of
+ // the parent element's text.
+ // XXX For vertical text we will need a new API to get the logical
+ // max-ascent here
+ nscoord parentAscent =
+ lineWM.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
+ if (frameSpan) {
+ pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent -
+ pfd->mBorderPadding.BStart(lineWM) +
+ frameSpan->mBStartLeading;
+ } else {
+ pfd->mBounds.BStart(lineWM) =
+ baselineBCoord - parentAscent + pfd->mMargin.BStart(lineWM);
+ }
+ pfd->mBlockDirAlign = VALIGN_OTHER;
+ break;
+ }
+
+ case StyleVerticalAlignKeyword::TextBottom: {
+ // The bottom of the logical box is aligned with the
+ // bottom of the parent elements text.
+ nscoord parentDescent =
+ lineWM.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
+ if (frameSpan) {
+ pfd->mBounds.BStart(lineWM) =
+ baselineBCoord + parentDescent - pfd->mBounds.BSize(lineWM) +
+ pfd->mBorderPadding.BEnd(lineWM) - frameSpan->mBEndLeading;
+ } else {
+ pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent -
+ pfd->mBounds.BSize(lineWM) -
+ pfd->mMargin.BEnd(lineWM);
+ }
+ pfd->mBlockDirAlign = VALIGN_OTHER;
+ break;
+ }
+
+ case StyleVerticalAlignKeyword::MozMiddleWithBaseline: {
+ // Align the midpoint of the frame with the baseline of the parent.
+ if (frameSpan) {
+ pfd->mBounds.BStart(lineWM) =
+ baselineBCoord - pfd->mBounds.BSize(lineWM) / 2;
+ } else {
+ pfd->mBounds.BStart(lineWM) =
+ baselineBCoord - logicalBSize / 2 + pfd->mMargin.BStart(lineWM);
+ }
+ pfd->mBlockDirAlign = VALIGN_OTHER;
+ break;
+ }
+ }
+ } else {
+ // We have either a coord, a percent, or a calc().
+ nscoord offset = verticalAlign.AsLength().Resolve([&] {
+ // Percentages are like lengths, except treated as a percentage
+ // of the elements line block size value.
+ float inflation =
+ GetInflationForBlockDirAlignment(frame, mInflationMinFontSize);
+ return ReflowInput::CalcLineHeight(
+ *frame->Style(), frame->PresContext(), frame->GetContent(),
+ mLineContainerRI.ComputedBSize(), inflation);
+ });
+
+ // According to the CSS2 spec (10.8.1), a positive value
+ // "raises" the box by the given distance while a negative value
+ // "lowers" the box by the given distance (with zero being the
+ // baseline). Since Y coordinates increase towards the bottom of
+ // the screen we reverse the sign, unless the line orientation is
+ // inverted relative to block direction.
+ nscoord revisedBaselineBCoord =
+ baselineBCoord - offset * lineWM.FlowRelativeToLineRelativeFactor();
+ if (lineWM.IsCentralBaseline()) {
+ // If we're using a dominant center baseline, we align with the center
+ // of the frame being placed (bug 1133945).
+ pfd->mBounds.BStart(lineWM) =
+ revisedBaselineBCoord - pfd->mBounds.BSize(lineWM) / 2;
+ } else {
+ pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord - pfd->mAscent;
+ }
+ pfd->mBlockDirAlign = VALIGN_OTHER;
+ }
+
+ // Update minBCoord/maxBCoord for frames that we just placed. Do not factor
+ // text into the equation.
+ if (pfd->mBlockDirAlign == VALIGN_OTHER) {
+ // Text frames do not contribute to the min/max Y values for the
+ // line (instead their parent frame's font-size contributes).
+ // XXXrbs -- relax this restriction because it causes text frames
+ // to jam together when 'font-size-adjust' is enabled
+ // and layout is using dynamic font heights (bug 20394)
+ // -- Note #1: With this code enabled and with the fact that we are
+ // not using Em[Ascent|Descent] as nsDimensions for text
+ // metrics in GFX mean that the discussion in bug 13072 cannot
+ // hold.
+ // -- Note #2: We still don't want empty-text frames to interfere.
+ // For example in quirks mode, avoiding empty text frames
+ // prevents "tall" lines around elements like <hr> since the
+ // rules of <hr> in quirks.css have pseudo text contents with LF
+ // in them.
+ bool canUpdate;
+ if (pfd->mIsTextFrame) {
+ // Only consider text frames if they're not empty and
+ // line-height=normal.
+ canUpdate = pfd->mIsNonWhitespaceTextFrame &&
+ frame->StyleFont()->mLineHeight.IsNormal();
+ } else {
+ canUpdate = !pfd->mIsPlaceholder;
+ }
+
+ if (canUpdate) {
+ nscoord blockStart, blockEnd;
+ if (frameSpan) {
+ // For spans that were are now placing, use their position
+ // plus their already computed min-Y and max-Y values for
+ // computing blockStart and blockEnd.
+ blockStart = pfd->mBounds.BStart(lineWM) + frameSpan->mMinBCoord;
+ blockEnd = pfd->mBounds.BStart(lineWM) + frameSpan->mMaxBCoord;
+ } else {
+ blockStart =
+ pfd->mBounds.BStart(lineWM) - pfd->mMargin.BStart(lineWM);
+ blockEnd = blockStart + logicalBSize;
+ }
+ if (!preMode &&
+ mPresContext->CompatibilityMode() != eCompatibility_FullStandards &&
+ !logicalBSize) {
+ // Check if it's a BR frame that is not alone on its line (it
+ // is given a block size of zero to indicate this), and if so reset
+ // blockStart and blockEnd so that BR frames don't influence the line.
+ if (frame->IsBrFrame()) {
+ blockStart = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM;
+ blockEnd = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM;
+ }
+ }
+ if (blockStart < minBCoord) minBCoord = blockStart;
+ if (blockEnd > maxBCoord) maxBCoord = blockEnd;
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(
+ " [frame]raw: a=%d h=%d bp=%d,%d logical: h=%d leading=%d y=%d "
+ "minBCoord=%d maxBCoord=%d\n",
+ pfd->mAscent, pfd->mBounds.BSize(lineWM),
+ pfd->mBorderPadding.Top(lineWM), pfd->mBorderPadding.Bottom(lineWM),
+ logicalBSize, frameSpan ? frameSpan->mBStartLeading : 0,
+ pfd->mBounds.BStart(lineWM), minBCoord, maxBCoord);
+#endif
+ }
+ if (psd != mRootSpan) {
+ frame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd));
+ }
+ }
+ pfd = pfd->mNext;
+ }
+
+ // Factor in the minimum line block-size when handling the root-span for
+ // the block.
+ if (psd == mRootSpan) {
+ // We should factor in the block element's minimum line-height (as
+ // defined in section 10.8.1 of the css2 spec) assuming that
+ // zeroEffectiveSpanBox is not set on the root span. This only happens
+ // in some cases in quirks mode:
+ // (1) if the root span contains non-whitespace text directly (this
+ // is handled by zeroEffectiveSpanBox
+ // (2) if this line has a ::marker
+ // (3) if this is the last line of an LI, DT, or DD element
+ // (The last line before a block also counts, but not before a
+ // BR) (NN4/IE5 quirk)
+
+ // (1) and (2) above
+ bool applyMinLH = !zeroEffectiveSpanBox || mHasMarker;
+ bool isLastLine =
+ !mGotLineBox || (!mLineBox->IsLineWrapped() && !mLineEndsInBR);
+ if (!applyMinLH && isLastLine) {
+ nsIContent* blockContent = mRootSpan->mFrame->mFrame->GetContent();
+ if (blockContent) {
+ // (3) above, if the last line of LI, DT, or DD
+ if (blockContent->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dt,
+ nsGkAtoms::dd)) {
+ applyMinLH = true;
+ }
+ }
+ }
+ if (applyMinLH) {
+ if (psd->mHasNonemptyContent ||
+ (preMode && ShouldApplyLineHeightInPreserveWhiteSpace(psd)) ||
+ mHasMarker) {
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" [span]==> adjusting min/maxBCoord: currentValues: %d,%d",
+ minBCoord, maxBCoord);
+#endif
+ nscoord minimumLineBSize = mMinLineBSize;
+ nscoord blockStart = -nsLayoutUtils::GetCenteredFontBaseline(
+ fm, minimumLineBSize, lineWM.IsLineInverted());
+ nscoord blockEnd = blockStart + minimumLineBSize;
+
+ if (mStyleText->HasEffectiveTextEmphasis()) {
+ nscoord fontMaxHeight = fm->MaxHeight();
+ nscoord emphasisHeight =
+ GetBSizeOfEmphasisMarks(spanFrame, inflation);
+ nscoord delta = fontMaxHeight + emphasisHeight - minimumLineBSize;
+ if (delta > 0) {
+ if (minimumLineBSize < fontMaxHeight) {
+ // If the leadings are negative, fill them first.
+ nscoord ascent = fm->MaxAscent();
+ nscoord descent = fm->MaxDescent();
+ if (lineWM.IsLineInverted()) {
+ std::swap(ascent, descent);
+ }
+ blockStart = -ascent;
+ blockEnd = descent;
+ delta = emphasisHeight;
+ }
+ LogicalSide side = mStyleText->TextEmphasisSide(lineWM);
+ if (side == eLogicalSideBStart) {
+ blockStart -= delta;
+ } else {
+ blockEnd += delta;
+ }
+ }
+ }
+
+ if (blockStart < minBCoord) minBCoord = blockStart;
+ if (blockEnd > maxBCoord) maxBCoord = blockEnd;
+
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" new values: %d,%d\n", minBCoord, maxBCoord);
+#endif
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(
+ " Used mMinLineBSize: %d, blockStart: %d, blockEnd: "
+ "%d\n",
+ mMinLineBSize, blockStart, blockEnd);
+#endif
+ } else {
+ // XXX issues:
+ // [1] BR's on empty lines stop working
+ // [2] May not honor css2's notion of handling empty elements
+ // [3] blank lines in a pre-section ("\n") (handled with preMode)
+
+ // XXX Are there other problems with this?
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(
+ " [span]==> zapping min/maxBCoord: currentValues: %d,%d "
+ "newValues: 0,0\n",
+ minBCoord, maxBCoord);
+#endif
+ minBCoord = maxBCoord = 0;
+ }
+ }
+ }
+
+ if ((minBCoord == BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM) ||
+ (maxBCoord == BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM)) {
+ minBCoord = maxBCoord = baselineBCoord;
+ }
+
+ if (psd != mRootSpan && zeroEffectiveSpanBox) {
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(" [span]adjusting for zeroEffectiveSpanBox\n");
+ printf(
+ " Original: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, "
+ "logicalBSize=%d, topLeading=%d, bottomLeading=%d\n",
+ minBCoord, maxBCoord, spanFramePFD->mBounds.BSize(lineWM),
+ spanFramePFD->mAscent, psd->mLogicalBSize, psd->mBStartLeading,
+ psd->mBEndLeading);
+#endif
+ nscoord goodMinBCoord =
+ spanFramePFD->mBorderPadding.BStart(lineWM) - psd->mBStartLeading;
+ nscoord goodMaxBCoord = goodMinBCoord + psd->mLogicalBSize;
+
+ // For cases like the one in bug 714519 (text-decoration placement
+ // or making nsLineLayout::IsZeroBSize() handle
+ // vertical-align:top/bottom on a descendant of the line that's not
+ // a child of it), we want to treat elements that are
+ // vertical-align: top or bottom somewhat like children for the
+ // purposes of this quirk. To some extent, this is guessing, since
+ // they might end up being aligned anywhere. However, we'll guess
+ // that they'll be placed aligned with the top or bottom of this
+ // frame (as though this frame is the only thing in the line).
+ // (Guessing isn't unreasonable, since all we're doing is reducing the
+ // scope of a quirk and making the behavior more standards-like.)
+ if (maxStartBoxBSize > maxBCoord - minBCoord) {
+ // Distribute maxStartBoxBSize to ascent (baselineBCoord - minBCoord), and
+ // then to descent (maxBCoord - baselineBCoord) by adjusting minBCoord or
+ // maxBCoord, but not to exceed goodMinBCoord and goodMaxBCoord.
+ nscoord distribute = maxStartBoxBSize - (maxBCoord - minBCoord);
+ nscoord ascentSpace = std::max(minBCoord - goodMinBCoord, 0);
+ if (distribute > ascentSpace) {
+ distribute -= ascentSpace;
+ minBCoord -= ascentSpace;
+ nscoord descentSpace = std::max(goodMaxBCoord - maxBCoord, 0);
+ if (distribute > descentSpace) {
+ maxBCoord += descentSpace;
+ } else {
+ maxBCoord += distribute;
+ }
+ } else {
+ minBCoord -= distribute;
+ }
+ }
+ if (maxEndBoxBSize > maxBCoord - minBCoord) {
+ // Likewise, but preferring descent to ascent.
+ nscoord distribute = maxEndBoxBSize - (maxBCoord - minBCoord);
+ nscoord descentSpace = std::max(goodMaxBCoord - maxBCoord, 0);
+ if (distribute > descentSpace) {
+ distribute -= descentSpace;
+ maxBCoord += descentSpace;
+ nscoord ascentSpace = std::max(minBCoord - goodMinBCoord, 0);
+ if (distribute > ascentSpace) {
+ minBCoord -= ascentSpace;
+ } else {
+ minBCoord -= distribute;
+ }
+ } else {
+ maxBCoord += distribute;
+ }
+ }
+
+ if (minBCoord > goodMinBCoord) {
+ nscoord adjust = minBCoord - goodMinBCoord; // positive
+
+ // shrink the logical extents
+ psd->mLogicalBSize -= adjust;
+ psd->mBStartLeading -= adjust;
+ }
+ if (maxBCoord < goodMaxBCoord) {
+ nscoord adjust = goodMaxBCoord - maxBCoord;
+ psd->mLogicalBSize -= adjust;
+ psd->mBEndLeading -= adjust;
+ }
+ if (minBCoord > 0) {
+ // shrink the content by moving its block start down. This is tricky,
+ // since the block start is the 0 for many coordinates, so what we do is
+ // move everything else up.
+ spanFramePFD->mAscent -= minBCoord; // move the baseline up
+ spanFramePFD->mBounds.BSize(lineWM) -=
+ minBCoord; // move the block end up
+ psd->mBStartLeading += minBCoord;
+ *psd->mBaseline -= minBCoord;
+
+ pfd = psd->mFirstFrame;
+ while (nullptr != pfd) {
+ pfd->mBounds.BStart(lineWM) -= minBCoord; // move all the children
+ // back up
+ pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd));
+ pfd = pfd->mNext;
+ }
+ maxBCoord -= minBCoord; // since minBCoord is in the frame's own
+ // coordinate system
+ minBCoord = 0;
+ }
+ if (maxBCoord < spanFramePFD->mBounds.BSize(lineWM)) {
+ nscoord adjust = spanFramePFD->mBounds.BSize(lineWM) - maxBCoord;
+ spanFramePFD->mBounds.BSize(lineWM) -= adjust; // move the bottom up
+ psd->mBEndLeading += adjust;
+ }
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(
+ " New: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, "
+ "logicalBSize=%d, topLeading=%d, bottomLeading=%d\n",
+ minBCoord, maxBCoord, spanFramePFD->mBounds.BSize(lineWM),
+ spanFramePFD->mAscent, psd->mLogicalBSize, psd->mBStartLeading,
+ psd->mBEndLeading);
+#endif
+ }
+
+ psd->mMinBCoord = minBCoord;
+ psd->mMaxBCoord = maxBCoord;
+#ifdef NOISY_BLOCKDIR_ALIGN
+ printf(
+ " [span]==> minBCoord=%d maxBCoord=%d delta=%d maxStartBoxBSize=%d "
+ "maxEndBoxBSize=%d\n",
+ minBCoord, maxBCoord, maxBCoord - minBCoord, maxStartBoxBSize,
+ maxEndBoxBSize);
+#endif
+ if (maxStartBoxBSize > mMaxStartBoxBSize) {
+ mMaxStartBoxBSize = maxStartBoxBSize;
+ }
+ if (maxEndBoxBSize > mMaxEndBoxBSize) {
+ mMaxEndBoxBSize = maxEndBoxBSize;
+ }
+}
+
+static void SlideSpanFrameRect(nsIFrame* aFrame, nscoord aDeltaWidth) {
+ // This should not use nsIFrame::MovePositionBy because it happens
+ // prior to relative positioning. In particular, because
+ // nsBlockFrame::PlaceLine calls aLineLayout.TrimTrailingWhiteSpace()
+ // prior to calling aLineLayout.RelativePositionFrames().
+ nsPoint p = aFrame->GetPosition();
+ p.x -= aDeltaWidth;
+ aFrame->SetPosition(p);
+}
+
+bool nsLineLayout::TrimTrailingWhiteSpaceIn(PerSpanData* psd,
+ nscoord* aDeltaISize) {
+ PerFrameData* pfd = psd->mFirstFrame;
+ if (!pfd) {
+ *aDeltaISize = 0;
+ return false;
+ }
+ pfd = pfd->Last();
+ while (nullptr != pfd) {
+#ifdef REALLY_NOISY_TRIM
+ psd->mFrame->mFrame->ListTag(stdout);
+ printf(": attempting trim of ");
+ pfd->mFrame->ListTag(stdout);
+ printf("\n");
+#endif
+ PerSpanData* childSpan = pfd->mSpan;
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ if (childSpan) {
+ // Maybe the child span has the trailing white-space in it?
+ if (TrimTrailingWhiteSpaceIn(childSpan, aDeltaISize)) {
+ nscoord deltaISize = *aDeltaISize;
+ if (deltaISize) {
+ // Adjust the child spans frame size
+ pfd->mBounds.ISize(lineWM) -= deltaISize;
+ if (psd != mRootSpan) {
+ // When the child span is not a direct child of the block
+ // we need to update the child spans frame rectangle
+ // because it most likely will not be done again. Spans
+ // that are direct children of the block will be updated
+ // later, however, because the VerticalAlignFrames method
+ // will be run after this method.
+ nsSize containerSize = ContainerSizeForSpan(childSpan);
+ nsIFrame* f = pfd->mFrame;
+ LogicalRect r(lineWM, f->GetRect(), containerSize);
+ r.ISize(lineWM) -= deltaISize;
+ f->SetRect(lineWM, r, containerSize);
+ }
+
+ // Adjust the inline end edge of the span that contains the child span
+ psd->mICoord -= deltaISize;
+
+ // Slide any frames that follow the child span over by the
+ // correct amount. The only thing that can follow the child
+ // span is empty stuff, so we are just making things
+ // sensible (keeping the combined area honest).
+ while (pfd->mNext) {
+ pfd = pfd->mNext;
+ pfd->mBounds.IStart(lineWM) -= deltaISize;
+ if (psd != mRootSpan) {
+ // When the child span is not a direct child of the block
+ // we need to update the child span's frame rectangle
+ // because it most likely will not be done again. Spans
+ // that are direct children of the block will be updated
+ // later, however, because the VerticalAlignFrames method
+ // will be run after this method.
+ SlideSpanFrameRect(pfd->mFrame, deltaISize);
+ }
+ }
+ }
+ return true;
+ }
+ } else if (!pfd->mIsTextFrame && !pfd->mSkipWhenTrimmingWhitespace) {
+ // If we hit a frame on the end that's not text and not a placeholder,
+ // then there is no trailing whitespace to trim. Stop the search.
+ *aDeltaISize = 0;
+ return true;
+ } else if (pfd->mIsTextFrame) {
+ // Call TrimTrailingWhiteSpace even on empty textframes because they
+ // might have a soft hyphen which should now appear, changing the frame's
+ // width
+ nsTextFrame::TrimOutput trimOutput =
+ static_cast<nsTextFrame*>(pfd->mFrame)
+ ->TrimTrailingWhiteSpace(
+ mLineContainerRI.mRenderingContext->GetDrawTarget());
+#ifdef NOISY_TRIM
+ psd->mFrame->mFrame->ListTag(stdout);
+ printf(": trim of ");
+ pfd->mFrame->ListTag(stdout);
+ printf(" returned %d\n", trimOutput.mDeltaWidth);
+#endif
+
+ if (trimOutput.mChanged) {
+ pfd->mRecomputeOverflow = true;
+ }
+
+ // Delta width not being zero means that
+ // there is trimmed space in the frame.
+ if (trimOutput.mDeltaWidth) {
+ pfd->mBounds.ISize(lineWM) -= trimOutput.mDeltaWidth;
+
+ // If any trailing space is trimmed, the justification opportunity
+ // generated by the space should be removed as well.
+ pfd->mJustificationInfo.CancelOpportunityForTrimmedSpace();
+
+ // See if the text frame has already been placed in its parent
+ if (psd != mRootSpan) {
+ // The frame was already placed during psd's
+ // reflow. Update the frames rectangle now.
+ pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd));
+ }
+
+ // Adjust containing span's right edge
+ psd->mICoord -= trimOutput.mDeltaWidth;
+
+ // Slide any frames that follow the text frame over by the
+ // right amount. The only thing that can follow the text
+ // frame is empty stuff, so we are just making things
+ // sensible (keeping the combined area honest).
+ while (pfd->mNext) {
+ pfd = pfd->mNext;
+ pfd->mBounds.IStart(lineWM) -= trimOutput.mDeltaWidth;
+ if (psd != mRootSpan) {
+ // When the child span is not a direct child of the block
+ // we need to update the child spans frame rectangle
+ // because it most likely will not be done again. Spans
+ // that are direct children of the block will be updated
+ // later, however, because the VerticalAlignFrames method
+ // will be run after this method.
+ SlideSpanFrameRect(pfd->mFrame, trimOutput.mDeltaWidth);
+ }
+ }
+ }
+
+ if (pfd->mIsNonEmptyTextFrame || trimOutput.mChanged) {
+ // Pass up to caller so they can shrink their span
+ *aDeltaISize = trimOutput.mDeltaWidth;
+ return true;
+ }
+ }
+ pfd = pfd->mPrev;
+ }
+
+ *aDeltaISize = 0;
+ return false;
+}
+
+bool nsLineLayout::TrimTrailingWhiteSpace() {
+ PerSpanData* psd = mRootSpan;
+ nscoord deltaISize;
+ TrimTrailingWhiteSpaceIn(psd, &deltaISize);
+ return 0 != deltaISize;
+}
+
+bool nsLineLayout::PerFrameData::ParticipatesInJustification() const {
+ if (mIsMarker || mIsEmpty || mSkipWhenTrimmingWhitespace) {
+ // Skip ::markers, empty frames, and placeholders
+ return false;
+ }
+ if (mIsTextFrame && !mIsNonWhitespaceTextFrame &&
+ static_cast<nsTextFrame*>(mFrame)->IsAtEndOfLine()) {
+ // Skip trimmed whitespaces
+ return false;
+ }
+ return true;
+}
+
+struct nsLineLayout::JustificationComputationState {
+ PerFrameData* mFirstParticipant;
+ PerFrameData* mLastParticipant;
+ // When we are going across a boundary of ruby base, i.e. entering
+ // one, leaving one, or both, the following fields will be set to
+ // the corresponding ruby base frame for handling ruby-align.
+ PerFrameData* mLastExitedRubyBase;
+ PerFrameData* mLastEnteredRubyBase;
+
+ JustificationComputationState()
+ : mFirstParticipant(nullptr),
+ mLastParticipant(nullptr),
+ mLastExitedRubyBase(nullptr),
+ mLastEnteredRubyBase(nullptr) {}
+};
+
+static bool IsRubyAlignSpaceAround(nsIFrame* aRubyBase) {
+ return aRubyBase->StyleText()->mRubyAlign == StyleRubyAlign::SpaceAround;
+}
+
+/**
+ * Assign justification gaps for justification
+ * opportunities across two frames.
+ */
+/* static */
+int nsLineLayout::AssignInterframeJustificationGaps(
+ PerFrameData* aFrame, JustificationComputationState& aState) {
+ PerFrameData* prev = aState.mLastParticipant;
+ MOZ_ASSERT(prev);
+
+ auto& assign = aFrame->mJustificationAssignment;
+ auto& prevAssign = prev->mJustificationAssignment;
+
+ if (aState.mLastExitedRubyBase || aState.mLastEnteredRubyBase) {
+ PerFrameData* exitedRubyBase = aState.mLastExitedRubyBase;
+ if (!exitedRubyBase || IsRubyAlignSpaceAround(exitedRubyBase->mFrame)) {
+ prevAssign.mGapsAtEnd = 1;
+ } else {
+ exitedRubyBase->mJustificationAssignment.mGapsAtEnd = 1;
+ }
+
+ PerFrameData* enteredRubyBase = aState.mLastEnteredRubyBase;
+ if (!enteredRubyBase || IsRubyAlignSpaceAround(enteredRubyBase->mFrame)) {
+ assign.mGapsAtStart = 1;
+ } else {
+ enteredRubyBase->mJustificationAssignment.mGapsAtStart = 1;
+ }
+
+ // We are no longer going across a ruby base boundary.
+ aState.mLastExitedRubyBase = nullptr;
+ aState.mLastEnteredRubyBase = nullptr;
+ return 1;
+ }
+
+ const auto& info = aFrame->mJustificationInfo;
+ const auto& prevInfo = prev->mJustificationInfo;
+ if (!info.mIsStartJustifiable && !prevInfo.mIsEndJustifiable) {
+ return 0;
+ }
+
+ if (!info.mIsStartJustifiable) {
+ prevAssign.mGapsAtEnd = 2;
+ assign.mGapsAtStart = 0;
+ } else if (!prevInfo.mIsEndJustifiable) {
+ prevAssign.mGapsAtEnd = 0;
+ assign.mGapsAtStart = 2;
+ } else {
+ prevAssign.mGapsAtEnd = 1;
+ assign.mGapsAtStart = 1;
+ }
+ return 1;
+}
+
+/**
+ * Compute the justification info of the given span, and store the
+ * number of inner opportunities into the frame's justification info.
+ * It returns the number of non-inner opportunities it detects.
+ */
+int32_t nsLineLayout::ComputeFrameJustification(
+ PerSpanData* aPSD, JustificationComputationState& aState) {
+ NS_ASSERTION(aPSD, "null arg");
+ NS_ASSERTION(!aState.mLastParticipant || !aState.mLastParticipant->mSpan,
+ "Last participant shall always be a leaf frame");
+ bool firstChild = true;
+ int32_t& innerOpportunities =
+ aPSD->mFrame->mJustificationInfo.mInnerOpportunities;
+ MOZ_ASSERT(innerOpportunities == 0,
+ "Justification info should not have been set yet.");
+ int32_t outerOpportunities = 0;
+
+ for (PerFrameData* pfd = aPSD->mFirstFrame; pfd; pfd = pfd->mNext) {
+ if (!pfd->ParticipatesInJustification()) {
+ continue;
+ }
+
+ bool isRubyBase = pfd->mFrame->IsRubyBaseFrame();
+ PerFrameData* outerRubyBase = aState.mLastEnteredRubyBase;
+ if (isRubyBase) {
+ aState.mLastEnteredRubyBase = pfd;
+ }
+
+ int extraOpportunities = 0;
+ if (pfd->mSpan) {
+ PerSpanData* span = pfd->mSpan;
+ extraOpportunities = ComputeFrameJustification(span, aState);
+ innerOpportunities += pfd->mJustificationInfo.mInnerOpportunities;
+ } else {
+ if (pfd->mIsTextFrame) {
+ innerOpportunities += pfd->mJustificationInfo.mInnerOpportunities;
+ }
+
+ if (!aState.mLastParticipant) {
+ aState.mFirstParticipant = pfd;
+ // It is not an empty ruby base, but we are not assigning gaps
+ // to the content for now. Clear the last entered ruby base so
+ // that we can correctly set the last exited ruby base.
+ aState.mLastEnteredRubyBase = nullptr;
+ } else {
+ extraOpportunities = AssignInterframeJustificationGaps(pfd, aState);
+ }
+
+ aState.mLastParticipant = pfd;
+ }
+
+ if (isRubyBase) {
+ if (aState.mLastEnteredRubyBase == pfd) {
+ // There is no justification participant inside this ruby base.
+ // Ignore this ruby base completely and restore the outer ruby
+ // base here.
+ aState.mLastEnteredRubyBase = outerRubyBase;
+ } else {
+ aState.mLastExitedRubyBase = pfd;
+ }
+ }
+
+ if (firstChild) {
+ outerOpportunities = extraOpportunities;
+ firstChild = false;
+ } else {
+ innerOpportunities += extraOpportunities;
+ }
+ }
+
+ return outerOpportunities;
+}
+
+void nsLineLayout::AdvanceAnnotationInlineBounds(PerFrameData* aPFD,
+ const nsSize& aContainerSize,
+ nscoord aDeltaICoord,
+ nscoord aDeltaISize) {
+ nsIFrame* frame = aPFD->mFrame;
+ LayoutFrameType frameType = frame->Type();
+ MOZ_ASSERT(frameType == LayoutFrameType::RubyText ||
+ frameType == LayoutFrameType::RubyTextContainer);
+ MOZ_ASSERT(aPFD->mSpan, "rt and rtc should have span.");
+
+ PerSpanData* psd = aPFD->mSpan;
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ aPFD->mBounds.IStart(lineWM) += aDeltaICoord;
+
+ // Check whether this expansion should be counted into the reserved
+ // isize or not. When it is a ruby text container, and it has some
+ // children linked to the base, it must not have reserved isize,
+ // or its children won't align with their bases. Otherwise, this
+ // expansion should be reserved. There are two cases a ruby text
+ // container does not have children linked to the base:
+ // 1. it is a container for span; 2. its children are collapsed.
+ // See bug 1055674 for the second case.
+ if (frameType == LayoutFrameType::RubyText ||
+ // This ruby text container is a span.
+ (psd->mFirstFrame == psd->mLastFrame && psd->mFirstFrame &&
+ !psd->mFirstFrame->mIsLinkedToBase)) {
+ // For ruby text frames, only increase frames
+ // which are not auto-hidden.
+ if (frameType != LayoutFrameType::RubyText ||
+ !static_cast<nsRubyTextFrame*>(frame)->IsCollapsed()) {
+ nscoord reservedISize = RubyUtils::GetReservedISize(frame);
+ RubyUtils::SetReservedISize(frame, reservedISize + aDeltaISize);
+ }
+ } else {
+ // It is a normal ruby text container. Its children will expand
+ // themselves properly. We only need to expand its own size here.
+ aPFD->mBounds.ISize(lineWM) += aDeltaISize;
+ }
+ aPFD->mFrame->SetRect(lineWM, aPFD->mBounds, aContainerSize);
+}
+
+/**
+ * This function applies the changes of icoord and isize caused by
+ * justification to annotations of the given frame.
+ */
+void nsLineLayout::ApplyLineJustificationToAnnotations(PerFrameData* aPFD,
+ nscoord aDeltaICoord,
+ nscoord aDeltaISize) {
+ PerFrameData* pfd = aPFD->mNextAnnotation;
+ while (pfd) {
+ nsSize containerSize = pfd->mFrame->GetParent()->GetSize();
+ AdvanceAnnotationInlineBounds(pfd, containerSize, aDeltaICoord,
+ aDeltaISize);
+
+ // There are two cases where an annotation frame has siblings which
+ // do not attached to a ruby base-level frame:
+ // 1. there's an intra-annotation whitespace which has no intra-base
+ // white-space to pair with;
+ // 2. there are not enough ruby bases to be paired with annotations.
+ // In these cases, their size should not be affected, but we still
+ // need to move them so that they won't overlap other frames.
+ PerFrameData* sibling = pfd->mNext;
+ while (sibling && !sibling->mIsLinkedToBase) {
+ AdvanceAnnotationInlineBounds(sibling, containerSize,
+ aDeltaICoord + aDeltaISize, 0);
+ sibling = sibling->mNext;
+ }
+
+ pfd = pfd->mNextAnnotation;
+ }
+}
+
+nscoord nsLineLayout::ApplyFrameJustification(
+ PerSpanData* aPSD, JustificationApplicationState& aState) {
+ NS_ASSERTION(aPSD, "null arg");
+
+ nscoord deltaICoord = 0;
+ for (PerFrameData* pfd = aPSD->mFirstFrame; pfd != nullptr;
+ pfd = pfd->mNext) {
+ nscoord dw = 0;
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ const auto& assign = pfd->mJustificationAssignment;
+ bool isInlineText =
+ pfd->mIsTextFrame && !pfd->mWritingMode.IsOrthogonalTo(lineWM);
+
+ // Don't apply justification if the frame doesn't participate. Same
+ // as the condition used in ComputeFrameJustification. Note that,
+ // we still need to move the frame based on deltaICoord even if the
+ // frame itself doesn't expand.
+ if (pfd->ParticipatesInJustification()) {
+ if (isInlineText) {
+ if (aState.IsJustifiable()) {
+ // Set corresponding justification gaps here, so that the
+ // text frame knows how it should add gaps at its sides.
+ const auto& info = pfd->mJustificationInfo;
+ auto textFrame = static_cast<nsTextFrame*>(pfd->mFrame);
+ textFrame->AssignJustificationGaps(assign);
+ dw = aState.Consume(JustificationUtils::CountGaps(info, assign));
+ }
+
+ if (dw) {
+ pfd->mRecomputeOverflow = true;
+ }
+ } else {
+ if (nullptr != pfd->mSpan) {
+ dw = ApplyFrameJustification(pfd->mSpan, aState);
+ }
+ }
+ } else {
+ MOZ_ASSERT(!assign.TotalGaps(),
+ "Non-participants shouldn't have assigned gaps");
+ }
+
+ pfd->mBounds.ISize(lineWM) += dw;
+ nscoord gapsAtEnd = 0;
+ if (!isInlineText && assign.TotalGaps()) {
+ // It is possible that we assign gaps to non-text frame or an
+ // orthogonal text frame. Apply the gaps as margin for them.
+ deltaICoord += aState.Consume(assign.mGapsAtStart);
+ gapsAtEnd = aState.Consume(assign.mGapsAtEnd);
+ dw += gapsAtEnd;
+ }
+ pfd->mBounds.IStart(lineWM) += deltaICoord;
+
+ // The gaps added to the end of the frame should also be
+ // excluded from the isize added to the annotation.
+ ApplyLineJustificationToAnnotations(pfd, deltaICoord, dw - gapsAtEnd);
+ deltaICoord += dw;
+ pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(aPSD));
+ }
+ return deltaICoord;
+}
+
+static nsIFrame* FindNearestRubyBaseAncestor(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->Style()->ShouldSuppressLineBreak());
+ while (aFrame && !aFrame->IsRubyBaseFrame()) {
+ aFrame = aFrame->GetParent();
+ }
+ // XXX It is possible that no ruby base ancestor is found because of
+ // some edge cases like form control or canvas inside ruby text.
+ // See bug 1138092 comment 4.
+ NS_WARNING_ASSERTION(aFrame, "no ruby base ancestor?");
+ return aFrame;
+}
+
+/**
+ * This method expands the given frame by the given reserved isize.
+ */
+void nsLineLayout::ExpandRubyBox(PerFrameData* aFrame, nscoord aReservedISize,
+ const nsSize& aContainerSize) {
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ auto rubyAlign = aFrame->mFrame->StyleText()->mRubyAlign;
+ switch (rubyAlign) {
+ case StyleRubyAlign::Start:
+ // do nothing for start
+ break;
+ case StyleRubyAlign::SpaceBetween:
+ case StyleRubyAlign::SpaceAround: {
+ int32_t opportunities = aFrame->mJustificationInfo.mInnerOpportunities;
+ int32_t gaps = opportunities * 2;
+ if (rubyAlign == StyleRubyAlign::SpaceAround) {
+ // Each expandable ruby box with ruby-align space-around has a
+ // gap at each of its sides. For rb/rbc, see comment in
+ // AssignInterframeJustificationGaps; for rt/rtc, see comment
+ // in ExpandRubyBoxWithAnnotations.
+ gaps += 2;
+ }
+ if (gaps > 0) {
+ JustificationApplicationState state(gaps, aReservedISize);
+ ApplyFrameJustification(aFrame->mSpan, state);
+ break;
+ }
+ // If there are no justification opportunities for space-between,
+ // fall-through to center per spec.
+ [[fallthrough]];
+ }
+ case StyleRubyAlign::Center:
+ // Indent all children by half of the reserved inline size.
+ for (PerFrameData* child = aFrame->mSpan->mFirstFrame; child;
+ child = child->mNext) {
+ child->mBounds.IStart(lineWM) += aReservedISize / 2;
+ child->mFrame->SetRect(lineWM, child->mBounds, aContainerSize);
+ }
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown ruby-align value");
+ }
+
+ aFrame->mBounds.ISize(lineWM) += aReservedISize;
+ aFrame->mFrame->SetRect(lineWM, aFrame->mBounds, aContainerSize);
+}
+
+/**
+ * This method expands the given frame by the reserved inline size.
+ * It also expands its annotations if they are expandable and have
+ * reserved isize larger than zero.
+ */
+void nsLineLayout::ExpandRubyBoxWithAnnotations(PerFrameData* aFrame,
+ const nsSize& aContainerSize) {
+ nscoord reservedISize = RubyUtils::GetReservedISize(aFrame->mFrame);
+ if (reservedISize) {
+ ExpandRubyBox(aFrame, reservedISize, aContainerSize);
+ }
+
+ WritingMode lineWM = mRootSpan->mWritingMode;
+ bool isLevelContainer = aFrame->mFrame->IsRubyBaseContainerFrame();
+ for (PerFrameData* annotation = aFrame->mNextAnnotation; annotation;
+ annotation = annotation->mNextAnnotation) {
+ if (lineWM.IsOrthogonalTo(annotation->mFrame->GetWritingMode())) {
+ // Inter-character case: don't attempt to expand ruby annotations.
+ continue;
+ }
+ if (isLevelContainer) {
+ nsIFrame* rtcFrame = annotation->mFrame;
+ MOZ_ASSERT(rtcFrame->IsRubyTextContainerFrame());
+ // It is necessary to set the rect again because the container
+ // width was unknown, and zero was used instead when we reflow
+ // them. The corresponding base containers were repositioned in
+ // VerticalAlignFrames and PlaceTopBottomFrames.
+ MOZ_ASSERT(rtcFrame->GetLogicalSize(lineWM) ==
+ annotation->mBounds.Size(lineWM));
+ rtcFrame->SetPosition(lineWM, annotation->mBounds.Origin(lineWM),
+ aContainerSize);
+ }
+
+ nscoord reservedISize = RubyUtils::GetReservedISize(annotation->mFrame);
+ if (!reservedISize) {
+ continue;
+ }
+
+ MOZ_ASSERT(annotation->mSpan);
+ JustificationComputationState computeState;
+ ComputeFrameJustification(annotation->mSpan, computeState);
+ if (!computeState.mFirstParticipant) {
+ continue;
+ }
+ if (IsRubyAlignSpaceAround(annotation->mFrame)) {
+ // Add one gap at each side of this annotation.
+ computeState.mFirstParticipant->mJustificationAssignment.mGapsAtStart = 1;
+ computeState.mLastParticipant->mJustificationAssignment.mGapsAtEnd = 1;
+ }
+ nsIFrame* parentFrame = annotation->mFrame->GetParent();
+ nsSize containerSize = parentFrame->GetSize();
+ MOZ_ASSERT(containerSize == aContainerSize ||
+ parentFrame->IsRubyTextContainerFrame(),
+ "Container width should only be different when the current "
+ "annotation is a ruby text frame, whose parent is not same "
+ "as its base frame.");
+ ExpandRubyBox(annotation, reservedISize, containerSize);
+ ExpandInlineRubyBoxes(annotation->mSpan);
+ }
+}
+
+/**
+ * This method looks for all expandable ruby box in the given span, and
+ * calls ExpandRubyBox to expand them in depth-first preorder.
+ */
+void nsLineLayout::ExpandInlineRubyBoxes(PerSpanData* aSpan) {
+ nsSize containerSize = ContainerSizeForSpan(aSpan);
+ for (PerFrameData* pfd = aSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
+ if (RubyUtils::IsExpandableRubyBox(pfd->mFrame)) {
+ ExpandRubyBoxWithAnnotations(pfd, containerSize);
+ }
+ if (pfd->mSpan) {
+ ExpandInlineRubyBoxes(pfd->mSpan);
+ }
+ }
+}
+
+nscoord nsLineLayout::GetHangFrom(const PerSpanData* aSpan,
+ bool aLineIsRTL) const {
+ const PerFrameData* pfd = aSpan->mLastFrame;
+ nscoord result = 0;
+ while (pfd) {
+ if (const PerSpanData* childSpan = pfd->mSpan) {
+ return GetHangFrom(childSpan, aLineIsRTL);
+ }
+ if (pfd->mIsTextFrame) {
+ auto* lastText = static_cast<nsTextFrame*>(pfd->mFrame);
+ result = lastText->GetHangableISize();
+ if (result) {
+ // If the hangable space will be at the start edge of the line, due to
+ // its bidi direction being against the line direction, we flag this by
+ // negating the advance.
+ lastText->EnsureTextRun(nsTextFrame::eInflated);
+ auto* textRun = lastText->GetTextRun(nsTextFrame::eInflated);
+ if (textRun && textRun->IsRightToLeft() != aLineIsRTL) {
+ result = -result;
+ }
+ }
+ return result;
+ }
+ if (!pfd->mSkipWhenTrimmingWhitespace) {
+ // If we hit a frame on the end that's not text and not a placeholder or
+ // <br>, then there is no trailing whitespace to hang. Stop the search.
+ return result;
+ }
+ // Scan back for a preceding frame whose whitespace we can hang.
+ pfd = pfd->mPrev;
+ }
+ return result;
+}
+
+gfxTextRun::TrimmableWS nsLineLayout::GetTrimFrom(const PerSpanData* aSpan,
+ bool aLineIsRTL) const {
+ const PerFrameData* pfd = aSpan->mLastFrame;
+ while (pfd) {
+ if (const PerSpanData* childSpan = pfd->mSpan) {
+ return GetTrimFrom(childSpan, aLineIsRTL);
+ }
+ if (pfd->mIsTextFrame) {
+ auto* lastText = static_cast<nsTextFrame*>(pfd->mFrame);
+ auto result = lastText->GetTrimmableWS();
+ if (result.mAdvance) {
+ lastText->EnsureTextRun(nsTextFrame::eInflated);
+ auto* textRun = lastText->GetTextRun(nsTextFrame::eInflated);
+ if (textRun && textRun->IsRightToLeft() != aLineIsRTL) {
+ result.mAdvance = -result.mAdvance;
+ }
+ }
+ return result;
+ }
+ if (!pfd->mSkipWhenTrimmingWhitespace) {
+ // If we hit a frame on the end that's not text and not a placeholder or
+ // <br>, then there is no trailing whitespace to trim. Stop the search.
+ return gfxTextRun::TrimmableWS{};
+ }
+ // Scan back for a preceding frame whose whitespace we can trim.
+ pfd = pfd->mPrev;
+ }
+ return gfxTextRun::TrimmableWS{};
+}
+
+// Align inline frames within the line according to the CSS text-align
+// property.
+void nsLineLayout::TextAlignLine(nsLineBox* aLine, bool aIsLastLine) {
+ /**
+ * NOTE: aIsLastLine ain't necessarily so: it is correctly set by caller
+ * only in cases where the last line needs special handling.
+ */
+ PerSpanData* psd = mRootSpan;
+ WritingMode lineWM = psd->mWritingMode;
+ LAYOUT_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained width; this should only result from "
+ "very large sizes, not attempts at intrinsic width "
+ "calculation");
+ nscoord availISize = psd->mIEnd - psd->mIStart;
+ nscoord remainingISize = availISize - aLine->ISize();
+#ifdef NOISY_INLINEDIR_ALIGN
+ LineContainerFrame()->ListTag(stdout);
+ printf(": availISize=%d lineBounds.IStart=%d lineISize=%d delta=%d\n",
+ availISize, aLine->IStart(), aLine->ISize(), remainingISize);
+#endif
+
+ nscoord dx = 0;
+ StyleTextAlign textAlign =
+ aIsLastLine ? mStyleText->TextAlignForLastLine() : mStyleText->mTextAlign;
+
+ // Check if there's trailing whitespace we need to "hang" at line-wrap.
+ nscoord hang = 0;
+ uint32_t trimCount = 0;
+ if (aLine->IsLineWrapped()) {
+ if (textAlign == StyleTextAlign::Justify) {
+ auto trim = GetTrimFrom(mRootSpan, lineWM.IsBidiRTL());
+ hang = NSToCoordRound(trim.mAdvance);
+ trimCount = trim.mCount;
+ } else {
+ hang = GetHangFrom(mRootSpan, lineWM.IsBidiRTL());
+ }
+ }
+
+ bool isSVG = LineContainerFrame()->IsInSVGTextSubtree();
+ bool doTextAlign = remainingISize > 0 || hang != 0;
+
+ int32_t additionalGaps = 0;
+ if (!isSVG &&
+ (mHasRuby || (doTextAlign && textAlign == StyleTextAlign::Justify))) {
+ JustificationComputationState computeState;
+ ComputeFrameJustification(psd, computeState);
+ if (mHasRuby && computeState.mFirstParticipant) {
+ PerFrameData* firstFrame = computeState.mFirstParticipant;
+ if (firstFrame->mFrame->Style()->ShouldSuppressLineBreak()) {
+ MOZ_ASSERT(!firstFrame->mJustificationAssignment.mGapsAtStart);
+ nsIFrame* rubyBase = FindNearestRubyBaseAncestor(firstFrame->mFrame);
+ if (rubyBase && IsRubyAlignSpaceAround(rubyBase)) {
+ firstFrame->mJustificationAssignment.mGapsAtStart = 1;
+ additionalGaps++;
+ }
+ }
+ PerFrameData* lastFrame = computeState.mLastParticipant;
+ if (lastFrame->mFrame->Style()->ShouldSuppressLineBreak()) {
+ MOZ_ASSERT(!lastFrame->mJustificationAssignment.mGapsAtEnd);
+ nsIFrame* rubyBase = FindNearestRubyBaseAncestor(lastFrame->mFrame);
+ if (rubyBase && IsRubyAlignSpaceAround(rubyBase)) {
+ lastFrame->mJustificationAssignment.mGapsAtEnd = 1;
+ additionalGaps++;
+ }
+ }
+ }
+ }
+
+ if (!isSVG && doTextAlign) {
+ switch (textAlign) {
+ case StyleTextAlign::Justify: {
+ int32_t opportunities =
+ psd->mFrame->mJustificationInfo.mInnerOpportunities -
+ (hang ? trimCount : 0);
+ if (opportunities > 0) {
+ int32_t gaps = opportunities * 2 + additionalGaps;
+ remainingISize += std::abs(hang);
+ JustificationApplicationState applyState(gaps, remainingISize);
+
+ // Apply the justification, and make sure to update our linebox
+ // width to account for it.
+ aLine->ExpandBy(ApplyFrameJustification(psd, applyState),
+ ContainerSizeForSpan(psd));
+
+ // If the trimmable trailing whitespace that we want to hang had
+ // reverse-inline directionality, adjust line position to account for
+ // it being at the inline-start side.
+ // On top of the original "hang" amount, justification will have
+ // modified its width, so we include that adjustment here.
+ if (hang < 0) {
+ dx = hang - trimCount * remainingISize / opportunities;
+ }
+
+ // Gaps that belong to trimmed whitespace were not included in the
+ // applyState count, so we need to add them here for the assert.
+ DebugOnly<int32_t> trimmedGaps = hang ? trimCount * 2 : 0;
+ MOZ_ASSERT(applyState.mGaps.mHandled ==
+ applyState.mGaps.mCount + trimmedGaps,
+ "Unprocessed justification gaps");
+ // Similarly, account for the adjustment applied to the trimmed
+ // whitespace, which is in addition to the adjustment that applies
+ // within the actual width of the line.
+ DebugOnly<int32_t> trimmedAdjustment =
+ trimCount * remainingISize / opportunities;
+ NS_ASSERTION(applyState.mWidth.mConsumed ==
+ applyState.mWidth.mAvailable + trimmedAdjustment,
+ "Unprocessed justification width");
+ break;
+ }
+ // Fall through to the default case if we could not justify to fill
+ // the space.
+ [[fallthrough]];
+ }
+
+ case StyleTextAlign::Start:
+ case StyleTextAlign::Char:
+ // Default alignment is to start edge so do nothing, except to apply
+ // any "reverse-hang" amount resulting from reversed-direction trailing
+ // space.
+ // Char is for tables so treat as start if we find it in block layout.
+ if (hang < 0) {
+ dx = hang;
+ }
+ break;
+
+ case StyleTextAlign::Left:
+ case StyleTextAlign::MozLeft:
+ if (lineWM.IsBidiRTL()) {
+ dx = remainingISize + (hang > 0 ? hang : 0);
+ } else if (hang < 0) {
+ dx = hang;
+ }
+ break;
+
+ case StyleTextAlign::Right:
+ case StyleTextAlign::MozRight:
+ if (lineWM.IsBidiLTR()) {
+ dx = remainingISize + (hang > 0 ? hang : 0);
+ } else if (hang < 0) {
+ dx = hang;
+ }
+ break;
+
+ case StyleTextAlign::End:
+ dx = remainingISize + (hang > 0 ? hang : 0);
+ break;
+
+ case StyleTextAlign::Center:
+ case StyleTextAlign::MozCenter:
+ dx = (remainingISize + hang) / 2;
+ break;
+ }
+ }
+
+ if (mHasRuby) {
+ ExpandInlineRubyBoxes(mRootSpan);
+ }
+
+ PerFrameData* startFrame = psd->mFirstFrame;
+ MOZ_ASSERT(startFrame, "empty line?");
+ if (startFrame->mIsMarker) {
+ // ::marker shouldn't participate in bidi reordering nor text alignment.
+ startFrame = startFrame->mNext;
+ MOZ_ASSERT(startFrame, "no frame after ::marker?");
+ MOZ_ASSERT(!startFrame->mIsMarker, "multiple ::markers?");
+ }
+
+ const bool bidi = mPresContext->BidiEnabled() &&
+ (!mPresContext->IsVisualMode() || lineWM.IsBidiRTL());
+ if (bidi) {
+ nsBidiPresUtils::ReorderFrames(startFrame->mFrame, aLine->GetChildCount(),
+ lineWM, mContainerSize,
+ psd->mIStart + mTextIndent + dx);
+ }
+
+ if (dx) {
+ // For the bidi case, if startFrame is a ::first-line frame, the mIStart and
+ // mTextIndent offsets will already have been applied to its position, but
+ // we still need to apply the text-align adjustment |dx| to its position.
+ const bool needToAdjustFrames = !bidi || startFrame->mFrame->IsLineFrame();
+ MOZ_ASSERT_IF(startFrame->mFrame->IsLineFrame(), !startFrame->mNext);
+ if (needToAdjustFrames) {
+ for (PerFrameData* pfd = startFrame; pfd; pfd = pfd->mNext) {
+ pfd->mBounds.IStart(lineWM) += dx;
+ pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd));
+ }
+ }
+ aLine->IndentBy(dx, ContainerSize());
+ }
+}
+
+// This method applies any relative positioning to the given frame.
+void nsLineLayout::ApplyRelativePositioning(PerFrameData* aPFD) {
+ if (!aPFD->mIsRelativelyOrStickyPos) {
+ return;
+ }
+
+ nsIFrame* frame = aPFD->mFrame;
+ WritingMode frameWM = aPFD->mWritingMode;
+ LogicalPoint origin = frame->GetLogicalPosition(ContainerSize());
+ // right and bottom are handled by
+ // ReflowInput::ComputeRelativeOffsets
+ ReflowInput::ApplyRelativePositioning(frame, frameWM, aPFD->mOffsets, &origin,
+ ContainerSize());
+ frame->SetPosition(frameWM, origin, ContainerSize());
+}
+
+// This method do relative positioning for ruby annotations.
+void nsLineLayout::RelativePositionAnnotations(PerSpanData* aRubyPSD,
+ OverflowAreas& aOverflowAreas) {
+ MOZ_ASSERT(aRubyPSD->mFrame->mFrame->IsRubyFrame());
+ for (PerFrameData* pfd = aRubyPSD->mFirstFrame; pfd; pfd = pfd->mNext) {
+ MOZ_ASSERT(pfd->mFrame->IsRubyBaseContainerFrame());
+ for (PerFrameData* rtc = pfd->mNextAnnotation; rtc;
+ rtc = rtc->mNextAnnotation) {
+ nsIFrame* rtcFrame = rtc->mFrame;
+ MOZ_ASSERT(rtcFrame->IsRubyTextContainerFrame());
+ ApplyRelativePositioning(rtc);
+ OverflowAreas rtcOverflowAreas;
+ RelativePositionFrames(rtc->mSpan, rtcOverflowAreas);
+ aOverflowAreas.UnionWith(rtcOverflowAreas + rtcFrame->GetPosition());
+ }
+ }
+}
+
+void nsLineLayout::RelativePositionFrames(PerSpanData* psd,
+ OverflowAreas& aOverflowAreas) {
+ OverflowAreas overflowAreas;
+ WritingMode wm = psd->mWritingMode;
+ if (psd != mRootSpan) {
+ // The span's overflow areas come in three parts:
+ // -- this frame's width and height
+ // -- pfd->mOverflowAreas, which is the area of a ::marker or the union
+ // of a relatively positioned frame's absolute children
+ // -- the bounds of all inline descendants
+ // The former two parts are computed right here, we gather the descendants
+ // below.
+ // At this point psd->mFrame->mBounds might be out of date since
+ // bidi reordering can move and resize the frames. So use the frame's
+ // rect instead of mBounds.
+ nsRect adjustedBounds(nsPoint(0, 0), psd->mFrame->mFrame->GetSize());
+
+ overflowAreas.ScrollableOverflow().UnionRect(
+ psd->mFrame->mOverflowAreas.ScrollableOverflow(), adjustedBounds);
+ overflowAreas.InkOverflow().UnionRect(
+ psd->mFrame->mOverflowAreas.InkOverflow(), adjustedBounds);
+ } else {
+ LogicalRect rect(wm, psd->mIStart, mBStartEdge, psd->mICoord - psd->mIStart,
+ mFinalLineBSize);
+ // The minimum combined area for the frames that are direct
+ // children of the block starts at the upper left corner of the
+ // line and is sized to match the size of the line's bounding box
+ // (the same size as the values returned from VerticalAlignFrames)
+ overflowAreas.InkOverflow() = rect.GetPhysicalRect(wm, ContainerSize());
+ overflowAreas.ScrollableOverflow() = overflowAreas.InkOverflow();
+ }
+
+ for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
+ nsIFrame* frame = pfd->mFrame;
+
+ // Adjust the origin of the frame
+ ApplyRelativePositioning(pfd);
+
+ // We must position the view correctly before positioning its
+ // descendants so that widgets are positioned properly (since only
+ // some views have widgets).
+ if (frame->HasView())
+ nsContainerFrame::SyncFrameViewAfterReflow(
+ mPresContext, frame, frame->GetView(),
+ pfd->mOverflowAreas.InkOverflow(),
+ nsIFrame::ReflowChildFlags::NoSizeView);
+
+ // Note: the combined area of a child is in its coordinate
+ // system. We adjust the childs combined area into our coordinate
+ // system before computing the aggregated value by adding in
+ // <b>x</b> and <b>y</b> which were computed above.
+ OverflowAreas r;
+ if (pfd->mSpan) {
+ // Compute a new combined area for the child span before
+ // aggregating it into our combined area.
+ RelativePositionFrames(pfd->mSpan, r);
+ } else {
+ r = pfd->mOverflowAreas;
+ if (pfd->mIsTextFrame) {
+ // We need to recompute overflow areas in four cases:
+ // (1) When PFD_RECOMPUTEOVERFLOW is set due to trimming
+ // (2) When there are text decorations, since we can't recompute the
+ // overflow area until Reflow and VerticalAlignLine have finished
+ // (3) When there are text emphasis marks, since the marks may be
+ // put further away if the text is inside ruby.
+ // (4) When there are text strokes
+ if (pfd->mRecomputeOverflow ||
+ frame->Style()->HasTextDecorationLines() ||
+ frame->StyleText()->HasEffectiveTextEmphasis() ||
+ frame->StyleText()->HasWebkitTextStroke()) {
+ nsTextFrame* f = static_cast<nsTextFrame*>(frame);
+ r = f->RecomputeOverflow(LineContainerFrame());
+ }
+ frame->FinishAndStoreOverflow(r, frame->GetSize());
+ }
+
+ // If we have something that's not an inline but with a complex frame
+ // hierarchy inside that contains views, they need to be
+ // positioned.
+ // All descendant views must be repositioned even if this frame
+ // does have a view in case this frame's view does not have a
+ // widget and some of the descendant views do have widgets --
+ // otherwise the widgets won't be repositioned.
+ nsContainerFrame::PositionChildViews(frame);
+ }
+
+ // Do this here (rather than along with setting the overflow rect
+ // below) so we get leaf frames as well. No need to worry
+ // about the root span, since it doesn't have a frame.
+ if (frame->HasView())
+ nsContainerFrame::SyncFrameViewAfterReflow(
+ mPresContext, frame, frame->GetView(), r.InkOverflow(),
+ nsIFrame::ReflowChildFlags::NoMoveView);
+
+ overflowAreas.UnionWith(r + frame->GetPosition());
+ }
+
+ // Also compute relative position in the annotations.
+ if (psd->mFrame->mFrame->IsRubyFrame()) {
+ RelativePositionAnnotations(psd, overflowAreas);
+ }
+
+ // If we just computed a spans combined area, we need to update its
+ // overflow rect...
+ if (psd != mRootSpan) {
+ PerFrameData* spanPFD = psd->mFrame;
+ nsIFrame* frame = spanPFD->mFrame;
+ frame->FinishAndStoreOverflow(overflowAreas, frame->GetSize());
+ }
+ aOverflowAreas = overflowAreas;
+}
diff --git a/layout/generic/nsLineLayout.h b/layout/generic/nsLineLayout.h
new file mode 100644
index 0000000000..03140dc4e4
--- /dev/null
+++ b/layout/generic/nsLineLayout.h
@@ -0,0 +1,709 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* state and methods used while laying out a single line of a block frame */
+
+#ifndef nsLineLayout_h___
+#define nsLineLayout_h___
+
+#include "gfxTypes.h"
+#include "gfxTextRun.h"
+#include "JustificationUtils.h"
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/WritingModes.h"
+#include "BlockReflowState.h"
+#include "nsLineBox.h"
+
+class nsFloatManager;
+struct nsStyleText;
+
+class nsLineLayout {
+ using BlockReflowState = mozilla::BlockReflowState;
+ using ReflowInput = mozilla::ReflowInput;
+ using ReflowOutput = mozilla::ReflowOutput;
+
+ public:
+ /**
+ * @param aBaseLineLayout the nsLineLayout for ruby base,
+ * nullptr if no separate base nsLineLayout is needed.
+ */
+ nsLineLayout(nsPresContext* aPresContext, nsFloatManager* aFloatManager,
+ const ReflowInput& aLineContainerRI,
+ const nsLineList::iterator* aLine,
+ nsLineLayout* aBaseLineLayout);
+ ~nsLineLayout();
+
+ void Init(BlockReflowState* aState, nscoord aMinLineBSize,
+ int32_t aLineNumber) {
+ mBlockRS = aState;
+ mMinLineBSize = aMinLineBSize;
+ mLineNumber = aLineNumber;
+ }
+
+ int32_t GetLineNumber() const { return mLineNumber; }
+
+ void BeginLineReflow(nscoord aICoord, nscoord aBCoord, nscoord aISize,
+ nscoord aBSize, bool aImpactedByFloats,
+ bool aIsTopOfPage, mozilla::WritingMode aWritingMode,
+ const nsSize& aContainerSize,
+ // aInset is used during text-wrap:balance to reduce
+ // the effective available space on the line.
+ nscoord aInset = 0);
+
+ /**
+ * Returns true if the line had to use an overflow-wrap break position.
+ */
+ bool EndLineReflow();
+
+ /**
+ * Called when a float has been placed. This method updates the
+ * inline frame and span data to account for any change in positions
+ * due to available space for the line boxes changing.
+ * @param aX/aY/aWidth/aHeight are the new available
+ * space rectangle, relative to the containing block.
+ * @param aFloatFrame the float frame that was placed.
+ */
+ void UpdateBand(mozilla::WritingMode aWM,
+ const mozilla::LogicalRect& aNewAvailableSpace,
+ nsIFrame* aFloatFrame);
+
+ void BeginSpan(nsIFrame* aFrame, const ReflowInput* aSpanReflowInput,
+ nscoord aLeftEdge, nscoord aRightEdge, nscoord* aBaseline);
+
+ // Returns the width of the span
+ nscoord EndSpan(nsIFrame* aFrame);
+
+ // This method attaches the last frame reflowed in this line layout
+ // to that in the base line layout.
+ void AttachLastFrameToBaseLineLayout() {
+ AttachFrameToBaseLineLayout(LastFrame());
+ }
+
+ // This method attaches the root frame of this line layout to the
+ // last reflowed frame in the base line layout.
+ void AttachRootFrameToBaseLineLayout() {
+ AttachFrameToBaseLineLayout(mRootSpan->mFrame);
+ }
+
+ int32_t GetCurrentSpanCount() const;
+
+ void SplitLineTo(int32_t aNewCount);
+
+ bool IsZeroBSize();
+
+ // Reflows the frame and returns the reflow status. aPushedFrame is true
+ // if the frame is pushed to the next line because it doesn't fit.
+ void ReflowFrame(nsIFrame* aFrame, nsReflowStatus& aReflowStatus,
+ ReflowOutput* aMetrics, bool& aPushedFrame);
+
+ void AddMarkerFrame(nsIFrame* aFrame, const ReflowOutput& aMetrics);
+
+ void RemoveMarkerFrame(nsIFrame* aFrame);
+
+ /**
+ * Place frames in the block direction (CSS property vertical-align)
+ */
+ void VerticalAlignLine();
+
+ bool TrimTrailingWhiteSpace();
+
+ /**
+ * Place frames in the inline direction (CSS property text-align).
+ */
+ void TextAlignLine(nsLineBox* aLine, bool aIsLastLine);
+
+ /**
+ * Handle all the relative positioning in the line, compute the
+ * combined area (== overflow area) for the line, and handle view
+ * sizing/positioning and the setting of the overflow rect.
+ */
+ void RelativePositionFrames(mozilla::OverflowAreas& aOverflowAreas) {
+ RelativePositionFrames(mRootSpan, aOverflowAreas);
+ }
+
+ // Support methods for word-wrapping during line reflow
+
+ void SetJustificationInfo(const mozilla::JustificationInfo& aInfo) {
+ mJustificationInfo = aInfo;
+ }
+
+ /**
+ * @return true if so far during reflow no non-empty content has been
+ * placed in the line (according to nsIFrame::IsEmpty())
+ */
+ bool LineIsEmpty() const { return mLineIsEmpty; }
+
+ /**
+ * @return true if so far during reflow no non-empty leaf content
+ * (non-collapsed whitespace, replaced element, inline-block, etc) has been
+ * placed in the line
+ */
+ bool LineAtStart() const { return mLineAtStart; }
+
+ bool LineIsBreakable() const;
+
+ bool GetLineEndsInBR() const { return mLineEndsInBR; }
+
+ void SetLineEndsInBR(bool aOn) { mLineEndsInBR = aOn; }
+
+ //----------------------------------------
+ // Inform the line-layout about the presence of a floating frame
+ // XXX get rid of this: use get-frame-type?
+ bool AddFloat(nsIFrame* aFloat, nscoord aAvailableISize) {
+ // When reflowing ruby text frames, no block reflow state is
+ // provided to the line layout. However, floats should never be
+ // associated with ruby text containers, hence this method should
+ // not be called in that case.
+ MOZ_ASSERT(mBlockRS,
+ "Should not call this method if there is no block reflow state "
+ "available");
+ return mBlockRS->AddFloat(this, aFloat, aAvailableISize);
+ }
+
+ void SetTrimmableISize(nscoord aTrimmableISize) {
+ mTrimmableISize = aTrimmableISize;
+ }
+
+ //----------------------------------------
+
+ bool GetFirstLetterStyleOK() const { return mFirstLetterStyleOK; }
+
+ void SetFirstLetterStyleOK(bool aSetting) { mFirstLetterStyleOK = aSetting; }
+
+ bool GetInFirstLetter() const { return mInFirstLetter; }
+
+ void SetInFirstLetter(bool aSetting) { mInFirstLetter = aSetting; }
+
+ bool GetInFirstLine() const { return mInFirstLine; }
+
+ void SetInFirstLine(bool aSetting) { mInFirstLine = aSetting; }
+
+ // Calling this during block reflow ensures that the next line of inlines
+ // will be marked dirty, if there is one.
+ void SetDirtyNextLine() { mDirtyNextLine = true; }
+ bool GetDirtyNextLine() { return mDirtyNextLine; }
+
+ //----------------------------------------
+
+ nsPresContext* mPresContext;
+
+ /**
+ * Record where an optional break could have been placed. During line reflow,
+ * frames containing optional break points (e.g., whitespace in text frames)
+ * can call SetLastOptionalBreakPosition to record where a break could
+ * have been made, but wasn't because we decided to place more content on
+ * the line. For non-text frames, offset 0 means before the frame, offset
+ * INT32_MAX means after the frame.
+ *
+ * Currently this is used to handle cases where a single word comprises
+ * multiple frames, and the first frame fits on the line but the whole word
+ * doesn't. We look back to the last optional break position and
+ * reflow the whole line again, forcing a break at that position. The last
+ * optional break position could be in a text frame or else after a frame
+ * that cannot be part of a text run, so those are the positions we record.
+ *
+ * @param aFrame the frame which contains the optional break position.
+ *
+ * @param aFits set to true if the break position is within the available
+ * width.
+ *
+ * @param aPriority the priority of the break opportunity. If we are
+ * prioritizing break opportunities, we will not set a break if we have
+ * already set a break with a higher priority. @see gfxBreakPriority.
+ *
+ * @return true if we are actually reflowing with forced break position and we
+ * should break here
+ */
+ bool NotifyOptionalBreakPosition(nsIFrame* aFrame, int32_t aOffset,
+ bool aFits, gfxBreakPriority aPriority);
+
+ // Tries to place a float, and records whether the float actually was placed.
+ bool TryToPlaceFloat(nsIFrame* aFloat);
+
+ // Records a floating frame in a nowrap context for it to be placed on the
+ // next break opportunity.
+ void RecordNoWrapFloat(nsIFrame* aFloat);
+
+ // Tries to place the floats from the nowrap context.
+ void FlushNoWrapFloats();
+
+ /**
+ * Like NotifyOptionalBreakPosition, but here it's OK for mNeedBackup
+ * to be set, because the caller is merely pruning some saved break
+ * position(s) that are actually not feasible.
+ */
+ void RestoreSavedBreakPosition(nsIFrame* aFrame, int32_t aOffset,
+ gfxBreakPriority aPriority) {
+ mLastOptionalBreakFrame = aFrame;
+ mLastOptionalBreakFrameOffset = aOffset;
+ mLastOptionalBreakPriority = aPriority;
+ }
+ /**
+ * Signal that no backing up will be required after all.
+ */
+ void ClearOptionalBreakPosition() {
+ mNeedBackup = false;
+ mLastOptionalBreakFrame = nullptr;
+ mLastOptionalBreakFrameOffset = -1;
+ mLastOptionalBreakPriority = gfxBreakPriority::eNoBreak;
+ }
+ // Retrieve last set optional break position. When this returns null, no
+ // optional break has been recorded (which means that the line can't break
+ // yet).
+ nsIFrame* GetLastOptionalBreakPosition(int32_t* aOffset,
+ gfxBreakPriority* aPriority) {
+ *aOffset = mLastOptionalBreakFrameOffset;
+ *aPriority = mLastOptionalBreakPriority;
+ return mLastOptionalBreakFrame;
+ }
+ // Whether any optional break position has been recorded.
+ bool HasOptionalBreakPosition() const {
+ return mLastOptionalBreakFrame != nullptr;
+ }
+ // Get the priority of the last optional break position recorded.
+ gfxBreakPriority LastOptionalBreakPriority() const {
+ return mLastOptionalBreakPriority;
+ }
+
+ /**
+ * Check whether frames overflowed the available width and CanPlaceFrame
+ * requested backing up to a saved break position.
+ */
+ bool NeedsBackup() { return mNeedBackup; }
+
+ // Line layout may place too much content on a line, overflowing its available
+ // width. When that happens, if SetLastOptionalBreakPosition has been
+ // used to record an optional break that wasn't taken, we can reflow the line
+ // again and force the break to happen at that point (i.e., backtracking
+ // to the last choice point).
+
+ // Record that we want to break at the given content+offset (which
+ // should have been previously returned by GetLastOptionalBreakPosition
+ // from another nsLineLayout).
+ void ForceBreakAtPosition(nsIFrame* aFrame, int32_t aOffset) {
+ mForceBreakFrame = aFrame;
+ mForceBreakFrameOffset = aOffset;
+ }
+ bool HaveForcedBreakPosition() { return mForceBreakFrame != nullptr; }
+ int32_t GetForcedBreakPosition(nsIFrame* aFrame) {
+ return mForceBreakFrame == aFrame ? mForceBreakFrameOffset : -1;
+ }
+
+ /**
+ * This can't be null. It usually returns a block frame but may return
+ * some other kind of frame when inline frames are reflowed in a non-block
+ * context (e.g. MathML or floating first-letter).
+ */
+ nsIFrame* LineContainerFrame() const { return mLineContainerRI.mFrame; }
+ const ReflowInput& LineContainerRI() const { return mLineContainerRI; }
+ const nsLineList::iterator* GetLine() const {
+ return mGotLineBox ? &mLineBox : nullptr;
+ }
+ nsLineList::iterator* GetLine() { return mGotLineBox ? &mLineBox : nullptr; }
+
+ /**
+ * Returns the accumulated advance width of frames before the current frame
+ * on the line, plus the line container's left border+padding.
+ * This is always positive, the advance width is measured from
+ * the right edge for RTL blocks and from the left edge for LTR blocks.
+ * In other words, the current frame's distance from the line container's
+ * start content edge is:
+ * <code>GetCurrentFrameInlineDistanceFromBlock() -
+ * lineContainer->GetUsedBorderAndPadding().left</code> Note the use of
+ * <code>.left</code> for both LTR and RTL line containers.
+ */
+ nscoord GetCurrentFrameInlineDistanceFromBlock();
+
+ /**
+ * Move the inline position where the next frame will be reflowed forward by
+ * aAmount.
+ */
+ void AdvanceICoord(nscoord aAmount) { mCurrentSpan->mICoord += aAmount; }
+ /**
+ * Returns the writing mode for the root span.
+ */
+ mozilla::WritingMode GetWritingMode() { return mRootSpan->mWritingMode; }
+ /**
+ * Returns the inline position where the next frame will be reflowed.
+ */
+ nscoord GetCurrentICoord() { return mCurrentSpan->mICoord; }
+
+ void SetSuppressLineWrap(bool aEnabled) { mSuppressLineWrap = aEnabled; }
+
+ /**
+ * Record that the line had to resort to an overflow-wrap break.
+ */
+ void SetUsedOverflowWrap() { mUsedOverflowWrap = true; }
+
+ protected:
+ // This state is constant for a given block frame doing line layout
+
+ // A non-owning pointer, which points to the object owned by
+ // nsAutoFloatManager::mNew.
+ nsFloatManager* mFloatManager;
+
+ const nsStyleText* mStyleText; // for the block
+ const ReflowInput& mLineContainerRI;
+
+ // The line layout for the base text. It is usually nullptr.
+ // It becomes not null when the current line layout is for ruby
+ // annotations. When there is nested ruby inside annotation, it
+ // forms a linked list from the inner annotation to the outermost
+ // line layout. The outermost line layout, which has this member
+ // being nullptr, is responsible for managing the life cycle of
+ // per-frame data and per-span data, and handling floats.
+ nsLineLayout* const mBaseLineLayout;
+
+ nsLineLayout* GetOutermostLineLayout() {
+ nsLineLayout* lineLayout = this;
+ while (lineLayout->mBaseLineLayout) {
+ lineLayout = lineLayout->mBaseLineLayout;
+ }
+ return lineLayout;
+ }
+
+ nsIFrame* mLastOptionalBreakFrame;
+ nsIFrame* mForceBreakFrame;
+
+ // XXX remove this when landing bug 154892 (splitting absolute positioned
+ // frames)
+ friend class nsInlineFrame;
+
+ // XXX Take care that nsRubyBaseContainer would give nullptr to this
+ // member. It should not be a problem currently, since the only
+ // code use it is handling float, which does not affect ruby.
+ // See comment in nsLineLayout::AddFloat
+ BlockReflowState* mBlockRS = nullptr; /* XXX hack! */
+
+ nsLineList::iterator mLineBox;
+
+ // Per-frame data recorded by the line-layout reflow logic. This
+ // state is the state needed to post-process the line after reflow
+ // has completed (block-direction alignment, inline-direction alignment,
+ // justification and relative positioning).
+
+ struct PerSpanData;
+ struct PerFrameData;
+ friend struct PerSpanData;
+ friend struct PerFrameData;
+ struct PerFrameData {
+ // link to next/prev frame in same span
+ PerFrameData* mNext;
+ PerFrameData* mPrev;
+
+ // Link to the frame of next ruby annotation. It is a linked list
+ // through this pointer from ruby base to all its annotations. It
+ // could be nullptr if there is no more annotation.
+ // If PFD_ISLINKEDTOBASE is set, the current PFD is one of the ruby
+ // annotations in the base's list, otherwise it is the ruby base,
+ // and its mNextAnnotation is the start of the linked list.
+ PerFrameData* mNextAnnotation;
+
+ // pointer to child span data if this is an inline container frame
+ PerSpanData* mSpan;
+
+ // The frame
+ nsIFrame* mFrame;
+
+ // From metrics
+ nscoord mAscent;
+ // note that mBounds is a logical rect in the *line*'s writing mode.
+ // When setting frame coordinates, we have to convert to the frame's
+ // writing mode
+ mozilla::LogicalRect mBounds;
+ mozilla::OverflowAreas mOverflowAreas;
+
+ // From reflow-state
+ mozilla::LogicalMargin mMargin; // in *line* writing mode
+ mozilla::LogicalMargin mBorderPadding; // in *line* writing mode
+ mozilla::LogicalMargin mOffsets; // in *frame* writing mode
+
+ // state for text justification
+ // Note that, although all frames would have correct inner
+ // opportunities computed after ComputeFrameJustification, start
+ // and end justifiable info are not reliable for non-text frames.
+ mozilla::JustificationInfo mJustificationInfo;
+ mozilla::JustificationAssignment mJustificationAssignment;
+
+ // PerFrameData flags
+ bool mIsRelativelyOrStickyPos : 1;
+ bool mIsTextFrame : 1;
+ bool mIsNonEmptyTextFrame : 1;
+ bool mIsNonWhitespaceTextFrame : 1;
+ bool mIsLetterFrame : 1;
+ bool mRecomputeOverflow : 1;
+ bool mIsMarker : 1;
+ bool mSkipWhenTrimmingWhitespace : 1;
+ bool mIsEmpty : 1;
+ bool mIsPlaceholder : 1;
+ bool mIsLinkedToBase : 1;
+
+ // Other state we use
+ uint8_t mBlockDirAlign;
+ mozilla::WritingMode mWritingMode;
+
+ PerFrameData* Last() {
+ PerFrameData* pfd = this;
+ while (pfd->mNext) {
+ pfd = pfd->mNext;
+ }
+ return pfd;
+ }
+
+ bool IsStartJustifiable() const {
+ return mJustificationInfo.mIsStartJustifiable;
+ }
+
+ bool IsEndJustifiable() const {
+ return mJustificationInfo.mIsEndJustifiable;
+ }
+
+ bool ParticipatesInJustification() const;
+ };
+ PerFrameData* mFrameFreeList;
+
+ // In nsLineLayout, a "span" is a container inline frame, and a "frame" is one
+ // of its children.
+ //
+ // nsLineLayout::BeginLineReflow() creates the initial PerSpanData which is
+ // called the "root span". nsInlineFrame::ReflowFrames() creates a new
+ // PerSpanData when it calls nsLineLayout::BeginSpan(); at this time, the
+ // nsLineLayout object's mCurrentSpan is switched to the new span. The new
+ // span records the old mCurrentSpan as its parent. After reflowing the child
+ // inline frames, nsInlineFrame::ReflowFrames() calls nsLineLayout::EndSpan(),
+ // which pops the PerSpanData and re-sets mCurrentSpan.
+ struct PerSpanData {
+ union {
+ PerSpanData* mParent;
+ PerSpanData* mNextFreeSpan;
+ };
+
+ // The PerFrameData of the inline frame that "owns" the span, or null if
+ // this is the root span. mFrame is initialized to the containing inline
+ // frame's PerFrameData when a new PerSpanData is pushed in
+ // nsLineLayout::BeginSpan().
+ PerFrameData* mFrame;
+
+ // The first PerFrameData structure in the span.
+ PerFrameData* mFirstFrame;
+
+ // The last PerFrameData structure in the span. PerFrameData structures are
+ // added to the span as they are reflowed. mLastFrame may also be directly
+ // manipulated if a line is split, or if frames are pushed from one line to
+ // the next.
+ PerFrameData* mLastFrame;
+
+ const ReflowInput* mReflowInput;
+ bool mNoWrap;
+ mozilla::WritingMode mWritingMode;
+ bool mContainsFloat;
+ bool mHasNonemptyContent;
+
+ nscoord mIStart;
+ nscoord mICoord;
+ nscoord mIEnd;
+ nscoord mInset;
+
+ nscoord mBStartLeading, mBEndLeading;
+ nscoord mLogicalBSize;
+ nscoord mMinBCoord, mMaxBCoord;
+ nscoord* mBaseline;
+
+ void AppendFrame(PerFrameData* pfd) {
+ if (!mLastFrame) {
+ mFirstFrame = pfd;
+ } else {
+ mLastFrame->mNext = pfd;
+ pfd->mPrev = mLastFrame;
+ }
+ mLastFrame = pfd;
+ }
+ };
+ PerSpanData* mSpanFreeList;
+ PerSpanData* mRootSpan;
+ PerSpanData* mCurrentSpan;
+
+ // The container size to use when converting between logical and
+ // physical coordinates for frames in this span. For the root span
+ // this is the size of the block cached in mContainerSize; for
+ // child spans it's the size of the root span.
+ nsSize ContainerSizeForSpan(PerSpanData* aPSD) {
+ return (aPSD == mRootSpan)
+ ? mContainerSize
+ : aPSD->mFrame->mBounds.Size(mRootSpan->mWritingMode)
+ .GetPhysicalSize(mRootSpan->mWritingMode);
+ }
+
+ // Get the advance of any trailing hangable whitespace. If the whitespace
+ // has directionality opposite to the line, the result is negated.
+ nscoord GetHangFrom(const PerSpanData* aSpan, bool aLineIsRTL) const;
+ gfxTextRun::TrimmableWS GetTrimFrom(const PerSpanData* aSpan,
+ bool aLineIsRTL) const;
+
+ gfxBreakPriority mLastOptionalBreakPriority;
+ int32_t mLastOptionalBreakFrameOffset;
+ int32_t mForceBreakFrameOffset;
+
+ nscoord mMinLineBSize;
+
+ // The amount of text indent that we applied to this line, needed for
+ // max-element-size calculation.
+ nscoord mTextIndent;
+
+ // This state varies during the reflow of a line but is line
+ // "global" state not span "local" state.
+ int32_t mLineNumber;
+ mozilla::JustificationInfo mJustificationInfo;
+
+ int32_t mTotalPlacedFrames;
+
+ nscoord mBStartEdge;
+ nscoord mMaxStartBoxBSize;
+ nscoord mMaxEndBoxBSize;
+
+ nscoord mInflationMinFontSize;
+
+ // Final computed line-bSize value after VerticalAlignFrames for
+ // the block has been called.
+ nscoord mFinalLineBSize;
+
+ // Amount of trimmable whitespace inline size for the trailing text
+ // frame, if any
+ nscoord mTrimmableISize;
+
+ // Physical size. Use only for physical <-> logical coordinate conversion.
+ nsSize mContainerSize;
+ const nsSize& ContainerSize() const { return mContainerSize; }
+
+ bool mFirstLetterStyleOK : 1;
+ bool mIsTopOfPage : 1;
+ bool mImpactedByFloats : 1;
+ bool mLastFloatWasLetterFrame : 1;
+ bool mLineIsEmpty : 1;
+ bool mLineEndsInBR : 1;
+ bool mNeedBackup : 1;
+ bool mInFirstLine : 1;
+ bool mGotLineBox : 1;
+ bool mInFirstLetter : 1;
+ bool mHasMarker : 1;
+ bool mDirtyNextLine : 1;
+ bool mLineAtStart : 1;
+ bool mHasRuby : 1;
+ bool mSuppressLineWrap : 1;
+ bool mUsedOverflowWrap : 1;
+
+ int32_t mSpanDepth;
+#ifdef DEBUG
+ int32_t mSpansAllocated, mSpansFreed;
+ int32_t mFramesAllocated, mFramesFreed;
+#endif
+
+ /**
+ * Per span and per frame data.
+ */
+ mozilla::ArenaAllocator<1024, sizeof(void*)> mArena;
+
+ /**
+ * Allocate a PerFrameData from the mArena pool. The allocation is infallible.
+ */
+ PerFrameData* NewPerFrameData(nsIFrame* aFrame);
+
+ /**
+ * Allocate a PerSpanData from the mArena pool. The allocation is infallible.
+ */
+ PerSpanData* NewPerSpanData();
+
+ PerFrameData* LastFrame() const { return mCurrentSpan->mLastFrame; }
+
+ /**
+ * Unlink the given PerFrameData and all the siblings after it from
+ * the span. The unlinked PFDs are usually freed immediately.
+ * However, if PFD_ISLINKEDTOBASE is set, it won't be freed until
+ * the frame of its base is unlinked.
+ */
+ void UnlinkFrame(PerFrameData* pfd);
+
+ /**
+ * Free the given PerFrameData.
+ */
+ void FreeFrame(PerFrameData* pfd);
+
+ void FreeSpan(PerSpanData* psd);
+
+ bool InBlockContext() const { return mSpanDepth == 0; }
+
+ void PushFrame(nsIFrame* aFrame);
+
+ void AllowForStartMargin(PerFrameData* pfd, ReflowInput& aReflowInput);
+
+ void SyncAnnotationBounds(PerFrameData* aRubyFrame);
+
+ bool CanPlaceFrame(PerFrameData* pfd, bool aNotSafeToBreak,
+ bool aFrameCanContinueTextRun,
+ bool aCanRollBackBeforeFrame, ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus, bool* aOptionalBreakAfterFits);
+
+ void PlaceFrame(PerFrameData* pfd, ReflowOutput& aMetrics);
+
+ void AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd,
+ const nsStyleText* aStyleText, float aInflation,
+ bool* aZeroEffectiveSpanBox);
+
+ void VerticalAlignFrames(PerSpanData* psd);
+
+ void PlaceTopBottomFrames(PerSpanData* psd, nscoord aDistanceFromStart,
+ nscoord aLineBSize);
+
+ void ApplyRelativePositioning(PerFrameData* aPFD);
+
+ void RelativePositionAnnotations(PerSpanData* aRubyPSD,
+ mozilla::OverflowAreas& aOverflowAreas);
+
+ void RelativePositionFrames(PerSpanData* psd,
+ mozilla::OverflowAreas& aOverflowAreas);
+
+ bool TrimTrailingWhiteSpaceIn(PerSpanData* psd, nscoord* aDeltaISize);
+
+ struct JustificationComputationState;
+
+ static int AssignInterframeJustificationGaps(
+ PerFrameData* aFrame, JustificationComputationState& aState);
+
+ int32_t ComputeFrameJustification(PerSpanData* psd,
+ JustificationComputationState& aState);
+
+ void AdvanceAnnotationInlineBounds(PerFrameData* aPFD,
+ const nsSize& aContainerSize,
+ nscoord aDeltaICoord, nscoord aDeltaISize);
+
+ void ApplyLineJustificationToAnnotations(PerFrameData* aPFD,
+ nscoord aDeltaICoord,
+ nscoord aDeltaISize);
+
+ // Apply justification. The return value is the amount by which the width of
+ // the span corresponding to aPSD got increased due to justification.
+ nscoord ApplyFrameJustification(
+ PerSpanData* aPSD, mozilla::JustificationApplicationState& aState);
+
+ void ExpandRubyBox(PerFrameData* aFrame, nscoord aReservedISize,
+ const nsSize& aContainerSize);
+
+ void ExpandRubyBoxWithAnnotations(PerFrameData* aFrame,
+ const nsSize& aContainerSize);
+
+ void ExpandInlineRubyBoxes(PerSpanData* aSpan);
+
+ void AttachFrameToBaseLineLayout(PerFrameData* aFrame);
+
+#ifdef DEBUG
+ void DumpPerSpanData(PerSpanData* psd, int32_t aIndent);
+#endif
+
+ private:
+ static bool ShouldApplyLineHeightInPreserveWhiteSpace(const PerSpanData* psd);
+};
+
+#endif /* nsLineLayout_h___ */
diff --git a/layout/generic/nsPageContentFrame.cpp b/layout/generic/nsPageContentFrame.cpp
new file mode 100644
index 0000000000..11e634894d
--- /dev/null
+++ b/layout/generic/nsPageContentFrame.cpp
@@ -0,0 +1,434 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsPageContentFrame.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/dom/Document.h"
+
+#include "nsContentUtils.h"
+#include "nsPageFrame.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsPageSequenceFrame.h"
+
+using namespace mozilla;
+
+nsPageContentFrame* NS_NewPageContentFrame(
+ PresShell* aPresShell, ComputedStyle* aStyle,
+ already_AddRefed<const nsAtom> aPageName) {
+ return new (aPresShell) nsPageContentFrame(
+ aStyle, aPresShell->GetPresContext(), std::move(aPageName));
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsPageContentFrame)
+
+void nsPageContentFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsPageContentFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ MOZ_ASSERT(mPD, "Need a pointer to nsSharedPageData before reflow starts");
+
+ if (GetPrevInFlow() && HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ nsresult rv =
+ aPresContext->PresShell()->FrameConstructor()->ReplicateFixedFrames(
+ this);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ // Set our size up front, since some parts of reflow depend on it
+ // being already set. Note that the computed height may be
+ // unconstrained; that's ok. Consumers should watch out for that.
+ const nsSize maxSize = aReflowInput.ComputedPhysicalSize();
+ SetSize(maxSize);
+
+ // Writing mode for the page content frame.
+ const WritingMode pcfWM = aReflowInput.GetWritingMode();
+ aReflowOutput.ISize(pcfWM) = aReflowInput.ComputedISize();
+ if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
+ aReflowOutput.BSize(pcfWM) = aReflowInput.ComputedBSize();
+ }
+ aReflowOutput.SetOverflowAreasToDesiredBounds();
+
+ // A PageContentFrame must always have one child: the canvas frame.
+ // Resize our frame allowing it only to be as big as we are
+ // XXX Pay attention to the page's border and padding...
+ if (mFrames.NotEmpty()) {
+ nsIFrame* const frame = mFrames.FirstChild();
+ const WritingMode frameWM = frame->GetWritingMode();
+ const LogicalSize logicalSize(frameWM, maxSize);
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, frame, logicalSize);
+ kidReflowInput.SetComputedBSize(logicalSize.BSize(frameWM));
+ ReflowOutput kidReflowOutput(kidReflowInput);
+ ReflowChild(frame, aPresContext, kidReflowOutput, kidReflowInput, 0, 0,
+ ReflowChildFlags::Default, aStatus);
+
+ // The document element's background should cover the entire canvas, so
+ // take into account the combined area and any space taken up by
+ // absolutely positioned elements
+ nsMargin padding(0, 0, 0, 0);
+
+ // XXXbz this screws up percentage padding (sets padding to zero
+ // in the percentage padding case)
+ frame->StylePadding()->GetPadding(padding);
+
+ // This is for shrink-to-fit, and therefore we want to use the
+ // scrollable overflow, since the purpose of shrink to fit is to
+ // make the content that ought to be reachable (represented by the
+ // scrollable overflow) fit in the page.
+ if (frame->HasOverflowAreas()) {
+ // The background covers the content area and padding area, so check
+ // for children sticking outside the child frame's padding edge
+ nscoord xmost = kidReflowOutput.ScrollableOverflow().XMost();
+ if (xmost > kidReflowOutput.Width()) {
+ const nscoord widthToFit =
+ xmost + padding.right +
+ kidReflowInput.mStyleBorder->GetComputedBorderWidth(eSideRight);
+ const float ratio = float(maxSize.width) / float(widthToFit);
+ NS_ASSERTION(ratio >= 0.0 && ratio < 1.0,
+ "invalid shrink-to-fit ratio");
+ mPD->mShrinkToFitRatio = std::min(mPD->mShrinkToFitRatio, ratio);
+ }
+ // In the case of pdf.js documents, we also want to consider the height,
+ // so that we don't clip the page in either axis if the aspect ratio of
+ // the PDF doesn't match the destination.
+ if (nsContentUtils::IsPDFJS(PresContext()->Document()->GetPrincipal())) {
+ nscoord ymost = kidReflowOutput.ScrollableOverflow().YMost();
+ if (ymost > kidReflowOutput.Height()) {
+ const nscoord heightToFit =
+ ymost + padding.bottom +
+ kidReflowInput.mStyleBorder->GetComputedBorderWidth(eSideBottom);
+ const float ratio = float(maxSize.height) / float(heightToFit);
+ MOZ_ASSERT(ratio >= 0.0 && ratio < 1.0);
+ mPD->mShrinkToFitRatio = std::min(mPD->mShrinkToFitRatio, ratio);
+ }
+
+ // pdf.js pages should never overflow given the scaling above.
+ // nsPrintJob::SetupToPrintContent ignores some ratios close to 1.0
+ // though and doesn't reflow us again in that case, so we need to clear
+ // the overflow area here in case that happens. (bug 1689789)
+ frame->ClearOverflowRects();
+ kidReflowOutput.mOverflowAreas = aReflowOutput.mOverflowAreas;
+ }
+ }
+
+ // Place and size the child
+ FinishReflowChild(frame, aPresContext, kidReflowOutput, &kidReflowInput, 0,
+ 0, ReflowChildFlags::Default);
+
+ NS_ASSERTION(aPresContext->IsDynamic() || !aStatus.IsFullyComplete() ||
+ !frame->GetNextInFlow(),
+ "bad child flow list");
+
+ aReflowOutput.mOverflowAreas.UnionWith(kidReflowOutput.mOverflowAreas);
+ }
+
+ FinishAndStoreOverflow(&aReflowOutput);
+
+ // Reflow our fixed frames
+ nsReflowStatus fixedStatus;
+ ReflowAbsoluteFrames(aPresContext, aReflowOutput, aReflowInput, fixedStatus);
+ NS_ASSERTION(fixedStatus.IsComplete(),
+ "fixed frames can be truncated, but not incomplete");
+
+ if (StaticPrefs::layout_display_list_improve_fragmentation() &&
+ mFrames.NotEmpty()) {
+ auto* const previous =
+ static_cast<nsPageContentFrame*>(GetPrevContinuation());
+ const nscoord previousPageOverflow =
+ previous ? previous->mRemainingOverflow : 0;
+ const nsSize containerSize(aReflowInput.AvailableWidth(),
+ aReflowInput.AvailableHeight());
+ const nscoord pageBSize = GetLogicalRect(containerSize).BSize(pcfWM);
+ const nscoord overflowBSize =
+ LogicalRect(pcfWM, ScrollableOverflowRect(), GetSize()).BEnd(pcfWM);
+ const nscoord currentPageOverflow = overflowBSize - pageBSize;
+ nscoord remainingOverflow =
+ std::max(currentPageOverflow, previousPageOverflow - pageBSize);
+
+ if (aStatus.IsFullyComplete() && remainingOverflow > 0) {
+ // If we have ScrollableOverflow off the end of our page, then we report
+ // ourselves as overflow-incomplete in order to produce an additional
+ // content-less page, which we expect to draw our overflow on our behalf.
+ aStatus.SetOverflowIncomplete();
+ }
+
+ mRemainingOverflow = std::max(remainingOverflow, 0);
+ }
+}
+
+using PageAndOffset = std::pair<nsPageContentFrame*, nscoord>;
+
+// Returns the previous continuation PageContentFrames that have overflow areas,
+// and their offsets to the top of the given PageContentFrame |aPage|. Since the
+// iteration is done backwards, the returned pages are arranged in descending
+// order of page number.
+static nsTArray<PageAndOffset> GetPreviousPagesWithOverflow(
+ nsPageContentFrame* aPage) {
+ nsTArray<PageAndOffset> pages(8);
+
+ auto GetPreviousPageContentFrame = [](nsPageContentFrame* aPageCF) {
+ nsIFrame* prevCont = aPageCF->GetPrevContinuation();
+ MOZ_ASSERT(!prevCont || prevCont->IsPageContentFrame(),
+ "Expected nsPageContentFrame or nullptr");
+
+ return static_cast<nsPageContentFrame*>(prevCont);
+ };
+
+ nsPageContentFrame* pageCF = aPage;
+ // The collective height of all prev-continuations we've traversed so far:
+ nscoord offsetToCurrentPageBStart = 0;
+ const auto wm = pageCF->GetWritingMode();
+ while ((pageCF = GetPreviousPageContentFrame(pageCF))) {
+ offsetToCurrentPageBStart += pageCF->BSize(wm);
+
+ if (pageCF->HasOverflowAreas()) {
+ pages.EmplaceBack(pageCF, offsetToCurrentPageBStart);
+ }
+ }
+
+ return pages;
+}
+
+static void BuildPreviousPageOverflow(nsDisplayListBuilder* aBuilder,
+ nsPageFrame* aPageFrame,
+ nsPageContentFrame* aCurrentPageCF,
+ const nsDisplayListSet& aLists) {
+ const auto previousPagesAndOffsets =
+ GetPreviousPagesWithOverflow(aCurrentPageCF);
+
+ const auto wm = aCurrentPageCF->GetWritingMode();
+ for (const PageAndOffset& pair : Reversed(previousPagesAndOffsets)) {
+ auto* prevPageCF = pair.first;
+ const nscoord offsetToCurrentPageBStart = pair.second;
+ // Only scrollable overflow create new pages, not ink overflow.
+ const LogicalRect scrollableOverflow(
+ wm, prevPageCF->ScrollableOverflowRectRelativeToSelf(),
+ prevPageCF->GetSize());
+ const auto remainingOverflow =
+ scrollableOverflow.BEnd(wm) - offsetToCurrentPageBStart;
+ if (remainingOverflow <= 0) {
+ continue;
+ }
+
+ // This rect represents the piece of prevPageCF's overflow that ends up on
+ // the current pageContentFrame (in prevPageCF's coordinate system).
+ // Note that we use InkOverflow here since this is for painting.
+ LogicalRect overflowRect(wm, prevPageCF->InkOverflowRectRelativeToSelf(),
+ prevPageCF->GetSize());
+ overflowRect.BStart(wm) = offsetToCurrentPageBStart;
+ overflowRect.BSize(wm) = std::min(remainingOverflow, prevPageCF->BSize(wm));
+
+ {
+ // Convert the overflowRect to the coordinate system of aPageFrame, and
+ // set it as the visible rect for display list building.
+ const nsRect visibleRect =
+ overflowRect.GetPhysicalRect(wm, prevPageCF->GetSize()) +
+ prevPageCF->GetOffsetTo(aPageFrame);
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, aPageFrame, visibleRect, visibleRect);
+
+ // This part is tricky. Because display items are positioned based on the
+ // frame tree, building a display list for the previous page yields
+ // display items that are outside of the current page bounds.
+ // To fix that, an additional reference frame offset is added, which
+ // shifts the display items down (block axis) as if the current and
+ // previous page were one long page in the same coordinate system.
+ const nsSize containerSize = aPageFrame->GetSize();
+ LogicalPoint pageOffset(wm, aCurrentPageCF->GetOffsetTo(prevPageCF),
+ containerSize);
+ pageOffset.B(wm) -= offsetToCurrentPageBStart;
+ buildingForChild.SetAdditionalOffset(
+ pageOffset.GetPhysicalPoint(wm, containerSize));
+
+ aPageFrame->BuildDisplayListForChild(aBuilder, prevPageCF, aLists);
+ }
+ }
+}
+
+/**
+ * Remove all leaf display items that are not for descendants of
+ * aBuilder->GetReferenceFrame() from aList.
+ * @param aPage the page we're constructing the display list for
+ * @param aList the list that is modified in-place
+ */
+static void PruneDisplayListForExtraPage(nsDisplayListBuilder* aBuilder,
+ nsPageFrame* aPage,
+ nsDisplayList* aList) {
+ for (nsDisplayItem* i : aList->TakeItems()) {
+ if (!i) break;
+ nsDisplayList* subList = i->GetSameCoordinateSystemChildren();
+ if (subList) {
+ PruneDisplayListForExtraPage(aBuilder, aPage, subList);
+ i->UpdateBounds(aBuilder);
+ } else {
+ nsIFrame* f = i->Frame();
+ if (!nsLayoutUtils::IsProperAncestorFrameCrossDocInProcess(aPage, f)) {
+ // We're throwing this away so call its destructor now. The memory
+ // is owned by aBuilder which destroys all items at once.
+ i->Destroy(aBuilder);
+ continue;
+ }
+ }
+ aList->AppendToTop(i);
+ }
+}
+
+static void BuildDisplayListForExtraPage(nsDisplayListBuilder* aBuilder,
+ nsPageFrame* aPage,
+ nsIFrame* aExtraPage,
+ nsDisplayList* aList) {
+ // The only content in aExtraPage we care about is out-of-flow content from
+ // aPage, whose placeholders have occurred in aExtraPage. If
+ // NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO is not set, then aExtraPage has
+ // no such content.
+ if (!aExtraPage->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
+ return;
+ }
+ nsDisplayList list(aBuilder);
+ aExtraPage->BuildDisplayListForStackingContext(aBuilder, &list);
+ PruneDisplayListForExtraPage(aBuilder, aPage, &list);
+ aList->AppendToTop(&list);
+}
+
+static gfx::Matrix4x4 ComputePageContentTransform(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel) {
+ float scale = aFrame->PresContext()->GetPageScale();
+ return gfx::Matrix4x4::Scaling(scale, scale, 1);
+}
+
+nsIFrame::ComputeTransformFunction nsPageContentFrame::GetTransformGetter()
+ const {
+ return ComputePageContentTransform;
+}
+
+void nsPageContentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ MOZ_ASSERT(GetParent());
+ MOZ_ASSERT(GetParent()->IsPageFrame());
+ auto* pageFrame = static_cast<nsPageFrame*>(GetParent());
+ auto pageNum = pageFrame->GetPageNum();
+ NS_ASSERTION(pageNum <= 255, "Too many pages to handle OOFs");
+
+ if (aBuilder->GetBuildingExtraPagesForPageNum()) {
+ return mozilla::ViewportFrame::BuildDisplayList(aBuilder, aLists);
+ }
+
+ nsDisplayListCollection set(aBuilder);
+
+ nsDisplayList content(aBuilder);
+ {
+ const nsRect clipRect(aBuilder->ToReferenceFrame(this), GetSize());
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+
+ // Overwrite current clip, since we're going to wrap in a transform and the
+ // current clip is no longer meaningful.
+ clipState.Clear();
+ clipState.ClipContentDescendants(clipRect);
+
+ if (StaticPrefs::layout_display_list_improve_fragmentation() &&
+ pageNum <= 255) {
+ nsDisplayListBuilder::AutoPageNumberSetter p(aBuilder, pageNum);
+ BuildPreviousPageOverflow(aBuilder, pageFrame, this, set);
+ }
+ mozilla::ViewportFrame::BuildDisplayList(aBuilder, set);
+
+ set.SerializeWithCorrectZOrder(&content, GetContent());
+
+ // We may need to paint out-of-flow frames whose placeholders are on other
+ // pages. Add those pages to our display list. Note that out-of-flow frames
+ // can't be placed after their placeholders so
+ // we don't have to process earlier pages. The display lists for
+ // these extra pages are pruned so that only display items for the
+ // page we currently care about (which we would have reached by
+ // following placeholders to their out-of-flows) end up on the list.
+ //
+ // Stacking context frames that wrap content on their normal page,
+ // as well as OOF content for this page will have their container
+ // items duplicated. We tell the builder to include our page number
+ // in the unique key for any extra page items so that they can be
+ // differentiated from the ones created on the normal page.
+ if (pageNum <= 255) {
+ const nsRect overflowRect = ScrollableOverflowRectRelativeToSelf();
+ nsDisplayListBuilder::AutoPageNumberSetter p(aBuilder, pageNum);
+
+ // The static_cast here is technically unnecessary, but it helps
+ // devirtualize the GetNextContinuation() function call if pcf has a
+ // concrete type (with an inherited `final` GetNextContinuation() impl).
+ auto* pageCF = this;
+ while ((pageCF = static_cast<nsPageContentFrame*>(
+ pageCF->GetNextContinuation()))) {
+ nsRect childVisible = overflowRect + GetOffsetTo(pageCF);
+
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, pageCF, childVisible, childVisible);
+ BuildDisplayListForExtraPage(aBuilder, pageFrame, pageCF, &content);
+ }
+ }
+
+ // Add the canvas background color to the bottom of the list. This
+ // happens after we've built the list so that AddCanvasBackgroundColorItem
+ // can monkey with the contents if necessary.
+ const nsRect backgroundRect(aBuilder->ToReferenceFrame(this), GetSize());
+ PresShell()->AddCanvasBackgroundColorItem(
+ aBuilder, &content, this, backgroundRect, NS_RGBA(0, 0, 0, 0));
+ }
+
+ content.AppendNewToTop<nsDisplayTransform>(
+ aBuilder, this, &content, content.GetBuildingRect(),
+ nsDisplayTransform::WithTransformGetter);
+
+ aLists.Content()->AppendToTop(&content);
+}
+
+void nsPageContentFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ MOZ_ASSERT(mFrames.FirstChild(),
+ "pageContentFrame must have a canvasFrame child");
+ aResult.AppendElement(mFrames.FirstChild());
+}
+
+void nsPageContentFrame::EnsurePageName() {
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_FIRST_REFLOW),
+ "Should only have been called on first reflow");
+ if (mPageName) {
+ return;
+ }
+ MOZ_ASSERT(!GetPrevInFlow(),
+ "Only the first page should initially have a null page name.");
+ // This was the first page, we need to find our own page name and then set
+ // our computed style based on that.
+ mPageName = ComputePageValue();
+
+ MOZ_ASSERT(mPageName, "Page name should never be null");
+ // Resolve the computed style given this page-name and the :first pseudo.
+ RefPtr<ComputedStyle> pageContentPseudoStyle =
+ PresShell()->StyleSet()->ResolvePageContentStyle(
+ mPageName, StylePagePseudoClassFlags::FIRST);
+ SetComputedStyleWithoutNotification(pageContentPseudoStyle);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsPageContentFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"PageContent"_ns, aResult);
+}
+void nsPageContentFrame::ExtraContainerFrameInfo(nsACString& aTo) const {
+ if (mPageName) {
+ aTo += " [page=";
+ aTo += nsAtomCString(mPageName);
+ aTo += "]";
+ }
+}
+#endif
diff --git a/layout/generic/nsPageContentFrame.h b/layout/generic/nsPageContentFrame.h
new file mode 100644
index 0000000000..5e936e5e81
--- /dev/null
+++ b/layout/generic/nsPageContentFrame.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsPageContentFrame_h___
+#define nsPageContentFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ViewportFrame.h"
+
+class nsPageFrame;
+class nsSharedPageData;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+// Page content frame class. Represents a page's content, in paginated mode.
+class nsPageContentFrame final : public mozilla::ViewportFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsPageContentFrame)
+
+ friend nsPageContentFrame* NS_NewPageContentFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle,
+ already_AddRefed<const nsAtom> aPageName);
+ friend class nsPageFrame;
+
+ // nsIFrame
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ const nsAtom* GetPageName() const { return mPageName; }
+
+ void SetSharedPageData(nsSharedPageData* aPD) { mPD = aPD; }
+
+ ComputeTransformFunction GetTransformGetter() const override;
+ void BuildDisplayList(nsDisplayListBuilder*,
+ const nsDisplayListSet&) override;
+
+ /**
+ * Return our canvas frame.
+ */
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+ void EnsurePageName();
+
+#ifdef DEBUG_FRAME_DUMP
+ // Debugging
+ nsresult GetFrameName(nsAString& aResult) const override;
+ void ExtraContainerFrameInfo(nsACString& aTo) const override;
+#endif
+
+ protected:
+ explicit nsPageContentFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext,
+ already_AddRefed<const nsAtom> aPageName)
+ : ViewportFrame(aStyle, aPresContext, kClassID), mPageName(aPageName) {}
+
+ RefPtr<const nsAtom> mPageName;
+
+ // Note: this will be set before reflow, and it's strongly owned by our
+ // nsPageSequenceFrame, which outlives us.
+ nsSharedPageData* mPD = nullptr;
+
+ // The combined InkOverflow from the previous and current page that does not
+ // yet have space allocated for it.
+ nscoord mRemainingOverflow = 0;
+};
+
+#endif /* nsPageContentFrame_h___ */
diff --git a/layout/generic/nsPageFrame.cpp b/layout/generic/nsPageFrame.cpp
new file mode 100644
index 0000000000..fb9cfa2e81
--- /dev/null
+++ b/layout/generic/nsPageFrame.cpp
@@ -0,0 +1,980 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPageFrame.h"
+
+#include "mozilla/AppUnits.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/intl/Segmenter.h"
+#include "gfxContext.h"
+#include "nsDeviceContext.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsFieldSetFrame.h"
+#include "nsPageContentFrame.h"
+#include "nsDisplayList.h"
+#include "nsPageSequenceFrame.h" // for nsSharedPageData
+#include "nsTextFormatter.h" // for page number localization formatting
+#include "nsBidiUtils.h"
+#include "nsIPrintSettings.h"
+#include "PrintedSheetFrame.h"
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gLayoutPrintingLog;
+#define PR_PL(_p1) MOZ_LOG(gLayoutPrintingLog, mozilla::LogLevel::Debug, _p1)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+nsPageFrame* NS_NewPageFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsPageFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsPageFrame)
+
+NS_QUERYFRAME_HEAD(nsPageFrame)
+ NS_QUERYFRAME_ENTRY(nsPageFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+nsPageFrame::nsPageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {}
+
+nsPageFrame::~nsPageFrame() = default;
+
+nsReflowStatus nsPageFrame::ReflowPageContent(
+ nsPresContext* aPresContext, const ReflowInput& aPageReflowInput) {
+ nsPageContentFrame* const frame = PageContentFrame();
+ // If this is the first page, it won't have its page name and computed style
+ // set yet. Before reflow, make sure that page name and computed style have
+ // been applied.
+ frame->EnsurePageName();
+ // XXX Pay attention to the page's border and padding...
+ //
+ // Reflow our ::-moz-page-content frame, allowing it only to be as big as we
+ // are (minus margins).
+ const nsSize pageSize = ComputePageSize();
+ // Scaling applied to the page in the single page-per-sheet case (used for
+ // down-scaling when the page is too large to fit on the sheet we are printing
+ // on). In the single page-per-sheet case, we need this here to preemptively
+ // increase the margins by the same amount that the scaling will reduce them
+ // in order to make sure that their physical size is unchanged (particularly
+ // important for the unwriteable margins).
+ const auto* ppsInfo = GetSharedPageData()->PagesPerSheetInfo();
+ const float pageSizeScale =
+ ppsInfo->mNumPages == 1 ? ComputeSinglePPSPageSizeScale(pageSize) : 1.0f;
+ // Scaling applied to content, as given by the print UI.
+ // This is an additional scale factor that is applied to the content in the
+ // nsPageContentFrame.
+ const float extraContentScale = aPresContext->GetPageScale();
+ // Size for the page content. This will be scaled by extraContentScale, and
+ // is used to calculate the computed size of the nsPageContentFrame content
+ // by subtracting margins.
+ nsSize availableSpace = pageSize;
+
+ // When the reflow size is NS_UNCONSTRAINEDSIZE it means we are reflowing
+ // a single page to print selection. So this means we want to use
+ // NS_UNCONSTRAINEDSIZE without altering it.
+ //
+ // FIXME(emilio): Is this still true?
+ availableSpace.width =
+ NSToCoordCeil(availableSpace.width / extraContentScale);
+ if (availableSpace.height != NS_UNCONSTRAINEDSIZE) {
+ availableSpace.height =
+ NSToCoordCeil(availableSpace.height / extraContentScale);
+ }
+
+ // Get the number of Twips per pixel from the PresContext
+ const nscoord onePixel = AppUnitsPerCSSPixel();
+
+ // insurance against infinite reflow, when reflowing less than a pixel
+ // XXX Shouldn't we do something more friendly when invalid margins
+ // are set?
+ if (availableSpace.width < onePixel || availableSpace.height < onePixel) {
+ NS_WARNING("Reflow aborted; no space for content");
+ return {};
+ }
+
+ ReflowInput kidReflowInput(
+ aPresContext, aPageReflowInput, frame,
+ LogicalSize(frame->GetWritingMode(), availableSpace));
+ kidReflowInput.mFlags.mIsTopOfPage = true;
+ kidReflowInput.mFlags.mTableIsSplittable = true;
+
+ nsMargin defaultMargins = aPresContext->GetDefaultPageMargin();
+ // The default margins are in the coordinate space of the physical paper.
+ // Scale them by the pageSizeScale to convert them to the content coordinate
+ // space.
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ defaultMargins.Side(side) =
+ NSToCoordRound((float)defaultMargins.Side(side) / pageSizeScale);
+ }
+ mPageContentMargin = defaultMargins;
+
+ // Use the margins given in the @page rule if told to do so.
+ // We clamp to the paper's unwriteable margins to avoid clipping, *except*
+ // that we will respect a margin of zero if specified, assuming this means
+ // the document is intended to fit the paper size exactly, and the client is
+ // taking full responsibility for what happens around the edges.
+ if (mPD->mPrintSettings->GetHonorPageRuleMargins()) {
+ const auto& margin = kidReflowInput.mStyleMargin->mMargin;
+ for (const auto side : mozilla::AllPhysicalSides()) {
+ if (!margin.Get(side).IsAuto()) {
+ // Computed margins are already in the coordinate space of the content,
+ // do not scale.
+ const nscoord computed =
+ kidReflowInput.ComputedPhysicalMargin().Side(side);
+ // Respecting a zero margin is particularly important when the client
+ // is PDF.js where the PDF already contains the margins.
+ // User could also be asking to ignore unwriteable margins (Though
+ // currently, it is impossible through the print UI to set both
+ // `HonorPageRuleMargins` and `IgnoreUnwriteableMargins`).
+ if (computed == 0 ||
+ mPD->mPrintSettings->GetIgnoreUnwriteableMargins()) {
+ mPageContentMargin.Side(side) = computed;
+ } else {
+ // Unwriteable margins are in the coordinate space of the physical
+ // paper. Scale them by the pageSizeScale to convert them to the
+ // content coordinate space.
+ const int32_t unwriteableTwips =
+ mPD->mPrintSettings->GetUnwriteableMarginInTwips().Side(side);
+ const nscoord unwriteable = nsPresContext::CSSTwipsToAppUnits(
+ (float)unwriteableTwips / pageSizeScale);
+ mPageContentMargin.Side(side) = std::max(
+ kidReflowInput.ComputedPhysicalMargin().Side(side), unwriteable);
+ }
+ }
+ }
+ }
+
+ // TODO: This seems odd that we need to scale the margins by the extra
+ // scale factor, but this is needed for correct margins.
+ // Why are the margins already scaled? Shouldn't they be stored so that this
+ // scaling factor would be redundant?
+ nscoord computedWidth =
+ availableSpace.width - mPageContentMargin.LeftRight() / extraContentScale;
+ nscoord computedHeight;
+ if (availableSpace.height == NS_UNCONSTRAINEDSIZE) {
+ computedHeight = NS_UNCONSTRAINEDSIZE;
+ } else {
+ computedHeight = availableSpace.height -
+ mPageContentMargin.TopBottom() / extraContentScale;
+ }
+
+ // Check the width and height, if they're too small we reset the margins
+ // back to the default.
+ if (computedWidth < onePixel || computedHeight < onePixel) {
+ mPageContentMargin = defaultMargins;
+ computedWidth = availableSpace.width -
+ mPageContentMargin.LeftRight() / extraContentScale;
+ if (computedHeight != NS_UNCONSTRAINEDSIZE) {
+ computedHeight = availableSpace.height -
+ mPageContentMargin.TopBottom() / extraContentScale;
+ }
+ // And if they're still too small, we give up.
+ if (computedWidth < onePixel || computedHeight < onePixel) {
+ NS_WARNING("Reflow aborted; no space for content");
+ return {};
+ }
+ }
+
+ kidReflowInput.SetComputedWidth(computedWidth);
+ kidReflowInput.SetComputedHeight(computedHeight);
+
+ // calc location of frame
+ const nscoord xc = mPageContentMargin.left;
+ const nscoord yc = mPageContentMargin.top;
+
+ // Get the child's desired size
+ ReflowOutput kidOutput(kidReflowInput);
+ nsReflowStatus kidStatus;
+ ReflowChild(frame, aPresContext, kidOutput, kidReflowInput, xc, yc,
+ ReflowChildFlags::Default, kidStatus);
+
+ // Place and size the child
+ FinishReflowChild(frame, aPresContext, kidOutput, &kidReflowInput, xc, yc,
+ ReflowChildFlags::Default);
+
+ NS_ASSERTION(!kidStatus.IsFullyComplete() || !frame->GetNextInFlow(),
+ "bad child flow list");
+ return kidStatus;
+}
+
+void nsPageFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsPageFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ MOZ_ASSERT(mPD, "Need a pointer to nsSharedPageData before reflow starts");
+
+ // Our status is the same as our child's.
+ aStatus = ReflowPageContent(aPresContext, aReflowInput);
+
+ PR_PL(("PageFrame::Reflow %p ", this));
+ PR_PL(("[%d,%d][%d,%d]\n", aReflowOutput.Width(), aReflowOutput.Height(),
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ // Return our desired size
+ WritingMode wm = aReflowInput.GetWritingMode();
+ aReflowOutput.ISize(wm) = aReflowInput.AvailableISize();
+ if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
+ aReflowOutput.BSize(wm) = aReflowInput.AvailableBSize();
+ }
+
+ aReflowOutput.SetOverflowAreasToDesiredBounds();
+ FinishAndStoreOverflow(&aReflowOutput);
+
+ PR_PL(("PageFrame::Reflow %p ", this));
+ PR_PL(("[%d,%d]\n", aReflowInput.AvailableWidth(),
+ aReflowInput.AvailableHeight()));
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsPageFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Page"_ns, aResult);
+}
+#endif
+
+void nsPageFrame::ProcessSpecialCodes(const nsString& aStr, nsString& aNewStr) {
+ aNewStr = aStr;
+
+ // Search to see if the &D code is in the string
+ // then subst in the current date/time
+ constexpr auto kDate = u"&D"_ns;
+ if (aStr.Find(kDate) != kNotFound) {
+ aNewStr.ReplaceSubstring(kDate, mPD->mDateTimeStr);
+ }
+
+ // NOTE: Must search for &PT before searching for &P
+ //
+ // Search to see if the "page number and page" total code are in the string
+ // and replace the page number and page total code with the actual
+ // values
+ constexpr auto kPageAndTotal = u"&PT"_ns;
+ if (aStr.Find(kPageAndTotal) != kNotFound) {
+ nsAutoString uStr;
+ nsTextFormatter::ssprintf(uStr, mPD->mPageNumAndTotalsFormat.get(),
+ mPageNum, mPD->mRawNumPages);
+ aNewStr.ReplaceSubstring(kPageAndTotal, uStr);
+ }
+
+ // Search to see if the page number code is in the string
+ // and replace the page number code with the actual value
+ constexpr auto kPage = u"&P"_ns;
+ if (aStr.Find(kPage) != kNotFound) {
+ nsAutoString uStr;
+ nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(), mPageNum);
+ aNewStr.ReplaceSubstring(kPage, uStr);
+ }
+
+ constexpr auto kTitle = u"&T"_ns;
+ if (aStr.Find(kTitle) != kNotFound) {
+ aNewStr.ReplaceSubstring(kTitle, mPD->mDocTitle);
+ }
+
+ constexpr auto kDocURL = u"&U"_ns;
+ if (aStr.Find(kDocURL) != kNotFound) {
+ aNewStr.ReplaceSubstring(kDocURL, mPD->mDocURL);
+ }
+
+ constexpr auto kPageTotal = u"&L"_ns;
+ if (aStr.Find(kPageTotal) != kNotFound) {
+ nsAutoString uStr;
+ nsTextFormatter::ssprintf(uStr, mPD->mPageNumFormat.get(),
+ mPD->mRawNumPages);
+ aNewStr.ReplaceSubstring(kPageTotal, uStr);
+ }
+}
+
+//------------------------------------------------------------------------------
+nscoord nsPageFrame::GetXPosition(gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ const nsRect& aRect, int32_t aJust,
+ const nsString& aStr) {
+ nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
+ aStr, this, aFontMetrics, aRenderingContext);
+ nscoord x = aRect.x;
+ switch (aJust) {
+ case nsIPrintSettings::kJustLeft:
+ x += mPD->mEdgePaperMargin.left;
+ break;
+
+ case nsIPrintSettings::kJustCenter:
+ x += (aRect.width - width) / 2;
+ break;
+
+ case nsIPrintSettings::kJustRight:
+ x += aRect.width - width - mPD->mEdgePaperMargin.right;
+ break;
+ } // switch
+
+ return x;
+}
+
+// Draw a header or footer
+// @param aRenderingContext - rendering content to draw into
+// @param aHeaderFooter - indicates whether it is a header or footer
+// @param aStrLeft - string for the left header or footer; can be empty
+// @param aStrCenter - string for the center header or footer; can be empty
+// @param aStrRight - string for the right header or footer; can be empty
+// @param aRect - the rect of the page
+// @param aAscent - the ascent of the font
+// @param aHeight - the height of the font
+void nsPageFrame::DrawHeaderFooter(
+ gfxContext& aRenderingContext, nsFontMetrics& aFontMetrics,
+ nsHeaderFooterEnum aHeaderFooter, const nsString& aStrLeft,
+ const nsString& aStrCenter, const nsString& aStrRight, const nsRect& aRect,
+ nscoord aAscent, nscoord aHeight) {
+ int32_t numStrs = 0;
+ if (!aStrLeft.IsEmpty()) numStrs++;
+ if (!aStrCenter.IsEmpty()) numStrs++;
+ if (!aStrRight.IsEmpty()) numStrs++;
+
+ if (numStrs == 0) return;
+ const nscoord contentWidth =
+ aRect.width - (mPD->mEdgePaperMargin.left + mPD->mEdgePaperMargin.right);
+ const nscoord strSpace = contentWidth / numStrs;
+
+ if (!aStrLeft.IsEmpty()) {
+ DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter,
+ nsIPrintSettings::kJustLeft, aStrLeft, aRect, aAscent,
+ aHeight, strSpace);
+ }
+ if (!aStrCenter.IsEmpty()) {
+ DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter,
+ nsIPrintSettings::kJustCenter, aStrCenter, aRect, aAscent,
+ aHeight, strSpace);
+ }
+ if (!aStrRight.IsEmpty()) {
+ DrawHeaderFooter(aRenderingContext, aFontMetrics, aHeaderFooter,
+ nsIPrintSettings::kJustRight, aStrRight, aRect, aAscent,
+ aHeight, strSpace);
+ }
+}
+
+// Draw a header or footer string
+// @param aRenderingContext - rendering context to draw into
+// @param aHeaderFooter - indicates whether it is a header or footer
+// @param aJust - indicates where the string is located within the header/footer
+// @param aStr - the string to be drawn
+// @param aRect - the rect of the page
+// @param aHeight - the height of the font
+// @param aAscent - the ascent of the font
+// @param aWidth - available width for the string
+void nsPageFrame::DrawHeaderFooter(gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsHeaderFooterEnum aHeaderFooter,
+ int32_t aJust, const nsString& aStr,
+ const nsRect& aRect, nscoord aAscent,
+ nscoord aHeight, nscoord aWidth) {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ if ((aHeaderFooter == eHeader && aHeight < mPageContentMargin.top) ||
+ (aHeaderFooter == eFooter && aHeight < mPageContentMargin.bottom)) {
+ nsAutoString str;
+ ProcessSpecialCodes(aStr, str);
+
+ int32_t len = (int32_t)str.Length();
+ if (len == 0) {
+ return; // bail is empty string
+ }
+
+ int32_t index;
+ int32_t textWidth = 0;
+ const char16_t* text = str.get();
+ // find how much text fits, the "position" is the size of the available area
+ if (nsLayoutUtils::BinarySearchForPosition(drawTarget, aFontMetrics, text,
+ 0, 0, 0, len, int32_t(aWidth),
+ index, textWidth)) {
+ if (index < len - 1) {
+ // we can't fit in all the text, try to remove 3 glyphs and append
+ // three "." charactrers.
+
+ // TODO: This might not actually remove three glyphs in cases where
+ // ZWJ sequences, regional indicators, etc are used.
+ // We also have guarantee that removing three glyphs will make enough
+ // space for the ellipse, if they are zero-width or even just narrower
+ // than the "." character.
+ // See https://bugzilla.mozilla.org/1765008
+ mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16 revIter(str);
+
+ // Start iteration at the point where the text does properly fit.
+ revIter.Seek(index);
+
+ // Step backwards 3 times, checking if we have any string left by the
+ // end.
+ revIter.Next();
+ revIter.Next();
+ if (const Maybe<uint32_t> maybeIndex = revIter.Next()) {
+ // TODO: We should consider checking for the ellipse character, or
+ // possibly for another continuation indicator based on
+ // localization.
+ // See https://bugzilla.mozilla.org/1765007
+ str.Truncate(*maybeIndex);
+ str.AppendLiteral("...");
+ } else {
+ // We can only fit 3 or fewer chars. Just show nothing
+ str.Truncate();
+ }
+ }
+ } else {
+ return; // bail if couldn't find the correct length
+ }
+
+ if (HasRTLChars(str)) {
+ PresContext()->SetBidiEnabled();
+ }
+
+ // calc the x and y positions of the text
+ nscoord x =
+ GetXPosition(aRenderingContext, aFontMetrics, aRect, aJust, str);
+ nscoord y;
+ if (aHeaderFooter == eHeader) {
+ y = aRect.y + mPD->mEdgePaperMargin.top;
+ } else {
+ y = aRect.YMost() - aHeight - mPD->mEdgePaperMargin.bottom;
+ }
+
+ // set up new clip and draw the text
+ aRenderingContext.Save();
+ aRenderingContext.Clip(NSRectToSnappedRect(
+ aRect, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
+ aRenderingContext.SetColor(sRGBColor::OpaqueBlack());
+ nsLayoutUtils::DrawString(this, aFontMetrics, &aRenderingContext, str.get(),
+ str.Length(), nsPoint(x, y + aAscent), nullptr,
+ DrawStringFlags::ForceHorizontal);
+ aRenderingContext.Restore();
+ }
+}
+
+class nsDisplayHeaderFooter final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayHeaderFooter(nsDisplayListBuilder* aBuilder, nsPageFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayHeaderFooter);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayHeaderFooter)
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) override {
+#ifdef DEBUG
+ nsPageFrame* pageFrame = do_QueryFrame(mFrame);
+ MOZ_ASSERT(pageFrame, "We should have an nsPageFrame");
+#endif
+ static_cast<nsPageFrame*>(mFrame)->PaintHeaderFooter(
+ *aCtx, ToReferenceFrame(), false);
+ }
+ NS_DISPLAY_DECL_NAME("HeaderFooter", TYPE_HEADER_FOOTER)
+
+ virtual nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+};
+
+static void PaintMarginGuides(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect, nsPoint aPt) {
+ // Set up parameters needed to draw the guides: we draw them in blue,
+ // using 2px-long dashes with 2px separation and a line width of 0.5px.
+ // Drawing is antialiased, so on a non-hidpi screen where the line width is
+ // less than one device pixel, it doesn't disappear but renders fainter
+ // than a solid 1px-wide line would be.
+ // (In many cases, the entire preview is scaled down so that the guides
+ // will be nominally less than 1 dev px even on a hidpi screen, resulting
+ // in lighter antialiased rendering so they don't dominate the page.)
+ ColorPattern pattern(ToDeviceColor(sRGBColor(0.0f, 0.0f, 1.0f)));
+ Float dashes[] = {2.0f, 2.0f};
+ StrokeOptions stroke(/* line width (in CSS px) */ 0.5f,
+ JoinStyle::MITER_OR_BEVEL, CapStyle::BUTT,
+ /* mitre limit (default, not used) */ 10.0f,
+ /* set dash pattern of 2px stroke, 2px gap */
+ ArrayLength(dashes), dashes,
+ /* dash offset */ 0.0f);
+ DrawOptions options;
+
+ MOZ_RELEASE_ASSERT(aFrame->IsPageFrame());
+ const nsMargin& margin =
+ static_cast<nsPageFrame*>(aFrame)->GetUsedPageContentMargin();
+ int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // Get the frame's rect and inset by the margins to get the edges of the
+ // content area, where we want to draw the guides.
+ // We draw in two stages, first applying the top/bottom margins and drawing
+ // the horizontal guides across the full width of the page.
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(nsMargin(margin.top, 0, margin.bottom, 0));
+ Rect r = NSRectToRect(rect, appUnitsPerDevPx);
+ aDrawTarget->StrokeLine(r.TopLeft(), r.TopRight(), pattern, stroke, options);
+ aDrawTarget->StrokeLine(r.BottomLeft(), r.BottomRight(), pattern, stroke,
+ options);
+
+ // Then reset rect, apply the left/right margins, and draw vertical guides
+ // extending the full height of the page.
+ rect = nsRect(aPt, aFrame->GetSize());
+ rect.Deflate(nsMargin(0, margin.right, 0, margin.left));
+ r = NSRectToRect(rect, appUnitsPerDevPx);
+ aDrawTarget->StrokeLine(r.TopLeft(), r.BottomLeft(), pattern, stroke,
+ options);
+ aDrawTarget->StrokeLine(r.TopRight(), r.BottomRight(), pattern, stroke,
+ options);
+}
+
+static std::tuple<uint32_t, uint32_t> GetRowAndColFromIdx(uint32_t aIdxOnSheet,
+ uint32_t aNumCols) {
+ // Compute the row index by *dividing* the item's ordinal position by how
+ // many items fit in each row (i.e. the number of columns), and flooring.
+ // Compute the column index by getting the remainder of that division:
+ // Notably, mNumRows is irrelevant to this computation; that's because
+ // we're adding new items column-by-column rather than row-by-row.
+ return {aIdxOnSheet / aNumCols, aIdxOnSheet % aNumCols};
+}
+
+// Helper for BuildDisplayList:
+static gfx::Matrix4x4 ComputePagesPerSheetAndPageSizeTransform(
+ const nsIFrame* aFrame, float aAppUnitsPerPixel) {
+ MOZ_ASSERT(aFrame->IsPageFrame());
+ auto* pageFrame = static_cast<const nsPageFrame*>(aFrame);
+ const nsSize contentPageSize = pageFrame->ComputePageSize();
+ MOZ_ASSERT(contentPageSize.width > 0 && contentPageSize.height > 0);
+ nsSharedPageData* pd = pageFrame->GetSharedPageData();
+ const auto* ppsInfo = pd->PagesPerSheetInfo();
+
+ const nsContainerFrame* const parentFrame = pageFrame->GetParent();
+ MOZ_ASSERT(parentFrame->IsPrintedSheetFrame(),
+ "Parent of nsPageFrame should be PrintedSheetFrame");
+ const auto* sheetFrame = static_cast<const PrintedSheetFrame*>(parentFrame);
+
+ const double rotation =
+ pageFrame->GetPageOrientationRotation(pageFrame->GetSharedPageData());
+
+ gfx::Matrix4x4 transform;
+
+ if (ppsInfo->mNumPages == 1) {
+ if (rotation != 0.0) {
+ const nsSize sheetSize = sheetFrame->GetSizeForChildren();
+ const bool sheetIsPortrait = sheetSize.width < sheetSize.height;
+ const bool rotatingClockwise = rotation > 0.0;
+
+ // rotation point:
+ int32_t x, y;
+ if (rotatingClockwise != sheetIsPortrait) {
+ // rotating portrait clockwise, or landscape counterclockwise
+ x = y = std::min(sheetSize.width, sheetSize.height) / 2;
+ } else {
+ // rotating portrait counterclockwise, or landscape clockwise
+ x = y = std::max(sheetSize.width, sheetSize.height) / 2;
+ }
+
+ transform = gfx::Matrix4x4::Translation(
+ NSAppUnitsToFloatPixels(x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(y, aAppUnitsPerPixel), 0);
+ transform.RotateZ(rotation);
+ transform.PreTranslate(NSAppUnitsToFloatPixels(-x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(-y, aAppUnitsPerPixel), 0);
+ }
+
+ float scale = pageFrame->ComputeSinglePPSPageSizeScale(contentPageSize);
+ transform.PreScale(scale, scale, 1);
+ return transform;
+ }
+
+ // The multiple pages-per-sheet case.
+
+ // Begin with the translation of the page to its pages-per-sheet grid "cell"
+ // (the grid origin accounts for the sheet's unwriteable margins):
+ const nsPoint gridOrigin = sheetFrame->GetGridOrigin();
+ const nscoord cellWidth = sheetFrame->GetGridCellWidth();
+ const nscoord cellHeight = sheetFrame->GetGridCellHeight();
+ uint32_t rowIdx, colIdx;
+ std::tie(rowIdx, colIdx) = GetRowAndColFromIdx(pageFrame->IndexOnSheet(),
+ sheetFrame->GetGridNumCols());
+ transform = gfx::Matrix4x4::Translation(
+ NSAppUnitsToFloatPixels(gridOrigin.x + nscoord(colIdx) * cellWidth,
+ aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(gridOrigin.y + nscoord(rowIdx) * cellHeight,
+ aAppUnitsPerPixel),
+ 0.0f);
+
+ // Scale the page to fit, centered, in the grid cell:
+ float scaleX = float(cellWidth) / float(contentPageSize.width);
+ float scaleY = float(cellHeight) / float(contentPageSize.height);
+ MOZ_ASSERT(scaleX > 0.0f && scaleX <= 1.0f && scaleY > 0.0f &&
+ scaleY <= 1.0f);
+ float scale;
+ float dx = 0.0f, dy = 0.0f;
+ if (scaleX < scaleY) {
+ scale = scaleX;
+ // We need to scale down more for the width than the height, so we'll have
+ // some spare space in the page's vertical direction. We offset the page
+ // to share that space equally above and below the page to center it.
+ nscoord extraSpace =
+ cellHeight - NSToCoordRound(float(contentPageSize.height) * scale);
+ dy = NSAppUnitsToFloatPixels(extraSpace / 2, aAppUnitsPerPixel);
+ } else {
+ scale = scaleY;
+ nscoord extraSpace =
+ cellWidth - NSToCoordRound(float(contentPageSize.width) * scale);
+ dx = NSAppUnitsToFloatPixels(extraSpace / 2, aAppUnitsPerPixel);
+ }
+ transform.PreTranslate(dx, dy, 0.0f);
+ transform.PreScale(scale, scale, 1.0f);
+
+ // Apply 'page-orientation' rotation, if applicable:
+ if (rotation != 0.0) {
+ // We've already translated and scaled the page to fit the cell, ignoring
+ // rotation. Here we rotate the page around its center and, if necessary,
+ // also scale it to fit it to its cell for its orientation change.
+
+ float fitScale = 1.0f;
+ if (MOZ_LIKELY(cellWidth != cellHeight &&
+ contentPageSize.width != contentPageSize.height)) {
+ // If neither the cell nor the page are square, the scale must change.
+ float cellRatio = float(cellWidth) / float(cellHeight);
+ float pageRatio =
+ float(contentPageSize.width) / float(contentPageSize.height);
+ const bool orientationWillMatchAfterRotation =
+ floor(cellRatio) != floor(pageRatio);
+ if (cellRatio > 1.0f) {
+ cellRatio = 1.0f / cellRatio; // normalize
+ }
+ if (pageRatio > 1.0f) {
+ pageRatio = 1.0f / pageRatio; // normalize
+ }
+ fitScale = std::max(cellRatio, pageRatio);
+ if (orientationWillMatchAfterRotation) {
+ // Scale up, not down
+ fitScale = 1.0f / fitScale;
+ }
+ }
+
+ transform.PreTranslate(
+ NSAppUnitsToFloatPixels(contentPageSize.width / 2, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(contentPageSize.height / 2, aAppUnitsPerPixel),
+ 0);
+ if (MOZ_LIKELY(fitScale != 1.0f)) {
+ transform.PreScale(fitScale, fitScale, 1.0f);
+ }
+ transform.RotateZ(rotation);
+ transform.PreTranslate(
+ NSAppUnitsToFloatPixels(-contentPageSize.width / 2, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(-contentPageSize.height / 2, aAppUnitsPerPixel),
+ 0);
+ }
+
+ return transform;
+}
+
+nsIFrame::ComputeTransformFunction nsPageFrame::GetTransformGetter() const {
+ return ComputePagesPerSheetAndPageSizeTransform;
+}
+
+nsPageContentFrame* nsPageFrame::PageContentFrame() const {
+ nsIFrame* const frame = mFrames.FirstChild();
+ MOZ_ASSERT(frame, "pageFrame must have one child");
+ MOZ_ASSERT(frame->IsPageContentFrame(),
+ "pageFrame must have pageContentFrame as the first child");
+ return static_cast<nsPageContentFrame*>(frame);
+}
+
+nsSize nsPageFrame::ComputePageSize() const {
+ // Compute the expected page-size.
+ const nsPageFrame* const frame =
+ StaticPrefs::layout_css_allow_mixed_page_sizes()
+ ? this
+ : static_cast<nsPageFrame*>(FirstContinuation());
+ const StylePageSize& pageSize = frame->PageContentFrame()->StylePage()->mSize;
+ nsSize size = PresContext()->GetPageSize();
+ if (pageSize.IsSize()) {
+ // Use the specified size,
+ // ignoring sizes that include a zero width or height.
+ // These are also ignored in ServoStyleSet::GetPageSizeForPageName()
+ // when getting the paper size.
+ nscoord cssPageWidth = pageSize.AsSize().width.ToAppUnits();
+ nscoord cssPageHeight = pageSize.AsSize().height.ToAppUnits();
+ if (cssPageWidth > 0 && cssPageHeight > 0) {
+ return nsSize{cssPageWidth, cssPageHeight};
+ }
+ // Invalid size; just return the default
+ return size;
+ }
+
+ if (pageSize.IsOrientation()) {
+ // Ensure the correct orientation is applied.
+ if (pageSize.AsOrientation() == StylePageSizeOrientation::Portrait) {
+ if (size.width > size.height) {
+ std::swap(size.width, size.height);
+ }
+ } else {
+ MOZ_ASSERT(pageSize.AsOrientation() ==
+ StylePageSizeOrientation::Landscape);
+ if (size.width < size.height) {
+ std::swap(size.width, size.height);
+ }
+ }
+ } else {
+ MOZ_ASSERT(pageSize.IsAuto(), "Impossible page-size value?");
+ }
+ return size;
+}
+
+float nsPageFrame::ComputeSinglePPSPageSizeScale(
+ const nsSize aContentPageSize) const {
+ MOZ_ASSERT(GetSharedPageData()->PagesPerSheetInfo()->mNumPages == 1,
+ "Only intended for the pps==1 case");
+ MOZ_ASSERT(aContentPageSize == ComputePageSize(),
+ "Incorrect content page size");
+
+ // Check for the simplest case first, an auto page-size which requires no
+ // scaling at all.
+ {
+ const nsPageFrame* const frame =
+ StaticPrefs::layout_css_allow_mixed_page_sizes()
+ ? this
+ : static_cast<nsPageFrame*>(FirstContinuation());
+ const StylePageSize& pageSize =
+ frame->PageContentFrame()->StylePage()->mSize;
+ if (pageSize.IsAuto()) {
+ return 1.0f;
+ }
+ }
+
+ const nsContainerFrame* const parent = GetParent();
+ MOZ_ASSERT(parent && parent->IsPrintedSheetFrame(),
+ "Parent of nsPageFrame should be PrintedSheetFrame");
+ const auto* sheet = static_cast<const PrintedSheetFrame*>(parent);
+
+ // Compute scaling due to a possible mismatch in the paper size we are
+ // printing to (from the pres context) and the specified page size when the
+ // content uses "@page {size: ...}" to specify a page size for the content.
+ float scale = 1.0f;
+
+ const nsSize sheetSize = sheet->GetSizeForChildren();
+ nscoord contentPageHeight = aContentPageSize.height;
+ // Scale down if the target is too wide.
+ if (aContentPageSize.width > sheetSize.width) {
+ scale *= float(sheetSize.width) / float(aContentPageSize.width);
+ contentPageHeight = NSToCoordRound(contentPageHeight * scale);
+ }
+ // Scale down if the target is too tall.
+ if (contentPageHeight > sheetSize.height) {
+ scale *= float(sheetSize.height) / float(contentPageHeight);
+ }
+ MOZ_ASSERT(
+ scale <= 1.0f,
+ "Page-size mismatches should only have caused us to scale down, not up.");
+ return scale;
+}
+
+double nsPageFrame::GetPageOrientationRotation(nsSharedPageData* aPD) const {
+ if (!StaticPrefs::layout_css_page_orientation_enabled()) {
+ return 0.0;
+ }
+
+ if (aPD->PagesPerSheetInfo()->mNumPages == 1 && !PresContext()->IsScreen() &&
+ aPD->mPrintSettings->GetOutputFormat() !=
+ nsIPrintSettings::kOutputFormatPDF) {
+ // In the single page-per-sheet case we rotate the page by essentially
+ // rotating the entire sheet. But we can't do that when the output device
+ // doesn't support mixed sheet orientations.
+ return 0.0;
+ }
+
+ const StylePageOrientation& orientation =
+ PageContentFrame()->StylePage()->mPageOrientation;
+
+ if (orientation == StylePageOrientation::RotateLeft) {
+ return -M_PI / 2.0;
+ }
+ if (orientation == StylePageOrientation::RotateRight) {
+ return M_PI / 2.0;
+ }
+ return 0.0;
+}
+
+void nsPageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ nsDisplayList content(aBuilder);
+ nsDisplayListSet set(&content, &content, &content, &content, &content,
+ &content);
+ {
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ clipState.Clear();
+
+ nsPresContext* const pc = PresContext();
+ {
+ // We need to extend the building rect to include the specified page size
+ // (scaled by the print scaling factor), in case it is larger than the
+ // physical page size. In that case the nsPageFrame will be the size of
+ // the physical page, but the child nsPageContentFrame will be the larger
+ // specified page size. The more correct way to do this would be to fully
+ // reverse the result of ComputePagesPerSheetAndPageSizeTransform to
+ // handle this scaling, but this should have the same result and is
+ // easier.
+ const float scale = pc->GetPageScale();
+ const nsSize pageSize = ComputePageSize();
+ const nsRect scaledPageRect{0, 0, NSToCoordCeil(pageSize.width / scale),
+ NSToCoordCeil(pageSize.height / scale)};
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForPageContentFrame(
+ aBuilder, this, scaledPageRect, scaledPageRect);
+
+ nsContainerFrame::BuildDisplayList(aBuilder, set);
+ }
+
+ if (pc->IsRootPaginatedDocument()) {
+ content.AppendNewToTop<nsDisplayHeaderFooter>(aBuilder, this);
+
+ // For print-preview, show margin guides if requested in the settings.
+ if (pc->Type() == nsPresContext::eContext_PrintPreview &&
+ mPD->mPrintSettings->GetShowMarginGuides()) {
+ content.AppendNewToTop<nsDisplayGeneric>(
+ aBuilder, this, PaintMarginGuides, "MarginGuides",
+ DisplayItemType::TYPE_MARGIN_GUIDES);
+ }
+ }
+ }
+
+ // We'll be drawing the page with a (usually-trivial)
+ // N-pages-per-sheet transform applied, so our passed-in visible rect
+ // isn't meaningful while we're drawing our children, because the
+ // transform could scale down content whose coordinates are off-screen
+ // such that it ends up on-screen. So: we temporarily update the visible
+ // rect to be the child nsPageFrame's whole frame-rect (represented in
+ // this PrintedSheetFrame's coordinate space.
+ content.AppendNewToTop<nsDisplayTransform>(
+ aBuilder, this, &content, content.GetBuildingRect(),
+ nsDisplayTransform::WithTransformGetter);
+
+ set.MoveTo(aLists);
+}
+
+//------------------------------------------------------------------------------
+void nsPageFrame::DeterminePageNum() {
+ // If we have no previous continuation, we're page 1. Otherwise, we're
+ // just one more than our previous continuation's page number.
+ auto* prevContinuation = static_cast<nsPageFrame*>(GetPrevContinuation());
+ mPageNum = prevContinuation ? prevContinuation->GetPageNum() + 1 : 1;
+}
+
+void nsPageFrame::PaintHeaderFooter(gfxContext& aRenderingContext, nsPoint aPt,
+ bool aDisableSubpixelAA) {
+ nsPresContext* pc = PresContext();
+
+ nsRect rect(aPt, ComputePageSize());
+ aRenderingContext.SetColor(sRGBColor::OpaqueBlack());
+
+ DrawTargetAutoDisableSubpixelAntialiasing disable(
+ aRenderingContext.GetDrawTarget(), aDisableSubpixelAA);
+
+ // Get the FontMetrics to determine width.height of strings
+ nsFontMetrics::Params params;
+ params.userFontSet = pc->GetUserFontSet();
+ params.textPerf = pc->GetTextPerfMetrics();
+ params.featureValueLookup = pc->GetFontFeatureValuesLookup();
+ RefPtr<nsFontMetrics> fontMet = pc->GetMetricsFor(mPD->mHeadFootFont, params);
+
+ nscoord ascent = fontMet->MaxAscent();
+ nscoord visibleHeight = fontMet->MaxHeight();
+
+ // print document headers and footers
+ nsString headerLeft, headerCenter, headerRight;
+ mPD->mPrintSettings->GetHeaderStrLeft(headerLeft);
+ mPD->mPrintSettings->GetHeaderStrCenter(headerCenter);
+ mPD->mPrintSettings->GetHeaderStrRight(headerRight);
+ DrawHeaderFooter(aRenderingContext, *fontMet, eHeader, headerLeft,
+ headerCenter, headerRight, rect, ascent, visibleHeight);
+
+ nsString footerLeft, footerCenter, footerRight;
+ mPD->mPrintSettings->GetFooterStrLeft(footerLeft);
+ mPD->mPrintSettings->GetFooterStrCenter(footerCenter);
+ mPD->mPrintSettings->GetFooterStrRight(footerRight);
+ DrawHeaderFooter(aRenderingContext, *fontMet, eFooter, footerLeft,
+ footerCenter, footerRight, rect, ascent, visibleHeight);
+}
+
+void nsPageFrame::SetSharedPageData(nsSharedPageData* aPD) {
+ mPD = aPD;
+ // Set the shared data into the page frame before reflow
+ PageContentFrame()->SetSharedPageData(mPD);
+}
+
+nsIFrame* NS_NewPageBreakFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ MOZ_ASSERT(aPresShell, "null PresShell");
+ // check that we are only creating page break frames when printing
+ NS_ASSERTION(aPresShell->GetPresContext()->IsPaginated(),
+ "created a page break frame while not printing");
+
+ return new (aPresShell)
+ nsPageBreakFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsPageBreakFrame)
+
+nsPageBreakFrame::nsPageBreakFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsLeafFrame(aStyle, aPresContext, kClassID) {}
+
+nsPageBreakFrame::~nsPageBreakFrame() = default;
+
+nscoord nsPageBreakFrame::GetIntrinsicISize() {
+ return nsPresContext::CSSPixelsToAppUnits(1);
+}
+
+nscoord nsPageBreakFrame::GetIntrinsicBSize() { return 0; }
+
+void nsPageBreakFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ DO_GLOBAL_REFLOW_COUNT("nsPageBreakFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ // Override reflow, since we don't want to deal with what our
+ // computed values are.
+ const WritingMode wm = aReflowInput.GetWritingMode();
+ nscoord bSize = aReflowInput.AvailableBSize();
+ if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
+ bSize = nscoord(0);
+ } else if (GetContent()->IsHTMLElement(nsGkAtoms::legend)) {
+ // If this is a page break frame for a _rendered legend_ then it should be
+ // ignored since these frames are inserted inside the fieldset's inner
+ // frame and thus "misplaced". nsFieldSetFrame::Reflow deals with these
+ // forced breaks explicitly instead.
+ const nsContainerFrame* parent = GetParent();
+ if (parent &&
+ parent->Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) {
+ while ((parent = parent->GetParent())) {
+ if (const nsFieldSetFrame* const fieldset = do_QueryFrame(parent)) {
+ const auto* const legend = fieldset->GetLegend();
+ if (legend && legend->GetContent() == GetContent()) {
+ bSize = nscoord(0);
+ }
+ break;
+ }
+ }
+ }
+ }
+ LogicalSize finalSize(wm, GetIntrinsicISize(), bSize);
+ // round the height down to the nearest pixel
+ // XXX(mats) why???
+ finalSize.BSize(wm) -=
+ finalSize.BSize(wm) % nsPresContext::CSSPixelsToAppUnits(1);
+ aReflowOutput.SetSize(wm, finalSize);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsPageBreakFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"PageBreak"_ns, aResult);
+}
+#endif
diff --git a/layout/generic/nsPageFrame.h b/layout/generic/nsPageFrame.h
new file mode 100644
index 0000000000..ede3cb96f4
--- /dev/null
+++ b/layout/generic/nsPageFrame.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsPageFrame_h___
+#define nsPageFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsLeafFrame.h"
+
+class nsFontMetrics;
+class nsPageContentFrame;
+class nsSharedPageData;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+// Page frame class. Represents an individual page, in paginated mode.
+class nsPageFrame final : public nsContainerFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsPageFrame)
+
+ friend nsPageFrame* NS_NewPageFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ //////////////////
+ // For Printing
+ //////////////////
+
+ // Determine this page's page-number, based on its previous continuation
+ // (whose page number is presumed to already be known).
+ void DeterminePageNum();
+ int32_t GetPageNum() const { return mPageNum; }
+
+ void SetSharedPageData(nsSharedPageData* aPD);
+ nsSharedPageData* GetSharedPageData() const { return mPD; }
+
+ void PaintHeaderFooter(gfxContext& aRenderingContext, nsPoint aPt,
+ bool aSubpixelAA);
+
+ const nsMargin& GetUsedPageContentMargin() const {
+ return mPageContentMargin;
+ }
+
+ uint32_t IndexOnSheet() const { return mIndexOnSheet; }
+ void SetIndexOnSheet(uint32_t aIndexOnSheet) {
+ mIndexOnSheet = aIndexOnSheet;
+ }
+
+ ComputeTransformFunction GetTransformGetter() const override;
+
+ nsPageContentFrame* PageContentFrame() const;
+
+ nsSize ComputePageSize() const;
+
+ // Computes the scaling factor to fit the page to the sheet in the single
+ // page-per-sheet case. (The multiple pages-per-sheet case is currently
+ // different - see the comment for
+ // PrintedSheetFrame::ComputePagesPerSheetGridMetrics and code in
+ // ComputePagesPerSheetAndPageSizeTransform.) The page and sheet dimensions
+ // may be different due to a CSS page-size that gives the page a size that is
+ // too large to fit on the sheet that we are printing to.
+ float ComputeSinglePPSPageSizeScale(const nsSize aContentPageSize) const;
+
+ // Returns the rotation from CSS `page-orientation` property, if set, and if
+ // it applies. Note: the single page-per-sheet case is special since in that
+ // case we effectively rotate the sheet (as opposed to rotating pages in
+ // their pages-per-sheet grid cell). In this case we return zero if the
+ // output medium does not support changing the dimensions (orientation) of
+ // the sheet (i.e. only print preview and save-to-PDF are supported).
+ double GetPageOrientationRotation(nsSharedPageData* aPD) const;
+
+ protected:
+ explicit nsPageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
+ virtual ~nsPageFrame();
+
+ typedef enum { eHeader, eFooter } nsHeaderFooterEnum;
+
+ nscoord GetXPosition(gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics, const nsRect& aRect,
+ int32_t aJust, const nsString& aStr);
+
+ nsReflowStatus ReflowPageContent(nsPresContext*,
+ const ReflowInput& aPageReflowInput);
+
+ void DrawHeaderFooter(gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsHeaderFooterEnum aHeaderFooter, int32_t aJust,
+ const nsString& sStr, const nsRect& aRect,
+ nscoord aHeight, nscoord aAscent, nscoord aWidth);
+
+ void DrawHeaderFooter(gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics,
+ nsHeaderFooterEnum aHeaderFooter,
+ const nsString& aStrLeft, const nsString& aStrRight,
+ const nsString& aStrCenter, const nsRect& aRect,
+ nscoord aAscent, nscoord aHeight);
+
+ void ProcessSpecialCodes(const nsString& aStr, nsString& aNewStr);
+
+ static constexpr int32_t kPageNumUnset = -1;
+ // 1-based page-num
+ int32_t mPageNum = kPageNumUnset;
+
+ // 0-based index on the sheet that we belong to. Unused/meaningless if this
+ // page has frame state bit NS_PAGE_SKIPPED_BY_CUSTOM_RANGE.
+ uint32_t mIndexOnSheet = 0;
+
+ // Note: this will be set before reflow, and it's strongly owned by our
+ // nsPageSequenceFrame, which outlives us.
+ nsSharedPageData* mPD = nullptr;
+
+ // Computed page content margins.
+ //
+ // This is the amount of space from the edges of the content to the edges,
+ // measured in the content coordinate space. This is as opposed to the
+ // coordinate space of the physical paper. This might be different due to
+ // a CSS page-size that is too large to fit on the paper, causing content to
+ // be scaled to fit.
+ //
+ // These margins take into account:
+ // * CSS-defined margins (content units)
+ // * User-supplied margins (physical units)
+ // * Unwriteable-supplied margins (physical units)
+ //
+ // When computing these margins, all physical units have the inverse of the
+ // scaling factor caused by CSS page-size downscaling applied. This ensures
+ // that even if the content will be downscaled, it will respect the (now
+ // upscaled) physical unwriteable margins required by the printer.
+ // For user-supplied margins, it isn't immediately obvious to the user what
+ // the intended page-size of the document is, so we consider these margins to
+ // be in the physical space of the paper.
+ nsMargin mPageContentMargin;
+};
+
+class nsPageBreakFrame final : public nsLeafFrame {
+ NS_DECL_FRAMEARENA_HELPERS(nsPageBreakFrame)
+
+ explicit nsPageBreakFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
+ ~nsPageBreakFrame();
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ protected:
+ nscoord GetIntrinsicISize() override;
+ nscoord GetIntrinsicBSize() override;
+
+ friend nsIFrame* NS_NewPageBreakFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+};
+
+#endif /* nsPageFrame_h___ */
diff --git a/layout/generic/nsPageSequenceFrame.cpp b/layout/generic/nsPageSequenceFrame.cpp
new file mode 100644
index 0000000000..a3348433cb
--- /dev/null
+++ b/layout/generic/nsPageSequenceFrame.cpp
@@ -0,0 +1,788 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPageSequenceFrame.h"
+
+#include "mozilla/intl/AppDateTimeFormat.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PrintedSheetFrame.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/StaticPresData.h"
+
+#include "nsCOMPtr.h"
+#include "nsDeviceContext.h"
+#include "nsPresContext.h"
+#include "gfxContext.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsIPrintSettings.h"
+#include "nsPageFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "nsRegion.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsContentUtils.h"
+#include "nsDisplayList.h"
+#include "nsHTMLCanvasFrame.h"
+#include "nsICanvasRenderingContextInternal.h"
+#include "nsServiceManagerUtils.h"
+#include <algorithm>
+#include <limits>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+mozilla::LazyLogModule gLayoutPrintingLog("printing-layout");
+
+#define PR_PL(_p1) MOZ_LOG(gLayoutPrintingLog, mozilla::LogLevel::Debug, _p1)
+
+nsPageSequenceFrame* NS_NewPageSequenceFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsPageSequenceFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsPageSequenceFrame)
+
+static const nsPagesPerSheetInfo kSupportedPagesPerSheet[] = {
+ /* Members are: {mNumPages, mLargerNumTracks} */
+ // clang-format off
+ {1, 1},
+ {2, 2},
+ {4, 2},
+ {6, 3},
+ {9, 3},
+ {16, 4},
+ // clang-format on
+};
+
+inline void SanityCheckPagesPerSheetInfo() {
+#ifdef DEBUG
+ // Sanity-checks:
+ MOZ_ASSERT(ArrayLength(kSupportedPagesPerSheet) > 0,
+ "Should have at least one pages-per-sheet option.");
+ MOZ_ASSERT(kSupportedPagesPerSheet[0].mNumPages == 1,
+ "The 0th index is reserved for default 1-page-per-sheet entry");
+
+ uint16_t prevInfoPPS = 0;
+ for (const auto& info : kSupportedPagesPerSheet) {
+ MOZ_ASSERT(info.mNumPages > prevInfoPPS,
+ "page count field should be positive & monotonically increase");
+ MOZ_ASSERT(info.mLargerNumTracks > 0,
+ "page grid has to have a positive number of tracks");
+ MOZ_ASSERT(info.mNumPages % info.mLargerNumTracks == 0,
+ "page count field should be evenly divisible by "
+ "the given track-count");
+ prevInfoPPS = info.mNumPages;
+ }
+#endif
+}
+
+const nsPagesPerSheetInfo& nsPagesPerSheetInfo::LookupInfo(int32_t aPPS) {
+ SanityCheckPagesPerSheetInfo();
+
+ // Walk the array, looking for a match:
+ for (const auto& info : kSupportedPagesPerSheet) {
+ if (aPPS == info.mNumPages) {
+ return info;
+ }
+ }
+
+ NS_WARNING("Unsupported pages-per-sheet value");
+ // If no match was found, return the first entry (for 1 page per sheet).
+ return kSupportedPagesPerSheet[0];
+}
+
+const nsPagesPerSheetInfo* nsSharedPageData::PagesPerSheetInfo() {
+ if (mPagesPerSheetInfo) {
+ return mPagesPerSheetInfo;
+ }
+
+ int32_t pagesPerSheet;
+ if (!mPrintSettings ||
+ NS_FAILED(mPrintSettings->GetNumPagesPerSheet(&pagesPerSheet))) {
+ // If we can't read the value from print settings, just fall back to 1.
+ pagesPerSheet = 1;
+ }
+
+ mPagesPerSheetInfo = &nsPagesPerSheetInfo::LookupInfo(pagesPerSheet);
+ return mPagesPerSheetInfo;
+}
+
+nsPageSequenceFrame::nsPageSequenceFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID),
+ mMaxSheetSize(mWritingMode),
+ mScrollportSize(mWritingMode),
+ mCalledBeginPage(false),
+ mCurrentCanvasListSetup(false) {
+ mPageData = MakeUnique<nsSharedPageData>();
+ mPageData->mHeadFootFont =
+ *PresContext()
+ ->Document()
+ ->GetFontPrefsForLang(aStyle->StyleFont()->mLanguage)
+ ->GetDefaultFont(StyleGenericFontFamily::Serif);
+ mPageData->mHeadFootFont.size =
+ Length::FromPixels(CSSPixel::FromPoints(10.0f));
+ mPageData->mPrintSettings = aPresContext->GetPrintSettings();
+ MOZ_RELEASE_ASSERT(mPageData->mPrintSettings, "How?");
+
+ // Doing this here so we only have to go get these formats once
+ SetPageNumberFormat("pagenumber", "%1$d", true);
+ SetPageNumberFormat("pageofpages", "%1$d of %2$d", false);
+}
+
+nsPageSequenceFrame::~nsPageSequenceFrame() { ResetPrintCanvasList(); }
+
+NS_QUERYFRAME_HEAD(nsPageSequenceFrame)
+ NS_QUERYFRAME_ENTRY(nsPageSequenceFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+//----------------------------------------------------------------------
+
+float nsPageSequenceFrame::GetPrintPreviewScale() const {
+ nsPresContext* pc = PresContext();
+ float scale = pc->GetPrintPreviewScaleForSequenceFrameOrScrollbars();
+
+ WritingMode wm = GetWritingMode();
+ if (pc->IsScreen() && MOZ_LIKELY(mScrollportSize.ISize(wm) > 0 &&
+ mScrollportSize.BSize(wm) > 0)) {
+ // For print preview, scale down as-needed to ensure that each of our
+ // sheets will fit in the the scrollport.
+
+ // Check if the current scale is sufficient for our sheets to fit in inline
+ // axis (and if not, reduce the scale so that it will fit).
+ nscoord scaledISize = NSToCoordCeil(mMaxSheetSize.ISize(wm) * scale);
+ if (scaledISize > mScrollportSize.ISize(wm)) {
+ scale *= float(mScrollportSize.ISize(wm)) / float(scaledISize);
+ }
+
+ // Further reduce the scale (if needed) to be sure each sheet will fit in
+ // block axis, too.
+ // NOTE: in general, a scrollport's BSize *could* be unconstrained,
+ // i.e. sized to its contents. If that happens, then shrinking the contents
+ // to fit the scrollport is not a meaningful operation in this axis, so we
+ // skip over this. But we can be pretty sure that the print-preview UI
+ // will have given the scrollport a fixed size; hence the MOZ_LIKELY here.
+ if (MOZ_LIKELY(mScrollportSize.BSize(wm) != NS_UNCONSTRAINEDSIZE)) {
+ nscoord scaledBSize = NSToCoordCeil(mMaxSheetSize.BSize(wm) * scale);
+ if (scaledBSize > mScrollportSize.BSize(wm)) {
+ scale *= float(mScrollportSize.BSize(wm)) / float(scaledBSize);
+ }
+ }
+ }
+ return scale;
+}
+
+void nsPageSequenceFrame::PopulateReflowOutput(
+ ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput) {
+ // Aim to fill the whole available space, not only so we can act as a
+ // background in print preview but also handle overflow in child page frames
+ // correctly.
+ // Use availableISize so we don't cause a needless horizontal scrollbar.
+ float scale = GetPrintPreviewScale();
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nscoord iSize = wm.IsVertical() ? mSize.Height() : mSize.Width();
+ nscoord bSize = wm.IsVertical() ? mSize.Width() : mSize.Height();
+
+ nscoord availableISize = aReflowInput.AvailableISize();
+ nscoord computedBSize = aReflowInput.ComputedBSize();
+ if (MOZ_UNLIKELY(computedBSize == NS_UNCONSTRAINEDSIZE)) {
+ // We have unconstrained BSize, which should only happen if someone calls
+ // SizeToContent() on our window (which we don't expect to happen for
+ // actual user flows, but is possible for fuzzers to trigger). We just nerf
+ // the ReflowInput's contributions to the std::max() expressions below,
+ // which does indeed make us "size to content", via letting std::max()
+ // choose the scaled iSize/bSize expressions.
+ availableISize = computedBSize = 0;
+ }
+ aReflowOutput.ISize(wm) =
+ std::max(NSToCoordFloor(iSize * scale), availableISize);
+ aReflowOutput.BSize(wm) =
+ std::max(NSToCoordFloor(bSize * scale), computedBSize);
+ aReflowOutput.SetOverflowAreasToDesiredBounds();
+}
+
+// Helper function to compute the offset needed to center a child
+// page-frame's margin-box inside our content-box.
+nscoord nsPageSequenceFrame::ComputeCenteringMargin(
+ nscoord aContainerContentBoxWidth, nscoord aChildPaddingBoxWidth,
+ const nsMargin& aChildPhysicalMargin) {
+ // We'll be centering our child's margin-box, so get the size of that:
+ nscoord childMarginBoxWidth =
+ aChildPaddingBoxWidth + aChildPhysicalMargin.LeftRight();
+
+ // When rendered, our child's rect will actually be scaled up by the
+ // print-preview scale factor, via ComputePageSequenceTransform().
+ // We really want to center *that scaled-up rendering* inside of
+ // aContainerContentBoxWidth. So, we scale up its margin-box here...
+ float scale = GetPrintPreviewScale();
+ nscoord scaledChildMarginBoxWidth =
+ NSToCoordRound(childMarginBoxWidth * scale);
+
+ // ...and see we how much space is left over, when we subtract that scaled-up
+ // size from the container width:
+ nscoord scaledExtraSpace =
+ aContainerContentBoxWidth - scaledChildMarginBoxWidth;
+
+ if (scaledExtraSpace <= 0) {
+ // (Don't bother centering if there's zero/negative space.)
+ return 0;
+ }
+
+ // To center the child, we want to give it an additional left-margin of half
+ // of the extra space. And then, we have to scale that space back down, so
+ // that it'll produce the correct scaled-up amount when we render (because
+ // rendering will scale it back up):
+ return NSToCoordRound(scaledExtraSpace * 0.5 / scale);
+}
+
+uint32_t nsPageSequenceFrame::GetPagesInFirstSheet() const {
+ nsIFrame* firstSheet = mFrames.FirstChild();
+ if (!firstSheet) {
+ return 0;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(firstSheet->IsPrintedSheetFrame());
+ return static_cast<PrintedSheetFrame*>(firstSheet)->GetNumPages();
+}
+
+/*
+ * Note: we largely position/size out our children (page frames) using
+ * \*physical\* x/y/width/height values, because the print preview UI is always
+ * arranged in the same orientation, regardless of writing mode.
+ */
+void nsPageSequenceFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ MOZ_ASSERT(aPresContext->IsRootPaginatedDocument(),
+ "A Page Sequence is only for real pages");
+ DO_GLOBAL_REFLOW_COUNT("nsPageSequenceFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE_REFLOW_IN("nsPageSequenceFrame::Reflow");
+
+ auto CenterPages = [&] {
+ for (nsIFrame* child : mFrames) {
+ nsMargin pageCSSMargin = child->GetUsedMargin();
+ nscoord centeringMargin =
+ ComputeCenteringMargin(aReflowInput.ComputedWidth(),
+ child->GetRect().Width(), pageCSSMargin);
+ nscoord newX = pageCSSMargin.left + centeringMargin;
+
+ // Adjust the child's x-position:
+ child->MovePositionBy(nsPoint(newX - child->GetNormalPosition().x, 0));
+ }
+ };
+
+ if (aPresContext->IsScreen()) {
+ // When we're displayed on-screen, the computed size that we're given is
+ // the size of our scrollport. We need to save this for use in
+ // GetPrintPreviewScale.
+ // (NOTE: It's possible but unlikely that we have an unconstrained BSize
+ // here, if we're being sized to content. GetPrintPreviewScale() checks
+ // for and handles this, when making use of this member-var.)
+ mScrollportSize = aReflowInput.ComputedSize();
+ }
+
+ // Don't do incremental reflow until we've taught tables how to do
+ // it right in paginated mode.
+ if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // Return our desired size
+ PopulateReflowOutput(aReflowOutput, aReflowInput);
+ FinishAndStoreOverflow(&aReflowOutput);
+
+ if (GetSize() != aReflowOutput.PhysicalSize()) {
+ CenterPages();
+ }
+ return;
+ }
+
+ nsIntMargin unwriteableTwips =
+ mPageData->mPrintSettings->GetUnwriteableMarginInTwips();
+
+ nsIntMargin edgeTwips = mPageData->mPrintSettings->GetEdgeInTwips();
+
+ // sanity check the values. three inches are sometimes needed
+ int32_t threeInches = NS_INCHES_TO_INT_TWIPS(3.0);
+ edgeTwips.EnsureAtMost(
+ nsIntMargin(threeInches, threeInches, threeInches, threeInches));
+ edgeTwips.EnsureAtLeast(unwriteableTwips);
+
+ mPageData->mEdgePaperMargin = nsPresContext::CSSTwipsToAppUnits(edgeTwips);
+
+ // Get the custom page-range state:
+ mPageData->mPrintSettings->GetPageRanges(mPageData->mPageRanges);
+
+ // We use the CSS "margin" property on the -moz-printed-sheet pseudoelement
+ // to determine the space between each printed sheet in print preview.
+ // Keep a running y-offset for each printed sheet.
+ nscoord y = 0;
+
+ // These represent the maximum sheet size across all our sheets (in each
+ // axis), inflated a bit to account for the -moz-printed-sheet 'margin'.
+ nscoord maxInflatedSheetWidth = 0;
+ nscoord maxInflatedSheetHeight = 0;
+
+ // Tile the sheets vertically
+ for (nsIFrame* kidFrame : mFrames) {
+ // Set the shared data into the page frame before reflow
+ MOZ_ASSERT(kidFrame->IsPrintedSheetFrame(),
+ "we're only expecting PrintedSheetFrame as children");
+ auto* sheet = static_cast<PrintedSheetFrame*>(kidFrame);
+ sheet->SetSharedPageData(mPageData.get());
+
+ // If we want to reliably access the nsPageFrame before reflowing the sheet
+ // frame, we need to call this:
+ sheet->ClaimPageFrameFromPrevInFlow();
+
+ const nsSize sheetSize = sheet->ComputeSheetSize(aPresContext);
+
+ // Reflow the sheet
+ ReflowInput kidReflowInput(
+ aPresContext, aReflowInput, kidFrame,
+ LogicalSize(kidFrame->GetWritingMode(), sheetSize));
+ kidReflowInput.mBreakType = ReflowInput::BreakType::Page;
+
+ ReflowOutput kidReflowOutput(kidReflowInput);
+ nsReflowStatus status;
+
+ kidReflowInput.SetComputedISize(kidReflowInput.AvailableISize());
+ // kidReflowInput.SetComputedHeight(kidReflowInput.AvailableHeight());
+ PR_PL(("AV ISize: %d BSize: %d\n", kidReflowInput.AvailableISize(),
+ kidReflowInput.AvailableBSize()));
+
+ nsMargin pageCSSMargin = kidReflowInput.ComputedPhysicalMargin();
+ y += pageCSSMargin.top;
+
+ nscoord x = pageCSSMargin.left;
+
+ // Place and size the sheet.
+ ReflowChild(kidFrame, aPresContext, kidReflowOutput, kidReflowInput, x, y,
+ ReflowChildFlags::Default, status);
+
+ FinishReflowChild(kidFrame, aPresContext, kidReflowOutput, &kidReflowInput,
+ x, y, ReflowChildFlags::Default);
+ MOZ_ASSERT(kidFrame->GetSize() == sheetSize,
+ "PrintedSheetFrame::ComputeSheetSize() gave the wrong size!");
+ y += kidReflowOutput.Height();
+ y += pageCSSMargin.bottom;
+
+ maxInflatedSheetWidth =
+ std::max(maxInflatedSheetWidth,
+ kidReflowOutput.Width() + pageCSSMargin.LeftRight());
+ maxInflatedSheetHeight =
+ std::max(maxInflatedSheetHeight,
+ kidReflowOutput.Height() + pageCSSMargin.TopBottom());
+
+ // Is the sheet complete?
+ nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow();
+
+ if (status.IsFullyComplete()) {
+ NS_ASSERTION(!kidNextInFlow, "bad child flow list");
+ } else if (!kidNextInFlow) {
+ // The sheet isn't complete and it doesn't have a next-in-flow, so
+ // create a continuing sheet.
+ nsIFrame* continuingSheet =
+ PresShell()->FrameConstructor()->CreateContinuingFrame(kidFrame,
+ this);
+
+ // Add it to our child list
+ mFrames.InsertFrame(nullptr, kidFrame, continuingSheet);
+ }
+ }
+
+ nsAutoString formattedDateString;
+ PRTime now = PR_Now();
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.date = Some(mozilla::intl::DateTimeFormat::Style::Short);
+ style.time = Some(mozilla::intl::DateTimeFormat::Style::Short);
+ if (NS_SUCCEEDED(mozilla::intl::AppDateTimeFormat::Format(
+ style, now, formattedDateString))) {
+ SetDateTimeStr(formattedDateString);
+ }
+
+ // cache the size so we can set the desired size for the other reflows that
+ // happen. Since we're tiling our sheets vertically: in the x axis, we are
+ // as wide as our widest sheet (inflated via "margin"); and in the y axis,
+ // we're as tall as the sum of our sheets' inflated heights, which the 'y'
+ // variable is conveniently storing at this point.
+ mSize = nsSize(maxInflatedSheetWidth, y);
+
+ if (aPresContext->IsScreen()) {
+ // Also cache the maximum size of all our sheets, to use together with the
+ // scrollport size (available as our computed size, and captured higher up
+ // in this function), so that we can scale to ensure that every sheet will
+ // fit in the scrollport.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ mMaxSheetSize =
+ LogicalSize(wm, nsSize(maxInflatedSheetWidth, maxInflatedSheetHeight));
+ }
+
+ // Return our desired size
+ // Adjust the reflow size by PrintPreviewScale so the scrollbars end up the
+ // correct size
+ PopulateReflowOutput(aReflowOutput, aReflowInput);
+
+ FinishAndStoreOverflow(&aReflowOutput);
+
+ // Now center our pages.
+ CenterPages();
+
+ NS_FRAME_TRACE_REFLOW_OUT("nsPageSequenceFrame::Reflow", aStatus);
+}
+
+//----------------------------------------------------------------------
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsPageSequenceFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"PageSequence"_ns, aResult);
+}
+#endif
+
+// Helper Function
+void nsPageSequenceFrame::SetPageNumberFormat(const char* aPropName,
+ const char* aDefPropVal,
+ bool aPageNumOnly) {
+ // Doing this here so we only have to go get these formats once
+ nsAutoString pageNumberFormat;
+ // Now go get the Localized Page Formating String
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::ePRINTING_PROPERTIES, aPropName, pageNumberFormat);
+ if (NS_FAILED(rv)) { // back stop formatting
+ pageNumberFormat.AssignASCII(aDefPropVal);
+ }
+
+ SetPageNumberFormat(pageNumberFormat, aPageNumOnly);
+}
+
+nsresult nsPageSequenceFrame::StartPrint(nsPresContext* aPresContext,
+ nsIPrintSettings* aPrintSettings,
+ const nsAString& aDocTitle,
+ const nsAString& aDocURL) {
+ NS_ENSURE_ARG_POINTER(aPresContext);
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ if (!mPageData->mPrintSettings) {
+ mPageData->mPrintSettings = aPrintSettings;
+ }
+
+ if (!aDocTitle.IsEmpty()) {
+ mPageData->mDocTitle = aDocTitle;
+ }
+ if (!aDocURL.IsEmpty()) {
+ mPageData->mDocURL = aDocURL;
+ }
+
+ // Begin printing of the document
+ mCurrentSheetIdx = 0;
+ return NS_OK;
+}
+
+static void GetPrintCanvasElementsInFrame(
+ nsIFrame* aFrame, nsTArray<RefPtr<HTMLCanvasElement>>* aArr) {
+ if (!aFrame) {
+ return;
+ }
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ // Check if child is a nsHTMLCanvasFrame.
+ nsHTMLCanvasFrame* canvasFrame = do_QueryFrame(child);
+
+ // If there is a canvasFrame, try to get actual canvas element.
+ if (canvasFrame) {
+ HTMLCanvasElement* canvas =
+ HTMLCanvasElement::FromNodeOrNull(canvasFrame->GetContent());
+ if (canvas && canvas->GetMozPrintCallback()) {
+ aArr->AppendElement(canvas);
+ continue;
+ }
+ }
+
+ if (!child->PrincipalChildList().FirstChild()) {
+ nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(child);
+ if (subdocumentFrame) {
+ // Descend into the subdocument
+ nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame();
+ child = root;
+ }
+ }
+ // The current child is not a nsHTMLCanvasFrame OR it is but there is
+ // no HTMLCanvasElement on it. Check if children of `child` might
+ // contain a HTMLCanvasElement.
+ GetPrintCanvasElementsInFrame(child, aArr);
+ }
+ }
+}
+
+// Note: this isn't quite a full tree traversal, since we exclude any
+// nsPageFame children that have the NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state-bit.
+static void GetPrintCanvasElementsInSheet(
+ PrintedSheetFrame* aSheetFrame, nsTArray<RefPtr<HTMLCanvasElement>>* aArr) {
+ MOZ_ASSERT(aSheetFrame, "Caller should've null-checked for us already");
+ for (nsIFrame* child : aSheetFrame->PrincipalChildList()) {
+ // Exclude any pages that are technically children but are skipped by a
+ // custom range; they're not meant to be printed, so we don't want to
+ // waste time rendering their canvas descendants.
+ MOZ_ASSERT(child->IsPageFrame(),
+ "PrintedSheetFrame's children must all be nsPageFrames");
+ auto* pageFrame = static_cast<nsPageFrame*>(child);
+ if (!pageFrame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE)) {
+ GetPrintCanvasElementsInFrame(pageFrame, aArr);
+ }
+ }
+}
+
+PrintedSheetFrame* nsPageSequenceFrame::GetCurrentSheetFrame() {
+ uint32_t i = 0;
+ for (nsIFrame* child : mFrames) {
+ MOZ_ASSERT(child->IsPrintedSheetFrame(),
+ "Our children must all be PrintedSheetFrame");
+ if (i == mCurrentSheetIdx) {
+ return static_cast<PrintedSheetFrame*>(child);
+ }
+ ++i;
+ }
+ return nullptr;
+}
+
+nsresult nsPageSequenceFrame::PrePrintNextSheet(nsITimerCallback* aCallback,
+ bool* aDone) {
+ PrintedSheetFrame* currentSheet = GetCurrentSheetFrame();
+ if (!currentSheet) {
+ *aDone = true;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!PresContext()->IsRootPaginatedDocument()) {
+ // XXXdholbert I don't think this clause is ever actually visited in
+ // practice... Maybe we should warn & return a failure code? There used to
+ // be a comment here explaining why we don't need to proceed past this
+ // point for print preview, but in fact, this function isn't even called for
+ // print preview.
+ *aDone = true;
+ return NS_OK;
+ }
+
+ // If the canvasList is null, then generate it and start the render
+ // process for all the canvas.
+ if (!mCurrentCanvasListSetup) {
+ mCurrentCanvasListSetup = true;
+ GetPrintCanvasElementsInSheet(currentSheet, &mCurrentCanvasList);
+
+ if (!mCurrentCanvasList.IsEmpty()) {
+ nsresult rv = NS_OK;
+
+ // Begin printing of the document
+ nsDeviceContext* dc = PresContext()->DeviceContext();
+ PR_PL(("\n"));
+ PR_PL(("***************** BeginPage *****************\n"));
+ const gfx::IntSize sizeInPoints =
+ currentSheet->GetPrintTargetSizeInPoints(
+ dc->AppUnitsPerPhysicalInch());
+ rv = dc->BeginPage(sizeInPoints);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCalledBeginPage = true;
+
+ UniquePtr<gfxContext> renderingContext = dc->CreateRenderingContext();
+ NS_ENSURE_TRUE(renderingContext, NS_ERROR_OUT_OF_MEMORY);
+
+ DrawTarget* drawTarget = renderingContext->GetDrawTarget();
+ if (NS_WARN_IF(!drawTarget)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (HTMLCanvasElement* canvas : Reversed(mCurrentCanvasList)) {
+ nsIntSize size = canvas->GetSize();
+
+ RefPtr<DrawTarget> canvasTarget =
+ drawTarget->CreateSimilarDrawTarget(size, drawTarget->GetFormat());
+ if (!canvasTarget) {
+ continue;
+ }
+
+ nsICanvasRenderingContextInternal* ctx = canvas->GetCurrentContext();
+ if (!ctx) {
+ continue;
+ }
+
+ // Initialize the context with the new DrawTarget.
+ ctx->InitializeWithDrawTarget(nullptr, WrapNotNull(canvasTarget));
+
+ // Start the rendering process.
+ // Note: Other than drawing to our CanvasRenderingContext2D, the
+ // callback cannot access or mutate our static clone document. It is
+ // evaluated in its original context (the window of the original
+ // document) of course, and our canvas has a strong ref to the
+ // original HTMLCanvasElement (in mOriginalCanvas) so that if the
+ // callback calls GetCanvas() on our CanvasRenderingContext2D (passed
+ // to it via a MozCanvasPrintState argument) it will be given the
+ // original 'canvas' element.
+ AutoWeakFrame weakFrame = this;
+ canvas->DispatchPrintCallback(aCallback);
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ }
+ }
+ }
+ uint32_t doneCounter = 0;
+ for (HTMLCanvasElement* canvas : mCurrentCanvasList) {
+ if (canvas->IsPrintCallbackDone()) {
+ doneCounter++;
+ }
+ }
+ // If all canvas have finished rendering, return true, otherwise false.
+ *aDone = doneCounter == mCurrentCanvasList.Length();
+
+ return NS_OK;
+}
+
+void nsPageSequenceFrame::ResetPrintCanvasList() {
+ for (int32_t i = mCurrentCanvasList.Length() - 1; i >= 0; i--) {
+ HTMLCanvasElement* canvas = mCurrentCanvasList[i];
+ canvas->ResetPrintCallback();
+ }
+
+ mCurrentCanvasList.Clear();
+ mCurrentCanvasListSetup = false;
+}
+
+nsresult nsPageSequenceFrame::PrintNextSheet() {
+ // Note: When print al the pages or a page range the printed page shows the
+ // actual page number, when printing selection it prints the page number
+ // starting with the first page of the selection. For example if the user has
+ // a selection that starts on page 2 and ends on page 3, the page numbers when
+ // print are 1 and then two (which is different than printing a page range,
+ // where the page numbers would have been 2 and then 3)
+
+ PrintedSheetFrame* currentSheetFrame = GetCurrentSheetFrame();
+ if (!currentSheetFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+
+ nsDeviceContext* dc = PresContext()->DeviceContext();
+
+ if (PresContext()->IsRootPaginatedDocument()) {
+ if (!mCalledBeginPage) {
+ // We must make sure BeginPage() has been called since some printing
+ // backends can't give us a valid rendering context for a [physical]
+ // page otherwise.
+ PR_PL(("\n"));
+ PR_PL(("***************** BeginPage *****************\n"));
+ const gfx::IntSize sizeInPoints =
+ currentSheetFrame->GetPrintTargetSizeInPoints(
+ dc->AppUnitsPerPhysicalInch());
+ rv = dc->BeginPage(sizeInPoints);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ PR_PL(("SeqFr::PrintNextSheet -> %p SheetIdx: %d", currentSheetFrame,
+ mCurrentSheetIdx));
+
+ // CreateRenderingContext can fail
+ UniquePtr<gfxContext> gCtx = dc->CreateRenderingContext();
+ NS_ENSURE_TRUE(gCtx, NS_ERROR_OUT_OF_MEMORY);
+
+ nsRect drawingRect(nsPoint(0, 0), currentSheetFrame->GetSize());
+ nsRegion drawingRegion(drawingRect);
+ nsLayoutUtils::PaintFrame(gCtx.get(), currentSheetFrame, drawingRegion,
+ NS_RGBA(0, 0, 0, 0),
+ nsDisplayListBuilderMode::PaintForPrinting,
+ nsLayoutUtils::PaintFrameFlags::SyncDecodeImages);
+ return rv;
+}
+
+nsresult nsPageSequenceFrame::DoPageEnd() {
+ nsresult rv = NS_OK;
+ if (PresContext()->IsRootPaginatedDocument()) {
+ PR_PL(("***************** End Page (DoPageEnd) *****************\n"));
+ rv = PresContext()->DeviceContext()->EndPage();
+ // Fall through to clean up resources/state below even if EndPage failed.
+ }
+
+ ResetPrintCanvasList();
+ mCalledBeginPage = false;
+
+ mCurrentSheetIdx++;
+
+ return rv;
+}
+
+static gfx::Matrix4x4 ComputePageSequenceTransform(const nsIFrame* aFrame,
+ float aAppUnitsPerPixel) {
+ MOZ_ASSERT(aFrame->IsPageSequenceFrame());
+ float scale =
+ static_cast<const nsPageSequenceFrame*>(aFrame)->GetPrintPreviewScale();
+ return gfx::Matrix4x4::Scaling(scale, scale, 1);
+}
+
+nsIFrame::ComputeTransformFunction nsPageSequenceFrame::GetTransformGetter()
+ const {
+ return ComputePageSequenceTransform;
+}
+
+void nsPageSequenceFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ aBuilder->SetDisablePartialUpdates(true);
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ nsDisplayList content(aBuilder);
+
+ {
+ // Clear clip state while we construct the children of the
+ // nsDisplayTransform, since they'll be in a different coordinate system.
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ clipState.Clear();
+
+ nsIFrame* child = PrincipalChildList().FirstChild();
+ nsRect visible = aBuilder->GetVisibleRect();
+ visible.ScaleInverseRoundOut(GetPrintPreviewScale());
+
+ while (child) {
+ if (child->InkOverflowRectRelativeToParent().Intersects(visible)) {
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, child, visible - child->GetPosition(),
+ visible - child->GetPosition());
+ child->BuildDisplayListForStackingContext(aBuilder, &content);
+ aBuilder->ResetMarkedFramesForDisplayList(this);
+ }
+ child = child->GetNextSibling();
+ }
+ }
+
+ content.AppendNewToTop<nsDisplayTransform>(
+ aBuilder, this, &content, content.GetBuildingRect(),
+ nsDisplayTransform::WithTransformGetter);
+
+ aLists.Content()->AppendToTop(&content);
+}
+
+//------------------------------------------------------------------------------
+void nsPageSequenceFrame::SetPageNumberFormat(const nsAString& aFormatStr,
+ bool aForPageNumOnly) {
+ NS_ASSERTION(mPageData != nullptr, "mPageData string cannot be null!");
+
+ if (aForPageNumOnly) {
+ mPageData->mPageNumFormat = aFormatStr;
+ } else {
+ mPageData->mPageNumAndTotalsFormat = aFormatStr;
+ }
+}
+
+//------------------------------------------------------------------------------
+void nsPageSequenceFrame::SetDateTimeStr(const nsAString& aDateTimeStr) {
+ NS_ASSERTION(mPageData != nullptr, "mPageData string cannot be null!");
+
+ mPageData->mDateTimeStr = aDateTimeStr;
+}
diff --git a/layout/generic/nsPageSequenceFrame.h b/layout/generic/nsPageSequenceFrame.h
new file mode 100644
index 0000000000..06293192b0
--- /dev/null
+++ b/layout/generic/nsPageSequenceFrame.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsPageSequenceFrame_h___
+#define nsPageSequenceFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsContainerFrame.h"
+#include "nsIPrintSettings.h"
+
+namespace mozilla {
+
+class PresShell;
+class PrintedSheetFrame;
+
+namespace dom {
+
+class HTMLCanvasElement;
+
+} // namespace dom
+} // namespace mozilla
+
+//-----------------------------------------------
+// This class is used to manage some static data about the layout
+// characteristics of our various "Pages Per Sheet" options.
+struct nsPagesPerSheetInfo {
+ static const nsPagesPerSheetInfo& LookupInfo(int32_t aPPS);
+
+ uint16_t mNumPages;
+
+ // This is the larger of the row-count vs. column-count for this layout
+ // (if they aren't the same). We'll aim to stack this number of pages
+ // in the sheet's longer axis.
+ uint16_t mLargerNumTracks;
+};
+
+/**
+ * This class maintains various shared data that is used by printing-related
+ * frames. The nsPageSequenceFrame strongly owns an instance of this class,
+ * which lives for as long as the nsPageSequenceFrame does.
+ */
+class nsSharedPageData {
+ public:
+ nsString mDateTimeStr;
+ nsString mPageNumFormat;
+ nsString mPageNumAndTotalsFormat;
+ nsString mDocTitle;
+ nsString mDocURL;
+ nsFont mHeadFootFont;
+
+ // Total number of pages (populated by PrintedSheetFrame when it determines
+ // that it's reflowed the final page):
+ int32_t mRawNumPages = 0;
+
+ // If there's more than one page-range, then its components are stored here
+ // as pairs of (start,end). They're stored in the order provided (not
+ // necessarily in ascending order).
+ nsTArray<int32_t> mPageRanges;
+
+ // Margin for headers and footers; it defaults to 4/100 of an inch on UNIX
+ // and 0 elsewhere; I think it has to do with some inconsistency in page size
+ // computations
+ nsMargin mEdgePaperMargin;
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+
+ // The scaling ratio we need to apply to make all pages fit horizontally. It's
+ // the minimum "ComputedWidth / OverflowWidth" ratio of all page content
+ // frames that overflowed. It's 1.0 if none overflowed horizontally.
+ float mShrinkToFitRatio = 1.0f;
+
+ // Lazy getter, to look up our pages-per-sheet info based on mPrintSettings
+ // (if it's available). The result is stored in our mPagesPerSheetInfo
+ // member-var to speed up subsequent lookups.
+ // This API is infallible; in failure cases, it just returns the info struct
+ // that corresponds to 1 page per sheet.
+ const nsPagesPerSheetInfo* PagesPerSheetInfo();
+
+ private:
+ const nsPagesPerSheetInfo* mPagesPerSheetInfo = nullptr;
+};
+
+// Page sequence frame class. Manages a series of pages, in paginated mode.
+// (Strictly speaking, this frame's direct children are PrintedSheetFrame
+// instances, and each of those will usually contain one nsPageFrame, depending
+// on the "pages-per-sheet" setting and whether the print operation is
+// restricted to a custom page range.)
+class nsPageSequenceFrame final : public nsContainerFrame {
+ using LogicalSize = mozilla::LogicalSize;
+
+ public:
+ friend nsPageSequenceFrame* NS_NewPageSequenceFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsPageSequenceFrame)
+
+ // nsIFrame
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aReflowOutput,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ // For Shrink To Fit
+ float GetSTFPercent() const { return mPageData->mShrinkToFitRatio; }
+
+ // Gets the final print preview scale that we're applying to the previewed
+ // sheets of paper.
+ float GetPrintPreviewScale() const;
+
+ // Async Printing
+ nsresult StartPrint(nsPresContext* aPresContext,
+ nsIPrintSettings* aPrintSettings,
+ const nsAString& aDocTitle, const nsAString& aDocURL);
+ nsresult PrePrintNextSheet(nsITimerCallback* aCallback, bool* aDone);
+ nsresult PrintNextSheet();
+ void ResetPrintCanvasList();
+
+ uint32_t GetCurrentSheetIdx() const { return mCurrentSheetIdx; }
+
+ int32_t GetRawNumPages() const { return mPageData->mRawNumPages; }
+
+ uint32_t GetPagesInFirstSheet() const;
+
+ nsresult DoPageEnd();
+
+ ComputeTransformFunction GetTransformGetter() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ protected:
+ nsPageSequenceFrame(ComputedStyle*, nsPresContext*);
+ virtual ~nsPageSequenceFrame();
+
+ void SetPageNumberFormat(const char* aPropName, const char* aDefPropVal,
+ bool aPageNumOnly);
+
+ // SharedPageData Helper methods
+ void SetDateTimeStr(const nsAString& aDateTimeStr);
+ void SetPageNumberFormat(const nsAString& aFormatStr, bool aForPageNumOnly);
+
+ // Print scaling is applied in this function.
+ void PopulateReflowOutput(ReflowOutput&, const ReflowInput&);
+
+ // Helper function to compute the offset needed to center a child
+ // page-frame's margin-box inside our content-box.
+ nscoord ComputeCenteringMargin(nscoord aContainerContentBoxWidth,
+ nscoord aChildPaddingBoxWidth,
+ const nsMargin& aChildPhysicalMargin);
+
+ mozilla::PrintedSheetFrame* GetCurrentSheetFrame();
+
+ nsSize mSize;
+
+ // These next two LogicalSize members are used when we're in print-preview to
+ // ensure that each previewed sheet will fit in the print-preview scrollport:
+ // -------
+
+ // Each component of this LogicalSize represents the maximum length of all
+ // our print-previewed sheets in that axis, plus a little extra for the
+ // print-preview margin. Note that this LogicalSize doesn't necessarily
+ // correspond to any one particular sheet's size (especially if our sheets
+ // have different sizes), since the components are tracked independently such
+ // that we end up storing the maximum in each dimension.
+ LogicalSize mMaxSheetSize;
+ // The size of the scrollport where we're print-previewing sheets.
+ LogicalSize mScrollportSize;
+
+ // Data shared by all the nsPageFrames:
+ mozilla::UniquePtr<nsSharedPageData> mPageData;
+
+ // The zero-based index of the PrintedSheetFrame child that is being printed
+ // (or about-to-be-printed), in an async print operation.
+ // This is an index into our PrincipalChildList, effectively.
+ uint32_t mCurrentSheetIdx = 0;
+
+ nsTArray<RefPtr<mozilla::dom::HTMLCanvasElement> > mCurrentCanvasList;
+
+ bool mCalledBeginPage;
+
+ bool mCurrentCanvasListSetup;
+};
+
+#endif /* nsPageSequenceFrame_h___ */
diff --git a/layout/generic/nsPlaceholderFrame.cpp b/layout/generic/nsPlaceholderFrame.cpp
new file mode 100644
index 0000000000..f6b4d53193
--- /dev/null
+++ b/layout/generic/nsPlaceholderFrame.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * rendering object for the point that anchors out-of-flow rendering
+ * objects such as floats and absolutely positioned elements
+ */
+
+#include "nsPlaceholderFrame.h"
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ServoStyleSetInlines.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsDisplayList.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsIFrameInlines.h"
+#include "nsIContentInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+nsPlaceholderFrame* NS_NewPlaceholderFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle,
+ nsFrameState aTypeBits) {
+ return new (aPresShell)
+ nsPlaceholderFrame(aStyle, aPresShell->GetPresContext(), aTypeBits);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsPlaceholderFrame)
+
+#ifdef DEBUG
+NS_QUERYFRAME_HEAD(nsPlaceholderFrame)
+ NS_QUERYFRAME_ENTRY(nsPlaceholderFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
+#endif
+
+/* virtual */
+void nsPlaceholderFrame::AddInlineMinISize(
+ gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
+ // Override AddInlineMinWith so that *nothing* happens. In
+ // particular, we don't want to zero out |aData->mTrailingWhitespace|,
+ // since nsLineLayout skips placeholders when trimming trailing
+ // whitespace, and we don't want to set aData->mSkipWhitespace to
+ // false.
+
+ // ...but push floats onto the list
+ if (mOutOfFlowFrame->IsFloating()) {
+ nscoord floatWidth = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, mOutOfFlowFrame, IntrinsicISizeType::MinISize);
+ aData->mFloats.AppendElement(
+ InlineIntrinsicISizeData::FloatInfo(mOutOfFlowFrame, floatWidth));
+ }
+}
+
+/* virtual */
+void nsPlaceholderFrame::AddInlinePrefISize(
+ gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
+ // Override AddInlinePrefWith so that *nothing* happens. In
+ // particular, we don't want to zero out |aData->mTrailingWhitespace|,
+ // since nsLineLayout skips placeholders when trimming trailing
+ // whitespace, and we don't want to set aData->mSkipWhitespace to
+ // false.
+
+ // ...but push floats onto the list
+ if (mOutOfFlowFrame->IsFloating()) {
+ nscoord floatWidth = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, mOutOfFlowFrame, IntrinsicISizeType::PrefISize);
+ aData->mFloats.AppendElement(
+ InlineIntrinsicISizeData::FloatInfo(mOutOfFlowFrame, floatWidth));
+ }
+}
+
+void nsPlaceholderFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ // NOTE that the ReflowInput passed to this method is not fully initialized,
+ // on the grounds that reflowing a placeholder is a rather trivial operation.
+ // (See bug 1367711.)
+
+#ifdef DEBUG
+ // We should be getting reflowed before our out-of-flow. If this is our first
+ // reflow, and our out-of-flow has already received its first reflow (before
+ // us), complain.
+ //
+ // Popups are an exception though, because their position doesn't depend on
+ // the placeholder, so they don't have this requirement (and this condition
+ // doesn't hold anyways because the default popupgroup goes before than the
+ // default tooltip, for example).
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
+ !mOutOfFlowFrame->IsMenuPopupFrame() &&
+ !mOutOfFlowFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // Unfortunately, this can currently happen when the placeholder is in a
+ // later continuation or later IB-split sibling than its out-of-flow (as
+ // is the case in some of our existing unit tests). So for now, in that
+ // case, we'll warn instead of asserting.
+ bool isInContinuationOrIBSplit = false;
+ nsIFrame* ancestor = this;
+ while ((ancestor = ancestor->GetParent())) {
+ if (nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(ancestor)) {
+ isInContinuationOrIBSplit = true;
+ break;
+ }
+ }
+
+ if (isInContinuationOrIBSplit) {
+ NS_WARNING("Out-of-flow frame got reflowed before its placeholder");
+ } else {
+ NS_ERROR("Out-of-flow frame got reflowed before its placeholder");
+ }
+ }
+#endif
+
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsPlaceholderFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ aDesiredSize.ClearSize();
+}
+
+static FrameChildListID ChildListIDForOutOfFlow(nsFrameState aPlaceholderState,
+ const nsIFrame* aChild) {
+ if (aPlaceholderState & PLACEHOLDER_FOR_FLOAT) {
+ return FrameChildListID::Float;
+ }
+ if (aPlaceholderState & PLACEHOLDER_FOR_FIXEDPOS) {
+ return nsLayoutUtils::MayBeReallyFixedPos(aChild)
+ ? FrameChildListID::Fixed
+ : FrameChildListID::Absolute;
+ }
+ if (aPlaceholderState & PLACEHOLDER_FOR_ABSPOS) {
+ return FrameChildListID::Absolute;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(false, "unknown list");
+ return FrameChildListID::Float;
+}
+
+void nsPlaceholderFrame::Destroy(DestroyContext& aContext) {
+ if (nsIFrame* oof = mOutOfFlowFrame) {
+ mOutOfFlowFrame = nullptr;
+ oof->RemoveProperty(nsIFrame::PlaceholderFrameProperty());
+
+ // Destroy the out of flow now.
+ ChildListID listId = ChildListIDForOutOfFlow(GetStateBits(), oof);
+ nsFrameManager* fm = PresContext()->FrameConstructor();
+ fm->RemoveFrame(aContext, listId, oof);
+ }
+
+ nsIFrame::Destroy(aContext);
+}
+
+/* virtual */
+bool nsPlaceholderFrame::CanContinueTextRun() const {
+ if (!mOutOfFlowFrame) {
+ return false;
+ }
+ // first-letter frames can continue text runs, and placeholders for floated
+ // first-letter frames can too
+ return mOutOfFlowFrame->CanContinueTextRun();
+}
+
+ComputedStyle* nsPlaceholderFrame::GetParentComputedStyleForOutOfFlow(
+ nsIFrame** aProviderFrame) const {
+ MOZ_ASSERT(GetParent(), "How can we not have a parent here?");
+
+ Element* parentElement =
+ mContent ? mContent->GetFlattenedTreeParentElement() : nullptr;
+ // See the similar code in nsIFrame::DoGetParentComputedStyle.
+ if (parentElement && MOZ_LIKELY(parentElement->HasServoData()) &&
+ Servo_Element_IsDisplayContents(parentElement)) {
+ RefPtr<ComputedStyle> style =
+ ServoStyleSet::ResolveServoStyle(*parentElement);
+ *aProviderFrame = nullptr;
+ // See the comment in GetParentComputedStyle to see why returning this as a
+ // weak ref is fine.
+ return style;
+ }
+
+ return GetLayoutParentStyleForOutOfFlow(aProviderFrame);
+}
+
+ComputedStyle* nsPlaceholderFrame::GetLayoutParentStyleForOutOfFlow(
+ nsIFrame** aProviderFrame) const {
+ // Lie about our pseudo so we can step out of all anon boxes and
+ // pseudo-elements. The other option would be to reimplement the
+ // {ib} split gunk here.
+ //
+ // See the hack in CorrectStyleParentFrame for why we pass `MAX`.
+ *aProviderFrame = CorrectStyleParentFrame(GetParent(), PseudoStyleType::MAX);
+ return *aProviderFrame ? (*aProviderFrame)->Style() : nullptr;
+}
+
+#if defined(DEBUG) || (defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF))
+
+void nsPlaceholderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsPlaceholderFrame");
+}
+#endif // DEBUG || (MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsPlaceholderFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Placeholder"_ns, aResult);
+}
+
+void nsPlaceholderFrame::List(FILE* out, const char* aPrefix,
+ ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+
+ if (mOutOfFlowFrame) {
+ str += " outOfFlowFrame=";
+ str += mOutOfFlowFrame->ListTag();
+ }
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
diff --git a/layout/generic/nsPlaceholderFrame.h b/layout/generic/nsPlaceholderFrame.h
new file mode 100644
index 0000000000..4cbb54259a
--- /dev/null
+++ b/layout/generic/nsPlaceholderFrame.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * rendering object for the point that anchors out-of-flow rendering
+ * objects such as floats and absolutely positioned elements
+ */
+
+/*
+ * Destruction of a placeholder and its out-of-flow must observe the
+ * following constraints:
+ *
+ * - The mapping from the out-of-flow to the placeholder must be
+ * removed from the frame manager before the placeholder is destroyed.
+ * - The mapping from the out-of-flow to the placeholder must be
+ * removed from the frame manager before the out-of-flow is destroyed.
+ * - The placeholder must be removed from the frame tree, or have the
+ * mapping from it to its out-of-flow cleared, before the out-of-flow
+ * is destroyed (so that the placeholder will not point to a destroyed
+ * frame while it's in the frame tree).
+ *
+ * Furthermore, some code assumes that placeholders point to something
+ * useful, so placeholders without an associated out-of-flow should not
+ * remain in the tree.
+ *
+ * The placeholder's Destroy() implementation handles the destruction of
+ * the placeholder and its out-of-flow. To avoid crashes, frame removal
+ * and destruction code that works with placeholders must not assume
+ * that the placeholder points to its out-of-flow.
+ */
+
+#ifndef nsPlaceholderFrame_h___
+#define nsPlaceholderFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+class nsPlaceholderFrame;
+nsPlaceholderFrame* NS_NewPlaceholderFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle,
+ nsFrameState aTypeBits);
+
+#define PLACEHOLDER_TYPE_MASK \
+ (PLACEHOLDER_FOR_FLOAT | PLACEHOLDER_FOR_ABSPOS | PLACEHOLDER_FOR_FIXEDPOS | \
+ PLACEHOLDER_FOR_TOPLAYER)
+
+/**
+ * Implementation of a frame that's used as a placeholder for a frame that
+ * has been moved out of the flow.
+ */
+class nsPlaceholderFrame final : public nsIFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsPlaceholderFrame)
+#ifdef DEBUG
+ NS_DECL_QUERYFRAME
+#endif
+
+ /**
+ * Create a new placeholder frame. aTypeBit must be one of the
+ * PLACEHOLDER_FOR_* constants above.
+ */
+ friend nsPlaceholderFrame* NS_NewPlaceholderFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle,
+ nsFrameState aTypeBits);
+
+ nsPlaceholderFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ nsFrameState aTypeBits)
+ : nsIFrame(aStyle, aPresContext, kClassID), mOutOfFlowFrame(nullptr) {
+ MOZ_ASSERT(
+ aTypeBits == PLACEHOLDER_FOR_FLOAT ||
+ aTypeBits == PLACEHOLDER_FOR_ABSPOS ||
+ aTypeBits == PLACEHOLDER_FOR_FIXEDPOS ||
+ aTypeBits == (PLACEHOLDER_FOR_TOPLAYER | PLACEHOLDER_FOR_ABSPOS) ||
+ aTypeBits == (PLACEHOLDER_FOR_TOPLAYER | PLACEHOLDER_FOR_FIXEDPOS),
+ "Unexpected type bit");
+ AddStateBits(aTypeBits);
+ }
+
+ // Get/Set the associated out of flow frame
+ nsIFrame* GetOutOfFlowFrame() const { return mOutOfFlowFrame; }
+ void SetOutOfFlowFrame(nsIFrame* aFrame) {
+ NS_ASSERTION(!aFrame || !aFrame->GetPrevContinuation(),
+ "OOF must be first continuation");
+ mOutOfFlowFrame = aFrame;
+ }
+
+ // nsIFrame overrides
+ void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) override;
+ void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) override;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void Destroy(DestroyContext&) override;
+
+#if defined(DEBUG) || (defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF))
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+#endif // DEBUG || (MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF)
+
+#ifdef DEBUG_FRAME_DUMP
+ void List(FILE* out = stderr, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const override;
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif // DEBUG
+
+ bool IsEmpty() override { return true; }
+ bool IsSelfEmpty() override { return true; }
+
+ bool CanContinueTextRun() const override;
+
+ void SetLineIsEmptySoFar(bool aValue) {
+ AddOrRemoveStateBits(PLACEHOLDER_LINE_IS_EMPTY_SO_FAR, aValue);
+ AddStateBits(PLACEHOLDER_HAVE_LINE_IS_EMPTY_SO_FAR);
+ }
+ bool GetLineIsEmptySoFar(bool* aResult) const {
+ bool haveValue = HasAnyStateBits(PLACEHOLDER_HAVE_LINE_IS_EMPTY_SO_FAR);
+ if (haveValue) {
+ *aResult = HasAnyStateBits(PLACEHOLDER_LINE_IS_EMPTY_SO_FAR);
+ }
+ return haveValue;
+ }
+ void ForgetLineIsEmptySoFar() {
+ RemoveStateBits(PLACEHOLDER_HAVE_LINE_IS_EMPTY_SO_FAR);
+ }
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() override {
+ nsIFrame* realFrame = GetRealFrameForPlaceholder(this);
+ return realFrame ? realFrame->AccessibleType() : nsIFrame::AccessibleType();
+ }
+#endif
+
+ ComputedStyle* GetParentComputedStyleForOutOfFlow(
+ nsIFrame** aProviderFrame) const;
+
+ // Like GetParentComputedStyleForOutOfFlow, but ignores display:contents bits.
+ ComputedStyle* GetLayoutParentStyleForOutOfFlow(
+ nsIFrame** aProviderFrame) const;
+
+ bool RenumberFrameAndDescendants(int32_t* aOrdinal, int32_t aDepth,
+ int32_t aIncrement,
+ bool aForCounting) override {
+ return mOutOfFlowFrame->RenumberFrameAndDescendants(
+ aOrdinal, aDepth, aIncrement, aForCounting);
+ }
+
+ /**
+ * @return the out-of-flow for aFrame if aFrame is a placeholder; otherwise
+ * aFrame
+ */
+ static nsIFrame* GetRealFrameFor(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "Must have a frame to work with");
+ if (aFrame->IsPlaceholderFrame()) {
+ return GetRealFrameForPlaceholder(aFrame);
+ }
+ return aFrame;
+ }
+
+ /**
+ * @return the out-of-flow for aFrame, which is known to be a placeholder
+ */
+ static nsIFrame* GetRealFrameForPlaceholder(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->IsPlaceholderFrame(),
+ "Must have placeholder frame as input");
+ nsIFrame* outOfFlow =
+ static_cast<nsPlaceholderFrame*>(aFrame)->GetOutOfFlowFrame();
+ NS_ASSERTION(outOfFlow, "Null out-of-flow for placeholder?");
+ return outOfFlow;
+ }
+
+ protected:
+ nsIFrame* mOutOfFlowFrame;
+};
+
+#endif /* nsPlaceholderFrame_h___ */
diff --git a/layout/generic/nsQueryFrame.h b/layout/generic/nsQueryFrame.h
new file mode 100644
index 0000000000..326b29266c
--- /dev/null
+++ b/layout/generic/nsQueryFrame.h
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsQueryFrame_h
+#define nsQueryFrame_h
+
+#include <type_traits>
+
+#include "nscore.h"
+#include "mozilla/Assertions.h"
+
+// NOTE: the long lines in this file are intentional to make compiler error
+// messages more readable.
+
+#define NS_DECL_QUERYFRAME_TARGET(classname) \
+ static const nsQueryFrame::FrameIID kFrameIID = \
+ nsQueryFrame::classname##_id; \
+ typedef classname Has_NS_DECL_QUERYFRAME_TARGET;
+
+#define NS_DECL_QUERYFRAME void* QueryFrame(FrameIID id) const override;
+
+#define NS_QUERYFRAME_HEAD(class) \
+ void* class ::QueryFrame(FrameIID id) const { \
+ switch (id) {
+#define NS_QUERYFRAME_ENTRY(class) \
+ case class ::kFrameIID: { \
+ static_assert( \
+ std::is_same_v<class, class ::Has_NS_DECL_QUERYFRAME_TARGET>, \
+ #class " must declare itself as a queryframe target"); \
+ return const_cast<class*>(static_cast<const class*>(this)); \
+ }
+
+#define NS_QUERYFRAME_ENTRY_CONDITIONAL(class, condition) \
+ case class ::kFrameIID: \
+ if (condition) { \
+ static_assert( \
+ std::is_same_v<class, class ::Has_NS_DECL_QUERYFRAME_TARGET>, \
+ #class " must declare itself as a queryframe target"); \
+ return const_cast<class*>(static_cast<const class*>(this)); \
+ } \
+ break;
+
+#define NS_QUERYFRAME_TAIL_INHERITING(class) \
+ default: \
+ break; \
+ } \
+ return class ::QueryFrame(id); \
+ }
+
+#define NS_QUERYFRAME_TAIL_INHERITANCE_ROOT \
+ default: \
+ break; \
+ } \
+ MOZ_ASSERT(id != GetFrameId(), \
+ "A frame failed to QueryFrame to its *own type*. " \
+ "It may be missing NS_DECL_QUERYFRAME, or a " \
+ "NS_QUERYFRAME_ENTRY() line with its own type name"); \
+ return nullptr; \
+ }
+
+class nsQueryFrame {
+ public:
+ enum FrameIID {
+#define FRAME_ID(classname, ...) classname##_id,
+#define ABSTRACT_FRAME_ID(classname) classname##_id,
+#include "mozilla/FrameIdList.h"
+#undef FRAME_ID
+#undef ABSTRACT_FRAME_ID
+ };
+
+ // A strict subset of FrameIID above for frame classes that we instantiate.
+ enum class ClassID : uint8_t {
+#define FRAME_ID(classname, ...) classname##_id,
+#define ABSTRACT_FRAME_ID(classname)
+#include "mozilla/FrameIdList.h"
+#undef FRAME_ID
+#undef ABSTRACT_FRAME_ID
+ };
+
+ virtual void* QueryFrame(FrameIID id) const = 0;
+};
+
+class nsIFrame;
+
+template <class Source>
+class do_QueryFrameHelper {
+ public:
+ explicit do_QueryFrameHelper(Source* s) : mRawPtr(s) {}
+
+ // The return and argument types here are arbitrarily selected so no
+ // corresponding member function exists.
+ typedef void (do_QueryFrameHelper::*MatchNullptr)(double, float);
+ // Implicit constructor for nullptr, trick borrowed from already_AddRefed.
+ MOZ_IMPLICIT do_QueryFrameHelper(MatchNullptr aRawPtr) : mRawPtr(nullptr) {}
+
+ template <class Dest>
+ operator Dest*() {
+ static_assert(std::is_same_v<std::remove_const_t<Dest>,
+ typename Dest::Has_NS_DECL_QUERYFRAME_TARGET>,
+ "Dest must declare itself as a queryframe target");
+ if (!mRawPtr) {
+ return nullptr;
+ }
+ if (Dest* f = FastQueryFrame<Source, Dest>::QueryFrame(mRawPtr)) {
+ MOZ_ASSERT(
+ f == reinterpret_cast<Dest*>(mRawPtr->QueryFrame(Dest::kFrameIID)),
+ "fast and slow paths should give the same result");
+ return f;
+ }
+ return reinterpret_cast<Dest*>(mRawPtr->QueryFrame(Dest::kFrameIID));
+ }
+
+ private:
+ // For non-nsIFrame types there is no fast-path.
+ template <class Src, class Dst, typename = void, typename = void>
+ struct FastQueryFrame {
+ static Dst* QueryFrame(Src* aFrame) { return nullptr; }
+ };
+
+ // Specialization for any nsIFrame type to any nsIFrame type -- if the source
+ // instance's mClass matches kFrameIID of the destination type then
+ // downcasting is safe.
+ template <class Src, class Dst>
+ struct FastQueryFrame<
+ Src, Dst, std::enable_if_t<std::is_base_of<nsIFrame, Src>::value>,
+ std::enable_if_t<std::is_base_of<nsIFrame, Dst>::value>> {
+ static Dst* QueryFrame(Src* aFrame) {
+ return nsQueryFrame::FrameIID(aFrame->mClass) == Dst::kFrameIID
+ ? reinterpret_cast<Dst*>(aFrame)
+ : nullptr;
+ }
+ };
+
+ Source* mRawPtr;
+};
+
+template <class T>
+inline do_QueryFrameHelper<T> do_QueryFrame(T* s) {
+ return do_QueryFrameHelper<T>(s);
+}
+
+#endif // nsQueryFrame_h
diff --git a/layout/generic/nsRubyBaseContainerFrame.cpp b/layout/generic/nsRubyBaseContainerFrame.cpp
new file mode 100644
index 0000000000..6e29152bc9
--- /dev/null
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -0,0 +1,820 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-base-container" */
+
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include "nsRubyBaseFrame.h"
+#include "nsRubyTextFrame.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/WritingModes.h"
+#include "nsLayoutUtils.h"
+#include "nsLineLayout.h"
+#include "nsPresContext.h"
+#include "nsStyleStructInlines.h"
+#include "nsTextFrame.h"
+#include "gfxContext.h"
+#include "RubyUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//----------------------------------------------------------------------
+
+// Frame class boilerplate
+// =======================
+
+NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRubyBaseContainerFrame)
+
+nsContainerFrame* NS_NewRubyBaseContainerFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsRubyBaseContainerFrame(aStyle, aPresShell->GetPresContext());
+}
+
+//----------------------------------------------------------------------
+
+// nsRubyBaseContainerFrame Method Implementations
+// ===============================================
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsRubyBaseContainerFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"RubyBaseContainer"_ns, aResult);
+}
+#endif
+
+static gfxBreakPriority LineBreakBefore(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ nsIFrame* aLineContainerFrame,
+ const nsLineList::iterator* aLine) {
+ for (nsIFrame* child = aFrame; child;
+ child = child->PrincipalChildList().FirstChild()) {
+ if (!child->CanContinueTextRun()) {
+ // It is not an inline element. We can break before it.
+ return gfxBreakPriority::eNormalBreak;
+ }
+ if (!child->IsTextFrame()) {
+ continue;
+ }
+
+ auto textFrame = static_cast<nsTextFrame*>(child);
+ gfxSkipCharsIterator iter = textFrame->EnsureTextRun(
+ nsTextFrame::eInflated, aDrawTarget, aLineContainerFrame, aLine);
+ iter.SetOriginalOffset(textFrame->GetContentOffset());
+ uint32_t pos = iter.GetSkippedOffset();
+ gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated);
+ MOZ_ASSERT(textRun, "fail to build textrun?");
+ if (!textRun || pos >= textRun->GetLength()) {
+ // The text frame contains no character at all.
+ return gfxBreakPriority::eNoBreak;
+ }
+ // Return whether we can break before the first character.
+ if (textRun->CanBreakLineBefore(pos)) {
+ return gfxBreakPriority::eNormalBreak;
+ }
+ // Check whether we can wrap word here.
+ const nsStyleText* textStyle = textFrame->StyleText();
+ if (textStyle->WordCanWrap(textFrame) && textRun->IsClusterStart(pos)) {
+ return gfxBreakPriority::eWordWrapBreak;
+ }
+ // We cannot break before.
+ return gfxBreakPriority::eNoBreak;
+ }
+ // Neither block, nor text frame is found as a leaf. We won't break
+ // before this base frame. It is the behavior of empty spans.
+ return gfxBreakPriority::eNoBreak;
+}
+
+static void GetIsLineBreakAllowed(nsIFrame* aFrame, bool aIsLineBreakable,
+ bool* aAllowInitialLineBreak,
+ bool* aAllowLineBreak) {
+ nsIFrame* parent = aFrame->GetParent();
+ bool lineBreakSuppressed = parent->Style()->ShouldSuppressLineBreak();
+ // Allow line break between ruby bases when white-space allows,
+ // we are not inside a nested ruby, and there is no span.
+ bool allowLineBreak =
+ !lineBreakSuppressed && aFrame->StyleText()->WhiteSpaceCanWrap(aFrame);
+ bool allowInitialLineBreak = allowLineBreak;
+ if (!aFrame->GetPrevInFlow()) {
+ allowInitialLineBreak =
+ !lineBreakSuppressed && parent->StyleText()->WhiteSpaceCanWrap(parent);
+ }
+ if (!aIsLineBreakable) {
+ allowInitialLineBreak = false;
+ }
+ *aAllowInitialLineBreak = allowInitialLineBreak;
+ *aAllowLineBreak = allowLineBreak;
+}
+
+/**
+ * @param aBaseISizeData is an in/out param. This method updates the
+ * `skipWhitespace` and `trailingWhitespace` fields of the struct with
+ * the base level frame. Note that we don't need to do the same thing
+ * for ruby text frames, because they are text run container themselves
+ * (see nsTextFrame.cpp:BuildTextRuns), and thus no whitespace collapse
+ * happens across the boundary of those frames.
+ */
+static nscoord CalculateColumnPrefISize(
+ gfxContext* aRenderingContext, const RubyColumnEnumerator& aEnumerator,
+ nsIFrame::InlineIntrinsicISizeData* aBaseISizeData) {
+ nscoord max = 0;
+ uint32_t levelCount = aEnumerator.GetLevelCount();
+ for (uint32_t i = 0; i < levelCount; i++) {
+ nsIFrame* frame = aEnumerator.GetFrameAtLevel(i);
+ if (frame) {
+ nsIFrame::InlinePrefISizeData data;
+ if (i == 0) {
+ data.SetLineContainer(aBaseISizeData->LineContainer());
+ data.mSkipWhitespace = aBaseISizeData->mSkipWhitespace;
+ data.mTrailingWhitespace = aBaseISizeData->mTrailingWhitespace;
+ } else {
+ // The line container of ruby text frames is their parent,
+ // ruby text container frame.
+ data.SetLineContainer(frame->GetParent());
+ }
+ frame->AddInlinePrefISize(aRenderingContext, &data);
+ MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines");
+ max = std::max(max, data.mCurrentLine);
+ if (i == 0) {
+ aBaseISizeData->mSkipWhitespace = data.mSkipWhitespace;
+ aBaseISizeData->mTrailingWhitespace = data.mTrailingWhitespace;
+ }
+ }
+ }
+ return max;
+}
+
+// FIXME Currently we use pref isize of ruby content frames for
+// computing min isize of ruby frame, which may cause problem.
+// See bug 1134945.
+/* virtual */
+void nsRubyBaseContainerFrame::AddInlineMinISize(
+ gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
+ AutoRubyTextContainerArray textContainers(this);
+
+ for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
+ if (textContainers[i]->IsSpanContainer()) {
+ // Since spans are not breakable internally, use our pref isize
+ // directly if there is any span.
+ nsIFrame::InlinePrefISizeData data;
+ data.SetLineContainer(aData->LineContainer());
+ data.mSkipWhitespace = aData->mSkipWhitespace;
+ data.mTrailingWhitespace = aData->mTrailingWhitespace;
+ AddInlinePrefISize(aRenderingContext, &data);
+ aData->mCurrentLine += data.mCurrentLine;
+ if (data.mCurrentLine > 0) {
+ aData->mAtStartOfLine = false;
+ }
+ aData->mSkipWhitespace = data.mSkipWhitespace;
+ aData->mTrailingWhitespace = data.mTrailingWhitespace;
+ return;
+ }
+ }
+
+ bool firstFrame = true;
+ bool allowInitialLineBreak, allowLineBreak;
+ GetIsLineBreakAllowed(this, !aData->mAtStartOfLine, &allowInitialLineBreak,
+ &allowLineBreak);
+ for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
+ RubyColumnEnumerator enumerator(
+ static_cast<nsRubyBaseContainerFrame*>(frame), textContainers);
+ for (; !enumerator.AtEnd(); enumerator.Next()) {
+ if (firstFrame ? allowInitialLineBreak : allowLineBreak) {
+ nsIFrame* baseFrame = enumerator.GetFrameAtLevel(0);
+ if (baseFrame) {
+ gfxBreakPriority breakPriority = LineBreakBefore(
+ baseFrame, aRenderingContext->GetDrawTarget(), nullptr, nullptr);
+ if (breakPriority != gfxBreakPriority::eNoBreak) {
+ aData->OptionallyBreak();
+ }
+ }
+ }
+ firstFrame = false;
+ nscoord isize =
+ CalculateColumnPrefISize(aRenderingContext, enumerator, aData);
+ aData->mCurrentLine += isize;
+ if (isize > 0) {
+ aData->mAtStartOfLine = false;
+ }
+ }
+ }
+}
+
+/* virtual */
+void nsRubyBaseContainerFrame::AddInlinePrefISize(
+ gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
+ AutoRubyTextContainerArray textContainers(this);
+
+ nscoord sum = 0;
+ for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
+ RubyColumnEnumerator enumerator(
+ static_cast<nsRubyBaseContainerFrame*>(frame), textContainers);
+ for (; !enumerator.AtEnd(); enumerator.Next()) {
+ sum += CalculateColumnPrefISize(aRenderingContext, enumerator, aData);
+ }
+ }
+ for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
+ if (textContainers[i]->IsSpanContainer()) {
+ nsIFrame* frame = textContainers[i]->PrincipalChildList().FirstChild();
+ nsIFrame::InlinePrefISizeData data;
+ frame->AddInlinePrefISize(aRenderingContext, &data);
+ MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines");
+ sum = std::max(sum, data.mCurrentLine);
+ }
+ }
+ aData->mCurrentLine += sum;
+}
+
+/* virtual */
+bool nsRubyBaseContainerFrame::CanContinueTextRun() const { return true; }
+
+/* virtual */
+nsIFrame::SizeComputationResult nsRubyBaseContainerFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ // Ruby base container frame is inline,
+ // hence don't compute size before reflow.
+ return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
+ AspectRatioUsage::None};
+}
+
+Maybe<nscoord> nsRubyBaseContainerFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const {
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return Nothing{};
+ }
+ return Some(mBaseline);
+}
+
+struct nsRubyBaseContainerFrame::RubyReflowInput {
+ bool mAllowInitialLineBreak;
+ bool mAllowLineBreak;
+ const AutoRubyTextContainerArray& mTextContainers;
+ const ReflowInput& mBaseReflowInput;
+ const nsTArray<UniquePtr<ReflowInput>>& mTextReflowInputs;
+};
+
+/* virtual */
+void nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsRubyBaseContainerFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ if (!aReflowInput.mLineLayout) {
+ NS_ASSERTION(
+ aReflowInput.mLineLayout,
+ "No line layout provided to RubyBaseContainerFrame reflow method.");
+ return;
+ }
+
+ mDescendantLeadings.Reset();
+
+ nsIFrame* lineContainer = aReflowInput.mLineLayout->LineContainerFrame();
+ MoveInlineOverflowToChildList(lineContainer);
+ // Ask text containers to drain overflows
+ AutoRubyTextContainerArray textContainers(this);
+ const uint32_t rtcCount = textContainers.Length();
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ textContainers[i]->MoveInlineOverflowToChildList(lineContainer);
+ }
+
+ WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
+ LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
+ aReflowInput.AvailableBSize());
+
+ // We have a reflow input and a line layout for each RTC.
+ // They are conceptually the state of the RTCs, but we don't actually
+ // reflow those RTCs in this code. These two arrays are holders of
+ // the reflow inputs and line layouts.
+ // Since there are pointers refer to reflow inputs and line layouts,
+ // it is necessary to guarantee that they won't be moved. For this
+ // reason, they are wrapped in UniquePtr here.
+ AutoTArray<UniquePtr<ReflowInput>, RTC_ARRAY_SIZE> reflowInputs;
+ AutoTArray<UniquePtr<nsLineLayout>, RTC_ARRAY_SIZE> lineLayouts;
+ reflowInputs.SetCapacity(rtcCount);
+ lineLayouts.SetCapacity(rtcCount);
+
+ // Begin the line layout for each ruby text container in advance.
+ bool hasSpan = false;
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsRubyTextContainerFrame* textContainer = textContainers[i];
+ WritingMode rtcWM = textContainer->GetWritingMode();
+ WritingMode reflowWM = lineWM.IsOrthogonalTo(rtcWM) ? rtcWM : lineWM;
+ if (textContainer->IsSpanContainer()) {
+ hasSpan = true;
+ }
+
+ ReflowInput* reflowInput = new ReflowInput(
+ aPresContext, *aReflowInput.mParentReflowInput, textContainer,
+ availSize.ConvertTo(textContainer->GetWritingMode(), lineWM));
+ reflowInputs.AppendElement(reflowInput);
+ nsLineLayout* lineLayout =
+ new nsLineLayout(aPresContext, reflowInput->mFloatManager, *reflowInput,
+ nullptr, aReflowInput.mLineLayout);
+ lineLayout->SetSuppressLineWrap(true);
+ lineLayouts.AppendElement(lineLayout);
+
+ // Line number is useless for ruby text
+ // XXX nullptr here may cause problem, see comments for
+ // nsLineLayout::mBlockRI and nsLineLayout::AddFloat
+ lineLayout->Init(nullptr, reflowInput->GetLineHeight(), -1);
+ reflowInput->mLineLayout = lineLayout;
+
+ // Border and padding are suppressed on ruby text containers.
+ // If the writing mode is vertical-rl, the horizontal position of
+ // rt frames will be updated when reflowing this text container,
+ // hence leave container size 0 here for now.
+ lineLayout->BeginLineReflow(0, 0, reflowInput->ComputedISize(),
+ NS_UNCONSTRAINEDSIZE, false, false, reflowWM,
+ nsSize(0, 0));
+ lineLayout->AttachRootFrameToBaseLineLayout();
+ }
+
+ aReflowInput.mLineLayout->BeginSpan(
+ this, &aReflowInput, 0, aReflowInput.AvailableISize(), &mBaseline);
+
+ bool allowInitialLineBreak, allowLineBreak;
+ GetIsLineBreakAllowed(this, aReflowInput.mLineLayout->LineIsBreakable(),
+ &allowInitialLineBreak, &allowLineBreak);
+
+ // Reflow columns excluding any span
+ RubyReflowInput reflowInput = {allowInitialLineBreak,
+ allowLineBreak && !hasSpan, textContainers,
+ aReflowInput, reflowInputs};
+ aDesiredSize.BSize(lineWM) = 0;
+ aDesiredSize.SetBlockStartAscent(0);
+ nscoord isize = ReflowColumns(reflowInput, aDesiredSize, aStatus);
+ DebugOnly<nscoord> lineSpanSize = aReflowInput.mLineLayout->EndSpan(this);
+ aDesiredSize.ISize(lineWM) = isize;
+ // When there are no frames inside the ruby base container, EndSpan
+ // will return 0. However, in this case, the actual width of the
+ // container could be non-zero because of non-empty ruby annotations.
+ // XXX When bug 765861 gets fixed, this warning should be upgraded.
+ NS_WARNING_ASSERTION(
+ aStatus.IsInlineBreak() || isize == lineSpanSize || mFrames.IsEmpty(),
+ "bad isize");
+
+ // If there exists any span, the columns must either be completely
+ // reflowed, or be not reflowed at all.
+ MOZ_ASSERT(aStatus.IsInlineBreakBefore() || aStatus.IsComplete() || !hasSpan);
+ if (!aStatus.IsInlineBreakBefore() && aStatus.IsComplete() && hasSpan) {
+ // Reflow spans
+ RubyReflowInput reflowInput = {false, false, textContainers, aReflowInput,
+ reflowInputs};
+ nscoord spanISize = ReflowSpans(reflowInput);
+ isize = std::max(isize, spanISize);
+ }
+
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ // It happens before the ruby text container is reflowed, and that
+ // when it is reflowed, it will just use this size.
+ nsRubyTextContainerFrame* textContainer = textContainers[i];
+ nsLineLayout* lineLayout = lineLayouts[i].get();
+
+ RubyUtils::ClearReservedISize(textContainer);
+ nscoord rtcISize = lineLayout->GetCurrentICoord();
+ // Only span containers and containers with collapsed annotations
+ // need reserving isize. For normal ruby text containers, their
+ // children will be expanded properly. We only need to expand their
+ // own size.
+ if (!textContainer->IsSpanContainer()) {
+ rtcISize = isize;
+ } else if (isize > rtcISize) {
+ RubyUtils::SetReservedISize(textContainer, isize - rtcISize);
+ }
+
+ lineLayout->VerticalAlignLine();
+ textContainer->SetISize(rtcISize);
+ lineLayout->EndLineReflow();
+ }
+
+ // If this ruby base container is empty, size it as if there were
+ // an empty inline child inside.
+ if (mFrames.IsEmpty()) {
+ // Border and padding are suppressed on ruby base container, so we
+ // create a dummy zero-sized borderPadding for setting BSize.
+ WritingMode frameWM = aReflowInput.GetWritingMode();
+ LogicalMargin borderPadding(frameWM);
+ nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
+ lineWM, frameWM);
+ }
+
+ ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
+}
+
+/**
+ * This struct stores the continuations after this frame and
+ * corresponding text containers. It is used to speed up looking
+ * ahead for nonempty continuations.
+ */
+struct MOZ_STACK_CLASS nsRubyBaseContainerFrame::PullFrameState {
+ ContinuationTraversingState mBase;
+ AutoTArray<ContinuationTraversingState, RTC_ARRAY_SIZE> mTexts;
+ const AutoRubyTextContainerArray& mTextContainers;
+
+ PullFrameState(nsRubyBaseContainerFrame* aBaseContainer,
+ const AutoRubyTextContainerArray& aTextContainers);
+};
+
+nscoord nsRubyBaseContainerFrame::ReflowColumns(
+ const RubyReflowInput& aReflowInput, ReflowOutput& aDesiredSize,
+ nsReflowStatus& aStatus) {
+ nsLineLayout* lineLayout = aReflowInput.mBaseReflowInput.mLineLayout;
+ const uint32_t rtcCount = aReflowInput.mTextContainers.Length();
+ nscoord icoord = lineLayout->GetCurrentICoord();
+ MOZ_ASSERT(icoord == 0, "border/padding of rbc should have been suppressed");
+ nsReflowStatus reflowStatus;
+ aStatus.Reset();
+
+ uint32_t columnIndex = 0;
+ RubyColumn column;
+ column.mTextFrames.SetCapacity(rtcCount);
+ RubyColumnEnumerator e(this, aReflowInput.mTextContainers);
+ for (; !e.AtEnd(); e.Next()) {
+ e.GetColumn(column);
+ icoord += ReflowOneColumn(aReflowInput, columnIndex, column, aDesiredSize,
+ reflowStatus);
+ if (!reflowStatus.IsInlineBreakBefore()) {
+ columnIndex++;
+ }
+ if (reflowStatus.IsInlineBreak()) {
+ break;
+ }
+ // We are not handling overflow here.
+ MOZ_ASSERT(reflowStatus.IsEmpty());
+ }
+
+ bool isComplete = false;
+ PullFrameState pullFrameState(this, aReflowInput.mTextContainers);
+ while (!reflowStatus.IsInlineBreak()) {
+ // We are not handling overflow here.
+ MOZ_ASSERT(reflowStatus.IsEmpty());
+
+ // Try pull some frames from next continuations. This call replaces
+ // frames in |column| with the frame pulled in each level.
+ PullOneColumn(lineLayout, pullFrameState, column, isComplete);
+ if (isComplete) {
+ // No more frames can be pulled.
+ break;
+ }
+ icoord += ReflowOneColumn(aReflowInput, columnIndex, column, aDesiredSize,
+ reflowStatus);
+ if (!reflowStatus.IsInlineBreakBefore()) {
+ columnIndex++;
+ }
+ }
+
+ if (!e.AtEnd() && reflowStatus.IsInlineBreakAfter()) {
+ // The current column has been successfully placed.
+ // Skip to the next column and mark break before.
+ e.Next();
+ e.GetColumn(column);
+ reflowStatus.SetInlineLineBreakBeforeAndReset();
+ }
+ if (!e.AtEnd() || (GetNextInFlow() && !isComplete)) {
+ aStatus.SetIncomplete();
+ }
+
+ if (reflowStatus.IsInlineBreakBefore()) {
+ if (!columnIndex || !aReflowInput.mAllowLineBreak) {
+ // If no column has been placed yet, or we have any span,
+ // the whole container should be in the next line.
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return 0;
+ }
+ aStatus.SetInlineLineBreakAfter();
+ MOZ_ASSERT(aStatus.IsComplete() || aReflowInput.mAllowLineBreak);
+
+ // If we are on an intra-level whitespace column, null values in
+ // column.mBaseFrame and column.mTextFrames don't represent the
+ // end of the frame-sibling-chain at that level -- instead, they
+ // represent an anonymous empty intra-level whitespace box. It is
+ // likely that there are frames in the next column (which can't be
+ // intra-level whitespace). Those frames should be pushed as well.
+ Maybe<RubyColumn> nextColumn;
+ if (column.mIsIntraLevelWhitespace && !e.AtEnd()) {
+ e.Next();
+ nextColumn.emplace();
+ e.GetColumn(nextColumn.ref());
+ }
+ nsIFrame* baseFrame = column.mBaseFrame;
+ if (!baseFrame & nextColumn.isSome()) {
+ baseFrame = nextColumn->mBaseFrame;
+ }
+ if (baseFrame) {
+ PushChildrenToOverflow(baseFrame, baseFrame->GetPrevSibling());
+ }
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsRubyTextFrame* textFrame = column.mTextFrames[i];
+ if (!textFrame && nextColumn.isSome()) {
+ textFrame = nextColumn->mTextFrames[i];
+ }
+ if (textFrame) {
+ aReflowInput.mTextContainers[i]->PushChildrenToOverflow(
+ textFrame, textFrame->GetPrevSibling());
+ }
+ }
+ } else if (reflowStatus.IsInlineBreakAfter()) {
+ // |reflowStatus| being break after here may only happen when
+ // there is a break after the column just pulled, or the whole
+ // segment has been completely reflowed. In those cases, we do
+ // not need to push anything.
+ MOZ_ASSERT(e.AtEnd());
+ aStatus.SetInlineLineBreakAfter();
+ }
+
+ return icoord;
+}
+
+nscoord nsRubyBaseContainerFrame::ReflowOneColumn(
+ const RubyReflowInput& aReflowInput, uint32_t aColumnIndex,
+ const RubyColumn& aColumn, ReflowOutput& aDesiredSize,
+ nsReflowStatus& aStatus) {
+ const ReflowInput& baseReflowInput = aReflowInput.mBaseReflowInput;
+ const auto& textReflowInputs = aReflowInput.mTextReflowInputs;
+ nscoord istart = baseReflowInput.mLineLayout->GetCurrentICoord();
+
+ if (aColumn.mBaseFrame) {
+ bool allowBreakBefore = aColumnIndex ? aReflowInput.mAllowLineBreak
+ : aReflowInput.mAllowInitialLineBreak;
+ if (allowBreakBefore) {
+ gfxBreakPriority breakPriority =
+ LineBreakBefore(aColumn.mBaseFrame,
+ baseReflowInput.mRenderingContext->GetDrawTarget(),
+ baseReflowInput.mLineLayout->LineContainerFrame(),
+ baseReflowInput.mLineLayout->GetLine());
+ if (breakPriority != gfxBreakPriority::eNoBreak) {
+ gfxBreakPriority lastBreakPriority =
+ baseReflowInput.mLineLayout->LastOptionalBreakPriority();
+ if (breakPriority >= lastBreakPriority) {
+ // Either we have been overflow, or we are forced
+ // to break here, do break before.
+ if (istart > baseReflowInput.AvailableISize() ||
+ baseReflowInput.mLineLayout->NotifyOptionalBreakPosition(
+ aColumn.mBaseFrame, 0, true, breakPriority)) {
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ return 0;
+ }
+ }
+ }
+ }
+ }
+
+ const uint32_t rtcCount = aReflowInput.mTextContainers.Length();
+ MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount);
+ MOZ_ASSERT(textReflowInputs.Length() == rtcCount);
+ nscoord columnISize = 0;
+
+ nsAutoString baseText;
+ if (aColumn.mBaseFrame) {
+ nsLayoutUtils::GetFrameTextContent(aColumn.mBaseFrame, baseText);
+ }
+
+ // Reflow text frames
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
+ if (textFrame) {
+ bool isCollapsed = false;
+ if (textFrame->StyleVisibility()->mVisible == StyleVisibility::Collapse) {
+ isCollapsed = true;
+ } else {
+ // Per CSS Ruby spec, the content comparison for auto-hiding
+ // takes place prior to white spaces collapsing (white-space)
+ // and text transformation (text-transform), and ignores elements
+ // (considers only the textContent of the boxes). Which means
+ // using the content tree text comparison is correct.
+ nsAutoString annotationText;
+ nsLayoutUtils::GetFrameTextContent(textFrame, annotationText);
+ isCollapsed = annotationText.Equals(baseText);
+ }
+ if (isCollapsed) {
+ textFrame->AddStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED);
+ } else {
+ textFrame->RemoveStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED);
+ }
+ RubyUtils::ClearReservedISize(textFrame);
+
+ bool pushedFrame;
+ nsReflowStatus reflowStatus;
+ nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout;
+ nscoord textIStart = lineLayout->GetCurrentICoord();
+ lineLayout->ReflowFrame(textFrame, reflowStatus, nullptr, pushedFrame);
+ if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Any line break inside ruby box should have been suppressed");
+ // For safety, always drain the overflow list, so that
+ // no frames are left there after reflow.
+ textFrame->DrainSelfOverflowList();
+ }
+ nscoord textISize = lineLayout->GetCurrentICoord() - textIStart;
+ columnISize = std::max(columnISize, textISize);
+ }
+ }
+
+ // Reflow the base frame
+ if (aColumn.mBaseFrame) {
+ RubyUtils::ClearReservedISize(aColumn.mBaseFrame);
+
+ bool pushedFrame;
+ nsReflowStatus reflowStatus;
+ nsLineLayout* lineLayout = baseReflowInput.mLineLayout;
+ WritingMode lineWM = lineLayout->GetWritingMode();
+ nscoord baseIStart = lineLayout->GetCurrentICoord();
+ ReflowOutput metrics(lineWM);
+ lineLayout->ReflowFrame(aColumn.mBaseFrame, reflowStatus, &metrics,
+ pushedFrame);
+ if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Any line break inside ruby box should have been suppressed");
+ // For safety, always drain the overflow list, so that
+ // no frames are left there after reflow.
+ aColumn.mBaseFrame->DrainSelfOverflowList();
+ }
+ nscoord baseISize = lineLayout->GetCurrentICoord() - baseIStart;
+ columnISize = std::max(columnISize, baseISize);
+ // Calculate ascent & descent of the base frame and update desired
+ // size of this base container accordingly.
+ nscoord oldAscent = aDesiredSize.BlockStartAscent();
+ nscoord oldDescent = aDesiredSize.BSize(lineWM) - oldAscent;
+ nscoord baseAscent = metrics.BlockStartAscent();
+ nscoord baseDesent = metrics.BSize(lineWM) - baseAscent;
+ LogicalMargin margin = aColumn.mBaseFrame->GetLogicalUsedMargin(lineWM);
+ nscoord newAscent = std::max(baseAscent + margin.BStart(lineWM), oldAscent);
+ nscoord newDescent = std::max(baseDesent + margin.BEnd(lineWM), oldDescent);
+ aDesiredSize.SetBlockStartAscent(newAscent);
+ aDesiredSize.BSize(lineWM) = newAscent + newDescent;
+ }
+
+ // Align all the line layout to the new coordinate.
+ nscoord icoord = istart + columnISize;
+ nscoord deltaISize = icoord - baseReflowInput.mLineLayout->GetCurrentICoord();
+ if (deltaISize > 0) {
+ baseReflowInput.mLineLayout->AdvanceICoord(deltaISize);
+ if (aColumn.mBaseFrame) {
+ RubyUtils::SetReservedISize(aColumn.mBaseFrame, deltaISize);
+ }
+ }
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ if (aReflowInput.mTextContainers[i]->IsSpanContainer()) {
+ continue;
+ }
+ nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout;
+ nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
+ nscoord deltaISize = icoord - lineLayout->GetCurrentICoord();
+ if (deltaISize > 0) {
+ lineLayout->AdvanceICoord(deltaISize);
+ if (textFrame && !textFrame->IsCollapsed()) {
+ RubyUtils::SetReservedISize(textFrame, deltaISize);
+ }
+ }
+ if (aColumn.mBaseFrame && textFrame) {
+ lineLayout->AttachLastFrameToBaseLineLayout();
+ }
+ }
+
+ return columnISize;
+}
+
+nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
+ nsRubyBaseContainerFrame* aBaseContainer,
+ const AutoRubyTextContainerArray& aTextContainers)
+ : mBase(aBaseContainer), mTextContainers(aTextContainers) {
+ const uint32_t rtcCount = aTextContainers.Length();
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ mTexts.AppendElement(aTextContainers[i]);
+ }
+}
+
+void nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout* aLineLayout,
+ PullFrameState& aPullFrameState,
+ RubyColumn& aColumn,
+ bool& aIsComplete) {
+ const AutoRubyTextContainerArray& textContainers =
+ aPullFrameState.mTextContainers;
+ const uint32_t rtcCount = textContainers.Length();
+
+ nsIFrame* nextBase = GetNextInFlowChild(aPullFrameState.mBase);
+ MOZ_ASSERT(!nextBase || nextBase->IsRubyBaseFrame());
+ aColumn.mBaseFrame = static_cast<nsRubyBaseFrame*>(nextBase);
+ bool foundFrame = !!aColumn.mBaseFrame;
+ bool pullingIntraLevelWhitespace =
+ aColumn.mBaseFrame && aColumn.mBaseFrame->IsIntraLevelWhitespace();
+
+ aColumn.mTextFrames.ClearAndRetainStorage();
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsIFrame* nextText =
+ textContainers[i]->GetNextInFlowChild(aPullFrameState.mTexts[i]);
+ MOZ_ASSERT(!nextText || nextText->IsRubyTextFrame());
+ nsRubyTextFrame* textFrame = static_cast<nsRubyTextFrame*>(nextText);
+ aColumn.mTextFrames.AppendElement(textFrame);
+ foundFrame = foundFrame || nextText;
+ if (nextText && !pullingIntraLevelWhitespace) {
+ pullingIntraLevelWhitespace = textFrame->IsIntraLevelWhitespace();
+ }
+ }
+ // If there exists any frame in continations, we haven't
+ // completed the reflow process.
+ aIsComplete = !foundFrame;
+ if (!foundFrame) {
+ return;
+ }
+
+ aColumn.mIsIntraLevelWhitespace = pullingIntraLevelWhitespace;
+ if (pullingIntraLevelWhitespace) {
+ // We are pulling an intra-level whitespace. Drop all frames which
+ // are not part of this intra-level whitespace column. (Those frames
+ // are really part of the *next* column, after the pulled one.)
+ if (aColumn.mBaseFrame && !aColumn.mBaseFrame->IsIntraLevelWhitespace()) {
+ aColumn.mBaseFrame = nullptr;
+ }
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsRubyTextFrame*& textFrame = aColumn.mTextFrames[i];
+ if (textFrame && !textFrame->IsIntraLevelWhitespace()) {
+ textFrame = nullptr;
+ }
+ }
+ } else {
+ // We are not pulling an intra-level whitespace, which means all
+ // elements we are going to pull can have non-whitespace content,
+ // which may contain float which we need to reparent.
+ MOZ_ASSERT(aColumn.begin() != aColumn.end(),
+ "Ruby column shouldn't be empty");
+ nsBlockFrame* oldFloatCB =
+ nsLayoutUtils::GetFloatContainingBlock(*aColumn.begin());
+#ifdef DEBUG
+ MOZ_ASSERT(oldFloatCB, "Must have found a float containing block");
+ for (nsIFrame* frame : aColumn) {
+ MOZ_ASSERT(nsLayoutUtils::GetFloatContainingBlock(frame) == oldFloatCB,
+ "All frames in the same ruby column should share "
+ "the same old float containing block");
+ }
+#endif
+ nsBlockFrame* newFloatCB = do_QueryFrame(aLineLayout->LineContainerFrame());
+ MOZ_ASSERT(newFloatCB, "Must have a float containing block");
+ if (oldFloatCB != newFloatCB) {
+ for (nsIFrame* frame : aColumn) {
+ newFloatCB->ReparentFloats(frame, oldFloatCB, false);
+ }
+ }
+ }
+
+ // Pull the frames of this column.
+ if (aColumn.mBaseFrame) {
+ DebugOnly<nsIFrame*> pulled = PullNextInFlowChild(aPullFrameState.mBase);
+ MOZ_ASSERT(pulled == aColumn.mBaseFrame, "pulled a wrong frame?");
+ }
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ if (aColumn.mTextFrames[i]) {
+ DebugOnly<nsIFrame*> pulled =
+ textContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]);
+ MOZ_ASSERT(pulled == aColumn.mTextFrames[i], "pulled a wrong frame?");
+ }
+ }
+
+ if (!aIsComplete) {
+ // We pulled frames from the next line, hence mark it dirty.
+ aLineLayout->SetDirtyNextLine();
+ }
+}
+
+nscoord nsRubyBaseContainerFrame::ReflowSpans(
+ const RubyReflowInput& aReflowInput) {
+ nscoord spanISize = 0;
+ for (uint32_t i = 0, iend = aReflowInput.mTextContainers.Length(); i < iend;
+ i++) {
+ nsRubyTextContainerFrame* container = aReflowInput.mTextContainers[i];
+ if (!container->IsSpanContainer()) {
+ continue;
+ }
+
+ nsIFrame* rtFrame = container->PrincipalChildList().FirstChild();
+ nsReflowStatus reflowStatus;
+ bool pushedFrame;
+ nsLineLayout* lineLayout = aReflowInput.mTextReflowInputs[i]->mLineLayout;
+ MOZ_ASSERT(lineLayout->GetCurrentICoord() == 0,
+ "border/padding of rtc should have been suppressed");
+ lineLayout->ReflowFrame(rtFrame, reflowStatus, nullptr, pushedFrame);
+ MOZ_ASSERT(!reflowStatus.IsInlineBreak() && !pushedFrame,
+ "Any line break inside ruby box should has been suppressed");
+ spanISize = std::max(spanISize, lineLayout->GetCurrentICoord());
+ }
+ return spanISize;
+}
diff --git a/layout/generic/nsRubyBaseContainerFrame.h b/layout/generic/nsRubyBaseContainerFrame.h
new file mode 100644
index 0000000000..8e7c8e66d6
--- /dev/null
+++ b/layout/generic/nsRubyBaseContainerFrame.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-base-container" */
+
+#ifndef nsRubyBaseContainerFrame_h___
+#define nsRubyBaseContainerFrame_h___
+
+#include "nsContainerFrame.h"
+#include "RubyUtils.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Factory function.
+ * @return a newly allocated nsRubyBaseContainerFrame (infallible)
+ */
+nsContainerFrame* NS_NewRubyBaseContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+class nsRubyBaseContainerFrame final : public nsContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsRubyBaseContainerFrame)
+ NS_DECL_QUERYFRAME
+
+ // nsIFrame overrides
+ virtual bool CanContinueTextRun() const override;
+ virtual void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) override;
+ virtual void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) override;
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ void UpdateDescendantLeadings(const mozilla::RubyBlockLeadings& aLeadings) {
+ mDescendantLeadings.Update(aLeadings);
+ }
+ mozilla::RubyBlockLeadings GetDescendantLeadings() const {
+ return mDescendantLeadings;
+ }
+
+ protected:
+ friend nsContainerFrame* NS_NewRubyBaseContainerFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ explicit nsRubyBaseContainerFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {}
+
+ struct RubyReflowInput;
+ nscoord ReflowColumns(const RubyReflowInput& aReflowInput,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus);
+ nscoord ReflowOneColumn(const RubyReflowInput& aReflowInput,
+ uint32_t aColumnIndex,
+ const mozilla::RubyColumn& aColumn,
+ ReflowOutput& aDesiredSize, nsReflowStatus& aStatus);
+ nscoord ReflowSpans(const RubyReflowInput& aReflowInput);
+
+ struct PullFrameState;
+
+ // Pull ruby base and corresponding ruby text frames from
+ // continuations after them.
+ void PullOneColumn(nsLineLayout* aLineLayout, PullFrameState& aPullFrameState,
+ mozilla::RubyColumn& aColumn, bool& aIsComplete);
+
+ nscoord mBaseline;
+
+ // Leading produced by descendant ruby annotations.
+ mozilla::RubyBlockLeadings mDescendantLeadings;
+};
+
+#endif /* nsRubyBaseContainerFrame_h___ */
diff --git a/layout/generic/nsRubyBaseFrame.cpp b/layout/generic/nsRubyBaseFrame.cpp
new file mode 100644
index 0000000000..4350a43a28
--- /dev/null
+++ b/layout/generic/nsRubyBaseFrame.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-base" */
+
+#include "nsRubyBaseFrame.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/WritingModes.h"
+#include "nsLineLayout.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+// Frame class boilerplate
+// =======================
+
+NS_QUERYFRAME_HEAD(nsRubyBaseFrame)
+ NS_QUERYFRAME_ENTRY(nsRubyBaseFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsRubyContentFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRubyBaseFrame)
+
+nsContainerFrame* NS_NewRubyBaseFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) nsRubyBaseFrame(aStyle, aPresShell->GetPresContext());
+}
+
+//----------------------------------------------------------------------
+
+// nsRubyBaseFrame Method Implementations
+// ======================================
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsRubyBaseFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"RubyBase"_ns, aResult);
+}
+#endif
diff --git a/layout/generic/nsRubyBaseFrame.h b/layout/generic/nsRubyBaseFrame.h
new file mode 100644
index 0000000000..605c9d4235
--- /dev/null
+++ b/layout/generic/nsRubyBaseFrame.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-base" */
+
+#ifndef nsRubyBaseFrame_h___
+#define nsRubyBaseFrame_h___
+
+#include "nsRubyContentFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Factory function.
+ * @return a newly allocated nsRubyBaseFrame (infallible)
+ */
+nsContainerFrame* NS_NewRubyBaseFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+class nsRubyBaseFrame final : public nsRubyContentFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsRubyBaseFrame)
+ NS_DECL_QUERYFRAME
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ protected:
+ friend nsContainerFrame* NS_NewRubyBaseFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+ explicit nsRubyBaseFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsRubyContentFrame(aStyle, aPresContext, kClassID) {}
+};
+
+#endif /* nsRubyBaseFrame_h___ */
diff --git a/layout/generic/nsRubyContentFrame.cpp b/layout/generic/nsRubyContentFrame.cpp
new file mode 100644
index 0000000000..d344cee907
--- /dev/null
+++ b/layout/generic/nsRubyContentFrame.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for ruby rendering objects that directly contain content */
+
+#include "nsRubyContentFrame.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "nsPresContext.h"
+#include "nsCSSAnonBoxes.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+// nsRubyContentFrame Method Implementations
+// ======================================
+
+bool nsRubyContentFrame::IsIntraLevelWhitespace() const {
+ auto pseudoType = Style()->GetPseudoType();
+ if (pseudoType != PseudoStyleType::rubyBase &&
+ pseudoType != PseudoStyleType::rubyText) {
+ return false;
+ }
+
+ nsIFrame* child = mFrames.OnlyChild();
+ return child && child->GetContent()->TextIsOnlyWhitespace();
+}
diff --git a/layout/generic/nsRubyContentFrame.h b/layout/generic/nsRubyContentFrame.h
new file mode 100644
index 0000000000..df0b1125a1
--- /dev/null
+++ b/layout/generic/nsRubyContentFrame.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* base class for ruby rendering objects that directly contain content */
+
+#ifndef nsRubyContentFrame_h___
+#define nsRubyContentFrame_h___
+
+#include "nsInlineFrame.h"
+
+class nsRubyContentFrame : public nsInlineFrame {
+ public:
+ NS_DECL_ABSTRACT_FRAME(nsRubyContentFrame)
+
+ // Indicates whether this is an "intra-level whitespace" frame, i.e.
+ // an anonymous frame that was created to contain non-droppable
+ // whitespaces directly inside a ruby level container. This impacts
+ // ruby pairing behavior.
+ // See http://dev.w3.org/csswg/css-ruby/#anon-gen-interpret-space
+ bool IsIntraLevelWhitespace() const;
+
+ protected:
+ nsRubyContentFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : nsInlineFrame(aStyle, aPresContext, aID) {}
+};
+
+#endif /* nsRubyContentFrame_h___ */
diff --git a/layout/generic/nsRubyFrame.cpp b/layout/generic/nsRubyFrame.cpp
new file mode 100644
index 0000000000..d7da5bc321
--- /dev/null
+++ b/layout/generic/nsRubyFrame.cpp
@@ -0,0 +1,419 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby" */
+
+#include "nsRubyFrame.h"
+
+#include "RubyUtils.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/WritingModes.h"
+#include "nsLayoutUtils.h"
+#include "nsLineLayout.h"
+#include "nsPresContext.h"
+#include "nsContainerFrameInlines.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextContainerFrame.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+// Frame class boilerplate
+// =======================
+
+NS_QUERYFRAME_HEAD(nsRubyFrame)
+ NS_QUERYFRAME_ENTRY(nsRubyFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame)
+
+nsContainerFrame* NS_NewRubyFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) nsRubyFrame(aStyle, aPresShell->GetPresContext());
+}
+
+//----------------------------------------------------------------------
+
+// nsRubyFrame Method Implementations
+// ==================================
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsRubyFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Ruby"_ns, aResult);
+}
+#endif
+
+/* virtual */
+void nsRubyFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ auto handleChildren = [aRenderingContext](auto frame, auto data) {
+ for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
+ e.Next()) {
+ e.GetBaseContainer()->AddInlineMinISize(aRenderingContext, data);
+ }
+ };
+ DoInlineIntrinsicISize(aData, handleChildren);
+}
+
+/* virtual */
+void nsRubyFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
+ nsIFrame::InlinePrefISizeData* aData) {
+ auto handleChildren = [aRenderingContext](auto frame, auto data) {
+ for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
+ e.Next()) {
+ e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, data);
+ }
+ };
+ DoInlineIntrinsicISize(aData, handleChildren);
+ aData->mLineIsEmpty = false;
+}
+
+static nsRubyBaseContainerFrame* FindRubyBaseContainerAncestor(
+ nsIFrame* aFrame) {
+ for (nsIFrame* ancestor = aFrame->GetParent();
+ ancestor && ancestor->IsLineParticipant();
+ ancestor = ancestor->GetParent()) {
+ if (ancestor->IsRubyBaseContainerFrame()) {
+ return static_cast<nsRubyBaseContainerFrame*>(ancestor);
+ }
+ }
+ return nullptr;
+}
+
+/* virtual */
+void nsRubyFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ if (!aReflowInput.mLineLayout) {
+ NS_ASSERTION(aReflowInput.mLineLayout,
+ "No line layout provided to RubyFrame reflow method.");
+ return;
+ }
+
+ // Grab overflow frames from prev-in-flow and its own.
+ MoveInlineOverflowToChildList(aReflowInput.mLineLayout->LineContainerFrame());
+
+ // Clear leadings
+ mLeadings.Reset();
+
+ // Begin the span for the ruby frame
+ WritingMode frameWM = aReflowInput.GetWritingMode();
+ WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
+ LogicalMargin borderPadding =
+ aReflowInput.ComputedLogicalBorderPadding(frameWM);
+ nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
+ lineWM, frameWM);
+
+ nscoord startEdge = 0;
+ const bool boxDecorationBreakClone =
+ StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone;
+ if (boxDecorationBreakClone || !GetPrevContinuation()) {
+ startEdge = borderPadding.IStart(frameWM);
+ }
+ NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
+ "should no longer use available widths");
+ nscoord endEdge = aReflowInput.AvailableISize() - borderPadding.IEnd(frameWM);
+ aReflowInput.mLineLayout->BeginSpan(this, &aReflowInput, startEdge, endEdge,
+ &mBaseline);
+
+ for (RubySegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
+ ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
+ aDesiredSize.BSize(lineWM), e.GetBaseContainer(), aStatus);
+
+ if (aStatus.IsInlineBreak()) {
+ // A break occurs when reflowing the segment.
+ // Don't continue reflowing more segments.
+ break;
+ }
+ }
+
+ ContinuationTraversingState pullState(this);
+ while (aStatus.IsEmpty()) {
+ nsRubyBaseContainerFrame* baseContainer =
+ PullOneSegment(aReflowInput.mLineLayout, pullState);
+ if (!baseContainer) {
+ // No more continuations after, finish now.
+ break;
+ }
+ ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
+ aDesiredSize.BSize(lineWM), baseContainer, aStatus);
+ }
+ // We never handle overflow in ruby.
+ MOZ_ASSERT(!aStatus.IsOverflowIncomplete());
+
+ aDesiredSize.ISize(lineWM) = aReflowInput.mLineLayout->EndSpan(this);
+ if (boxDecorationBreakClone || !GetPrevContinuation()) {
+ aDesiredSize.ISize(lineWM) += borderPadding.IStart(frameWM);
+ }
+ if (boxDecorationBreakClone || aStatus.IsComplete()) {
+ aDesiredSize.ISize(lineWM) += borderPadding.IEnd(frameWM);
+ }
+
+ // Update descendant leadings of ancestor ruby base container.
+ if (nsRubyBaseContainerFrame* rbc = FindRubyBaseContainerAncestor(this)) {
+ rbc->UpdateDescendantLeadings(mLeadings);
+ }
+
+ ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
+}
+
+void nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nscoord aBlockStartAscent, nscoord aBlockSize,
+ nsRubyBaseContainerFrame* aBaseContainer,
+ nsReflowStatus& aStatus) {
+ WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
+ LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
+ aReflowInput.AvailableBSize());
+ NS_ASSERTION(!GetWritingMode().IsOrthogonalTo(lineWM),
+ "Ruby frame writing-mode shouldn't be orthogonal to its line");
+
+ AutoRubyTextContainerArray textContainers(aBaseContainer);
+ const uint32_t rtcCount = textContainers.Length();
+
+ ReflowOutput baseMetrics(aReflowInput);
+ bool pushedFrame;
+ aReflowInput.mLineLayout->ReflowFrame(aBaseContainer, aStatus, &baseMetrics,
+ pushedFrame);
+
+ if (aStatus.IsInlineBreakBefore()) {
+ if (aBaseContainer != mFrames.FirstChild()) {
+ // Some segments may have been reflowed before, hence it is not
+ // a break-before for the ruby container.
+ aStatus.Reset();
+ aStatus.SetInlineLineBreakAfter();
+ aStatus.SetIncomplete();
+ PushChildrenToOverflow(aBaseContainer, aBaseContainer->GetPrevSibling());
+ aReflowInput.mLineLayout->SetDirtyNextLine();
+ }
+ // This base container is not placed at all, we can skip all
+ // text containers paired with it.
+ return;
+ }
+ if (aStatus.IsIncomplete()) {
+ // It always promise that if the status is incomplete, there is a
+ // break occurs. Break before has been processed above. However,
+ // it is possible that break after happens with the frame reflow
+ // completed. It happens if there is a force break at the end.
+ MOZ_ASSERT(aStatus.IsInlineBreakAfter());
+ // Find the previous sibling which we will
+ // insert new continuations after.
+ nsIFrame* lastChild;
+ if (rtcCount > 0) {
+ lastChild = textContainers.LastElement();
+ } else {
+ lastChild = aBaseContainer;
+ }
+
+ // Create continuations for the base container
+ nsIFrame* newBaseContainer = CreateNextInFlow(aBaseContainer);
+ // newBaseContainer is null if there are existing next-in-flows.
+ // We only need to move and push if there were not.
+ if (newBaseContainer) {
+ // Move the new frame after all the text containers
+ mFrames.RemoveFrame(newBaseContainer);
+ mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
+
+ // Create continuations for text containers
+ nsIFrame* newLastChild = newBaseContainer;
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsIFrame* newTextContainer = CreateNextInFlow(textContainers[i]);
+ MOZ_ASSERT(newTextContainer,
+ "Next-in-flow of rtc should not exist "
+ "if the corresponding rbc does not");
+ mFrames.RemoveFrame(newTextContainer);
+ mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
+ newLastChild = newTextContainer;
+ }
+ }
+ if (lastChild != mFrames.LastChild()) {
+ // Always push the next frame after the last child in this segment.
+ // It is possible that we pulled it back before our next-in-flow
+ // drain our overflow.
+ PushChildrenToOverflow(lastChild->GetNextSibling(), lastChild);
+ aReflowInput.mLineLayout->SetDirtyNextLine();
+ }
+ } else if (rtcCount) {
+ DestroyContext context(PresShell());
+ // If the ruby base container is reflowed completely, the line
+ // layout will remove the next-in-flows of that frame. But the
+ // line layout is not aware of the ruby text containers, hence
+ // it is necessary to remove them here.
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ if (nsIFrame* nextRTC = textContainers[i]->GetNextInFlow()) {
+ nextRTC->GetParent()->DeleteNextInFlowChild(context, nextRTC, true);
+ }
+ }
+ }
+
+ nscoord segmentISize = baseMetrics.ISize(lineWM);
+ const nsSize dummyContainerSize;
+ LogicalRect baseRect =
+ aBaseContainer->GetLogicalRect(lineWM, dummyContainerSize);
+ // We need to position our rtc frames on one side or the other of the
+ // base container's rect, using a coordinate space that's relative to
+ // the ruby frame. Right now, the base container's rect's block-axis
+ // position is relative to the block container frame containing the
+ // lines, so here we reset it to the different between the ascents of
+ // the ruby container and the ruby base container, assuming they are
+ // aligned with the baseline.
+ // XXX We may need to add border/padding here. See bug 1055667.
+ baseRect.BStart(lineWM) = aBlockStartAscent - baseMetrics.BlockStartAscent();
+ // The rect for offsets of text containers.
+ LogicalRect offsetRect = baseRect;
+ RubyBlockLeadings descLeadings = aBaseContainer->GetDescendantLeadings();
+ offsetRect.BStart(lineWM) -= descLeadings.mStart;
+ offsetRect.BSize(lineWM) += descLeadings.mStart + descLeadings.mEnd;
+ Maybe<LineRelativeDir> lastLineSide;
+ for (uint32_t i = 0; i < rtcCount; i++) {
+ nsRubyTextContainerFrame* textContainer = textContainers[i];
+ WritingMode rtcWM = textContainer->GetWritingMode();
+ nsReflowStatus textReflowStatus;
+ ReflowOutput textMetrics(aReflowInput);
+ ReflowInput textReflowInput(aPresContext, aReflowInput, textContainer,
+ availSize.ConvertTo(rtcWM, lineWM));
+ textContainer->Reflow(aPresContext, textMetrics, textReflowInput,
+ textReflowStatus);
+ // Ruby text containers always return complete reflow status even when
+ // they have continuations, because the breaking has already been
+ // handled when reflowing the base containers.
+ NS_ASSERTION(textReflowStatus.IsEmpty(),
+ "Ruby text container must not break itself inside");
+ const LogicalSize size = textMetrics.Size(lineWM);
+ textContainer->SetSize(lineWM, size);
+
+ nscoord reservedISize = RubyUtils::GetReservedISize(textContainer);
+ segmentISize = std::max(segmentISize, size.ISize(lineWM) + reservedISize);
+
+ Maybe<LineRelativeDir> lineSide;
+ switch (textContainer->StyleText()->mRubyPosition) {
+ case StyleRubyPosition::Over:
+ lineSide.emplace(eLineRelativeDirOver);
+ break;
+ case StyleRubyPosition::Under:
+ lineSide.emplace(eLineRelativeDirUnder);
+ break;
+ case StyleRubyPosition::AlternateOver:
+ if (lastLineSide.isSome() &&
+ lastLineSide.value() == eLineRelativeDirOver) {
+ lineSide.emplace(eLineRelativeDirUnder);
+ } else {
+ lineSide.emplace(eLineRelativeDirOver);
+ }
+ break;
+ case StyleRubyPosition::AlternateUnder:
+ if (lastLineSide.isSome() &&
+ lastLineSide.value() == eLineRelativeDirUnder) {
+ lineSide.emplace(eLineRelativeDirOver);
+ } else {
+ lineSide.emplace(eLineRelativeDirUnder);
+ }
+ break;
+ default:
+ // XXX inter-character support in bug 1055672
+ MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
+ }
+ lastLineSide = lineSide;
+
+ LogicalPoint position(lineWM);
+ if (lineSide.isSome()) {
+ LogicalSide logicalSide =
+ lineWM.LogicalSideForLineRelativeDir(lineSide.value());
+ if (StaticPrefs::layout_css_ruby_intercharacter_enabled() &&
+ rtcWM.IsVerticalRL() &&
+ lineWM.GetInlineDir() == WritingMode::eInlineLTR) {
+ // Inter-character ruby annotations are only supported for vertical-rl
+ // in ltr horizontal writing. Fall back to non-inter-character behavior
+ // otherwise.
+ LogicalPoint offset(
+ lineWM, offsetRect.ISize(lineWM),
+ offsetRect.BSize(lineWM) > size.BSize(lineWM)
+ ? (offsetRect.BSize(lineWM) - size.BSize(lineWM)) / 2
+ : 0);
+ position = offsetRect.Origin(lineWM) + offset;
+ aReflowInput.mLineLayout->AdvanceICoord(size.ISize(lineWM));
+ } else if (logicalSide == eLogicalSideBStart) {
+ offsetRect.BStart(lineWM) -= size.BSize(lineWM);
+ offsetRect.BSize(lineWM) += size.BSize(lineWM);
+ position = offsetRect.Origin(lineWM);
+ } else if (logicalSide == eLogicalSideBEnd) {
+ position = offsetRect.Origin(lineWM) +
+ LogicalPoint(lineWM, 0, offsetRect.BSize(lineWM));
+ offsetRect.BSize(lineWM) += size.BSize(lineWM);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("???");
+ }
+ }
+ // Using a dummy container-size here, so child positioning may not be
+ // correct. We will fix it in nsLineLayout after the whole line is
+ // reflowed.
+ FinishReflowChild(textContainer, aPresContext, textMetrics,
+ &textReflowInput, lineWM, position, dummyContainerSize,
+ ReflowChildFlags::Default);
+ }
+ MOZ_ASSERT(baseRect.ISize(lineWM) == offsetRect.ISize(lineWM),
+ "Annotations should only be placed on the block directions");
+
+ nscoord deltaISize = segmentISize - baseMetrics.ISize(lineWM);
+ if (deltaISize <= 0) {
+ RubyUtils::ClearReservedISize(aBaseContainer);
+ } else {
+ RubyUtils::SetReservedISize(aBaseContainer, deltaISize);
+ aReflowInput.mLineLayout->AdvanceICoord(deltaISize);
+ }
+
+ // Set block leadings of the base container.
+ // The leadings are the difference between the offsetRect and the rect
+ // of this ruby container, which has block start zero and block size
+ // aBlockSize.
+ nscoord startLeading = -offsetRect.BStart(lineWM);
+ nscoord endLeading = offsetRect.BEnd(lineWM) - aBlockSize;
+ // XXX When bug 765861 gets fixed, this warning should be upgraded.
+ NS_WARNING_ASSERTION(startLeading >= 0 && endLeading >= 0,
+ "Leadings should be non-negative (because adding "
+ "ruby annotation can only increase the size)");
+ mLeadings.Update(startLeading, endLeading);
+}
+
+nsRubyBaseContainerFrame* nsRubyFrame::PullOneSegment(
+ const nsLineLayout* aLineLayout, ContinuationTraversingState& aState) {
+ // Pull a ruby base container
+ nsIFrame* baseFrame = GetNextInFlowChild(aState);
+ if (!baseFrame) {
+ return nullptr;
+ }
+ MOZ_ASSERT(baseFrame->IsRubyBaseContainerFrame());
+
+ // Get the float containing block of the base frame before we pull it.
+ nsBlockFrame* oldFloatCB = nsLayoutUtils::GetFloatContainingBlock(baseFrame);
+ PullNextInFlowChild(aState);
+
+ // Pull all ruby text containers following the base container
+ nsIFrame* nextFrame;
+ while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
+ nextFrame->IsRubyTextContainerFrame()) {
+ PullNextInFlowChild(aState);
+ }
+
+ if (nsBlockFrame* newFloatCB =
+ do_QueryFrame(aLineLayout->LineContainerFrame())) {
+ if (oldFloatCB && oldFloatCB != newFloatCB) {
+ newFloatCB->ReparentFloats(baseFrame, oldFloatCB, true);
+ }
+ }
+
+ return static_cast<nsRubyBaseContainerFrame*>(baseFrame);
+}
diff --git a/layout/generic/nsRubyFrame.h b/layout/generic/nsRubyFrame.h
new file mode 100644
index 0000000000..36bee3770d
--- /dev/null
+++ b/layout/generic/nsRubyFrame.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby" */
+
+#ifndef nsRubyFrame_h___
+#define nsRubyFrame_h___
+
+#include "nsInlineFrame.h"
+#include "RubyUtils.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Factory function.
+ * @return a newly allocated nsRubyFrame (infallible)
+ */
+nsContainerFrame* NS_NewRubyFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+class nsRubyFrame final : public nsInlineFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsRubyFrame)
+ NS_DECL_QUERYFRAME
+
+ // nsIFrame overrides
+ virtual void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) override;
+ virtual void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) override;
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ mozilla::RubyBlockLeadings GetBlockLeadings() const { return mLeadings; }
+
+ protected:
+ friend nsContainerFrame* NS_NewRubyFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+ explicit nsRubyFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsInlineFrame(aStyle, aPresContext, kClassID) {}
+
+ void ReflowSegment(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput, nscoord aBlockStartAscent,
+ nscoord aBlockSize,
+ nsRubyBaseContainerFrame* aBaseContainer,
+ nsReflowStatus& aStatus);
+
+ nsRubyBaseContainerFrame* PullOneSegment(const nsLineLayout* aLineLayout,
+ ContinuationTraversingState& aState);
+
+ // The leadings required to put the annotations. They are dummy-
+ // initialized to 0, and get meaningful values at first reflow.
+ mozilla::RubyBlockLeadings mLeadings;
+};
+
+#endif /* nsRubyFrame_h___ */
diff --git a/layout/generic/nsRubyTextContainerFrame.cpp b/layout/generic/nsRubyTextContainerFrame.cpp
new file mode 100644
index 0000000000..855d7c1825
--- /dev/null
+++ b/layout/generic/nsRubyTextContainerFrame.cpp
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-text-container" */
+
+#include "nsRubyTextContainerFrame.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WritingModes.h"
+#include "nsLayoutUtils.h"
+#include "nsLineLayout.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+// Frame class boilerplate
+// =======================
+
+NS_QUERYFRAME_HEAD(nsRubyTextContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsRubyTextContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRubyTextContainerFrame)
+
+nsContainerFrame* NS_NewRubyTextContainerFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsRubyTextContainerFrame(aStyle, aPresShell->GetPresContext());
+}
+
+//----------------------------------------------------------------------
+
+// nsRubyTextContainerFrame Method Implementations
+// ===============================================
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsRubyTextContainerFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"RubyTextContainer"_ns, aResult);
+}
+#endif
+
+/* virtual */
+void nsRubyTextContainerFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) {
+ nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
+ if (aListID == FrameChildListID::Principal) {
+ UpdateSpanFlag();
+ }
+}
+
+/* virtual */
+void nsRubyTextContainerFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
+ UpdateSpanFlag();
+}
+
+/* virtual */
+void nsRubyTextContainerFrame::InsertFrames(
+ ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
+ nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+ UpdateSpanFlag();
+}
+
+/* virtual */
+void nsRubyTextContainerFrame::RemoveFrame(DestroyContext& aContext,
+ ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
+ UpdateSpanFlag();
+}
+
+void nsRubyTextContainerFrame::UpdateSpanFlag() {
+ bool isSpan = false;
+ // The continuation checks are safe here because spans never break.
+ if (!GetPrevContinuation() && !GetNextContinuation()) {
+ nsIFrame* onlyChild = mFrames.OnlyChild();
+ if (onlyChild && onlyChild->IsPseudoFrame(GetContent())) {
+ // Per CSS Ruby spec, if the only child of an rtc frame is
+ // a pseudo rt frame, it spans all bases in the segment.
+ isSpan = true;
+ }
+ }
+
+ if (isSpan) {
+ AddStateBits(NS_RUBY_TEXT_CONTAINER_IS_SPAN);
+ } else {
+ RemoveStateBits(NS_RUBY_TEXT_CONTAINER_IS_SPAN);
+ }
+}
+
+/* virtual */
+void nsRubyTextContainerFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsRubyTextContainerFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ // Although a ruby text container may have continuations, returning
+ // complete reflow status is still safe, since its parent, ruby frame,
+ // ignores the status, and continuations of the ruby base container
+ // will take care of our continuations.
+ WritingMode rtcWM = GetWritingMode();
+
+ nscoord minBCoord = nscoord_MAX;
+ nscoord maxBCoord = nscoord_MIN;
+ // The container size is not yet known, so we use a dummy (0, 0) size.
+ // The block-dir position will be corrected below after containerSize
+ // is finalized.
+ const nsSize dummyContainerSize;
+ for (nsIFrame* child : mFrames) {
+ MOZ_ASSERT(child->IsRubyTextFrame());
+ LogicalRect rect = child->GetLogicalRect(rtcWM, dummyContainerSize);
+ LogicalMargin margin = child->GetLogicalUsedMargin(rtcWM);
+ nscoord blockStart = rect.BStart(rtcWM) - margin.BStart(rtcWM);
+ minBCoord = std::min(minBCoord, blockStart);
+ nscoord blockEnd = rect.BEnd(rtcWM) + margin.BEnd(rtcWM);
+ maxBCoord = std::max(maxBCoord, blockEnd);
+ }
+
+ if (!mFrames.IsEmpty()) {
+ if (MOZ_UNLIKELY(minBCoord > maxBCoord)) {
+ // XXX When bug 765861 gets fixed, this warning should be upgraded.
+ NS_WARNING("bad block coord");
+ minBCoord = maxBCoord = 0;
+ }
+ LogicalSize size(rtcWM, mISize, maxBCoord - minBCoord);
+ nsSize containerSize = size.GetPhysicalSize(rtcWM);
+ for (nsIFrame* child : mFrames) {
+ // We reflowed the child with a dummy container size, as the true size
+ // was not yet known at that time.
+ LogicalPoint pos = child->GetLogicalPosition(rtcWM, dummyContainerSize);
+ // Adjust block position to account for minBCoord,
+ // then reposition child based on the true container width.
+ pos.B(rtcWM) -= minBCoord;
+ // Relative positioning hasn't happened yet.
+ // So MovePositionBy should not be used here.
+ child->SetPosition(rtcWM, pos, containerSize);
+ nsContainerFrame::PlaceFrameView(child);
+ }
+ aDesiredSize.SetSize(rtcWM, size);
+ } else {
+ // If this ruby text container is empty, size it as if there were
+ // an empty inline child inside.
+ // Border and padding are suppressed on ruby text container, so we
+ // create a dummy zero-sized borderPadding for setting BSize.
+ aDesiredSize.ISize(rtcWM) = mISize;
+ LogicalMargin borderPadding(rtcWM);
+ nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
+ rtcWM, rtcWM);
+ }
+}
diff --git a/layout/generic/nsRubyTextContainerFrame.h b/layout/generic/nsRubyTextContainerFrame.h
new file mode 100644
index 0000000000..10ef194000
--- /dev/null
+++ b/layout/generic/nsRubyTextContainerFrame.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-text-container" */
+
+#ifndef nsRubyTextContainerFrame_h___
+#define nsRubyTextContainerFrame_h___
+
+#include "nsBlockFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Factory function.
+ * @return a newly allocated nsRubyTextContainerFrame (infallible)
+ */
+nsContainerFrame* NS_NewRubyTextContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+class nsRubyTextContainerFrame final : public nsContainerFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsRubyTextContainerFrame)
+ NS_DECL_QUERYFRAME
+
+ // nsIFrame overrides
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ // nsContainerFrame overrides
+ void SetInitialChildList(ChildListID aListID,
+ nsFrameList&& aChildList) override;
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override;
+
+ bool IsSpanContainer() const {
+ return HasAnyStateBits(NS_RUBY_TEXT_CONTAINER_IS_SPAN);
+ }
+
+ protected:
+ friend nsContainerFrame* NS_NewRubyTextContainerFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ explicit nsRubyTextContainerFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID), mISize(0) {}
+
+ void UpdateSpanFlag();
+
+ friend class nsRubyBaseContainerFrame;
+ void SetISize(nscoord aISize) { mISize = aISize; }
+
+ // The intended inline size of the ruby text container. It is set by
+ // the corresponding ruby base container when the segment is reflowed,
+ // and used when the ruby text container is reflowed by its parent.
+ nscoord mISize;
+};
+
+#endif /* nsRubyTextContainerFrame_h___ */
diff --git a/layout/generic/nsRubyTextFrame.cpp b/layout/generic/nsRubyTextFrame.cpp
new file mode 100644
index 0000000000..cf112bf7f5
--- /dev/null
+++ b/layout/generic/nsRubyTextFrame.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-text" */
+
+#include "nsRubyTextFrame.h"
+
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/WritingModes.h"
+#include "nsLineLayout.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+
+// Frame class boilerplate
+// =======================
+
+NS_QUERYFRAME_HEAD(nsRubyTextFrame)
+ NS_QUERYFRAME_ENTRY(nsRubyTextFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsRubyContentFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRubyTextFrame)
+
+nsContainerFrame* NS_NewRubyTextFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) nsRubyTextFrame(aStyle, aPresShell->GetPresContext());
+}
+
+//----------------------------------------------------------------------
+
+// nsRubyTextFrame Method Implementations
+// ======================================
+
+/* virtual */
+bool nsRubyTextFrame::CanContinueTextRun() const { return false; }
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsRubyTextFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"RubyText"_ns, aResult);
+}
+#endif
+
+/* virtual */
+void nsRubyTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (IsCollapsed()) {
+ return;
+ }
+
+ nsRubyContentFrame::BuildDisplayList(aBuilder, aLists);
+}
+
+/* virtual */
+void nsRubyTextFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ // Even if we want to hide this frame, we have to reflow it first.
+ // If we leave it dirty, changes to its content will never be
+ // propagated to the ancestors, then it won't be displayed even if
+ // the content is no longer the same, until next reflow triggered by
+ // some other change. In general, we always reflow all the frames we
+ // created. There might be other problems if we don't do that.
+ nsRubyContentFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ if (IsCollapsed()) {
+ // Reset the ISize. The BSize is not changed so that it won't
+ // affect vertical positioning in unexpected way.
+ WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
+ aDesiredSize.ISize(lineWM) = 0;
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ }
+}
diff --git a/layout/generic/nsRubyTextFrame.h b/layout/generic/nsRubyTextFrame.h
new file mode 100644
index 0000000000..92f48c83d7
--- /dev/null
+++ b/layout/generic/nsRubyTextFrame.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for CSS "display: ruby-text" */
+
+#ifndef nsRubyTextFrame_h___
+#define nsRubyTextFrame_h___
+
+#include "nsRubyContentFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Factory function.
+ * @return a newly allocated nsRubyTextFrame (infallible)
+ */
+nsContainerFrame* NS_NewRubyTextFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+class nsRubyTextFrame final : public nsRubyContentFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsRubyTextFrame)
+ NS_DECL_QUERYFRAME
+
+ // nsIFrame overrides
+ virtual bool CanContinueTextRun() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ bool IsCollapsed() const {
+ return HasAnyStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED);
+ }
+
+ protected:
+ friend nsContainerFrame* NS_NewRubyTextFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+ explicit nsRubyTextFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsRubyContentFrame(aStyle, aPresContext, kClassID) {}
+};
+
+#endif /* nsRubyTextFrame_h___ */
diff --git a/layout/generic/nsSplittableFrame.cpp b/layout/generic/nsSplittableFrame.cpp
new file mode 100644
index 0000000000..4054e45f8c
--- /dev/null
+++ b/layout/generic/nsSplittableFrame.cpp
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * base class for rendering objects that can be split across lines,
+ * columns, or pages
+ */
+
+#include "nsSplittableFrame.h"
+#include "nsContainerFrame.h"
+#include "nsFieldSetFrame.h"
+#include "nsIFrameInlines.h"
+
+using namespace mozilla;
+
+NS_QUERYFRAME_HEAD(nsSplittableFrame)
+ NS_QUERYFRAME_ENTRY(nsSplittableFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
+
+// These frame properties cache the first-continuation and first-in-flow frame
+// pointers. All nsSplittableFrames other than the first one in the continuation
+// chain will have these properties set.
+NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(FirstContinuationProperty, nsIFrame);
+NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(FirstInFlowProperty, nsIFrame);
+
+void nsSplittableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ if (aPrevInFlow) {
+ // Hook the frame into the flow
+ SetPrevInFlow(aPrevInFlow);
+ aPrevInFlow->SetNextInFlow(this);
+ }
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+void nsSplittableFrame::Destroy(DestroyContext& aContext) {
+ // Disconnect from the flow list
+ if (mPrevContinuation || mNextContinuation) {
+ RemoveFromFlow(this);
+ }
+
+ // Let the base class destroy the frame
+ nsIFrame::Destroy(aContext);
+}
+
+nsIFrame* nsSplittableFrame::GetPrevContinuation() const {
+ return mPrevContinuation;
+}
+
+void nsSplittableFrame::SetPrevContinuation(nsIFrame* aFrame) {
+ NS_ASSERTION(!aFrame || Type() == aFrame->Type(),
+ "setting a prev continuation with incorrect type!");
+ NS_ASSERTION(!IsInPrevContinuationChain(aFrame, this),
+ "creating a loop in continuation chain!");
+ mPrevContinuation = aFrame;
+ RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ UpdateFirstContinuationAndFirstInFlowCache();
+}
+
+nsIFrame* nsSplittableFrame::GetNextContinuation() const {
+ return mNextContinuation;
+}
+
+void nsSplittableFrame::SetNextContinuation(nsIFrame* aFrame) {
+ NS_ASSERTION(!aFrame || Type() == aFrame->Type(),
+ "setting a next continuation with incorrect type!");
+ NS_ASSERTION(!IsInNextContinuationChain(aFrame, this),
+ "creating a loop in continuation chain!");
+ mNextContinuation = aFrame;
+ if (mNextContinuation) {
+ mNextContinuation->RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ }
+}
+
+nsIFrame* nsSplittableFrame::FirstContinuation() const {
+ if (!GetPrevContinuation()) {
+ MOZ_ASSERT(
+ !HasProperty(FirstContinuationProperty()),
+ "The property shouldn't be present on first-continuation itself!");
+ return const_cast<nsSplittableFrame*>(this);
+ }
+
+ nsIFrame* firstContinuation = GetProperty(FirstContinuationProperty());
+ MOZ_ASSERT(firstContinuation,
+ "The property should be set and non-null on all continuations "
+ "after the first!");
+ MOZ_ASSERT(!firstContinuation->GetPrevContinuation(),
+ "First continuation shouldn't have a prev continuation!");
+ return firstContinuation;
+}
+
+nsIFrame* nsSplittableFrame::LastContinuation() const {
+ nsSplittableFrame* lastContinuation = const_cast<nsSplittableFrame*>(this);
+ while (lastContinuation->mNextContinuation) {
+ lastContinuation =
+ static_cast<nsSplittableFrame*>(lastContinuation->mNextContinuation);
+ }
+ MOZ_ASSERT(lastContinuation, "post-condition failed");
+ return lastContinuation;
+}
+
+#ifdef DEBUG
+bool nsSplittableFrame::IsInPrevContinuationChain(nsIFrame* aFrame1,
+ nsIFrame* aFrame2) {
+ int32_t iterations = 0;
+ while (aFrame1 && iterations < 10) {
+ // Bail out after 10 iterations so we don't bog down debug builds too much
+ if (aFrame1 == aFrame2) return true;
+ aFrame1 = aFrame1->GetPrevContinuation();
+ ++iterations;
+ }
+ return false;
+}
+
+bool nsSplittableFrame::IsInNextContinuationChain(nsIFrame* aFrame1,
+ nsIFrame* aFrame2) {
+ int32_t iterations = 0;
+ while (aFrame1 && iterations < 10) {
+ // Bail out after 10 iterations so we don't bog down debug builds too much
+ if (aFrame1 == aFrame2) return true;
+ aFrame1 = aFrame1->GetNextContinuation();
+ ++iterations;
+ }
+ return false;
+}
+#endif
+
+nsIFrame* nsSplittableFrame::GetPrevInFlow() const {
+ return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation
+ : nullptr;
+}
+
+void nsSplittableFrame::SetPrevInFlow(nsIFrame* aFrame) {
+ NS_ASSERTION(!aFrame || Type() == aFrame->Type(),
+ "setting a prev in flow with incorrect type!");
+ NS_ASSERTION(!IsInPrevContinuationChain(aFrame, this),
+ "creating a loop in continuation chain!");
+ mPrevContinuation = aFrame;
+ AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ UpdateFirstContinuationAndFirstInFlowCache();
+}
+
+nsIFrame* nsSplittableFrame::GetNextInFlow() const {
+ return mNextContinuation && mNextContinuation->HasAnyStateBits(
+ NS_FRAME_IS_FLUID_CONTINUATION)
+ ? mNextContinuation
+ : nullptr;
+}
+
+void nsSplittableFrame::SetNextInFlow(nsIFrame* aFrame) {
+ NS_ASSERTION(!aFrame || Type() == aFrame->Type(),
+ "setting a next in flow with incorrect type!");
+ NS_ASSERTION(!IsInNextContinuationChain(aFrame, this),
+ "creating a loop in continuation chain!");
+ mNextContinuation = aFrame;
+ if (mNextContinuation) {
+ mNextContinuation->AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ }
+}
+
+nsIFrame* nsSplittableFrame::FirstInFlow() const {
+ if (!GetPrevInFlow()) {
+ MOZ_ASSERT(!HasProperty(FirstInFlowProperty()),
+ "The property shouldn't be present on first-in-flow itself!");
+ return const_cast<nsSplittableFrame*>(this);
+ }
+
+ nsIFrame* firstInFlow = GetProperty(FirstInFlowProperty());
+ MOZ_ASSERT(firstInFlow,
+ "The property should be set and non-null on all in-flows after "
+ "the first!");
+ MOZ_ASSERT(!firstInFlow->GetPrevInFlow(),
+ "First-in-flow shouldn't have a prev-in-flow!");
+ return firstInFlow;
+}
+
+nsIFrame* nsSplittableFrame::LastInFlow() const {
+ nsSplittableFrame* lastInFlow = const_cast<nsSplittableFrame*>(this);
+ while (nsIFrame* next = lastInFlow->GetNextInFlow()) {
+ lastInFlow = static_cast<nsSplittableFrame*>(next);
+ }
+ MOZ_ASSERT(lastInFlow, "post-condition failed");
+ return lastInFlow;
+}
+
+// Remove this frame from the flow. Connects prev in flow and next in flow
+void nsSplittableFrame::RemoveFromFlow(nsIFrame* aFrame) {
+ nsIFrame* prevContinuation = aFrame->GetPrevContinuation();
+ nsIFrame* nextContinuation = aFrame->GetNextContinuation();
+
+ // The new continuation is fluid only if the continuation on both sides
+ // of the removed frame was fluid
+ if (aFrame->GetPrevInFlow() && aFrame->GetNextInFlow()) {
+ if (prevContinuation) {
+ prevContinuation->SetNextInFlow(nextContinuation);
+ }
+ if (nextContinuation) {
+ nextContinuation->SetPrevInFlow(prevContinuation);
+ }
+ } else {
+ if (prevContinuation) {
+ prevContinuation->SetNextContinuation(nextContinuation);
+ }
+ if (nextContinuation) {
+ nextContinuation->SetPrevContinuation(prevContinuation);
+ }
+ }
+
+ // **Note: it is important here that we clear the Next link from aFrame
+ // BEFORE clearing its Prev link, because in nsContinuingTextFrame,
+ // SetPrevInFlow() would follow the Next pointers, wiping out the cached
+ // mFirstContinuation field from each following frame in the list.
+ aFrame->SetNextInFlow(nullptr);
+ aFrame->SetPrevInFlow(nullptr);
+}
+
+void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() {
+ nsIFrame* oldCachedFirstContinuation =
+ GetProperty(FirstContinuationProperty());
+ nsIFrame* newFirstContinuation;
+ if (nsIFrame* prevContinuation = GetPrevContinuation()) {
+ newFirstContinuation = prevContinuation->FirstContinuation();
+ SetProperty(FirstContinuationProperty(), newFirstContinuation);
+ } else {
+ newFirstContinuation = this;
+ RemoveProperty(FirstContinuationProperty());
+ }
+
+ if (oldCachedFirstContinuation != newFirstContinuation) {
+ // Update the first-continuation cache for our next-continuations in the
+ // chain.
+ for (nsIFrame* next = GetNextContinuation(); next;
+ next = next->GetNextContinuation()) {
+ next->SetProperty(FirstContinuationProperty(), newFirstContinuation);
+ }
+ }
+
+ nsIFrame* oldCachedFirstInFlow = GetProperty(FirstInFlowProperty());
+ nsIFrame* newFirstInFlow;
+ if (nsIFrame* prevInFlow = GetPrevInFlow()) {
+ newFirstInFlow = prevInFlow->FirstInFlow();
+ SetProperty(FirstInFlowProperty(), newFirstInFlow);
+ } else {
+ newFirstInFlow = this;
+ RemoveProperty(FirstInFlowProperty());
+ }
+
+ if (oldCachedFirstInFlow != newFirstInFlow) {
+ // Update the first-in-flow cache for our next-in-flows in the chain.
+ for (nsIFrame* next = GetNextInFlow(); next; next = next->GetNextInFlow()) {
+ next->SetProperty(FirstInFlowProperty(), newFirstInFlow);
+ }
+ }
+}
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ConsumedBSizeProperty, nscoord);
+
+nscoord nsSplittableFrame::CalcAndCacheConsumedBSize() {
+ nsIFrame* prev = GetPrevContinuation();
+ if (!prev) {
+ return 0;
+ }
+ const auto wm = GetWritingMode();
+ nscoord bSize = 0;
+ for (; prev; prev = prev->GetPrevContinuation()) {
+ if (prev->IsTrueOverflowContainer()) {
+ // Overflow containers might not get reflowed, and they have no bSize
+ // anyways.
+ continue;
+ }
+
+ bSize += prev->ContentBSize(wm);
+ bool found = false;
+ nscoord consumed = prev->GetProperty(ConsumedBSizeProperty(), &found);
+ if (found) {
+ bSize += consumed;
+ break;
+ }
+ MOZ_ASSERT(!prev->GetPrevContinuation(),
+ "Property should always be set on prev continuation if not "
+ "the first continuation");
+ }
+ SetProperty(ConsumedBSizeProperty(), bSize);
+ return bSize;
+}
+
+nscoord nsSplittableFrame::GetEffectiveComputedBSize(
+ const ReflowInput& aReflowInput, nscoord aConsumedBSize) const {
+ nscoord bSize = aReflowInput.ComputedBSize();
+ if (bSize == NS_UNCONSTRAINEDSIZE) {
+ return NS_UNCONSTRAINEDSIZE;
+ }
+
+ bSize -= aConsumedBSize;
+
+ // nsFieldSetFrame's inner frames are special since some of their content-box
+ // BSize may be consumed by positioning it below the legend. So we always
+ // report zero for true overflow containers here.
+ // XXXmats: hmm, can we fix this so that the sizes actually adds up instead?
+ if (IsTrueOverflowContainer() &&
+ Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) {
+ for (nsFieldSetFrame* fieldset = do_QueryFrame(GetParent()); fieldset;
+ fieldset = static_cast<nsFieldSetFrame*>(fieldset->GetPrevInFlow())) {
+ bSize -= fieldset->LegendSpace();
+ }
+ }
+
+ // We may have stretched the frame beyond its computed height. Oh well.
+ return std::max(0, bSize);
+}
+
+LogicalSides nsSplittableFrame::GetBlockLevelLogicalSkipSides(
+ bool aAfterReflow) const {
+ LogicalSides skip(mWritingMode);
+ if (MOZ_UNLIKELY(IsTrueOverflowContainer())) {
+ skip |= eLogicalSideBitsBBoth;
+ return skip;
+ }
+
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return skip;
+ }
+
+ if (GetPrevContinuation()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+
+ // Always skip block-end side if we have a *later* sibling across column-span
+ // split.
+ if (HasColumnSpanSiblings()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+
+ if (aAfterReflow) {
+ nsIFrame* nif = GetNextContinuation();
+ if (nif && !nif->IsTrueOverflowContainer()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ }
+
+ return skip;
+}
diff --git a/layout/generic/nsSplittableFrame.h b/layout/generic/nsSplittableFrame.h
new file mode 100644
index 0000000000..923a3e462f
--- /dev/null
+++ b/layout/generic/nsSplittableFrame.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * base class for rendering objects that can be split across lines,
+ * columns, or pages
+ */
+
+#ifndef nsSplittableFrame_h___
+#define nsSplittableFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIFrame.h"
+
+// Derived class that allows splitting
+class nsSplittableFrame : public nsIFrame {
+ public:
+ NS_DECL_ABSTRACT_FRAME(nsSplittableFrame)
+ NS_DECL_QUERYFRAME_TARGET(nsSplittableFrame)
+ NS_DECL_QUERYFRAME
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ void Destroy(DestroyContext&) override;
+
+ /*
+ * Frame continuations can be either fluid or non-fluid.
+ *
+ * Fluid continuations ("in-flows") are the result of line breaking,
+ * column breaking, or page breaking.
+ *
+ * Non-fluid continuations can be the result of BiDi frame splitting,
+ * column-span splitting, or <col span="N"> where N > 1.
+ *
+ * A "flow" is a chain of fluid continuations.
+ *
+ * For more information, see https://wiki.mozilla.org/Gecko:Continuation_Model
+ */
+
+ // Get the previous/next continuation, regardless of its type (fluid or
+ // non-fluid).
+ nsIFrame* GetPrevContinuation() const final;
+ nsIFrame* GetNextContinuation() const final;
+
+ // Set a previous non-fluid continuation.
+ void SetPrevContinuation(nsIFrame*) final;
+
+ // Set a next non-fluid continuation.
+ //
+ // WARNING: this method updates caches for next-continuations, so it has O(n)
+ // time complexity over the length of next-continuations in the chain.
+ void SetNextContinuation(nsIFrame*) final;
+
+ // Get the first/last continuation for this frame.
+ nsIFrame* FirstContinuation() const final;
+ nsIFrame* LastContinuation() const final;
+
+#ifdef DEBUG
+ // Can aFrame2 be reached from aFrame1 by following prev/next continuations?
+ static bool IsInPrevContinuationChain(nsIFrame* aFrame1, nsIFrame* aFrame2);
+ static bool IsInNextContinuationChain(nsIFrame* aFrame1, nsIFrame* aFrame2);
+#endif
+
+ // Get the previous/next continuation, only if it is fluid (an "in-flow").
+ nsIFrame* GetPrevInFlow() const final;
+ nsIFrame* GetNextInFlow() const final;
+
+ // Set a previous fluid continuation.
+ void SetPrevInFlow(nsIFrame*) final;
+
+ // Set a next fluid continuation.
+ //
+ // WARNING: this method updates caches for next-continuations, so it has O(n)
+ // time complexity over the length of next-continuations in the chain.
+ void SetNextInFlow(nsIFrame*) final;
+
+ // Get the first/last frame in the current flow.
+ nsIFrame* FirstInFlow() const final;
+ nsIFrame* LastInFlow() const final;
+
+ // Remove the frame from the flow. Connects the frame's prev-in-flow
+ // and its next-in-flow. This should only be called in frame Destroy()
+ // methods.
+ static void RemoveFromFlow(nsIFrame* aFrame);
+
+ protected:
+ nsSplittableFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : nsIFrame(aStyle, aPresContext, aID) {}
+
+ // Update the first-continuation and first-in-flow cache for this frame and
+ // the next-continuations in the chain.
+ //
+ // Note: this function assumes that the first-continuation and first-in-flow
+ // caches are already up-to-date on this frame's
+ // prev-continuation/prev-in-flow frame (if there is such a frame).
+ void UpdateFirstContinuationAndFirstInFlowCache();
+
+ /**
+ * Return the sum of the block-axis content size of our previous
+ * continuations.
+ *
+ * Classes that call this are _required_ to call this at least once for each
+ * reflow (unless you're the first continuation, in which case you can skip
+ * it, because as an optimization we don't cache it there).
+ *
+ * This guarantees that the internal cache works, by refreshing it. Calling it
+ * multiple times in the same reflow is wasteful, but not an error.
+ */
+ nscoord CalcAndCacheConsumedBSize();
+
+ /**
+ * This static wrapper over CalcAndCacheConsumedBSize() is intended for a
+ * specific scenario where an nsSplittableFrame's subclass needs to access
+ * another subclass' consumed block-size. For ordinary use cases,
+ * CalcAndCacheConsumedBSize() should be called.
+ *
+ * This has the same requirements as CalcAndCacheConsumedBSize(). In
+ * particular, classes that call this are _required_ to call this at least
+ * once for each reflow.
+ */
+ static nscoord ConsumedBSize(nsSplittableFrame* aFrame) {
+ return aFrame->CalcAndCacheConsumedBSize();
+ }
+
+ /**
+ * Retrieve the effective computed block size of this frame, which is the
+ * computed block size, minus the block size consumed by any previous
+ * continuations.
+ */
+ nscoord GetEffectiveComputedBSize(const ReflowInput& aReflowInput,
+ nscoord aConsumed) const;
+
+ /**
+ * @see nsIFrame::GetLogicalSkipSides()
+ */
+ LogicalSides GetLogicalSkipSides() const override {
+ return GetBlockLevelLogicalSkipSides(true);
+ }
+
+ LogicalSides GetBlockLevelLogicalSkipSides(bool aAfterReflow) const;
+
+ /**
+ * A version of GetLogicalSkipSides() that is intended to be used inside
+ * Reflow before it's known if |this| frame will be COMPLETE or not.
+ * It returns a result that assumes this fragment is the last and thus
+ * should apply the block-end border/padding etc (except for "true" overflow
+ * containers which always skip block sides). You're then expected to
+ * recalculate the block-end side (as needed) when you know |this| frame's
+ * reflow status is INCOMPLETE.
+ * This method is intended for frames that break in the block axis.
+ */
+ LogicalSides PreReflowBlockLevelLogicalSkipSides() const {
+ return GetBlockLevelLogicalSkipSides(false);
+ };
+
+ nsIFrame* mPrevContinuation = nullptr;
+ nsIFrame* mNextContinuation = nullptr;
+};
+
+#endif /* nsSplittableFrame_h___ */
diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp
new file mode 100644
index 0000000000..bfbd7763a9
--- /dev/null
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -0,0 +1,1413 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * rendering object for replaced elements that contain a document, such
+ * as <frame>, <iframe>, and some <object>s
+ */
+
+#include "nsSubDocumentFrame.h"
+
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/HTMLFrameElement.h"
+#include "mozilla/dom/ImageDocument.h"
+#include "mozilla/dom/BrowserParent.h"
+
+#include "nsCOMPtr.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGenericHTMLFrameElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsIDocShell.h"
+#include "nsIDocumentViewer.h"
+#include "nsIContentInlines.h"
+#include "nsPresContext.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsStyleStruct.h"
+#include "nsStyleStructInlines.h"
+#include "nsFrameSetFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsDisplayList.h"
+#include "nsIScrollableFrame.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsLayoutUtils.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsQueryObject.h"
+#include "RetainedDisplayListBuilder.h"
+#include "nsObjectLoadingContent.h"
+
+#include "mozilla/layers/WebRenderUserData.h"
+#include "mozilla/layers/WebRenderScrollData.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/StackingContextHelper.h" // for StackingContextHelper
+#include "mozilla/ProfilerLabels.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+static Document* GetDocumentFromView(nsView* aView) {
+ MOZ_ASSERT(aView, "null view");
+
+ nsViewManager* vm = aView->GetViewManager();
+ PresShell* presShell = vm ? vm->GetPresShell() : nullptr;
+ return presShell ? presShell->GetDocument() : nullptr;
+}
+
+static void PropagateIsUnderHiddenEmbedderElement(nsFrameLoader* aFrameLoader,
+ bool aValue) {
+ if (!aFrameLoader) {
+ return;
+ }
+
+ if (BrowsingContext* bc = aFrameLoader->GetExtantBrowsingContext()) {
+ if (bc->IsUnderHiddenEmbedderElement() != aValue) {
+ Unused << bc->SetIsUnderHiddenEmbedderElement(aValue);
+ }
+ }
+}
+
+nsSubDocumentFrame::nsSubDocumentFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsAtomicContainerFrame(aStyle, aPresContext, kClassID),
+ mOuterView(nullptr),
+ mInnerView(nullptr),
+ mIsInline(false),
+ mPostedReflowCallback(false),
+ mDidCreateDoc(false),
+ mCallingShow(false),
+ mIsInObjectOrEmbed(false) {}
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsSubDocumentFrame::AccessibleType() {
+ return a11y::eOuterDocType;
+}
+#endif
+
+NS_QUERYFRAME_HEAD(nsSubDocumentFrame)
+ NS_QUERYFRAME_ENTRY(nsSubDocumentFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame)
+
+class AsyncFrameInit : public Runnable {
+ public:
+ explicit AsyncFrameInit(nsIFrame* aFrame)
+ : mozilla::Runnable("AsyncFrameInit"), mFrame(aFrame) {}
+ NS_IMETHOD Run() override {
+ AUTO_PROFILER_LABEL("AsyncFrameInit::Run", OTHER);
+ if (mFrame.IsAlive()) {
+ static_cast<nsSubDocumentFrame*>(mFrame.GetFrame())->ShowViewer();
+ }
+ return NS_OK;
+ }
+
+ private:
+ WeakFrame mFrame;
+};
+
+void nsSubDocumentFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT(aContent);
+ // determine if we are a <frame> or <iframe>
+ mIsInline = !aContent->IsHTMLElement(nsGkAtoms::frame);
+
+ nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // CreateView() creates this frame's view, stored in mOuterView. It needs to
+ // be created first since it's the parent of the inner view, stored in
+ // mInnerView.
+ CreateView();
+ EnsureInnerView();
+
+ // Set the primary frame now so that nsDocumentViewer::FindContainerView
+ // called from within EndSwapDocShellsForViews below can find it if needed.
+ aContent->SetPrimaryFrame(this);
+
+ // If we have a detached subdoc's root view on our frame loader, re-insert it
+ // into the view tree. This happens when we've been reframed, and ensures the
+ // presentation persists across reframes.
+ if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) {
+ bool hadFrame = false;
+ nsIFrame* detachedFrame = frameloader->GetDetachedSubdocFrame(&hadFrame);
+ frameloader->SetDetachedSubdocFrame(nullptr);
+ nsView* detachedView = detachedFrame ? detachedFrame->GetView() : nullptr;
+ if (detachedView) {
+ // Restore stashed presentation.
+ InsertViewsInReverseOrder(detachedView, mInnerView);
+ EndSwapDocShellsForViews(mInnerView->GetFirstChild());
+ } else if (hadFrame) {
+ // Presentation is for a different document, don't restore it.
+ frameloader->Hide();
+ }
+ }
+
+ // NOTE: The frame loader might not yet be initialized yet. If it's not, the
+ // call in ShowViewer() should pick things up.
+ UpdateEmbeddedBrowsingContextDependentData();
+ nsContentUtils::AddScriptRunner(new AsyncFrameInit(this));
+}
+
+void nsSubDocumentFrame::UpdateEmbeddedBrowsingContextDependentData() {
+ if (!mFrameLoader) {
+ return;
+ }
+ BrowsingContext* bc = mFrameLoader->GetExtantBrowsingContext();
+ if (!bc) {
+ return;
+ }
+ mIsInObjectOrEmbed = bc->IsEmbedderTypeObjectOrEmbed();
+ MaybeUpdateRemoteStyle();
+ MaybeUpdateEmbedderColorScheme();
+ PropagateIsUnderHiddenEmbedderElement(
+ PresShell()->IsUnderHiddenEmbedderElement() ||
+ !StyleVisibility()->IsVisible());
+}
+
+void nsSubDocumentFrame::PropagateIsUnderHiddenEmbedderElement(bool aValue) {
+ ::PropagateIsUnderHiddenEmbedderElement(mFrameLoader, aValue);
+}
+
+void nsSubDocumentFrame::ShowViewer() {
+ if (mCallingShow) {
+ return;
+ }
+
+ RefPtr<nsFrameLoader> frameloader = FrameLoader();
+ if (!frameloader || frameloader->IsDead()) {
+ return;
+ }
+
+ if (!frameloader->IsRemoteFrame() && !PresContext()->IsDynamic()) {
+ // We let the printing code take care of loading the document and
+ // initializing the shell; just create the inner view for it to use.
+ (void)EnsureInnerView();
+ } else {
+ AutoWeakFrame weakThis(this);
+ mCallingShow = true;
+ bool didCreateDoc = frameloader->Show(this);
+ if (!weakThis.IsAlive()) {
+ return;
+ }
+ mCallingShow = false;
+ mDidCreateDoc = didCreateDoc;
+ if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ frameloader->UpdatePositionAndSize(this);
+ }
+ if (!weakThis.IsAlive()) {
+ return;
+ }
+ UpdateEmbeddedBrowsingContextDependentData();
+ InvalidateFrame();
+ }
+}
+
+nsIFrame* nsSubDocumentFrame::GetSubdocumentRootFrame() {
+ if (!mInnerView) return nullptr;
+ nsView* subdocView = mInnerView->GetFirstChild();
+ return subdocView ? subdocView->GetFrame() : nullptr;
+}
+
+mozilla::PresShell* nsSubDocumentFrame::GetSubdocumentPresShellForPainting(
+ uint32_t aFlags) {
+ if (!mInnerView) return nullptr;
+
+ nsView* subdocView = mInnerView->GetFirstChild();
+ if (!subdocView) return nullptr;
+
+ mozilla::PresShell* presShell = nullptr;
+
+ nsIFrame* subdocRootFrame = subdocView->GetFrame();
+ if (subdocRootFrame) {
+ presShell = subdocRootFrame->PresShell();
+ }
+
+ // If painting is suppressed in the presshell, we try to look for a better
+ // presshell to use.
+ if (!presShell || (presShell->IsPaintingSuppressed() &&
+ !(aFlags & IGNORE_PAINT_SUPPRESSION))) {
+ // During page transition mInnerView will sometimes have two children, the
+ // first being the new page that may not have any frame, and the second
+ // being the old page that will probably have a frame.
+ nsView* nextView = subdocView->GetNextSibling();
+ nsIFrame* frame = nullptr;
+ if (nextView) {
+ frame = nextView->GetFrame();
+ }
+ if (frame) {
+ mozilla::PresShell* presShellForNextView = frame->PresShell();
+ if (!presShell || (presShellForNextView &&
+ !presShellForNextView->IsPaintingSuppressed() &&
+ StaticPrefs::layout_show_previous_page())) {
+ subdocView = nextView;
+ subdocRootFrame = frame;
+ presShell = presShellForNextView;
+ }
+ }
+ if (!presShell) {
+ // If we don't have a frame we use this roundabout way to get the pres
+ // shell.
+ if (!mFrameLoader) return nullptr;
+ nsIDocShell* docShell = mFrameLoader->GetDocShell(IgnoreErrors());
+ if (!docShell) return nullptr;
+ presShell = docShell->GetPresShell();
+ }
+ }
+
+ return presShell;
+}
+
+nsRect nsSubDocumentFrame::GetDestRect() {
+ nsRect rect = GetContent()->IsHTMLElement(nsGkAtoms::frame)
+ ? GetRectRelativeToSelf()
+ : GetContentRectRelativeToSelf();
+
+ // Adjust subdocument size, according to 'object-fit' and the subdocument's
+ // intrinsic size and ratio.
+ return nsLayoutUtils::ComputeObjectDestRect(
+ rect, GetIntrinsicSize(), GetIntrinsicRatio(), StylePosition());
+}
+
+ScreenIntSize nsSubDocumentFrame::GetSubdocumentSize() {
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) {
+ nsIFrame* detachedFrame = frameloader->GetDetachedSubdocFrame();
+ if (nsView* view = detachedFrame ? detachedFrame->GetView() : nullptr) {
+ nsSize size = view->GetBounds().Size();
+ nsPresContext* presContext = detachedFrame->PresContext();
+ return ScreenIntSize(presContext->AppUnitsToDevPixels(size.width),
+ presContext->AppUnitsToDevPixels(size.height));
+ }
+ }
+ // Pick some default size for now. Using 10x10 because that's what the
+ // code used to do.
+ return ScreenIntSize(10, 10);
+ }
+
+ nsSize docSizeAppUnits = GetDestRect().Size();
+ nsPresContext* pc = PresContext();
+ return ScreenIntSize(pc->AppUnitsToDevPixels(docSizeAppUnits.width),
+ pc->AppUnitsToDevPixels(docSizeAppUnits.height));
+}
+
+static void WrapBackgroundColorInOwnLayer(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList) {
+ for (nsDisplayItem* item : aList->TakeItems()) {
+ if (item->GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) {
+ nsDisplayList tmpList(aBuilder);
+ tmpList.AppendToTop(item);
+ item = MakeDisplayItemWithIndex<nsDisplayOwnLayer>(
+ aBuilder, aFrame, /* aIndex = */ nsDisplayOwnLayer::OwnLayerForSubdoc,
+ &tmpList, aBuilder->CurrentActiveScrolledRoot(),
+ nsDisplayOwnLayerFlags::None, ScrollbarData{}, true, false);
+ }
+ aList->AppendToTop(item);
+ }
+}
+
+void nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) {
+ return;
+ }
+
+ nsFrameLoader* frameLoader = FrameLoader();
+ bool isRemoteFrame = frameLoader && frameLoader->IsRemoteFrame();
+
+ // If we are pointer-events:none then we don't need to HitTest background
+ const bool pointerEventsNone =
+ Style()->PointerEvents() == StylePointerEvents::None;
+ if (!aBuilder->IsForEventDelivery() || !pointerEventsNone) {
+ nsDisplayListCollection decorations(aBuilder);
+ DisplayBorderBackgroundOutline(aBuilder, decorations);
+ if (isRemoteFrame) {
+ // Wrap background colors of <iframe>s with remote subdocuments in their
+ // own layer so we generate a ColorLayer. This is helpful for optimizing
+ // compositing; we can skip compositing the ColorLayer when the
+ // remote content is opaque.
+ WrapBackgroundColorInOwnLayer(aBuilder, this,
+ decorations.BorderBackground());
+ }
+ decorations.MoveTo(aLists);
+ }
+
+ if (aBuilder->IsForEventDelivery() && pointerEventsNone) {
+ return;
+ }
+
+ if (HidesContent()) {
+ return;
+ }
+
+ // If we're passing pointer events to children then we have to descend into
+ // subdocuments no matter what, to determine which parts are transparent for
+ // hit-testing or event regions.
+ bool needToDescend = aBuilder->GetDescendIntoSubdocuments();
+ if (!mInnerView || !needToDescend) {
+ return;
+ }
+
+ if (isRemoteFrame) {
+ // We're the subdoc for <browser remote="true"> and it has
+ // painted content. Display its shadow layer tree.
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this);
+
+ aLists.Content()->AppendNewToTop<nsDisplayRemote>(aBuilder, this);
+ return;
+ }
+
+ RefPtr<mozilla::PresShell> presShell = GetSubdocumentPresShellForPainting(
+ aBuilder->IsIgnoringPaintSuppression() ? IGNORE_PAINT_SUPPRESSION : 0);
+
+ if (!presShell) {
+ return;
+ }
+
+ if (aBuilder->IsInFilter()) {
+ Document* outerDoc = PresShell()->GetDocument();
+ Document* innerDoc = presShell->GetDocument();
+ if (outerDoc && innerDoc) {
+ if (!outerDoc->NodePrincipal()->Equals(innerDoc->NodePrincipal())) {
+ outerDoc->SetUseCounter(eUseCounter_custom_FilteredCrossOriginIFrame);
+ }
+ }
+ }
+
+ nsIFrame* subdocRootFrame = presShell->GetRootFrame();
+
+ nsPresContext* presContext = presShell->GetPresContext();
+
+ int32_t parentAPD = PresContext()->AppUnitsPerDevPixel();
+ int32_t subdocAPD = presContext->AppUnitsPerDevPixel();
+
+ nsRect visible;
+ nsRect dirty;
+ bool ignoreViewportScrolling = false;
+ if (subdocRootFrame) {
+ // get the dirty rect relative to the root frame of the subdoc
+ visible = aBuilder->GetVisibleRect() + GetOffsetToCrossDoc(subdocRootFrame);
+ dirty = aBuilder->GetDirtyRect() + GetOffsetToCrossDoc(subdocRootFrame);
+ // and convert into the appunits of the subdoc
+ visible = visible.ScaleToOtherAppUnitsRoundOut(parentAPD, subdocAPD);
+ dirty = dirty.ScaleToOtherAppUnitsRoundOut(parentAPD, subdocAPD);
+
+ if (nsIScrollableFrame* rootScrollableFrame =
+ presShell->GetRootScrollFrameAsScrollable()) {
+ // Use a copy, so the rects don't get modified.
+ nsRect copyOfDirty = dirty;
+ nsRect copyOfVisible = visible;
+ // TODO(botond): Can we just axe this DecideScrollableLayer call?
+ rootScrollableFrame->DecideScrollableLayer(aBuilder, &copyOfVisible,
+ &copyOfDirty,
+ /* aSetBase = */ true);
+
+ ignoreViewportScrolling = presShell->IgnoringViewportScrolling();
+ }
+
+ aBuilder->EnterPresShell(subdocRootFrame, pointerEventsNone);
+ aBuilder->IncrementPresShellPaintCount(presShell);
+ } else {
+ visible = aBuilder->GetVisibleRect();
+ dirty = aBuilder->GetDirtyRect();
+ }
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this);
+
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ bool constructZoomItem = subdocRootFrame && parentAPD != subdocAPD;
+ bool needsOwnLayer = constructZoomItem ||
+ presContext->IsRootContentDocumentCrossProcess() ||
+ (sf && sf->IsScrollingActive());
+
+ nsDisplayList childItems(aBuilder);
+
+ {
+ DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
+ if (needsOwnLayer) {
+ // Clear current clip. There's no point in propagating it down, since
+ // the layer we will construct will be clipped by the current clip.
+ // In fact for nsDisplayZoom propagating it down would be incorrect since
+ // nsDisplayZoom changes the meaning of appunits.
+ nestedClipState.Clear();
+ }
+
+ // Invoke AutoBuildingDisplayList to ensure that the correct dirty rect
+ // is used to compute the visible rect if AddCanvasBackgroundColorItem
+ // creates a display item.
+ nsIFrame* frame = subdocRootFrame ? subdocRootFrame : this;
+ nsDisplayListBuilder::AutoBuildingDisplayList building(aBuilder, frame,
+ visible, dirty);
+
+ if (subdocRootFrame) {
+ bool hasDocumentLevelListenersForApzAwareEvents =
+ gfxPlatform::AsyncPanZoomEnabled() &&
+ nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell);
+
+ aBuilder->SetAncestorHasApzAwareEventHandler(
+ hasDocumentLevelListenersForApzAwareEvents);
+ subdocRootFrame->BuildDisplayListForStackingContext(aBuilder,
+ &childItems);
+ if (!aBuilder->IsForEventDelivery()) {
+ // If we are going to use a displayzoom below then any items we put
+ // under it need to have underlying frames from the subdocument. So we
+ // need to calculate the bounds based on which frame will be the
+ // underlying frame for the canvas background color item.
+ nsRect bounds =
+ GetContentRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
+ bounds = bounds.ScaleToOtherAppUnitsRoundOut(parentAPD, subdocAPD);
+
+ // Add the canvas background color to the bottom of the list. This
+ // happens after we've built the list so that
+ // AddCanvasBackgroundColorItem can monkey with the contents if
+ // necessary.
+ presShell->AddCanvasBackgroundColorItem(aBuilder, &childItems, frame,
+ bounds, NS_RGBA(0, 0, 0, 0));
+ }
+ }
+ }
+
+ if (subdocRootFrame) {
+ aBuilder->LeavePresShell(subdocRootFrame, &childItems);
+ }
+
+ // Generate a resolution and/or zoom item if needed. If one or both of those
+ // is created, we don't need to create a separate nsDisplaySubDocument.
+
+ nsDisplayOwnLayerFlags flags =
+ nsDisplayOwnLayerFlags::GenerateSubdocInvalidations;
+ // If ignoreViewportScrolling is true then the top most layer we create here
+ // is going to become the scrollable layer for the root scroll frame, so we
+ // want to add nsDisplayOwnLayer::GENERATE_SCROLLABLE_LAYER to whatever layer
+ // becomes the topmost. We do this below.
+ if (constructZoomItem) {
+ nsDisplayOwnLayerFlags zoomFlags = flags;
+ if (ignoreViewportScrolling) {
+ zoomFlags |= nsDisplayOwnLayerFlags::GenerateScrollableLayer;
+ }
+ childItems.AppendNewToTop<nsDisplayZoom>(aBuilder, subdocRootFrame, this,
+ &childItems, subdocAPD, parentAPD,
+ zoomFlags);
+
+ needsOwnLayer = false;
+ }
+ // Wrap the zoom item in the resolution item if we have both because we want
+ // the resolution scale applied on top of the app units per dev pixel
+ // conversion.
+ if (ignoreViewportScrolling) {
+ flags |= nsDisplayOwnLayerFlags::GenerateScrollableLayer;
+ }
+
+ // We always want top level content documents to be in their own layer.
+ nsDisplaySubDocument* layerItem = MakeDisplayItem<nsDisplaySubDocument>(
+ aBuilder, subdocRootFrame ? subdocRootFrame : this, this, &childItems,
+ flags);
+ if (layerItem) {
+ childItems.AppendToTop(layerItem);
+ layerItem->SetShouldFlattenAway(!needsOwnLayer);
+ }
+
+ if (aBuilder->IsForFrameVisibility()) {
+ // We don't add the childItems to the return list as we're dealing with them
+ // here.
+ presShell->RebuildApproximateFrameVisibilityDisplayList(childItems);
+ childItems.DeleteAll(aBuilder);
+ } else {
+ aLists.Content()->AppendToTop(&childItems);
+ }
+}
+
+#ifdef DEBUG_FRAME_DUMP
+void nsSubDocumentFrame::List(FILE* out, const char* aPrefix,
+ ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+ fprintf_stderr(out, "%s\n", str.get());
+
+ if (aFlags.contains(ListFlag::TraverseSubdocumentFrames)) {
+ nsSubDocumentFrame* f = const_cast<nsSubDocumentFrame*>(this);
+ nsIFrame* subdocRootFrame = f->GetSubdocumentRootFrame();
+ if (subdocRootFrame) {
+ nsCString pfx(aPrefix);
+ pfx += " ";
+ subdocRootFrame->List(out, pfx.get(), aFlags);
+ }
+ }
+}
+
+nsresult nsSubDocumentFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"FrameOuter"_ns, aResult);
+}
+#endif
+
+/* virtual */
+nscoord nsSubDocumentFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+
+ nsCOMPtr<nsIObjectLoadingContent> iolc = do_QueryInterface(mContent);
+ auto olc = static_cast<nsObjectLoadingContent*>(iolc.get());
+
+ if (olc && olc->GetSubdocumentIntrinsicSize()) {
+ // The subdocument is an SVG document, so technically we should call
+ // SVGOuterSVGFrame::GetMinISize() on its root frame. That method always
+ // returns 0, though, so we can just do that & don't need to bother with
+ // the cross-doc communication.
+ result = 0;
+ } else {
+ result = GetIntrinsicISize();
+ }
+
+ return result;
+}
+
+/* virtual */
+nscoord nsSubDocumentFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+
+ // If the subdocument is an SVG document, then in theory we want to return
+ // the same thing that SVGOuterSVGFrame::GetPrefISize does. That method
+ // has some special handling of percentage values to avoid unhelpful zero
+ // sizing in the presence of orthogonal writing modes. We don't bother
+ // with that for SVG documents in <embed> and <object>, since that special
+ // handling doesn't look up across document boundaries anyway.
+ result = GetIntrinsicISize();
+
+ return result;
+}
+
+/* virtual */
+IntrinsicSize nsSubDocumentFrame::GetIntrinsicSize() {
+ const auto containAxes = GetContainSizeAxes();
+ if (containAxes.IsBoth()) {
+ // Intrinsic size of 'contain:size' replaced elements is determined by
+ // contain-intrinsic-size.
+ return FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0));
+ }
+
+ if (nsCOMPtr<nsIObjectLoadingContent> iolc = do_QueryInterface(mContent)) {
+ const auto* olc = static_cast<nsObjectLoadingContent*>(iolc.get());
+ if (auto size = olc->GetSubdocumentIntrinsicSize()) {
+ // Use the intrinsic size from the child SVG document, if available.
+ return FinishIntrinsicSize(containAxes, *size);
+ }
+ }
+
+ if (!IsInline()) {
+ return {}; // <frame> elements have no useful intrinsic size.
+ }
+
+ if (mContent->IsXULElement()) {
+ return {}; // XUL <iframe> and <browser> have no useful intrinsic size
+ }
+
+ // We must be an HTML <iframe>. Return fallback size.
+ return FinishIntrinsicSize(containAxes,
+ IntrinsicSize(kFallbackIntrinsicSize));
+}
+
+/* virtual */
+AspectRatio nsSubDocumentFrame::GetIntrinsicRatio() const {
+ // FIXME(emilio): This should probably respect contain: size and return no
+ // ratio in the case subDocRoot is non-null. Otherwise we do it by virtue of
+ // using a zero-size below and reusing GetIntrinsicSize().
+ if (nsCOMPtr<nsIObjectLoadingContent> iolc = do_QueryInterface(mContent)) {
+ auto olc = static_cast<nsObjectLoadingContent*>(iolc.get());
+
+ auto ratio = olc->GetSubdocumentIntrinsicRatio();
+ if (ratio && *ratio) {
+ // Use the intrinsic aspect ratio from the child SVG document, if
+ // available.
+ return *ratio;
+ }
+ }
+
+ // NOTE(emilio): Even though we have an intrinsic size, we may not have an
+ // intrinsic ratio. For example `<iframe style="width: 100px">` should not
+ // shrink in the vertical axis to preserve the 300x150 ratio.
+ return nsAtomicContainerFrame::GetIntrinsicRatio();
+}
+
+/* virtual */
+LogicalSize nsSubDocumentFrame::ComputeAutoSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ if (!IsInline()) {
+ return nsIFrame::ComputeAutoSize(aRenderingContext, aWM, aCBSize,
+ aAvailableISize, aMargin, aBorderPadding,
+ aSizeOverrides, aFlags);
+ }
+
+ const WritingMode wm = GetWritingMode();
+ LogicalSize result(wm, GetIntrinsicISize(), GetIntrinsicBSize());
+ return result.ConvertTo(aWM, wm);
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult nsSubDocumentFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ return {ComputeSizeWithIntrinsicDimensions(
+ aRenderingContext, aWM, GetIntrinsicSize(), GetAspectRatio(),
+ aCBSize, aMargin, aBorderPadding, aSizeOverrides, aFlags),
+ AspectRatioUsage::None};
+}
+
+void nsSubDocumentFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsSubDocumentFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE(
+ NS_FRAME_TRACE_CALLS,
+ ("enter nsSubDocumentFrame::Reflow: maxSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ NS_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
+ "Shouldn't have unconstrained inline-size here "
+ "thanks to the rules of reflow");
+ NS_ASSERTION(aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE,
+ "Shouldn't have unconstrained block-size here "
+ "thanks to ComputeAutoSize");
+
+ NS_ASSERTION(mContent->GetPrimaryFrame() == this, "Shouldn't happen");
+
+ // XUL <iframe> or <browser>, or HTML <iframe>, <object> or <embed>
+ const auto wm = aReflowInput.GetWritingMode();
+ aDesiredSize.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm));
+
+ // "offset" is the offset of our content area from our frame's
+ // top-left corner.
+ nsPoint offset = nsPoint(aReflowInput.ComputedPhysicalBorderPadding().left,
+ aReflowInput.ComputedPhysicalBorderPadding().top);
+
+ if (mInnerView) {
+ const nsMargin& bp = aReflowInput.ComputedPhysicalBorderPadding();
+ nsSize innerSize(aDesiredSize.Width() - bp.LeftRight(),
+ aDesiredSize.Height() - bp.TopBottom());
+
+ // Size & position the view according to 'object-fit' & 'object-position'.
+ nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
+ nsRect(offset, innerSize), GetIntrinsicSize(), GetIntrinsicRatio(),
+ StylePosition());
+
+ nsViewManager* vm = mInnerView->GetViewManager();
+ vm->MoveViewTo(mInnerView, destRect.x, destRect.y);
+ vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), destRect.Size()), true);
+ }
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ if (!aPresContext->IsRootPaginatedDocument() && !mPostedReflowCallback) {
+ PresShell()->PostReflowCallback(this);
+ mPostedReflowCallback = true;
+ }
+
+ NS_FRAME_TRACE(
+ NS_FRAME_TRACE_CALLS,
+ ("exit nsSubDocumentFrame::Reflow: size=%d,%d status=%s",
+ aDesiredSize.Width(), aDesiredSize.Height(), ToString(aStatus).c_str()));
+}
+
+bool nsSubDocumentFrame::ReflowFinished() {
+ RefPtr<nsFrameLoader> frameloader = FrameLoader();
+ if (frameloader) {
+ AutoWeakFrame weakFrame(this);
+
+ frameloader->UpdatePositionAndSize(this);
+
+ if (weakFrame.IsAlive()) {
+ // Make sure that we can post a reflow callback in the future.
+ mPostedReflowCallback = false;
+ }
+ } else {
+ mPostedReflowCallback = false;
+ }
+ return false;
+}
+
+void nsSubDocumentFrame::ReflowCallbackCanceled() {
+ mPostedReflowCallback = false;
+}
+
+nsresult nsSubDocumentFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID != kNameSpaceID_None) {
+ return NS_OK;
+ }
+
+ // If the noResize attribute changes, dis/allow frame to be resized
+ if (aAttribute == nsGkAtoms::noresize) {
+ // Note that we're not doing content type checks, but that's ok -- if
+ // they'd fail we will just end up with a null framesetFrame.
+ if (mContent->GetParent()->IsHTMLElement(nsGkAtoms::frameset)) {
+ nsIFrame* parentFrame = GetParent();
+
+ if (parentFrame) {
+ // There is no interface for nsHTMLFramesetFrame so QI'ing to
+ // concrete class, yay!
+ nsHTMLFramesetFrame* framesetFrame = do_QueryFrame(parentFrame);
+ if (framesetFrame) {
+ framesetFrame->RecalculateBorderResize();
+ }
+ }
+ }
+ } else if (aAttribute == nsGkAtoms::marginwidth ||
+ aAttribute == nsGkAtoms::marginheight) {
+ // Notify the frameloader
+ if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) {
+ frameloader->MarginsChanged();
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsSubDocumentFrame::MaybeUpdateEmbedderColorScheme() {
+ nsFrameLoader* fl = mFrameLoader.get();
+ if (!fl) {
+ return;
+ }
+
+ BrowsingContext* bc = fl->GetExtantBrowsingContext();
+ if (!bc) {
+ return;
+ }
+
+ auto ToOverride = [](ColorScheme aScheme) -> PrefersColorSchemeOverride {
+ return aScheme == ColorScheme::Dark ? PrefersColorSchemeOverride::Dark
+ : PrefersColorSchemeOverride::Light;
+ };
+
+ EmbedderColorSchemes schemes{
+ ToOverride(LookAndFeel::ColorSchemeForFrame(this, ColorSchemeMode::Used)),
+ ToOverride(
+ LookAndFeel::ColorSchemeForFrame(this, ColorSchemeMode::Preferred))};
+ if (bc->GetEmbedderColorSchemes() == schemes) {
+ return;
+ }
+
+ Unused << bc->SetEmbedderColorSchemes(schemes);
+}
+
+void nsSubDocumentFrame::MaybeUpdateRemoteStyle(
+ ComputedStyle* aOldComputedStyle) {
+ if (!mIsInObjectOrEmbed) {
+ return;
+ }
+
+ if (aOldComputedStyle &&
+ aOldComputedStyle->StyleVisibility()->mImageRendering ==
+ Style()->StyleVisibility()->mImageRendering) {
+ return;
+ }
+
+ if (!mFrameLoader) {
+ return;
+ }
+
+ if (mFrameLoader->IsRemoteFrame()) {
+ mFrameLoader->UpdateRemoteStyle(
+ Style()->StyleVisibility()->mImageRendering);
+ return;
+ }
+
+ BrowsingContext* context = mFrameLoader->GetExtantBrowsingContext();
+ if (!context) {
+ return;
+ }
+
+ Document* document = context->GetDocument();
+ if (!document) {
+ return;
+ }
+
+ if (document->IsImageDocument()) {
+ document->AsImageDocument()->UpdateRemoteStyle(
+ Style()->StyleVisibility()->mImageRendering);
+ }
+}
+
+void nsSubDocumentFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ nsAtomicContainerFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ MaybeUpdateEmbedderColorScheme();
+
+ MaybeUpdateRemoteStyle(aOldComputedStyle);
+
+ // If this presshell has invisible ancestors, we don't need to propagate the
+ // visibility style change to the subdocument since the subdocument should
+ // have already set the IsUnderHiddenEmbedderElement flag in
+ // nsSubDocumentFrame::Init.
+ if (PresShell()->IsUnderHiddenEmbedderElement()) {
+ return;
+ }
+
+ const bool isVisible = StyleVisibility()->IsVisible();
+ if (!aOldComputedStyle ||
+ isVisible != aOldComputedStyle->StyleVisibility()->IsVisible()) {
+ PropagateIsUnderHiddenEmbedderElement(!isVisible);
+ }
+}
+
+nsIFrame* NS_NewSubDocumentFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsSubDocumentFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSubDocumentFrame)
+
+class nsHideViewer final : public Runnable {
+ public:
+ nsHideViewer(nsIContent* aFrameElement, nsFrameLoader* aFrameLoader,
+ PresShell* aPresShell, bool aHideViewerIfFrameless)
+ : mozilla::Runnable("nsHideViewer"),
+ mFrameElement(aFrameElement),
+ mFrameLoader(aFrameLoader),
+ mPresShell(aPresShell),
+ mHideViewerIfFrameless(aHideViewerIfFrameless) {
+ NS_ASSERTION(mFrameElement, "Must have a frame element");
+ NS_ASSERTION(mFrameLoader, "Must have a frame loader");
+ NS_ASSERTION(mPresShell, "Must have a presshell");
+ }
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ // Flush frames, to ensure any pending display:none changes are made.
+ // Note it can be unsafe to flush if we've destroyed the presentation
+ // for some other reason, like if we're shutting down.
+ //
+ // But avoid the flush if we know for sure we're away, like when we're out
+ // of the document already.
+ //
+ // FIXME(emilio): This could still be a perf footgun when removing lots of
+ // siblings where each of them cause the reframe of an ancestor which happen
+ // to contain a subdocument.
+ //
+ // We should find some way to avoid that!
+ if (!mPresShell->IsDestroying() && mFrameElement->IsInComposedDoc()) {
+ mPresShell->FlushPendingNotifications(FlushType::Frames);
+ }
+
+ // Either the frame has been constructed by now, or it never will be,
+ // either way we want to clear the stashed views.
+ mFrameLoader->SetDetachedSubdocFrame(nullptr);
+
+ nsSubDocumentFrame* frame = do_QueryFrame(mFrameElement->GetPrimaryFrame());
+ if (!frame || frame->FrameLoader() != mFrameLoader) {
+ PropagateIsUnderHiddenEmbedderElement(mFrameLoader, true);
+ if (mHideViewerIfFrameless) {
+ // The frame element has no nsIFrame for the same frame loader.
+ // Hide the nsFrameLoader, which destroys the presentation.
+ mFrameLoader->Hide();
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ const nsCOMPtr<nsIContent> mFrameElement;
+ const RefPtr<nsFrameLoader> mFrameLoader;
+ const RefPtr<PresShell> mPresShell;
+ const bool mHideViewerIfFrameless;
+};
+
+static nsView* BeginSwapDocShellsForViews(nsView* aSibling);
+
+void nsSubDocumentFrame::Destroy(DestroyContext& aContext) {
+ if (mPostedReflowCallback) {
+ PresShell()->CancelReflowCallback(this);
+ mPostedReflowCallback = false;
+ }
+
+ // Detach the subdocument's views and stash them in the frame loader.
+ // We can then reattach them if we're being reframed (for example if
+ // the frame has been made position:fixed).
+ if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) {
+ ClearDisplayItems();
+
+ nsView* detachedViews =
+ ::BeginSwapDocShellsForViews(mInnerView->GetFirstChild());
+
+ frameloader->SetDetachedSubdocFrame(
+ detachedViews ? detachedViews->GetFrame() : nullptr);
+
+ // We call nsFrameLoader::HideViewer() in a script runner so that we can
+ // safely determine whether the frame is being reframed or destroyed.
+ nsContentUtils::AddScriptRunner(new nsHideViewer(
+ mContent, frameloader, PresShell(), (mDidCreateDoc || mCallingShow)));
+ }
+
+ nsAtomicContainerFrame::Destroy(aContext);
+}
+
+nsFrameLoader* nsSubDocumentFrame::FrameLoader() const {
+ if (mFrameLoader) {
+ return mFrameLoader;
+ }
+
+ if (RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(GetContent())) {
+ mFrameLoader = loaderOwner->GetFrameLoader();
+ }
+
+ return mFrameLoader;
+}
+
+auto nsSubDocumentFrame::GetRemotePaintData() const -> RemoteFramePaintData {
+ if (mRetainedRemoteFrame) {
+ return *mRetainedRemoteFrame;
+ }
+
+ RemoteFramePaintData data;
+ nsFrameLoader* fl = FrameLoader();
+ if (!fl) {
+ return data;
+ }
+
+ auto* rb = fl->GetRemoteBrowser();
+ if (!rb) {
+ return data;
+ }
+ data.mLayersId = rb->GetLayersId();
+ data.mTabId = rb->GetTabId();
+ return data;
+}
+
+void nsSubDocumentFrame::ResetFrameLoader(RetainPaintData aRetain) {
+ if (aRetain == RetainPaintData::Yes && mFrameLoader) {
+ mRetainedRemoteFrame = Some(GetRemotePaintData());
+ } else {
+ mRetainedRemoteFrame.reset();
+ }
+ mFrameLoader = nullptr;
+ ClearDisplayItems();
+ nsContentUtils::AddScriptRunner(new AsyncFrameInit(this));
+}
+
+void nsSubDocumentFrame::ClearRetainedPaintData() {
+ mRetainedRemoteFrame.reset();
+ ClearDisplayItems();
+ InvalidateFrameSubtree();
+}
+
+// XXX this should be called ObtainDocShell or something like that,
+// to indicate that it could have side effects
+nsIDocShell* nsSubDocumentFrame::GetDocShell() const {
+ // How can FrameLoader() return null???
+ if (NS_WARN_IF(!FrameLoader())) {
+ return nullptr;
+ }
+ return mFrameLoader->GetDocShell(IgnoreErrors());
+}
+
+static void DestroyDisplayItemDataForFrames(nsIFrame* aFrame) {
+ // Destroying a WebRenderUserDataTable can cause destruction of other objects
+ // which can remove frame properties in their destructor. If we delete a frame
+ // property it runs the destructor of the stored object in the middle of
+ // updating the frame property table, so if the destruction of that object
+ // causes another update to the frame property table it would leave the frame
+ // property table in an inconsistent state. So we remove it from the table and
+ // then destroy it. (bug 1530657)
+ WebRenderUserDataTable* userDataTable =
+ aFrame->TakeProperty(WebRenderUserDataProperty::Key());
+ if (userDataTable) {
+ for (const auto& data : userDataTable->Values()) {
+ data->RemoveFromTable();
+ }
+ delete userDataTable;
+ }
+
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ DestroyDisplayItemDataForFrames(child);
+ }
+ }
+}
+
+static CallState BeginSwapDocShellsForDocument(Document& aDocument) {
+ if (PresShell* presShell = aDocument.GetPresShell()) {
+ // Disable painting while the views are detached, see bug 946929.
+ presShell->SetNeverPainting(true);
+
+ if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
+ ::DestroyDisplayItemDataForFrames(rootFrame);
+ }
+ }
+ aDocument.EnumerateSubDocuments(BeginSwapDocShellsForDocument);
+ return CallState::Continue;
+}
+
+static nsView* BeginSwapDocShellsForViews(nsView* aSibling) {
+ // Collect the removed sibling views in reverse order in 'removedViews'.
+ nsView* removedViews = nullptr;
+ while (aSibling) {
+ if (Document* doc = ::GetDocumentFromView(aSibling)) {
+ ::BeginSwapDocShellsForDocument(*doc);
+ }
+ nsView* next = aSibling->GetNextSibling();
+ aSibling->GetViewManager()->RemoveChild(aSibling);
+ aSibling->SetNextSibling(removedViews);
+ removedViews = aSibling;
+ aSibling = next;
+ }
+ return removedViews;
+}
+
+/* static */
+void nsSubDocumentFrame::InsertViewsInReverseOrder(nsView* aSibling,
+ nsView* aParent) {
+ MOZ_ASSERT(aParent, "null view");
+ MOZ_ASSERT(!aParent->GetFirstChild(), "inserting into non-empty list");
+
+ nsViewManager* vm = aParent->GetViewManager();
+ while (aSibling) {
+ nsView* next = aSibling->GetNextSibling();
+ aSibling->SetNextSibling(nullptr);
+ // true means 'after' in document order which is 'before' in view order,
+ // so this call prepends the child, thus reversing the siblings as we go.
+ vm->InsertChild(aParent, aSibling, nullptr, true);
+ aSibling = next;
+ }
+}
+
+nsresult nsSubDocumentFrame::BeginSwapDocShells(nsIFrame* aOther) {
+ if (!aOther || !aOther->IsSubDocumentFrame()) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther);
+ if (!mFrameLoader || !mDidCreateDoc || mCallingShow || !other->mFrameLoader ||
+ !other->mDidCreateDoc) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ ClearDisplayItems();
+ other->ClearDisplayItems();
+
+ if (mInnerView && other->mInnerView) {
+ nsView* ourSubdocViews = mInnerView->GetFirstChild();
+ nsView* ourRemovedViews = ::BeginSwapDocShellsForViews(ourSubdocViews);
+ nsView* otherSubdocViews = other->mInnerView->GetFirstChild();
+ nsView* otherRemovedViews = ::BeginSwapDocShellsForViews(otherSubdocViews);
+
+ InsertViewsInReverseOrder(ourRemovedViews, other->mInnerView);
+ InsertViewsInReverseOrder(otherRemovedViews, mInnerView);
+ }
+ mFrameLoader.swap(other->mFrameLoader);
+ return NS_OK;
+}
+
+static CallState EndSwapDocShellsForDocument(Document& aDocument) {
+ // Our docshell and view trees have been updated for the new hierarchy.
+ // Now also update all nsDeviceContext::mWidget to that of the
+ // container view in the new hierarchy.
+ if (nsCOMPtr<nsIDocShell> ds = aDocument.GetDocShell()) {
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ ds->GetDocViewer(getter_AddRefs(viewer));
+ while (viewer) {
+ RefPtr<nsPresContext> pc = viewer->GetPresContext();
+ if (pc && pc->GetPresShell()) {
+ pc->GetPresShell()->SetNeverPainting(ds->IsInvisible());
+ }
+ nsDeviceContext* dc = pc ? pc->DeviceContext() : nullptr;
+ if (dc) {
+ nsView* v = viewer->FindContainerView();
+ dc->Init(v ? v->GetNearestWidget(nullptr) : nullptr);
+ }
+ viewer = viewer->GetPreviousViewer();
+ }
+ }
+
+ aDocument.EnumerateSubDocuments(EndSwapDocShellsForDocument);
+ return CallState::Continue;
+}
+
+/* static */
+void nsSubDocumentFrame::EndSwapDocShellsForViews(nsView* aSibling) {
+ for (; aSibling; aSibling = aSibling->GetNextSibling()) {
+ if (Document* doc = ::GetDocumentFromView(aSibling)) {
+ ::EndSwapDocShellsForDocument(*doc);
+ }
+ nsIFrame* frame = aSibling->GetFrame();
+ if (frame) {
+ nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
+ if (parent->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ nsIFrame::AddInPopupStateBitToDescendants(frame);
+ } else {
+ nsIFrame::RemoveInPopupStateBitFromDescendants(frame);
+ }
+ if (frame->HasInvalidFrameInSubtree()) {
+ while (parent &&
+ !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT |
+ NS_FRAME_IS_NONDISPLAY)) {
+ parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
+ parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent);
+ }
+ }
+ }
+ }
+}
+
+void nsSubDocumentFrame::EndSwapDocShells(nsIFrame* aOther) {
+ nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther);
+ AutoWeakFrame weakThis(this);
+ AutoWeakFrame weakOther(aOther);
+
+ if (mInnerView) {
+ EndSwapDocShellsForViews(mInnerView->GetFirstChild());
+ }
+ if (other->mInnerView) {
+ EndSwapDocShellsForViews(other->mInnerView->GetFirstChild());
+ }
+
+ // Now make sure we reflow both frames, in case their contents
+ // determine their size.
+ // And repaint them, for good measure, in case there's nothing
+ // interesting that happens during reflow.
+ if (weakThis.IsAlive()) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_IS_DIRTY);
+ InvalidateFrameSubtree();
+ PropagateIsUnderHiddenEmbedderElement(
+ PresShell()->IsUnderHiddenEmbedderElement() ||
+ !StyleVisibility()->IsVisible());
+ }
+ if (weakOther.IsAlive()) {
+ other->PresShell()->FrameNeedsReflow(
+ other, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY);
+ other->InvalidateFrameSubtree();
+ other->PropagateIsUnderHiddenEmbedderElement(
+ other->PresShell()->IsUnderHiddenEmbedderElement() ||
+ !other->StyleVisibility()->IsVisible());
+ }
+}
+
+void nsSubDocumentFrame::ClearDisplayItems() {
+ if (auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(this)) {
+ DL_LOGD("nsSubDocumentFrame::ClearDisplayItems() %p", this);
+ builder->ClearRetainedData();
+ }
+}
+
+nsView* nsSubDocumentFrame::EnsureInnerView() {
+ if (mInnerView) {
+ return mInnerView;
+ }
+
+ // create, init, set the parent of the view
+ nsView* outerView = GetView();
+ NS_ASSERTION(outerView, "Must have an outer view already");
+ nsRect viewBounds(0, 0, 0, 0); // size will be fixed during reflow
+
+ nsViewManager* viewMan = outerView->GetViewManager();
+ nsView* innerView = viewMan->CreateView(viewBounds, outerView);
+ if (!innerView) {
+ NS_ERROR("Could not create inner view");
+ return nullptr;
+ }
+ mInnerView = innerView;
+ viewMan->InsertChild(outerView, innerView, nullptr, true);
+
+ return mInnerView;
+}
+
+nsPoint nsSubDocumentFrame::GetExtraOffset() const {
+ MOZ_ASSERT(mInnerView);
+ return mInnerView->GetPosition();
+}
+
+void nsSubDocumentFrame::SubdocumentIntrinsicSizeOrRatioChanged() {
+ const nsStylePosition* pos = StylePosition();
+ bool dependsOnIntrinsics =
+ !pos->mWidth.ConvertsToLength() || !pos->mHeight.ConvertsToLength();
+
+ if (dependsOnIntrinsics || pos->mObjectFit != StyleObjectFit::Fill) {
+ auto dirtyHint = dependsOnIntrinsics
+ ? IntrinsicDirty::FrameAncestorsAndDescendants
+ : IntrinsicDirty::None;
+ PresShell()->FrameNeedsReflow(this, dirtyHint, NS_FRAME_IS_DIRTY);
+ InvalidateFrame();
+ }
+}
+
+// Return true iff |aManager| is a "temporary layer manager". They're
+// used for small software rendering tasks, like drawWindow. That's
+// currently implemented by a BasicLayerManager without a backing
+// widget, and hence in non-retained mode.
+nsDisplayRemote::nsDisplayRemote(nsDisplayListBuilder* aBuilder,
+ nsSubDocumentFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame),
+ mEventRegionsOverride(EventRegionsOverride::NoOverride) {
+ const bool frameIsPointerEventsNone =
+ aFrame->Style()->PointerEvents() == StylePointerEvents::None;
+ if (aBuilder->IsInsidePointerEventsNoneDoc() || frameIsPointerEventsNone) {
+ mEventRegionsOverride |= EventRegionsOverride::ForceEmptyHitRegion;
+ }
+ if (nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(
+ aFrame->PresShell())) {
+ mEventRegionsOverride |= EventRegionsOverride::ForceDispatchToContent;
+ }
+
+ mPaintData = aFrame->GetRemotePaintData();
+}
+
+namespace mozilla {
+
+void nsDisplayRemote::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ nsPresContext* pc = mFrame->PresContext();
+ nsFrameLoader* fl = GetFrameLoader();
+ if (pc->GetPrintSettings() && fl->IsRemoteFrame()) {
+ // See the comment below in CreateWebRenderCommands() as for why doing this.
+ fl->UpdatePositionAndSize(static_cast<nsSubDocumentFrame*>(mFrame));
+ }
+
+ DrawTarget* target = aCtx->GetDrawTarget();
+ if (!target->IsRecording() || mPaintData.mTabId == 0) {
+ NS_WARNING("Remote iframe not rendered");
+ return;
+ }
+
+ // Rendering the inner document will apply a scale to account for its app
+ // units per dev pixel ratio. We want to apply the inverse scaling using our
+ // app units per dev pixel ratio, so that no actual scaling will be applied if
+ // they match. For in-process rendering, nsSubDocumentFrame creates an
+ // nsDisplayZoom item if the app units per dev pixel ratio changes.
+ //
+ // Similarly, rendering the inner document will scale up by the cross process
+ // paint scale again, so we also need to account for that.
+ const int32_t appUnitsPerDevPixel = pc->AppUnitsPerDevPixel();
+
+ gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
+ gfxFloat targetAuPerDev =
+ gfxFloat(AppUnitsPerCSSPixel()) / aCtx->GetCrossProcessPaintScale();
+
+ gfxFloat scale = targetAuPerDev / appUnitsPerDevPixel;
+ aCtx->Multiply(gfxMatrix::Scaling(scale, scale));
+
+ Rect destRect =
+ NSRectToSnappedRect(GetContentRect(), targetAuPerDev, *target);
+ target->DrawDependentSurface(mPaintData.mTabId, destRect);
+}
+
+bool nsDisplayRemote::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!mPaintData.mLayersId.IsValid()) {
+ return true;
+ }
+
+ nsPresContext* pc = mFrame->PresContext();
+ nsFrameLoader* fl = GetFrameLoader();
+
+ auto* subDocFrame = static_cast<nsSubDocumentFrame*>(mFrame);
+ nsRect destRect = subDocFrame->GetDestRect();
+ if (RefPtr<RemoteBrowser> remoteBrowser = fl->GetRemoteBrowser()) {
+ if (pc->GetPrintSettings()) {
+ // HACK(emilio): Usually we update sizing/positioning from
+ // ReflowFinished(). Print documents have no incremental reflow at all
+ // though, so we can't rely on it firing after a frame becomes remote.
+ // Thus, if we're painting a remote frame, update its sizing and position
+ // now.
+ //
+ // UpdatePositionAndSize() can cause havoc for non-remote frames but
+ // luckily we don't care about those, so this is fine.
+ fl->UpdatePositionAndSize(subDocFrame);
+ }
+
+ // Adjust mItemVisibleRect, which is relative to the reference frame, to be
+ // relative to this frame.
+ const nsRect buildingRect = GetBuildingRect() - ToReferenceFrame();
+ Maybe<nsRect> visibleRect =
+ buildingRect.EdgeInclusiveIntersection(destRect);
+ if (visibleRect) {
+ *visibleRect -= destRect.TopLeft();
+ }
+
+ // Generate an effects update notifying the browser it is visible
+ MatrixScales scale = aSc.GetInheritedScale();
+
+ ParentLayerToScreenScale2D transformToAncestorScale =
+ ParentLayerToParentLayerScale(
+ pc->GetPresShell() ? pc->GetPresShell()->GetCumulativeResolution()
+ : 1.f) *
+ nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
+ mFrame);
+
+ aDisplayListBuilder->AddEffectUpdate(
+ remoteBrowser, EffectsInfo::VisibleWithinRect(
+ visibleRect, scale, transformToAncestorScale));
+
+ // Create a WebRenderRemoteData to notify the RemoteBrowser when it is no
+ // longer visible
+ RefPtr<WebRenderRemoteData> userData =
+ aManager->CommandBuilder()
+ .CreateOrRecycleWebRenderUserData<WebRenderRemoteData>(this,
+ nullptr);
+ userData->SetRemoteBrowser(remoteBrowser);
+ }
+
+ nscoord auPerDevPixel = pc->AppUnitsPerDevPixel();
+ nsPoint layerOffset =
+ aDisplayListBuilder->ToReferenceFrame(mFrame) + destRect.TopLeft();
+ mOffset = LayoutDevicePoint::FromAppUnits(layerOffset, auPerDevPixel);
+
+ destRect.MoveTo(0, 0);
+ auto rect = LayoutDeviceRect::FromAppUnits(destRect, auPerDevPixel);
+ rect += mOffset;
+
+ aBuilder.PushIFrame(rect, !BackfaceIsHidden(),
+ mozilla::wr::AsPipelineId(mPaintData.mLayersId),
+ /*ignoreMissingPipelines*/ true);
+
+ return true;
+}
+
+bool nsDisplayRemote::UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) {
+ if (!mPaintData.mLayersId.IsValid()) {
+ return true;
+ }
+
+ if (aLayerData) {
+ aLayerData->SetReferentId(mPaintData.mLayersId);
+
+ auto size = static_cast<nsSubDocumentFrame*>(mFrame)->GetSubdocumentSize();
+ Matrix4x4 m = Matrix4x4::Translation(mOffset.x, mOffset.y, 0.0);
+ aLayerData->SetTransform(m);
+ aLayerData->SetEventRegionsOverride(mEventRegionsOverride);
+ aLayerData->SetRemoteDocumentSize(LayerIntSize(size.width, size.height));
+ }
+ return true;
+}
+
+nsFrameLoader* nsDisplayRemote::GetFrameLoader() const {
+ return static_cast<nsSubDocumentFrame*>(mFrame)->FrameLoader();
+}
+
+} // namespace mozilla
diff --git a/layout/generic/nsSubDocumentFrame.h b/layout/generic/nsSubDocumentFrame.h
new file mode 100644
index 0000000000..2fb3d48686
--- /dev/null
+++ b/layout/generic/nsSubDocumentFrame.h
@@ -0,0 +1,241 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSSUBDOCUMENTFRAME_H_
+#define NSSUBDOCUMENTFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "nsDisplayList.h"
+#include "nsAtomicContainerFrame.h"
+#include "nsIReflowCallback.h"
+#include "nsFrameLoader.h"
+#include "Units.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+namespace mozilla::layers {
+class Layer;
+class RenderRootStateManager;
+class WebRenderLayerScrollData;
+class WebRenderScrollData;
+} // namespace mozilla::layers
+
+/******************************************************************************
+ * nsSubDocumentFrame
+ *****************************************************************************/
+class nsSubDocumentFrame final : public nsAtomicContainerFrame,
+ public nsIReflowCallback {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsSubDocumentFrame)
+
+ explicit nsSubDocumentFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext);
+
+#ifdef DEBUG_FRAME_DUMP
+ void List(FILE* out = stderr, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const override;
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ NS_DECL_QUERYFRAME
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ void Destroy(DestroyContext&) override;
+
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ mozilla::IntrinsicSize GetIntrinsicSize() override;
+ mozilla::AspectRatio GetIntrinsicRatio() const override;
+
+ mozilla::LogicalSize ComputeAutoSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) override;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override;
+
+ // if the content is "visibility:hidden", then just hide the view
+ // and all our contents. We don't extend "visibility:hidden" to
+ // the child content ourselves, since it belongs to a different
+ // document and CSS doesn't inherit in there.
+ bool SupportsVisibilityHidden() override { return false; }
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ nsIDocShell* GetDocShell() const;
+ nsresult BeginSwapDocShells(nsIFrame* aOther);
+ void EndSwapDocShells(nsIFrame* aOther);
+
+ static void InsertViewsInReverseOrder(nsView* aSibling, nsView* aParent);
+ static void EndSwapDocShellsForViews(nsView* aView);
+
+ nsView* EnsureInnerView();
+ nsPoint GetExtraOffset() const;
+ nsIFrame* GetSubdocumentRootFrame();
+ enum { IGNORE_PAINT_SUPPRESSION = 0x1 };
+ mozilla::PresShell* GetSubdocumentPresShellForPainting(uint32_t aFlags);
+ nsRect GetDestRect();
+ mozilla::ScreenIntSize GetSubdocumentSize();
+
+ // nsIReflowCallback
+ bool ReflowFinished() override;
+ void ReflowCallbackCanceled() override;
+
+ /**
+ * Return true if pointer event hit-testing should be allowed to target
+ * content in the subdocument.
+ */
+ bool PassPointerEventsToChildren();
+
+ void MaybeShowViewer() {
+ if (!mDidCreateDoc && !mCallingShow) {
+ ShowViewer();
+ }
+ }
+
+ nsFrameLoader* FrameLoader() const;
+
+ enum class RetainPaintData : bool { No, Yes };
+ void ResetFrameLoader(RetainPaintData);
+ void ClearRetainedPaintData();
+
+ void ClearDisplayItems();
+
+ void SubdocumentIntrinsicSizeOrRatioChanged();
+
+ struct RemoteFramePaintData {
+ mozilla::layers::LayersId mLayersId;
+ mozilla::dom::TabId mTabId{0};
+ };
+
+ RemoteFramePaintData GetRemotePaintData() const;
+ bool HasRetainedPaintData() const { return mRetainedRemoteFrame.isSome(); }
+
+ protected:
+ friend class AsyncFrameInit;
+
+ void MaybeUpdateEmbedderColorScheme();
+ void MaybeUpdateRemoteStyle(ComputedStyle* aOldComputedStyle = nullptr);
+ void PropagateIsUnderHiddenEmbedderElement(bool aValue);
+ void UpdateEmbeddedBrowsingContextDependentData();
+
+ bool IsInline() { return mIsInline; }
+
+ nscoord GetIntrinsicBSize() {
+ auto size = GetIntrinsicSize();
+ Maybe<nscoord> bSize =
+ GetWritingMode().IsVertical() ? size.width : size.height;
+ return bSize.valueOr(0);
+ }
+
+ nscoord GetIntrinsicISize() {
+ if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
+ return *containISize;
+ }
+ auto size = GetIntrinsicSize();
+ Maybe<nscoord> iSize =
+ GetWritingMode().IsVertical() ? size.height : size.width;
+ return iSize.valueOr(0);
+ }
+
+ // Show our document viewer. The document viewer is hidden via a script
+ // runner, so that we can save and restore the presentation if we're
+ // being reframed.
+ void ShowViewer();
+
+ nsView* GetViewInternal() const override { return mOuterView; }
+ void SetViewInternal(nsView* aView) override { mOuterView = aView; }
+
+ mutable RefPtr<nsFrameLoader> mFrameLoader;
+
+ nsView* mOuterView;
+ nsView* mInnerView;
+
+ // When process-switching a remote tab, we might temporarily paint the old
+ // one.
+ Maybe<RemoteFramePaintData> mRetainedRemoteFrame;
+
+ bool mIsInline : 1;
+ bool mPostedReflowCallback : 1;
+ bool mDidCreateDoc : 1;
+ bool mCallingShow : 1;
+ bool mIsInObjectOrEmbed : 1;
+};
+
+namespace mozilla {
+
+/**
+ * A nsDisplayRemote will graft a remote frame's shadow layer tree (for a given
+ * nsFrameLoader) into its parent frame's layer tree.
+ */
+class nsDisplayRemote final : public nsPaintedDisplayItem {
+ typedef mozilla::dom::TabId TabId;
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+ typedef mozilla::layers::EventRegionsOverride EventRegionsOverride;
+ typedef mozilla::layers::LayersId LayersId;
+ typedef mozilla::layers::StackingContextHelper StackingContextHelper;
+ typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
+ typedef mozilla::LayoutDevicePoint LayoutDevicePoint;
+
+ public:
+ nsDisplayRemote(nsDisplayListBuilder* aBuilder, nsSubDocumentFrame* aFrame);
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ bool UpdateScrollData(
+ mozilla::layers::WebRenderScrollData* aData,
+ mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
+
+ NS_DISPLAY_DECL_NAME("Remote", TYPE_REMOTE)
+
+ private:
+ friend class nsDisplayItem;
+ using RemoteFramePaintData = nsSubDocumentFrame::RemoteFramePaintData;
+
+ nsFrameLoader* GetFrameLoader() const;
+
+ RemoteFramePaintData mPaintData;
+ LayoutDevicePoint mOffset;
+ EventRegionsOverride mEventRegionsOverride;
+};
+
+} // namespace mozilla
+
+#endif /* NSSUBDOCUMENTFRAME_H_ */
diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp
new file mode 100644
index 0000000000..369722fe8d
--- /dev/null
+++ b/layout/generic/nsTextFrame.cpp
@@ -0,0 +1,10542 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for textual content of elements */
+
+#include "nsTextFrame.h"
+
+#include "gfx2DGlue.h"
+
+#include "gfxUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPresData.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Unused.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/dom/PerformanceMainThread.h"
+
+#include "nsCOMPtr.h"
+#include "nsBlockFrame.h"
+#include "nsFontMetrics.h"
+#include "nsSplittableFrame.h"
+#include "nsLineLayout.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsStyleConsts.h"
+#include "nsStyleStruct.h"
+#include "nsStyleStructInlines.h"
+#include "nsCoord.h"
+#include "gfxContext.h"
+#include "nsTArray.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCompatibility.h"
+#include "nsCSSColorUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsIFrame.h"
+#include "nsIMathMLFrame.h"
+#include "nsFirstLetterFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTextFrameUtils.h"
+#include "nsTextPaintStyle.h"
+#include "nsTextRunTransformations.h"
+#include "MathMLTextRunFactory.h"
+#include "nsUnicodeProperties.h"
+#include "nsStyleUtil.h"
+#include "nsRubyFrame.h"
+#include "TextDrawTarget.h"
+
+#include "nsTextFragment.h"
+#include "nsGkAtoms.h"
+#include "nsFrameSelection.h"
+#include "nsRange.h"
+#include "nsCSSRendering.h"
+#include "nsContentUtils.h"
+#include "nsLineBreaker.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/intl/Bidi.h"
+#include "mozilla/intl/Segmenter.h"
+#include "mozilla/intl/UnicodeProperties.h"
+#include "mozilla/ServoStyleSet.h"
+
+#include <algorithm>
+#include <limits>
+#include <type_traits>
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+#include "nsPrintfCString.h"
+
+#include "mozilla/gfx/DrawTargetRecording.h"
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/ProfilerLabels.h"
+
+#ifdef DEBUG
+# undef NOISY_REFLOW
+# undef NOISY_TRIM
+#else
+# undef NOISY_REFLOW
+# undef NOISY_TRIM
+#endif
+
+#ifdef DrawText
+# undef DrawText
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+typedef mozilla::layout::TextDrawTarget TextDrawTarget;
+
+static bool NeedsToMaskPassword(nsTextFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aFrame->GetContent());
+ if (!aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED)) {
+ return false;
+ }
+ nsIFrame* frame =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::TextInput);
+ MOZ_ASSERT(frame, "How do we have a masked text node without a text input?");
+ return !frame || !frame->GetContent()->AsElement()->State().HasState(
+ ElementState::REVEALED);
+}
+
+struct TabWidth {
+ TabWidth(uint32_t aOffset, uint32_t aWidth)
+ : mOffset(aOffset), mWidth(float(aWidth)) {}
+
+ uint32_t mOffset; // DOM offset relative to the current frame's offset.
+ float mWidth; // extra space to be added at this position (in app units)
+};
+
+struct nsTextFrame::TabWidthStore {
+ explicit TabWidthStore(int32_t aValidForContentOffset)
+ : mLimit(0), mValidForContentOffset(aValidForContentOffset) {}
+
+ // Apply tab widths to the aSpacing array, which corresponds to characters
+ // beginning at aOffset and has length aLength. (Width records outside this
+ // range will be ignored.)
+ void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing,
+ uint32_t aOffset, uint32_t aLength);
+
+ // Offset up to which tabs have been measured; positions beyond this have not
+ // been calculated yet but may be appended if needed later. It's a DOM
+ // offset relative to the current frame's offset.
+ uint32_t mLimit;
+
+ // Need to recalc tab offsets if frame content offset differs from this.
+ int32_t mValidForContentOffset;
+
+ // A TabWidth record for each tab character measured so far.
+ nsTArray<TabWidth> mWidths;
+};
+
+namespace {
+
+struct TabwidthAdaptor {
+ const nsTArray<TabWidth>& mWidths;
+ explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
+ : mWidths(aWidths) {}
+ uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; }
+};
+
+} // namespace
+
+void nsTextFrame::TabWidthStore::ApplySpacing(
+ gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset,
+ uint32_t aLength) {
+ size_t i = 0;
+ const size_t len = mWidths.Length();
+
+ // If aOffset is non-zero, do a binary search to find where to start
+ // processing the tab widths, in case the list is really long. (See bug
+ // 953247.)
+ // We need to start from the first entry where mOffset >= aOffset.
+ if (aOffset > 0) {
+ mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
+ }
+
+ uint32_t limit = aOffset + aLength;
+ while (i < len) {
+ const TabWidth& tw = mWidths[i];
+ if (tw.mOffset >= limit) {
+ break;
+ }
+ aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
+ i++;
+ }
+}
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty,
+ nsTextFrame::TabWidthStore)
+
+NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)
+
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord)
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty,
+ gfxTextRun::TrimmableWS)
+
+struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams {
+ Point textBaselinePt;
+ PropertyProvider* provider = nullptr;
+ Range contentRange;
+ nsTextPaintStyle* textPaintStyle = nullptr;
+ Range glyphRange;
+ explicit PaintTextSelectionParams(const PaintTextParams& aParams)
+ : PaintTextParams(aParams) {}
+};
+
+struct nsTextFrame::DrawTextRunParams {
+ gfxContext* context;
+ mozilla::gfx::PaletteCache& paletteCache;
+ PropertyProvider* provider = nullptr;
+ gfxFloat* advanceWidth = nullptr;
+ mozilla::SVGContextPaint* contextPaint = nullptr;
+ DrawPathCallbacks* callbacks = nullptr;
+ nscolor textColor = NS_RGBA(0, 0, 0, 0);
+ nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0);
+ nsAtom* fontPalette = nullptr;
+ float textStrokeWidth = 0.0f;
+ bool drawSoftHyphen = false;
+ bool hasTextShadow = false;
+ DrawTextRunParams(gfxContext* aContext,
+ mozilla::gfx::PaletteCache& aPaletteCache)
+ : context(aContext), paletteCache(aPaletteCache) {}
+};
+
+struct nsTextFrame::ClipEdges {
+ ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
+ nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
+ nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame;
+ if (aFrame->GetWritingMode().IsVertical()) {
+ mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
+ mVisIEnd = aVisIEndEdge > 0
+ ? std::max(r.YMost() - aVisIEndEdge, mVisIStart)
+ : nscoord_MAX;
+ } else {
+ mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN;
+ mVisIEnd = aVisIEndEdge > 0
+ ? std::max(r.XMost() - aVisIEndEdge, mVisIStart)
+ : nscoord_MAX;
+ }
+ }
+
+ void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const {
+ nscoord end = *aVisIStart + *aVisISize;
+ *aVisIStart = std::max(*aVisIStart, mVisIStart);
+ *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
+ }
+
+ nscoord mVisIStart;
+ nscoord mVisIEnd;
+};
+
+struct nsTextFrame::DrawTextParams : nsTextFrame::DrawTextRunParams {
+ Point framePt;
+ LayoutDeviceRect dirtyRect;
+ const nsTextPaintStyle* textStyle = nullptr;
+ const ClipEdges* clipEdges = nullptr;
+ const nscolor* decorationOverrideColor = nullptr;
+ Range glyphRange;
+ DrawTextParams(gfxContext* aContext,
+ mozilla::gfx::PaletteCache& aPaletteCache)
+ : DrawTextRunParams(aContext, aPaletteCache) {}
+};
+
+struct nsTextFrame::PaintShadowParams {
+ gfxTextRun::Range range;
+ LayoutDeviceRect dirtyRect;
+ Point framePt;
+ Point textBaselinePt;
+ gfxContext* context;
+ nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
+ const ClipEdges* clipEdges = nullptr;
+ PropertyProvider* provider = nullptr;
+ nscoord leftSideOffset = 0;
+ explicit PaintShadowParams(const PaintTextParams& aParams)
+ : dirtyRect(aParams.dirtyRect),
+ framePt(aParams.framePt),
+ context(aParams.context) {}
+};
+
+/**
+ * A glyph observer for the change of a font glyph in a text run.
+ *
+ * This is stored in {Simple, Complex}TextRunUserData.
+ */
+class GlyphObserver final : public gfxFont::GlyphChangeObserver {
+ public:
+ GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
+ : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
+ MOZ_ASSERT(aTextRun->GetUserData());
+ }
+ void NotifyGlyphsChanged() override;
+
+ private:
+ gfxTextRun* mTextRun;
+};
+
+static const nsFrameState TEXT_REFLOW_FLAGS =
+ TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE |
+ TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE |
+ TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS |
+ TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS;
+
+static const nsFrameState TEXT_WHITESPACE_FLAGS =
+ TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE;
+
+/*
+ * Some general notes
+ *
+ * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
+ * transforms text to positioned glyphs. It can report the geometry of the
+ * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
+ * spacing, language, and other information.
+ *
+ * A gfxTextRun can cover more than one DOM text node. This is necessary to
+ * get kerning, ligatures and shaping for text that spans multiple text nodes
+ * but is all the same font.
+ *
+ * The userdata for a gfxTextRun object can be:
+ *
+ * - A nsTextFrame* in the case a text run maps to only one flow. In this
+ * case, the textrun's user data pointer is a pointer to mStartFrame for that
+ * flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
+ * length of the text node.
+ *
+ * - A SimpleTextRunUserData in the case a text run maps to one flow, but we
+ * still have to keep a list of glyph observers.
+ *
+ * - A ComplexTextRunUserData in the case a text run maps to multiple flows,
+ * but we need to keep a list of glyph observers.
+ *
+ * - A TextRunUserData in the case a text run maps multiple flows, but it
+ * doesn't have any glyph observer for changes in SVG fonts.
+ *
+ * You can differentiate between the four different cases with the
+ * IsSimpleFlow and MightHaveGlyphChanges flags.
+ *
+ * We go to considerable effort to make sure things work even if in-flow
+ * siblings have different ComputedStyles (i.e., first-letter and first-line).
+ *
+ * Our convention is that unsigned integer character offsets are offsets into
+ * the transformed string. Signed integer character offsets are offsets into
+ * the DOM string.
+ *
+ * XXX currently we don't handle hyphenated breaks between text frames where the
+ * hyphen occurs at the end of the first text frame, e.g.
+ * <b>Kit&shy;</b>ty
+ */
+
+/**
+ * This is our user data for the textrun, when textRun->GetFlags2() has
+ * IsSimpleFlow set, and also MightHaveGlyphChanges.
+ *
+ * This allows having an array of observers if there are fonts whose glyphs
+ * might change, but also avoid allocation in the simple case that there aren't.
+ */
+struct SimpleTextRunUserData {
+ nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
+ nsTextFrame* mFrame;
+ explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {}
+};
+
+/**
+ * We use an array of these objects to record which text frames
+ * are associated with the textrun. mStartFrame is the start of a list of
+ * text frames. Some sequence of its continuations are covered by the textrun.
+ * A content textnode can have at most one TextRunMappedFlow associated with it
+ * for a given textrun.
+ *
+ * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
+ * obtain the offset into the before-transformation text of the textrun. It can
+ * be positive (when a text node starts in the middle of a text run) or negative
+ * (when a text run starts in the middle of a text node). Of course it can also
+ * be zero.
+ */
+struct TextRunMappedFlow {
+ nsTextFrame* mStartFrame;
+ int32_t mDOMOffsetToBeforeTransformOffset;
+ // The text mapped starts at mStartFrame->GetContentOffset() and is this long
+ uint32_t mContentLength;
+};
+
+/**
+ * This is the type in the gfxTextRun's userdata field in the common case that
+ * the text run maps to multiple flows, but no fonts have been found with
+ * animatable glyphs.
+ *
+ * This way, we avoid allocating and constructing the extra nsTArray.
+ */
+struct TextRunUserData {
+#ifdef DEBUG
+ TextRunMappedFlow* mMappedFlows;
+#endif
+ uint32_t mMappedFlowCount;
+ uint32_t mLastFlowIndex;
+};
+
+/**
+ * This is our user data for the textrun, when textRun->GetFlags2() does not
+ * have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
+ */
+struct ComplexTextRunUserData : public TextRunUserData {
+ nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
+};
+
+static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) {
+ TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc(
+ sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow)));
+#ifdef DEBUG
+ data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
+#endif
+ data->mMappedFlowCount = aMappedFlowCount;
+ data->mLastFlowIndex = 0;
+ return data;
+}
+
+static void DestroyUserData(TextRunUserData* aUserData) {
+ if (aUserData) {
+ free(aUserData);
+ }
+}
+
+static ComplexTextRunUserData* CreateComplexUserData(
+ uint32_t aMappedFlowCount) {
+ ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>(
+ moz_xmalloc(sizeof(ComplexTextRunUserData) +
+ aMappedFlowCount * sizeof(TextRunMappedFlow)));
+ new (data) ComplexTextRunUserData();
+#ifdef DEBUG
+ data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
+#endif
+ data->mMappedFlowCount = aMappedFlowCount;
+ data->mLastFlowIndex = 0;
+ return data;
+}
+
+static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) {
+ if (aUserData) {
+ aUserData->~ComplexTextRunUserData();
+ free(aUserData);
+ }
+}
+
+static void DestroyTextRunUserData(gfxTextRun* aTextRun) {
+ MOZ_ASSERT(aTextRun->GetUserData());
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ if (aTextRun->GetFlags2() &
+ nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
+ delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
+ }
+ } else {
+ if (aTextRun->GetFlags2() &
+ nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
+ DestroyComplexUserData(
+ static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
+ } else {
+ DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData()));
+ }
+ }
+ aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
+ aTextRun->SetUserData(nullptr);
+}
+
+static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) {
+ MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
+ MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),
+ "The method should not be called for simple flows.");
+ TextRunMappedFlow* flows;
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
+ flows = reinterpret_cast<TextRunMappedFlow*>(
+ static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
+ } else {
+ flows = reinterpret_cast<TextRunMappedFlow*>(
+ static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
+ }
+ MOZ_ASSERT(
+ static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==
+ flows,
+ "GetMappedFlows should return the same pointer as mMappedFlows.");
+ return flows;
+}
+
+/**
+ * These are utility functions just for helping with the complexity related with
+ * the text runs user data.
+ */
+static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) {
+ MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,
+ "Not so simple flow?");
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
+ return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
+ }
+
+ return static_cast<nsTextFrame*>(aTextRun->GetUserData());
+}
+
+/**
+ * Remove |aTextRun| from the frame continuation chain starting at
+ * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
+ * Unmark |aFrame| as a text run owner if it's the frame we start at.
+ * Return true if |aStartContinuation| is non-null and was found
+ * in the next-continuation chain of |aFrame|.
+ */
+static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
+ nsTextFrame* aStartContinuation,
+ nsFrameState aWhichTextRunState) {
+ MOZ_ASSERT(aFrame, "null frame");
+ MOZ_ASSERT(!aStartContinuation ||
+ (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
+ aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==
+ aTextRun) ||
+ (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
+ aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==
+ aTextRun),
+ "wrong aStartContinuation for this text run");
+
+ if (!aStartContinuation || aStartContinuation == aFrame) {
+ aFrame->RemoveStateBits(aWhichTextRunState);
+ } else {
+ do {
+ NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
+ aFrame = aFrame->GetNextContinuation();
+ } while (aFrame && aFrame != aStartContinuation);
+ }
+ bool found = aStartContinuation == aFrame;
+ while (aFrame) {
+ NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
+ if (!aFrame->RemoveTextRun(aTextRun)) {
+ break;
+ }
+ aFrame = aFrame->GetNextContinuation();
+ }
+
+ MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
+ return found;
+}
+
+/**
+ * Kill all references to |aTextRun| starting at |aStartContinuation|.
+ * It could be referenced by any of its owners, and all their in-flows.
+ * If |aStartContinuation| is null then process all userdata frames
+ * and their continuations.
+ * @note the caller is expected to take care of possibly destroying the
+ * text run if all userdata frames were reset (userdata is deallocated
+ * by this function though). The caller can detect this has occured by
+ * checking |aTextRun->GetUserData() == nullptr|.
+ */
+static void UnhookTextRunFromFrames(gfxTextRun* aTextRun,
+ nsTextFrame* aStartContinuation) {
+ if (!aTextRun->GetUserData()) {
+ return;
+ }
+
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
+ nsFrameState whichTextRunState =
+ userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
+ ? TEXT_IN_TEXTRUN_USER_DATA
+ : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
+ DebugOnly<bool> found = ClearAllTextRunReferences(
+ userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
+ NS_ASSERTION(!aStartContinuation || found,
+ "aStartContinuation wasn't found in simple flow text run");
+ if (!userDataFrame->HasAnyStateBits(whichTextRunState)) {
+ DestroyTextRunUserData(aTextRun);
+ }
+ } else {
+ auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
+ TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
+ int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
+ for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
+ nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
+ nsFrameState whichTextRunState =
+ userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
+ ? TEXT_IN_TEXTRUN_USER_DATA
+ : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
+ bool found = ClearAllTextRunReferences(
+ userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
+ if (found) {
+ if (userDataFrame->HasAnyStateBits(whichTextRunState)) {
+ destroyFromIndex = i + 1;
+ } else {
+ destroyFromIndex = i;
+ }
+ aStartContinuation = nullptr;
+ }
+ }
+ NS_ASSERTION(destroyFromIndex >= 0,
+ "aStartContinuation wasn't found in multi flow text run");
+ if (destroyFromIndex == 0) {
+ DestroyTextRunUserData(aTextRun);
+ } else {
+ userData->mMappedFlowCount = uint32_t(destroyFromIndex);
+ if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
+ userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
+ }
+ }
+ }
+}
+
+static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+
+ PresShell* presShell = aFrame->PresShell();
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
+ f->InvalidateFrame();
+
+ // If this is a non-display text frame within SVG <text>, we need
+ // to reflow the SVGTextFrame. (This is similar to reflowing the
+ // SVGTextFrame in response to style changes, in
+ // SVGTextFrame::DidSetComputedStyle.)
+ if (f->IsInSVGTextSubtree() && f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ auto* svgTextFrame = static_cast<SVGTextFrame*>(
+ nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
+ svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::None);
+ } else {
+ // Theoretically we could just update overflow areas, perhaps using
+ // OverflowChangedTracker, but that would do a bunch of work eagerly that
+ // we should probably do lazily here since there could be a lot
+ // of text frames affected and we'd like to coalesce the work. So that's
+ // not easy to do well.
+ presShell->FrameNeedsReflow(f, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
+ }
+ }
+}
+
+void GlyphObserver::NotifyGlyphsChanged() {
+ if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
+ return;
+ }
+
+ auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
+ TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
+ for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
+ InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
+ }
+}
+
+int32_t nsTextFrame::GetContentEnd() const {
+ nsTextFrame* next = GetNextContinuation();
+ // In case of allocation failure when setting/modifying the textfragment,
+ // it's possible our text might be missing. So we check the fragment length,
+ // in addition to the offset of the next continuation (if any).
+ int32_t fragLen = TextFragment()->GetLength();
+ return next ? std::min(fragLen, next->GetContentOffset()) : fragLen;
+}
+
+struct FlowLengthProperty {
+ int32_t mStartOffset;
+ // The offset of the next fixed continuation after mStartOffset, or
+ // of the end of the text if there is none
+ int32_t mEndFlowOffset;
+};
+
+int32_t nsTextFrame::GetInFlowContentLength() {
+ if (!HasAnyStateBits(NS_FRAME_IS_BIDI)) {
+ return mContent->TextLength() - mContentOffset;
+ }
+
+ FlowLengthProperty* flowLength =
+ mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
+ ? static_cast<FlowLengthProperty*>(
+ mContent->GetProperty(nsGkAtoms::flowlength))
+ : nullptr;
+
+ /**
+ * This frame must start inside the cached flow. If the flow starts at
+ * mContentOffset but this frame is empty, logically it might be before the
+ * start of the cached flow.
+ */
+ if (flowLength &&
+ (flowLength->mStartOffset < mContentOffset ||
+ (flowLength->mStartOffset == mContentOffset &&
+ GetContentEnd() > mContentOffset)) &&
+ flowLength->mEndFlowOffset > mContentOffset) {
+#ifdef DEBUG
+ NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
+ "frame crosses fixed continuation boundary");
+#endif
+ return flowLength->mEndFlowOffset - mContentOffset;
+ }
+
+ nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
+ int32_t endFlow =
+ nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();
+
+ if (!flowLength) {
+ flowLength = new FlowLengthProperty;
+ if (NS_FAILED(mContent->SetProperty(
+ nsGkAtoms::flowlength, flowLength,
+ nsINode::DeleteProperty<FlowLengthProperty>))) {
+ delete flowLength;
+ flowLength = nullptr;
+ }
+ mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+ }
+ if (flowLength) {
+ flowLength->mStartOffset = mContentOffset;
+ flowLength->mEndFlowOffset = endFlow;
+ }
+
+ return endFlow - mContentOffset;
+}
+
+// Smarter versions of dom::IsSpaceCharacter.
+// Unicode is really annoying; sometimes a space character isn't whitespace ---
+// when it combines with another character
+// So we have several versions of IsSpace for use in different contexts.
+
+static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag,
+ uint32_t aPos) {
+ NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
+ if (!aFrag->Is2b()) {
+ return false;
+ }
+ return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
+ aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
+}
+
+// Check whether aPos is a space for CSS 'word-spacing' purposes
+static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
+ const nsTextFrame* aFrame,
+ const nsStyleText* aStyleText) {
+ NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
+
+ char16_t ch = aFrag->CharAt(aPos);
+ switch (ch) {
+ case ' ':
+ case CH_NBSP:
+ return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
+ case '\r':
+ case '\t':
+ return !aStyleText->WhiteSpaceIsSignificant();
+ case '\n':
+ return !aStyleText->NewlineIsSignificant(aFrame);
+ default:
+ return false;
+ }
+}
+
+constexpr char16_t kOghamSpaceMark = 0x1680;
+
+// Check whether the string aChars/aLength starts with space that's
+// trimmable according to CSS 'white-space:normal/nowrap'.
+static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) {
+ NS_ASSERTION(aLength > 0, "No text for IsSpace!");
+
+ char16_t ch = *aChars;
+ if (ch == ' ' || ch == kOghamSpaceMark) {
+ return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1,
+ aLength - 1);
+ }
+ return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
+}
+
+// Check whether the character aCh is trimmable according to CSS
+// 'white-space:normal/nowrap'
+static bool IsTrimmableSpace(char aCh) {
+ return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
+}
+
+static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
+ const nsStyleText* aStyleText,
+ bool aAllowHangingWS = false) {
+ NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
+
+ switch (aFrag->CharAt(aPos)) {
+ case ' ':
+ case kOghamSpaceMark:
+ return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) &&
+ !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
+ case '\n':
+ return !aStyleText->NewlineIsSignificantStyle() &&
+ aStyleText->mWhiteSpaceCollapse !=
+ StyleWhiteSpaceCollapse::PreserveSpaces;
+ case '\t':
+ case '\r':
+ case '\f':
+ return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS;
+ default:
+ return false;
+ }
+}
+
+static bool IsSelectionInlineWhitespace(const nsTextFragment* aFrag,
+ uint32_t aPos) {
+ NS_ASSERTION(aPos < aFrag->GetLength(),
+ "No text for IsSelectionInlineWhitespace!");
+ char16_t ch = aFrag->CharAt(aPos);
+ if (ch == ' ' || ch == CH_NBSP)
+ return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
+ return ch == '\t' || ch == '\f';
+}
+
+static bool IsSelectionNewline(const nsTextFragment* aFrag, uint32_t aPos) {
+ NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSelectionNewline!");
+ char16_t ch = aFrag->CharAt(aPos);
+ return ch == '\n' || ch == '\r';
+}
+
+// Count the amount of trimmable whitespace (as per CSS
+// 'white-space:normal/nowrap') in a text fragment. The first
+// character is at offset aStartOffset; the maximum number of characters
+// to check is aLength. aDirection is -1 or 1 depending on whether we should
+// progress backwards or forwards.
+static uint32_t GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
+ int32_t aStartOffset,
+ int32_t aLength,
+ int32_t aDirection) {
+ if (!aLength) {
+ return 0;
+ }
+
+ int32_t count = 0;
+ if (aFrag->Is2b()) {
+ const char16_t* str = aFrag->Get2b() + aStartOffset;
+ int32_t fragLen = aFrag->GetLength() - aStartOffset;
+ for (; count < aLength; ++count) {
+ if (!IsTrimmableSpace(str, fragLen)) {
+ break;
+ }
+ str += aDirection;
+ fragLen -= aDirection;
+ }
+ } else {
+ const char* str = aFrag->Get1b() + aStartOffset;
+ for (; count < aLength; ++count) {
+ if (!IsTrimmableSpace(*str)) {
+ break;
+ }
+ str += aDirection;
+ }
+ }
+ return count;
+}
+
+static bool IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline) {
+ if (aFrag->Is2b()) {
+ return false;
+ }
+ int32_t len = aFrag->GetLength();
+ const char* str = aFrag->Get1b();
+ for (int32_t i = 0; i < len; ++i) {
+ char ch = str[i];
+ if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
+ continue;
+ return false;
+ }
+ return true;
+}
+
+static void ClearObserversFromTextRun(gfxTextRun* aTextRun) {
+ if (!(aTextRun->GetFlags2() &
+ nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
+ return;
+ }
+
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
+ ->mGlyphObservers.Clear();
+ } else {
+ static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
+ ->mGlyphObservers.Clear();
+ }
+}
+
+static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) {
+ if (!aTextRun->GetUserData()) {
+ return;
+ }
+
+ ClearObserversFromTextRun(aTextRun);
+
+ nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
+ uint32_t numGlyphRuns;
+ const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numGlyphRuns);
+ for (uint32_t i = 0; i < numGlyphRuns; ++i) {
+ gfxFont* font = glyphRuns[i].mFont;
+ if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
+ fontsWithAnimatedGlyphs.AppendElement(font);
+ }
+ }
+ if (fontsWithAnimatedGlyphs.IsEmpty()) {
+ // NB: Theoretically, we should clear the MightHaveGlyphChanges
+ // here. That would involve de-allocating the simple user data struct if
+ // present too, and resetting the pointer to the frame. In practice, I
+ // don't think worth doing that work here, given the flag's only purpose is
+ // to distinguish what kind of user data is there.
+ return;
+ }
+
+ nsTArray<UniquePtr<GlyphObserver>>* observers;
+
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
+ // appropriate.
+ if (!(aTextRun->GetFlags2() &
+ nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
+ auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
+ aTextRun->SetUserData(new SimpleTextRunUserData(frame));
+ }
+
+ auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
+ observers = &data->mGlyphObservers;
+ } else {
+ if (!(aTextRun->GetFlags2() &
+ nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
+ auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
+ TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
+ ComplexTextRunUserData* data =
+ CreateComplexUserData(oldData->mMappedFlowCount);
+ TextRunMappedFlow* dataMappedFlows =
+ reinterpret_cast<TextRunMappedFlow*>(data + 1);
+ data->mLastFlowIndex = oldData->mLastFlowIndex;
+ for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
+ dataMappedFlows[i] = oldMappedFlows[i];
+ }
+ DestroyUserData(oldData);
+ aTextRun->SetUserData(data);
+ }
+ auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
+ observers = &data->mGlyphObservers;
+ }
+
+ aTextRun->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
+
+ for (auto font : fontsWithAnimatedGlyphs) {
+ observers->AppendElement(MakeUnique<GlyphObserver>(font, aTextRun));
+ }
+}
+
+/**
+ * This class accumulates state as we scan a paragraph of text. It detects
+ * textrun boundaries (changes from text to non-text, hard
+ * line breaks, and font changes) and builds a gfxTextRun at each boundary.
+ * It also detects linebreaker run boundaries (changes from text to non-text,
+ * and hard line breaks) and at each boundary runs the linebreaker to compute
+ * potential line breaks. It also records actual line breaks to store them in
+ * the textruns.
+ */
+class BuildTextRunsScanner {
+ public:
+ BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
+ nsIFrame* aLineContainer,
+ nsTextFrame::TextRunType aWhichTextRun,
+ bool aDoLineBreaking)
+ : mDrawTarget(aDrawTarget),
+ mLineContainer(aLineContainer),
+ mCommonAncestorWithLastFrame(nullptr),
+ mMissingFonts(aPresContext->MissingFontRecorder()),
+ mBidiEnabled(aPresContext->BidiEnabled()),
+ mStartOfLine(true),
+ mSkipIncompleteTextRuns(false),
+ mCanStopOnThisLine(false),
+ mDoLineBreaking(aDoLineBreaking),
+ mWhichTextRun(aWhichTextRun),
+ mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
+ mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
+ ResetRunInfo();
+ }
+ ~BuildTextRunsScanner() {
+ NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
+ NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
+ NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
+ }
+
+ void SetAtStartOfLine() {
+ mStartOfLine = true;
+ mCanStopOnThisLine = false;
+ }
+ void SetSkipIncompleteTextRuns(bool aSkip) {
+ mSkipIncompleteTextRuns = aSkip;
+ }
+ void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
+ mCommonAncestorWithLastFrame = aFrame;
+ }
+ bool CanStopOnThisLine() { return mCanStopOnThisLine; }
+ nsIFrame* GetCommonAncestorWithLastFrame() {
+ return mCommonAncestorWithLastFrame;
+ }
+ void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
+ if (mCommonAncestorWithLastFrame &&
+ mCommonAncestorWithLastFrame->GetParent() == aFrame) {
+ mCommonAncestorWithLastFrame = aFrame;
+ }
+ }
+ void ScanFrame(nsIFrame* aFrame);
+ bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
+ void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
+ void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
+ void ResetRunInfo() {
+ mLastFrame = nullptr;
+ mMappedFlows.Clear();
+ mLineBreakBeforeFrames.Clear();
+ mMaxTextLength = 0;
+ mDoubleByteText = false;
+ }
+ void AccumulateRunInfo(nsTextFrame* aFrame);
+ /**
+ * @return null to indicate either textrun construction failed or
+ * we constructed just a partial textrun to set up linebreaker and other
+ * state for following textruns.
+ */
+ already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
+ bool SetupLineBreakerContext(gfxTextRun* aTextRun);
+ void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
+ nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
+ void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
+ void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
+ struct FindBoundaryState {
+ nsIFrame* mStopAtFrame;
+ nsTextFrame* mFirstTextFrame;
+ nsTextFrame* mLastTextFrame;
+ bool mSeenTextRunBoundaryOnLaterLine;
+ bool mSeenTextRunBoundaryOnThisLine;
+ bool mSeenSpaceForLineBreakingOnThisLine;
+ nsTArray<char16_t>& mBuffer;
+ };
+ enum FindBoundaryResult {
+ FB_CONTINUE,
+ FB_STOPPED_AT_STOP_FRAME,
+ FB_FOUND_VALID_TEXTRUN_BOUNDARY
+ };
+ FindBoundaryResult FindBoundaries(nsIFrame* aFrame,
+ FindBoundaryState* aState);
+
+ bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
+
+ // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
+ // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
+ // continuations starting from mStartFrame are a sequence of in-flow frames).
+ struct MappedFlow {
+ nsTextFrame* mStartFrame;
+ nsTextFrame* mEndFrame;
+ // When we consider breaking between elements, the nearest common
+ // ancestor of the elements containing the characters is the one whose
+ // CSS 'white-space' property governs. So this records the nearest common
+ // ancestor of mStartFrame and the previous text frame, or null if there
+ // was no previous text frame on this line.
+ nsIFrame* mAncestorControllingInitialBreak;
+
+ int32_t GetContentEnd() const {
+ int32_t fragLen = mStartFrame->TextFragment()->GetLength();
+ return mEndFrame ? std::min(fragLen, mEndFrame->GetContentOffset())
+ : fragLen;
+ }
+ };
+
+ class BreakSink final : public nsILineBreakSink {
+ public:
+ BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
+ uint32_t aOffsetIntoTextRun)
+ : mTextRun(aTextRun),
+ mDrawTarget(aDrawTarget),
+ mOffsetIntoTextRun(aOffsetIntoTextRun) {}
+
+ void SetBreaks(uint32_t aOffset, uint32_t aLength,
+ uint8_t* aBreakBefore) final {
+ gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
+ aOffset + mOffsetIntoTextRun + aLength);
+ if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
+ // Be conservative and assume that some breaks have been set
+ mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks);
+ }
+ }
+
+ void SetCapitalization(uint32_t aOffset, uint32_t aLength,
+ bool* aCapitalize) final {
+ MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,
+ "Text run should be transformed!");
+ if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
+ nsTransformedTextRun* transformedTextRun =
+ static_cast<nsTransformedTextRun*>(mTextRun.get());
+ transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun,
+ aLength, aCapitalize);
+ }
+ }
+
+ void Finish(gfxMissingFontRecorder* aMFR) {
+ if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
+ nsTransformedTextRun* transformedTextRun =
+ static_cast<nsTransformedTextRun*>(mTextRun.get());
+ transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
+ }
+ // The way nsTransformedTextRun is implemented, its glyph runs aren't
+ // available until after nsTransformedTextRun::FinishSettingProperties()
+ // is called. So that's why we defer checking for animated glyphs to here.
+ CreateObserversForAnimatedGlyphs(mTextRun);
+ }
+
+ RefPtr<gfxTextRun> mTextRun;
+ DrawTarget* mDrawTarget;
+ uint32_t mOffsetIntoTextRun;
+ };
+
+ private:
+ AutoTArray<MappedFlow, 10> mMappedFlows;
+ AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames;
+ AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks;
+ nsLineBreaker mLineBreaker;
+ RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
+ DrawTarget* mDrawTarget;
+ nsIFrame* mLineContainer;
+ nsTextFrame* mLastFrame;
+ // The common ancestor of the current frame and the previous leaf frame
+ // on the line, or null if there was no previous leaf frame.
+ nsIFrame* mCommonAncestorWithLastFrame;
+ gfxMissingFontRecorder* mMissingFonts;
+ // mMaxTextLength is an upper bound on the size of the text in all mapped
+ // frames The value UINT32_MAX represents overflow; text will be discarded
+ uint32_t mMaxTextLength;
+ bool mDoubleByteText;
+ bool mBidiEnabled;
+ bool mStartOfLine;
+ bool mSkipIncompleteTextRuns;
+ bool mCanStopOnThisLine;
+ bool mDoLineBreaking;
+ nsTextFrame::TextRunType mWhichTextRun;
+ uint8_t mNextRunContextInfo;
+ uint8_t mCurrentRunContextInfo;
+};
+
+static nsIFrame* FindLineContainer(nsIFrame* aFrame) {
+ while (aFrame &&
+ (aFrame->IsLineParticipant() || aFrame->CanContinueTextRun())) {
+ aFrame = aFrame->GetParent();
+ }
+ return aFrame;
+}
+
+static bool IsLineBreakingWhiteSpace(char16_t aChar) {
+ // 0x0A (\n) is not handled as white-space by the line breaker, since
+ // we break before it, if it isn't transformed to a normal space.
+ // (If we treat it as normal white-space then we'd only break after it.)
+ // However, it does induce a line break or is converted to a regular
+ // space, and either way it can be used to bound the region of text
+ // that needs to be analyzed for line breaking.
+ return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
+}
+
+static bool TextContainsLineBreakerWhiteSpace(const void* aText,
+ uint32_t aLength,
+ bool aIsDoubleByte) {
+ if (aIsDoubleByte) {
+ const char16_t* chars = static_cast<const char16_t*>(aText);
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (IsLineBreakingWhiteSpace(chars[i])) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ const uint8_t* chars = static_cast<const uint8_t*>(aText);
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (IsLineBreakingWhiteSpace(chars[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode(
+ nsTextFrame* aFrame, const nsStyleText* aStyleText) {
+ switch (aStyleText->mWhiteSpaceCollapse) {
+ case StyleWhiteSpaceCollapse::Collapse:
+ return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
+ case StyleWhiteSpaceCollapse::PreserveBreaks:
+ return nsTextFrameUtils::COMPRESS_WHITESPACE;
+ case StyleWhiteSpaceCollapse::Preserve:
+ case StyleWhiteSpaceCollapse::PreserveSpaces:
+ case StyleWhiteSpaceCollapse::BreakSpaces:
+ if (!aStyleText->NewlineIsSignificant(aFrame)) {
+ // If newline is set to be preserved, but then suppressed,
+ // transform newline to space.
+ return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
+ }
+ return nsTextFrameUtils::COMPRESS_NONE;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown white-space-collapse value");
+ return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
+}
+
+struct FrameTextTraversal {
+ FrameTextTraversal()
+ : mFrameToScan(nullptr),
+ mOverflowFrameToScan(nullptr),
+ mScanSiblings(false),
+ mLineBreakerCanCrossFrameBoundary(false),
+ mTextRunCanCrossFrameBoundary(false) {}
+
+ // These fields identify which frames should be recursively scanned
+ // The first normal frame to scan (or null, if no such frame should be
+ // scanned)
+ nsIFrame* mFrameToScan;
+ // The first overflow frame to scan (or null, if no such frame should be
+ // scanned)
+ nsIFrame* mOverflowFrameToScan;
+ // Whether to scan the siblings of
+ // mFrameToDescendInto/mOverflowFrameToDescendInto
+ bool mScanSiblings;
+
+ // These identify the boundaries of the context required for
+ // line breaking or textrun construction
+ bool mLineBreakerCanCrossFrameBoundary;
+ bool mTextRunCanCrossFrameBoundary;
+
+ nsIFrame* NextFrameToScan() {
+ nsIFrame* f;
+ if (mFrameToScan) {
+ f = mFrameToScan;
+ mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
+ } else if (mOverflowFrameToScan) {
+ f = mOverflowFrameToScan;
+ mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
+ } else {
+ f = nullptr;
+ }
+ return f;
+ }
+};
+
+static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) {
+ FrameTextTraversal result;
+
+ bool continuesTextRun = aFrame->CanContinueTextRun();
+ if (aFrame->IsPlaceholderFrame()) {
+ // placeholders are "invisible", so a text run should be able to span
+ // across one. But don't descend into the out-of-flow.
+ result.mLineBreakerCanCrossFrameBoundary = true;
+ if (continuesTextRun) {
+ // ... Except for first-letter floats, which are really in-flow
+ // from the point of view of capitalization etc, so we'd better
+ // descend into them. But we actually need to break the textrun for
+ // first-letter floats since things look bad if, say, we try to make a
+ // ligature across the float boundary.
+ result.mFrameToScan =
+ (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
+ } else {
+ result.mTextRunCanCrossFrameBoundary = true;
+ }
+ } else {
+ if (continuesTextRun) {
+ result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
+ result.mOverflowFrameToScan =
+ aFrame->GetChildList(FrameChildListID::Overflow).FirstChild();
+ NS_WARNING_ASSERTION(
+ !result.mOverflowFrameToScan,
+ "Scanning overflow inline frames is something we should avoid");
+ result.mScanSiblings = true;
+ result.mTextRunCanCrossFrameBoundary = true;
+ result.mLineBreakerCanCrossFrameBoundary = true;
+ } else {
+ MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),
+ "Shouldn't call this method for ruby text container");
+ }
+ }
+ return result;
+}
+
+BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries(
+ nsIFrame* aFrame, FindBoundaryState* aState) {
+ LayoutFrameType frameType = aFrame->Type();
+ if (frameType == LayoutFrameType::RubyTextContainer) {
+ // Don't stop a text run for ruby text container. We want ruby text
+ // containers to be skipped, but continue the text run across them.
+ return FB_CONTINUE;
+ }
+
+ nsTextFrame* textFrame = frameType == LayoutFrameType::Text
+ ? static_cast<nsTextFrame*>(aFrame)
+ : nullptr;
+ if (textFrame) {
+ if (aState->mLastTextFrame &&
+ textFrame != aState->mLastTextFrame->GetNextInFlow() &&
+ !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
+ aState->mSeenTextRunBoundaryOnThisLine = true;
+ if (aState->mSeenSpaceForLineBreakingOnThisLine)
+ return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
+ }
+ if (!aState->mFirstTextFrame) {
+ aState->mFirstTextFrame = textFrame;
+ }
+ aState->mLastTextFrame = textFrame;
+ }
+
+ if (aFrame == aState->mStopAtFrame) {
+ return FB_STOPPED_AT_STOP_FRAME;
+ }
+
+ if (textFrame) {
+ if (aState->mSeenSpaceForLineBreakingOnThisLine) {
+ return FB_CONTINUE;
+ }
+ const nsTextFragment* frag = textFrame->TextFragment();
+ uint32_t start = textFrame->GetContentOffset();
+ uint32_t length = textFrame->GetContentLength();
+ const void* text;
+ if (frag->Is2b()) {
+ // It is possible that we may end up removing all whitespace in
+ // a piece of text because of The White Space Processing Rules,
+ // so we need to transform it before we can check existence of
+ // such whitespaces.
+ aState->mBuffer.EnsureLengthAtLeast(length);
+ nsTextFrameUtils::CompressionMode compression =
+ GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
+ uint8_t incomingFlags = 0;
+ gfxSkipChars skipChars;
+ nsTextFrameUtils::Flags analysisFlags;
+ char16_t* bufStart = aState->mBuffer.Elements();
+ char16_t* bufEnd = nsTextFrameUtils::TransformText(
+ frag->Get2b() + start, length, bufStart, compression, &incomingFlags,
+ &skipChars, &analysisFlags);
+ text = bufStart;
+ length = bufEnd - bufStart;
+ } else {
+ // If the text only contains ASCII characters, it is currently
+ // impossible that TransformText would remove all whitespaces,
+ // and thus the check below should return the same result for
+ // transformed text and original text. So we don't need to try
+ // transforming it here.
+ text = static_cast<const void*>(frag->Get1b() + start);
+ }
+ if (TextContainsLineBreakerWhiteSpace(text, length, frag->Is2b())) {
+ aState->mSeenSpaceForLineBreakingOnThisLine = true;
+ if (aState->mSeenTextRunBoundaryOnLaterLine) {
+ return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
+ }
+ }
+ return FB_CONTINUE;
+ }
+
+ FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
+ if (!traversal.mTextRunCanCrossFrameBoundary) {
+ aState->mSeenTextRunBoundaryOnThisLine = true;
+ if (aState->mSeenSpaceForLineBreakingOnThisLine)
+ return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
+ }
+
+ for (nsIFrame* f = traversal.NextFrameToScan(); f;
+ f = traversal.NextFrameToScan()) {
+ FindBoundaryResult result = FindBoundaries(f, aState);
+ if (result != FB_CONTINUE) {
+ return result;
+ }
+ }
+
+ if (!traversal.mTextRunCanCrossFrameBoundary) {
+ aState->mSeenTextRunBoundaryOnThisLine = true;
+ if (aState->mSeenSpaceForLineBreakingOnThisLine)
+ return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
+ }
+
+ return FB_CONTINUE;
+}
+
+// build text runs for the 200 lines following aForFrame, and stop after that
+// when we get a chance.
+#define NUM_LINES_TO_BUILD_TEXT_RUNS 200
+
+/**
+ * General routine for building text runs. This is hairy because of the need
+ * to build text runs that span content nodes.
+ *
+ * @param aContext The gfxContext we're using to construct this text run.
+ * @param aForFrame The nsTextFrame for which we're building this text run.
+ * @param aLineContainer the line container containing aForFrame; if null,
+ * we'll walk the ancestors to find it. It's required to be non-null
+ * when aForFrameLine is non-null.
+ * @param aForFrameLine the line containing aForFrame; if null, we'll figure
+ * out the line (slowly)
+ * @param aWhichTextRun The type of text run we want to build. If font inflation
+ * is enabled, this will be eInflated, otherwise it's eNotInflated.
+ */
+static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
+ nsIFrame* aLineContainer,
+ const nsLineList::iterator* aForFrameLine,
+ nsTextFrame::TextRunType aWhichTextRun) {
+ MOZ_ASSERT(aForFrame, "for no frame?");
+ NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container");
+
+ nsIFrame* lineContainerChild = aForFrame;
+ if (!aLineContainer) {
+ if (aForFrame->IsFloatingFirstLetterChild()) {
+ lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
+ }
+ aLineContainer = FindLineContainer(lineContainerChild);
+ } else {
+ NS_ASSERTION(
+ (aLineContainer == FindLineContainer(aForFrame) ||
+ (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),
+ "Wrong line container hint");
+ }
+
+ if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
+ aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
+ if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
+ aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
+ }
+ }
+ if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
+ aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+
+ nsPresContext* presContext = aLineContainer->PresContext();
+ bool doLineBreaking = !aForFrame->IsInSVGTextSubtree();
+ BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer,
+ aWhichTextRun, doLineBreaking);
+
+ nsBlockFrame* block = do_QueryFrame(aLineContainer);
+
+ if (!block) {
+ nsIFrame* textRunContainer = aLineContainer;
+ if (aLineContainer->IsRubyTextContainerFrame()) {
+ textRunContainer = aForFrame;
+ while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
+ textRunContainer = textRunContainer->GetParent();
+ }
+ MOZ_ASSERT(textRunContainer &&
+ textRunContainer->GetParent() == aLineContainer);
+ } else {
+ NS_ASSERTION(
+ !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
+ "Breakable non-block line containers other than "
+ "ruby text container is not supported");
+ }
+ // Just loop through all the children of the linecontainer ... it's really
+ // just one line
+ scanner.SetAtStartOfLine();
+ scanner.SetCommonAncestorWithLastFrame(nullptr);
+ for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
+ scanner.ScanFrame(child);
+ }
+ // Set mStartOfLine so FlushFrames knows its textrun ends a line
+ scanner.SetAtStartOfLine();
+ scanner.FlushFrames(true, false);
+ return;
+ }
+
+ // Find the line containing 'lineContainerChild'.
+
+ bool isValid = true;
+ nsBlockInFlowLineIterator backIterator(block, &isValid);
+ if (aForFrameLine) {
+ backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
+ } else {
+ backIterator =
+ nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
+ NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
+ NS_ASSERTION(backIterator.GetContainer() == block,
+ "Someone lied to us about the block");
+ }
+ nsBlockFrame::LineIterator startLine = backIterator.GetLine();
+
+ // Find a line where we can start building text runs. We choose the last line
+ // where:
+ // -- there is a textrun boundary between the start of the line and the
+ // start of aForFrame
+ // -- there is a space between the start of the line and the textrun boundary
+ // (this is so we can be sure the line breaks will be set properly
+ // on the textruns we construct).
+ // The possibly-partial text runs up to and including the first space
+ // are not reconstructed. We construct partial text runs for that text ---
+ // for the sake of simplifying the code and feeding the linebreaker ---
+ // but we discard them instead of assigning them to frames.
+ // This is a little awkward because we traverse lines in the reverse direction
+ // but we traverse the frames in each line in the forward direction.
+ nsBlockInFlowLineIterator forwardIterator = backIterator;
+ nsIFrame* stopAtFrame = lineContainerChild;
+ nsTextFrame* nextLineFirstTextFrame = nullptr;
+ AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
+ bool seenTextRunBoundaryOnLaterLine = false;
+ bool mayBeginInTextRun = true;
+ while (true) {
+ forwardIterator = backIterator;
+ nsBlockFrame::LineIterator line = backIterator.GetLine();
+ if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
+ mayBeginInTextRun = false;
+ break;
+ }
+
+ BuildTextRunsScanner::FindBoundaryState state = {
+ stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine),
+ false, false, buffer};
+ nsIFrame* child = line->mFirstChild;
+ bool foundBoundary = false;
+ for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
+ BuildTextRunsScanner::FindBoundaryResult result =
+ scanner.FindBoundaries(child, &state);
+ if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
+ foundBoundary = true;
+ break;
+ } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
+ break;
+ }
+ child = child->GetNextSibling();
+ }
+ if (foundBoundary) {
+ break;
+ }
+ if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
+ !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame,
+ nextLineFirstTextFrame)) {
+ // Found a usable textrun boundary at the end of the line
+ if (state.mSeenSpaceForLineBreakingOnThisLine) {
+ break;
+ }
+ seenTextRunBoundaryOnLaterLine = true;
+ } else if (state.mSeenTextRunBoundaryOnThisLine) {
+ seenTextRunBoundaryOnLaterLine = true;
+ }
+ stopAtFrame = nullptr;
+ if (state.mFirstTextFrame) {
+ nextLineFirstTextFrame = state.mFirstTextFrame;
+ }
+ }
+ scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
+
+ // Now iterate over all text frames starting from the current line.
+ // First-in-flow text frames will be accumulated into textRunFrames as we go.
+ // When a text run boundary is required we flush textRunFrames ((re)building
+ // their gfxTextRuns as necessary).
+ bool seenStartLine = false;
+ uint32_t linesAfterStartLine = 0;
+ do {
+ nsBlockFrame::LineIterator line = forwardIterator.GetLine();
+ if (line->IsBlock()) {
+ break;
+ }
+ line->SetInvalidateTextRuns(false);
+ scanner.SetAtStartOfLine();
+ scanner.SetCommonAncestorWithLastFrame(nullptr);
+ nsIFrame* child = line->mFirstChild;
+ for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
+ scanner.ScanFrame(child);
+ child = child->GetNextSibling();
+ }
+ if (line.get() == startLine.get()) {
+ seenStartLine = true;
+ }
+ if (seenStartLine) {
+ ++linesAfterStartLine;
+ if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS &&
+ scanner.CanStopOnThisLine()) {
+ // Don't flush frames; we may be in the middle of a textrun
+ // that we can't end here. That's OK, we just won't build it.
+ // Note that we must already have finished the textrun for aForFrame,
+ // because we've seen the end of a textrun in a line after the line
+ // containing aForFrame.
+ scanner.FlushLineBreaks(nullptr);
+ // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
+ // silences assertions in the scanner destructor.
+ scanner.ResetRunInfo();
+ return;
+ }
+ }
+ } while (forwardIterator.Next());
+
+ // Set mStartOfLine so FlushFrames knows its textrun ends a line
+ scanner.SetAtStartOfLine();
+ scanner.FlushFrames(true, false);
+}
+
+static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) {
+ while (aCount) {
+ *aDest = *aSrc;
+ ++aDest;
+ ++aSrc;
+ --aCount;
+ }
+ return aDest;
+}
+
+bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(
+ const gfxTextRun* aTextRun) {
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ return mMappedFlows.Length() == 1 &&
+ mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
+ mMappedFlows[0].mEndFrame == nullptr;
+ }
+
+ auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
+ TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
+ if (userData->mMappedFlowCount != mMappedFlows.Length()) {
+ return false;
+ }
+ for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
+ if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
+ int32_t(userMappedFlows[i].mContentLength) !=
+ mMappedFlows[i].GetContentEnd() -
+ mMappedFlows[i].mStartFrame->GetContentOffset()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * This gets called when we need to make a text run for the current list of
+ * frames.
+ */
+void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks,
+ bool aSuppressTrailingBreak) {
+ RefPtr<gfxTextRun> textRun;
+ if (!mMappedFlows.IsEmpty()) {
+ if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
+ !!(mCurrentFramesAllSameTextRun->GetFlags2() &
+ nsTextFrameUtils::Flags::IncomingWhitespace) ==
+ !!(mCurrentRunContextInfo &
+ nsTextFrameUtils::INCOMING_WHITESPACE) &&
+ !!(mCurrentFramesAllSameTextRun->GetFlags() &
+ gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
+ !!(mCurrentRunContextInfo &
+ nsTextFrameUtils::INCOMING_ARABICCHAR) &&
+ IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
+ // Optimization: We do not need to (re)build the textrun.
+ textRun = mCurrentFramesAllSameTextRun;
+
+ if (mDoLineBreaking) {
+ // Feed this run's text into the linebreaker to provide context.
+ if (!SetupLineBreakerContext(textRun)) {
+ return;
+ }
+ }
+
+ // Update mNextRunContextInfo appropriately
+ mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
+ if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) {
+ mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
+ }
+ if (textRun->GetFlags() &
+ gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
+ mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
+ }
+ } else {
+ AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
+ uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
+ if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
+ !buffer.AppendElements(bufferSize, fallible)) {
+ return;
+ }
+ textRun = BuildTextRunForFrames(buffer.Elements());
+ }
+ }
+
+ if (aFlushLineBreaks) {
+ FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
+ if (!mDoLineBreaking && textRun) {
+ CreateObserversForAnimatedGlyphs(textRun.get());
+ }
+ }
+
+ mCanStopOnThisLine = true;
+ ResetRunInfo();
+}
+
+void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) {
+ // If the line-breaker is buffering a potentially-unfinished word,
+ // preserve the state of being in-word so that we don't spuriously
+ // capitalize the next letter.
+ bool inWord = mLineBreaker.InWord();
+ bool trailingLineBreak;
+ nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
+ mLineBreaker.SetWordContinuation(inWord);
+ // textRun may be null for various reasons, including because we constructed
+ // a partial textrun just to get the linebreaker and other state set up
+ // to build the next textrun.
+ if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
+ aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak);
+ }
+
+ for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
+ // TODO cause frames associated with the textrun to be reflowed, if they
+ // aren't being reflowed already!
+ mBreakSinks[i]->Finish(mMissingFonts);
+ }
+ mBreakSinks.Clear();
+}
+
+void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) {
+ if (mMaxTextLength != UINT32_MAX) {
+ NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),
+ "integer overflow");
+ if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
+ mMaxTextLength = UINT32_MAX;
+ } else {
+ mMaxTextLength += aFrame->GetContentLength();
+ }
+ }
+ mDoubleByteText |= aFrame->TextFragment()->Is2b();
+ mLastFrame = aFrame;
+ mCommonAncestorWithLastFrame = aFrame->GetParent();
+
+ MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
+ NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
+ mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
+ "Overlapping or discontiguous frames => BAD");
+ mappedFlow->mEndFrame = aFrame->GetNextContinuation();
+ if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
+ mCurrentFramesAllSameTextRun = nullptr;
+ }
+
+ if (mStartOfLine) {
+ mLineBreakBeforeFrames.AppendElement(aFrame);
+ mStartOfLine = false;
+ }
+}
+
+static bool HasTerminalNewline(const nsTextFrame* aFrame) {
+ if (aFrame->GetContentLength() == 0) {
+ return false;
+ }
+ const nsTextFragment* frag = aFrame->TextFragment();
+ return frag->CharAt(AssertedCast<uint32_t>(aFrame->GetContentEnd()) - 1) ==
+ '\n';
+}
+
+static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup,
+ bool aVerticalMetrics) {
+ if (!aFontGroup) {
+ return gfxFont::Metrics();
+ }
+ RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont();
+ return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal);
+}
+
+static nscoord GetSpaceWidthAppUnits(const gfxTextRun* aTextRun) {
+ // Round the space width when converting to appunits the same way textruns
+ // do.
+ gfxFloat spaceWidthAppUnits =
+ NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
+ aTextRun->UseCenterBaseline())
+ .spaceWidth *
+ aTextRun->GetAppUnitsPerDevUnit());
+
+ return spaceWidthAppUnits;
+}
+
+static gfxFloat GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun) {
+ gfxFloat chWidthAppUnits = NS_round(
+ GetFirstFontMetrics(aTextRun->GetFontGroup(), aTextRun->IsVertical())
+ .ZeroOrAveCharWidth() *
+ aTextRun->GetAppUnitsPerDevUnit());
+ return 0.5 * chWidthAppUnits;
+}
+
+static float GetSVGFontSizeScaleFactor(nsIFrame* aFrame) {
+ if (!aFrame->IsInSVGTextSubtree()) {
+ return 1.0f;
+ }
+ auto* container =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
+ MOZ_ASSERT(container);
+ return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
+}
+
+static nscoord LetterSpacing(nsIFrame* aFrame, const nsStyleText& aStyleText) {
+ if (aFrame->IsInSVGTextSubtree()) {
+ // SVG text can have a scaling factor applied so that very small or very
+ // large font-sizes don't suffer from poor glyph placement due to app unit
+ // rounding. The used letter-spacing value must be scaled by the same
+ // factor.
+ Length spacing = aStyleText.mLetterSpacing;
+ spacing.ScaleBy(GetSVGFontSizeScaleFactor(aFrame));
+ return spacing.ToAppUnits();
+ }
+
+ return aStyleText.mLetterSpacing.ToAppUnits();
+}
+
+// This function converts non-coord values (e.g. percentages) to nscoord.
+static nscoord WordSpacing(nsIFrame* aFrame, const gfxTextRun* aTextRun,
+ const nsStyleText& aStyleText) {
+ if (aFrame->IsInSVGTextSubtree()) {
+ // SVG text can have a scaling factor applied so that very small or very
+ // large font-sizes don't suffer from poor glyph placement due to app unit
+ // rounding. The used word-spacing value must be scaled by the same
+ // factor, although any percentage basis has already effectively been
+ // scaled, since it's the space glyph width, which is based on the already-
+ // scaled font-size.
+ auto spacing = aStyleText.mWordSpacing;
+ spacing.ScaleLengthsBy(GetSVGFontSizeScaleFactor(aFrame));
+ return spacing.Resolve([&] { return GetSpaceWidthAppUnits(aTextRun); });
+ }
+
+ return aStyleText.mWordSpacing.Resolve(
+ [&] { return GetSpaceWidthAppUnits(aTextRun); });
+}
+
+// Returns gfxTextRunFactory::TEXT_ENABLE_SPACING if non-standard
+// letter-spacing or word-spacing is present.
+static gfx::ShapedTextFlags GetSpacingFlags(
+ nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr) {
+ const nsStyleText* styleText = aFrame->StyleText();
+ const auto& ls = styleText->mLetterSpacing;
+ const auto& ws = styleText->mWordSpacing;
+
+ // It's possible to have a calc() value that computes to zero but for which
+ // IsDefinitelyZero() is false, in which case we'll return
+ // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
+ // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
+ bool nonStandardSpacing = !ls.IsZero() || !ws.IsDefinitelyZero();
+ return nonStandardSpacing ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
+ : gfx::ShapedTextFlags();
+}
+
+bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1,
+ nsTextFrame* aFrame2) {
+ // We don't need to check font size inflation, since
+ // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
+ // ensures that text runs never cross block boundaries. This means
+ // that the font size inflation on all text frames in the text run is
+ // already guaranteed to be the same as each other (and for the line
+ // container).
+ if (mBidiEnabled) {
+ FrameBidiData data1 = aFrame1->GetBidiData();
+ FrameBidiData data2 = aFrame2->GetBidiData();
+ if (data1.embeddingLevel != data2.embeddingLevel ||
+ data2.precedingControl != kBidiLevelNone) {
+ return false;
+ }
+ }
+
+ ComputedStyle* sc1 = aFrame1->Style();
+ ComputedStyle* sc2 = aFrame2->Style();
+
+ // Any difference in writing-mode/directionality inhibits shaping across
+ // the boundary.
+ WritingMode wm(sc1);
+ if (wm != WritingMode(sc2)) {
+ return false;
+ }
+
+ const nsStyleText* textStyle1 = sc1->StyleText();
+ // If the first frame ends in a preformatted newline, then we end the textrun
+ // here. This avoids creating giant textruns for an entire plain text file.
+ // Note that we create a single text frame for a preformatted text node,
+ // even if it has newlines in it, so typically we won't see trailing newlines
+ // until after reflow has broken up the frame into one (or more) frames per
+ // line. That's OK though.
+ if (textStyle1->NewlineIsSignificant(aFrame1) &&
+ HasTerminalNewline(aFrame1)) {
+ return false;
+ }
+
+ if (aFrame1->GetParent()->GetContent() !=
+ aFrame2->GetParent()->GetContent()) {
+ // Does aFrame, or any ancestor between it and aAncestor, have a property
+ // that should inhibit cross-element-boundary shaping on aSide?
+ auto PreventCrossBoundaryShaping = [](const nsIFrame* aFrame,
+ const nsIFrame* aAncestor,
+ Side aSide) {
+ while (aFrame != aAncestor) {
+ ComputedStyle* ctx = aFrame->Style();
+ // According to https://drafts.csswg.org/css-text/#boundary-shaping:
+ //
+ // Text shaping must be broken at inline box boundaries when any of
+ // the following are true for any box whose boundary separates the
+ // two typographic character units:
+ //
+ // 1. Any of margin/border/padding separating the two typographic
+ // character units in the inline axis is non-zero.
+ const auto& margin = ctx->StyleMargin()->mMargin.Get(aSide);
+ if (!margin.ConvertsToLength() ||
+ margin.AsLengthPercentage().ToLength() != 0) {
+ return true;
+ }
+ const auto& padding = ctx->StylePadding()->mPadding.Get(aSide);
+ if (!padding.ConvertsToLength() || padding.ToLength() != 0) {
+ return true;
+ }
+ if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) {
+ return true;
+ }
+
+ // 2. vertical-align is not baseline.
+ //
+ // FIXME: Should this use VerticalAlignEnum()?
+ const auto& verticalAlign = ctx->StyleDisplay()->mVerticalAlign;
+ if (!verticalAlign.IsKeyword() ||
+ verticalAlign.AsKeyword() != StyleVerticalAlignKeyword::Baseline) {
+ return true;
+ }
+
+ // 3. The boundary is a bidi isolation boundary.
+ const auto unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi;
+ if (unicodeBidi == StyleUnicodeBidi::Isolate ||
+ unicodeBidi == StyleUnicodeBidi::IsolateOverride) {
+ return true;
+ }
+
+ aFrame = aFrame->GetParent();
+ }
+ return false;
+ };
+
+ const nsIFrame* ancestor =
+ nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1,
+ aFrame2);
+
+ if (!ancestor) {
+ // The two frames are within different blocks, e.g. due to block
+ // fragmentation. In theory we shouldn't prevent cross-frame shaping
+ // here, but it's an edge case where we should rarely decide to allow
+ // cross-frame shaping, so we don't try harder here.
+ return false;
+ }
+
+ // We inhibit cross-element-boundary shaping if we're in SVG content,
+ // as there are too many things SVG might be doing (like applying per-
+ // element positioning) that wouldn't make sense with shaping across
+ // the boundary.
+ if (ancestor->IsInSVGTextSubtree()) {
+ return false;
+ }
+
+ // Map inline-end and inline-start to physical sides for checking presence
+ // of non-zero margin/border/padding.
+ Side side1 = wm.PhysicalSide(eLogicalSideIEnd);
+ Side side2 = wm.PhysicalSide(eLogicalSideIStart);
+ // If the frames have an embedding level that is opposite to the writing
+ // mode, we need to swap which sides we're checking.
+ if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) {
+ std::swap(side1, side2);
+ }
+
+ if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) ||
+ PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) {
+ return false;
+ }
+ }
+
+ if (aFrame1->GetContent() == aFrame2->GetContent() &&
+ aFrame1->GetNextInFlow() != aFrame2) {
+ // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
+ // sometimes when the unicode-bidi property is used; the bidi resolver
+ // breaks text into different frames even though the text has the same
+ // direction. We can't allow these two frames to share the same textrun
+ // because that would violate our invariant that two flows in the same
+ // textrun have different content elements.
+ return false;
+ }
+
+ if (sc1 == sc2) {
+ return true;
+ }
+
+ const nsStyleText* textStyle2 = sc2->StyleText();
+ if (textStyle1->mTextTransform != textStyle2->mTextTransform ||
+ textStyle1->EffectiveWordBreak() != textStyle2->EffectiveWordBreak() ||
+ textStyle1->mLineBreak != textStyle2->mLineBreak) {
+ return false;
+ }
+
+ nsPresContext* pc = aFrame1->PresContext();
+ MOZ_ASSERT(pc == aFrame2->PresContext());
+
+ const nsStyleFont* fontStyle1 = sc1->StyleFont();
+ const nsStyleFont* fontStyle2 = sc2->StyleFont();
+ nscoord letterSpacing1 = LetterSpacing(aFrame1, *textStyle1);
+ nscoord letterSpacing2 = LetterSpacing(aFrame2, *textStyle2);
+ return fontStyle1->mFont == fontStyle2->mFont &&
+ fontStyle1->mLanguage == fontStyle2->mLanguage &&
+ nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1,
+ letterSpacing1) ==
+ nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2,
+ textStyle2, letterSpacing2);
+}
+
+void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) {
+ LayoutFrameType frameType = aFrame->Type();
+ if (frameType == LayoutFrameType::RubyTextContainer) {
+ // Don't include any ruby text container into the text run.
+ return;
+ }
+
+ // First check if we can extend the current mapped frame block. This is
+ // common.
+ if (mMappedFlows.Length() > 0) {
+ MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
+ if (mappedFlow->mEndFrame == aFrame &&
+ aFrame->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) {
+ NS_ASSERTION(frameType == LayoutFrameType::Text,
+ "Flow-sibling of a text frame is not a text frame?");
+
+ // Don't do this optimization if mLastFrame has a terminal newline...
+ // it's quite likely preformatted and we might want to end the textrun
+ // here. This is almost always true:
+ if (mLastFrame->Style() == aFrame->Style() &&
+ !HasTerminalNewline(mLastFrame)) {
+ AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
+ return;
+ }
+ }
+ }
+
+ // Now see if we can add a new set of frames to the current textrun
+ if (frameType == LayoutFrameType::Text) {
+ nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
+
+ if (mLastFrame) {
+ if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
+ FlushFrames(false, false);
+ } else {
+ if (mLastFrame->GetContent() == frame->GetContent()) {
+ AccumulateRunInfo(frame);
+ return;
+ }
+ }
+ }
+
+ MappedFlow* mappedFlow = mMappedFlows.AppendElement();
+ mappedFlow->mStartFrame = frame;
+ mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
+
+ AccumulateRunInfo(frame);
+ if (mMappedFlows.Length() == 1) {
+ mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
+ mCurrentRunContextInfo = mNextRunContextInfo;
+ }
+ return;
+ }
+
+ if (frameType == LayoutFrameType::Placeholder &&
+ aFrame->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS |
+ PLACEHOLDER_FOR_FIXEDPOS)) {
+ // Somewhat hacky fix for bug 1418472:
+ // If this is a placeholder for an absolute-positioned frame, we need to
+ // flush the line-breaker to prevent the placeholder becoming separated
+ // from the immediately-following content.
+ // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos
+ // element occurs within a word where shaping should be in effect, but
+ // that's an edge case, unlikely to occur in real content. A more precise
+ // fix might require better separation of line-breaking from textrun setup,
+ // but that's a big invasive change (and potentially expensive for perf, as
+ // it might introduce an additional pass over all the frames).
+ FlushFrames(true, false);
+ }
+
+ FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
+ bool isBR = frameType == LayoutFrameType::Br;
+ if (!traversal.mLineBreakerCanCrossFrameBoundary) {
+ // BR frames are special. We do not need or want to record a break
+ // opportunity before a BR frame.
+ FlushFrames(true, isBR);
+ mCommonAncestorWithLastFrame = aFrame;
+ mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
+ mStartOfLine = false;
+ } else if (!traversal.mTextRunCanCrossFrameBoundary) {
+ FlushFrames(false, false);
+ }
+
+ for (nsIFrame* f = traversal.NextFrameToScan(); f;
+ f = traversal.NextFrameToScan()) {
+ ScanFrame(f);
+ }
+
+ if (!traversal.mLineBreakerCanCrossFrameBoundary) {
+ // Really if we're a BR frame this is unnecessary since descendInto will be
+ // false. In fact this whole "if" statement should move into the
+ // descendInto.
+ FlushFrames(true, isBR);
+ mCommonAncestorWithLastFrame = aFrame;
+ mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
+ } else if (!traversal.mTextRunCanCrossFrameBoundary) {
+ FlushFrames(false, false);
+ }
+
+ LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
+}
+
+nsTextFrame* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) {
+ uint32_t index = *aIndex;
+ if (index >= mLineBreakBeforeFrames.Length()) {
+ return nullptr;
+ }
+ *aIndex = index + 1;
+ return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
+}
+
+static gfxFontGroup* GetFontGroupForFrame(
+ const nsIFrame* aFrame, float aFontSizeInflation,
+ nsFontMetrics** aOutFontMetrics = nullptr) {
+ RefPtr<nsFontMetrics> metrics =
+ nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
+ gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
+
+ // Populate outparam before we return:
+ if (aOutFontMetrics) {
+ metrics.forget(aOutFontMetrics);
+ }
+ // XXX this is a bit bogus, we're releasing 'metrics' so the
+ // returned font-group might actually be torn down, although because
+ // of the way the device context caches font metrics, this seems to
+ // not actually happen. But we should fix this.
+ return fontGroup;
+}
+
+nsFontMetrics* nsTextFrame::InflatedFontMetrics() const {
+ if (!mFontMetrics) {
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
+ }
+ return mFontMetrics;
+}
+
+static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) {
+ gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
+ if (textRun) {
+ return textRun->GetFontGroup();
+ }
+ return aFrame->InflatedFontMetrics()->GetThebesFontGroup();
+}
+
+static already_AddRefed<DrawTarget> CreateReferenceDrawTarget(
+ const nsTextFrame* aTextFrame) {
+ UniquePtr<gfxContext> ctx =
+ aTextFrame->PresShell()->CreateReferenceRenderingContext();
+ RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
+ return dt.forget();
+}
+
+static already_AddRefed<gfxTextRun> GetHyphenTextRun(nsTextFrame* aTextFrame,
+ DrawTarget* aDrawTarget) {
+ RefPtr<DrawTarget> dt = aDrawTarget;
+ if (!dt) {
+ dt = CreateReferenceDrawTarget(aTextFrame);
+ if (!dt) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aTextFrame);
+ auto* fontGroup = fm->GetThebesFontGroup();
+ auto appPerDev = aTextFrame->PresContext()->AppUnitsPerDevPixel();
+ const auto& hyphenateChar = aTextFrame->StyleText()->mHyphenateCharacter;
+ gfx::ShapedTextFlags flags =
+ nsLayoutUtils::GetTextRunOrientFlagsForStyle(aTextFrame->Style());
+ // Make the directionality of the hyphen run (in case it is multi-char) match
+ // the text frame.
+ if (aTextFrame->GetWritingMode().IsBidiRTL()) {
+ flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
+ }
+ if (hyphenateChar.IsAuto()) {
+ return fontGroup->MakeHyphenTextRun(dt, flags, appPerDev);
+ }
+ auto* missingFonts = aTextFrame->PresContext()->MissingFontRecorder();
+ const NS_ConvertUTF8toUTF16 hyphenStr(hyphenateChar.AsString().AsString());
+ return fontGroup->MakeTextRun(hyphenStr.BeginReading(), hyphenStr.Length(),
+ dt, appPerDev, flags, nsTextFrameUtils::Flags(),
+ missingFonts);
+}
+
+already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames(
+ void* aTextBuffer) {
+ gfxSkipChars skipChars;
+
+ const void* textPtr = aTextBuffer;
+ bool anyTextTransformStyle = false;
+ bool anyMathMLStyling = false;
+ bool anyTextEmphasis = false;
+ uint8_t sstyScriptLevel = 0;
+ uint32_t mathFlags = 0;
+ gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
+ nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::NoBreaks;
+
+ if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
+ flags2 |= nsTextFrameUtils::Flags::IncomingWhitespace;
+ }
+ if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
+ flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR;
+ }
+
+ AutoTArray<int32_t, 50> textBreakPoints;
+ TextRunUserData dummyData;
+ TextRunMappedFlow dummyMappedFlow;
+ TextRunMappedFlow* userMappedFlows;
+ TextRunUserData* userData;
+ TextRunUserData* userDataToDestroy;
+ // If the situation is particularly simple (and common) we don't need to
+ // allocate userData.
+ if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
+ mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
+ userData = &dummyData;
+ userMappedFlows = &dummyMappedFlow;
+ userDataToDestroy = nullptr;
+ dummyData.mMappedFlowCount = mMappedFlows.Length();
+ dummyData.mLastFlowIndex = 0;
+ } else {
+ userData = CreateUserData(mMappedFlows.Length());
+ userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
+ userDataToDestroy = userData;
+ }
+
+ uint32_t currentTransformedTextOffset = 0;
+
+ uint32_t nextBreakIndex = 0;
+ nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
+ bool isSVG = mLineContainer->IsInSVGTextSubtree();
+ bool enabledJustification =
+ (mLineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
+ mLineContainer->StyleText()->mTextAlignLast ==
+ StyleTextAlignLast::Justify);
+
+ const nsStyleText* textStyle = nullptr;
+ const nsStyleFont* fontStyle = nullptr;
+ ComputedStyle* lastComputedStyle = nullptr;
+ for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
+ MappedFlow* mappedFlow = &mMappedFlows[i];
+ nsTextFrame* f = mappedFlow->mStartFrame;
+
+ lastComputedStyle = f->Style();
+ // Detect use of text-transform or font-variant anywhere in the run
+ textStyle = f->StyleText();
+ if (!textStyle->mTextTransform.IsNone() ||
+ textStyle->mWebkitTextSecurity != StyleTextSecurity::None ||
+ // text-combine-upright requires converting from full-width
+ // characters to non-full-width correspendent in some cases.
+ lastComputedStyle->IsTextCombined()) {
+ anyTextTransformStyle = true;
+ }
+ if (textStyle->HasEffectiveTextEmphasis()) {
+ anyTextEmphasis = true;
+ }
+ flags |= GetSpacingFlags(f);
+ nsTextFrameUtils::CompressionMode compression =
+ GetCSSWhitespaceToCompressionMode(f, textStyle);
+ if ((enabledJustification || f->ShouldSuppressLineBreak()) && !isSVG) {
+ flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
+ }
+ fontStyle = f->StyleFont();
+ nsIFrame* parent = mLineContainer->GetParent();
+ if (StyleMathVariant::None != fontStyle->mMathVariant) {
+ if (StyleMathVariant::Normal != fontStyle->mMathVariant) {
+ anyMathMLStyling = true;
+ }
+ } else if (mLineContainer->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
+ flags2 |= nsTextFrameUtils::Flags::IsSingleCharMi;
+ anyMathMLStyling = true;
+ }
+ if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
+ // All MathML tokens except <mtext> use 'math' script.
+ if (!(parent && parent->GetContent() &&
+ parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext_))) {
+ flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT;
+ }
+ nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
+ if (mathFrame) {
+ nsPresentationData presData;
+ mathFrame->GetPresentationData(presData);
+ if (NS_MATHML_IS_DTLS_SET(presData.flags)) {
+ mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
+ anyMathMLStyling = true;
+ }
+ }
+ }
+ nsIFrame* child = mLineContainer;
+ uint8_t oldScriptLevel = 0;
+ while (parent &&
+ child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
+ // Reconstruct the script level ignoring any user overrides. It is
+ // calculated this way instead of using scriptlevel to ensure the
+ // correct ssty font feature setting is used even if the user sets a
+ // different (especially negative) scriptlevel.
+ nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
+ if (mathFrame) {
+ sstyScriptLevel += mathFrame->ScriptIncrement(child);
+ }
+ if (sstyScriptLevel < oldScriptLevel) {
+ // overflow
+ sstyScriptLevel = UINT8_MAX;
+ break;
+ }
+ child = parent;
+ parent = parent->GetParent();
+ oldScriptLevel = sstyScriptLevel;
+ }
+ if (sstyScriptLevel) {
+ anyMathMLStyling = true;
+ }
+
+ // Figure out what content is included in this flow.
+ nsIContent* content = f->GetContent();
+ const nsTextFragment* frag = f->TextFragment();
+ int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
+ int32_t contentEnd = mappedFlow->GetContentEnd();
+ int32_t contentLength = contentEnd - contentStart;
+
+ TextRunMappedFlow* newFlow = &userMappedFlows[i];
+ newFlow->mStartFrame = mappedFlow->mStartFrame;
+ newFlow->mDOMOffsetToBeforeTransformOffset =
+ skipChars.GetOriginalCharCount() -
+ mappedFlow->mStartFrame->GetContentOffset();
+ newFlow->mContentLength = contentLength;
+
+ while (nextBreakBeforeFrame &&
+ nextBreakBeforeFrame->GetContent() == content) {
+ textBreakPoints.AppendElement(nextBreakBeforeFrame->GetContentOffset() +
+ newFlow->mDOMOffsetToBeforeTransformOffset);
+ nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
+ }
+
+ nsTextFrameUtils::Flags analysisFlags;
+ if (frag->Is2b()) {
+ NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
+ char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
+ char16_t* bufEnd = nsTextFrameUtils::TransformText(
+ frag->Get2b() + contentStart, contentLength, bufStart, compression,
+ &mNextRunContextInfo, &skipChars, &analysisFlags);
+ aTextBuffer = bufEnd;
+ currentTransformedTextOffset =
+ bufEnd - static_cast<const char16_t*>(textPtr);
+ } else {
+ if (mDoubleByteText) {
+ // Need to expand the text. First transform it into a temporary buffer,
+ // then expand.
+ AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
+ uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
+ if (!bufStart) {
+ DestroyUserData(userDataToDestroy);
+ return nullptr;
+ }
+ uint8_t* end = nsTextFrameUtils::TransformText(
+ reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
+ contentLength, bufStart, compression, &mNextRunContextInfo,
+ &skipChars, &analysisFlags);
+ aTextBuffer =
+ ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
+ tempBuf.Elements(), end - tempBuf.Elements());
+ currentTransformedTextOffset = static_cast<char16_t*>(aTextBuffer) -
+ static_cast<const char16_t*>(textPtr);
+ } else {
+ uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
+ uint8_t* end = nsTextFrameUtils::TransformText(
+ reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
+ contentLength, bufStart, compression, &mNextRunContextInfo,
+ &skipChars, &analysisFlags);
+ aTextBuffer = end;
+ currentTransformedTextOffset =
+ end - static_cast<const uint8_t*>(textPtr);
+ }
+ }
+ flags2 |= analysisFlags;
+ }
+
+ void* finalUserData;
+ if (userData == &dummyData) {
+ flags2 |= nsTextFrameUtils::Flags::IsSimpleFlow;
+ userData = nullptr;
+ finalUserData = mMappedFlows[0].mStartFrame;
+ } else {
+ finalUserData = userData;
+ }
+
+ uint32_t transformedLength = currentTransformedTextOffset;
+
+ // Now build the textrun
+ nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
+ float fontInflation;
+ gfxFontGroup* fontGroup;
+ if (mWhichTextRun == nsTextFrame::eNotInflated) {
+ fontInflation = 1.0f;
+ fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
+ } else {
+ fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
+ fontGroup = GetInflatedFontGroupForFrame(firstFrame);
+ }
+
+ if (fontGroup) {
+ // Refresh fontgroup if necessary, before trying to build textruns.
+ fontGroup->CheckForUpdatedPlatformList();
+ } else {
+ DestroyUserData(userDataToDestroy);
+ return nullptr;
+ }
+
+ if (flags2 & nsTextFrameUtils::Flags::HasTab) {
+ flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
+ }
+ if (flags2 & nsTextFrameUtils::Flags::HasShy) {
+ flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
+ }
+ if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) {
+ flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
+ }
+ if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
+ flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace;
+ }
+ if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
+ flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
+ }
+ // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
+ // frame's style is used, so we use a mixture of the first frame and
+ // last frame's style
+ flags |= nsLayoutUtils::GetTextRunFlagsForStyle(
+ lastComputedStyle, firstFrame->PresContext(), fontStyle, textStyle,
+ LetterSpacing(firstFrame, *textStyle));
+ // XXX this is a bit of a hack. For performance reasons, if we're favouring
+ // performance over quality, don't try to get accurate glyph extents.
+ if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) {
+ flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX;
+ }
+
+ // Convert linebreak coordinates to transformed string offsets
+ NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
+ "Didn't find all the frames to break-before...");
+ gfxSkipCharsIterator iter(skipChars);
+ AutoTArray<uint32_t, 50> textBreakPointsAfterTransform;
+ for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
+ nsTextFrameUtils::AppendLineBreakOffset(
+ &textBreakPointsAfterTransform,
+ iter.ConvertOriginalToSkipped(textBreakPoints[i]));
+ }
+ if (mStartOfLine) {
+ nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
+ transformedLength);
+ }
+
+ // Setup factory chain
+ bool needsToMaskPassword = NeedsToMaskPassword(firstFrame);
+ UniquePtr<nsTransformingTextRunFactory> transformingFactory;
+ if (anyTextTransformStyle || needsToMaskPassword) {
+ char16_t maskChar =
+ needsToMaskPassword ? 0 : textStyle->TextSecurityMaskChar();
+ transformingFactory = MakeUnique<nsCaseTransformTextRunFactory>(
+ std::move(transformingFactory), false, maskChar);
+ }
+ if (anyMathMLStyling) {
+ transformingFactory = MakeUnique<MathMLTextRunFactory>(
+ std::move(transformingFactory), mathFlags, sstyScriptLevel,
+ fontInflation);
+ }
+ nsTArray<RefPtr<nsTransformedCharStyle>> styles;
+ if (transformingFactory) {
+ uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX;
+ if (needsToMaskPassword) {
+ unmaskStart = unmaskEnd = UINT32_MAX;
+ TextEditor* passwordEditor =
+ nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
+ firstFrame->GetContent());
+ if (passwordEditor && !passwordEditor->IsAllMasked()) {
+ unmaskStart = passwordEditor->UnmaskedStart();
+ unmaskEnd = passwordEditor->UnmaskedEnd();
+ }
+ }
+
+ iter.SetOriginalOffset(0);
+ for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
+ MappedFlow* mappedFlow = &mMappedFlows[i];
+ nsTextFrame* f;
+ ComputedStyle* sc = nullptr;
+ RefPtr<nsTransformedCharStyle> defaultStyle;
+ RefPtr<nsTransformedCharStyle> unmaskStyle;
+ for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
+ f = f->GetNextContinuation()) {
+ uint32_t skippedOffset = iter.GetSkippedOffset();
+ // Text-combined frames have content-dependent transform, so we
+ // want to create new nsTransformedCharStyle for them anyway.
+ if (sc != f->Style() || sc->IsTextCombined()) {
+ sc = f->Style();
+ defaultStyle = new nsTransformedCharStyle(sc, f->PresContext());
+ if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) {
+ defaultStyle->mForceNonFullWidth = true;
+ }
+ if (needsToMaskPassword) {
+ defaultStyle->mMaskPassword = true;
+ if (unmaskStart != unmaskEnd) {
+ unmaskStyle = new nsTransformedCharStyle(sc, f->PresContext());
+ unmaskStyle->mForceNonFullWidth =
+ defaultStyle->mForceNonFullWidth;
+ }
+ }
+ }
+ iter.AdvanceOriginal(f->GetContentLength());
+ uint32_t skippedEnd = iter.GetSkippedOffset();
+ if (unmaskStyle) {
+ uint32_t skippedUnmaskStart =
+ iter.ConvertOriginalToSkipped(unmaskStart);
+ uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd);
+ iter.SetSkippedOffset(skippedEnd);
+ for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart);
+ ++skippedOffset) {
+ styles.AppendElement(defaultStyle);
+ }
+ for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd);
+ ++skippedOffset) {
+ styles.AppendElement(unmaskStyle);
+ }
+ for (; skippedOffset < skippedEnd; ++skippedOffset) {
+ styles.AppendElement(defaultStyle);
+ }
+ } else {
+ for (; skippedOffset < skippedEnd; ++skippedOffset) {
+ styles.AppendElement(defaultStyle);
+ }
+ }
+ }
+ }
+ flags2 |= nsTextFrameUtils::Flags::IsTransformed;
+ NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
+ "We didn't cover all the characters in the text run!");
+ }
+
+ RefPtr<gfxTextRun> textRun;
+ gfxTextRunFactory::Parameters params = {
+ mDrawTarget,
+ finalUserData,
+ &skipChars,
+ textBreakPointsAfterTransform.Elements(),
+ uint32_t(textBreakPointsAfterTransform.Length()),
+ int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
+
+ if (mDoubleByteText) {
+ const char16_t* text = static_cast<const char16_t*>(textPtr);
+ if (transformingFactory) {
+ textRun = transformingFactory->MakeTextRun(
+ text, transformedLength, &params, fontGroup, flags, flags2,
+ std::move(styles), true);
+ } else {
+ textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
+ flags2, mMissingFonts);
+ }
+ } else {
+ const uint8_t* text = static_cast<const uint8_t*>(textPtr);
+ flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
+ if (transformingFactory) {
+ textRun = transformingFactory->MakeTextRun(
+ text, transformedLength, &params, fontGroup, flags, flags2,
+ std::move(styles), true);
+ } else {
+ textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
+ flags2, mMissingFonts);
+ }
+ }
+ if (!textRun) {
+ DestroyUserData(userDataToDestroy);
+ return nullptr;
+ }
+
+ // We have to set these up after we've created the textrun, because
+ // the breaks may be stored in the textrun during this very call.
+ // This is a bit annoying because it requires another loop over the frames
+ // making up the textrun, but I don't see a way to avoid this.
+ // We have to do this if line-breaking is required OR if a text-transform
+ // is in effect, because we depend on the line-breaker's scanner (via
+ // BreakSink::Finish) to finish building transformed textruns.
+ if (mDoLineBreaking || transformingFactory) {
+ SetupBreakSinksForTextRun(textRun.get(), textPtr);
+ }
+
+ // Ownership of the factory has passed to the textrun
+ // TODO: bug 1285316: clean up ownership transfer from the factory to
+ // the textrun
+ Unused << transformingFactory.release();
+
+ if (anyTextEmphasis) {
+ SetupTextEmphasisForTextRun(textRun.get(), textPtr);
+ }
+
+ if (mSkipIncompleteTextRuns) {
+ mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(
+ textPtr, transformedLength, mDoubleByteText);
+ // Since we're doing to destroy the user data now, avoid a dangling
+ // pointer. Strictly speaking we don't need to do this since it should
+ // not be used (since this textrun will not be used and will be
+ // itself deleted soon), but it's always better to not have dangling
+ // pointers around.
+ textRun->SetUserData(nullptr);
+ DestroyUserData(userDataToDestroy);
+ return nullptr;
+ }
+
+ // Actually wipe out the textruns associated with the mapped frames and
+ // associate those frames with this text run.
+ AssignTextRun(textRun.get(), fontInflation);
+ return textRun.forget();
+}
+
+// This is a cut-down version of BuildTextRunForFrames used to set up
+// context for the line-breaker, when the textrun has already been created.
+// So it does the same walk over the mMappedFlows, but doesn't actually
+// build a new textrun.
+bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun* aTextRun) {
+ AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
+ uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
+ if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
+ return false;
+ }
+ void* textPtr = buffer.AppendElements(bufferSize, fallible);
+ if (!textPtr) {
+ return false;
+ }
+
+ gfxSkipChars skipChars;
+
+ for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
+ MappedFlow* mappedFlow = &mMappedFlows[i];
+ nsTextFrame* f = mappedFlow->mStartFrame;
+
+ const nsStyleText* textStyle = f->StyleText();
+ nsTextFrameUtils::CompressionMode compression =
+ GetCSSWhitespaceToCompressionMode(f, textStyle);
+
+ // Figure out what content is included in this flow.
+ const nsTextFragment* frag = f->TextFragment();
+ int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
+ int32_t contentEnd = mappedFlow->GetContentEnd();
+ int32_t contentLength = contentEnd - contentStart;
+
+ nsTextFrameUtils::Flags analysisFlags;
+ if (frag->Is2b()) {
+ NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
+ char16_t* bufStart = static_cast<char16_t*>(textPtr);
+ char16_t* bufEnd = nsTextFrameUtils::TransformText(
+ frag->Get2b() + contentStart, contentLength, bufStart, compression,
+ &mNextRunContextInfo, &skipChars, &analysisFlags);
+ textPtr = bufEnd;
+ } else {
+ if (mDoubleByteText) {
+ // Need to expand the text. First transform it into a temporary buffer,
+ // then expand.
+ AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
+ uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
+ if (!bufStart) {
+ return false;
+ }
+ uint8_t* end = nsTextFrameUtils::TransformText(
+ reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
+ contentLength, bufStart, compression, &mNextRunContextInfo,
+ &skipChars, &analysisFlags);
+ textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
+ tempBuf.Elements(), end - tempBuf.Elements());
+ } else {
+ uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
+ uint8_t* end = nsTextFrameUtils::TransformText(
+ reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
+ contentLength, bufStart, compression, &mNextRunContextInfo,
+ &skipChars, &analysisFlags);
+ textPtr = end;
+ }
+ }
+ }
+
+ // We have to set these up after we've created the textrun, because
+ // the breaks may be stored in the textrun during this very call.
+ // This is a bit annoying because it requires another loop over the frames
+ // making up the textrun, but I don't see a way to avoid this.
+ SetupBreakSinksForTextRun(aTextRun, buffer.Elements());
+
+ return true;
+}
+
+static bool HasCompressedLeadingWhitespace(
+ nsTextFrame* aFrame, const nsStyleText* aStyleText,
+ int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) {
+ if (!aIterator.IsOriginalCharSkipped()) {
+ return false;
+ }
+
+ gfxSkipCharsIterator iter = aIterator;
+ int32_t frameContentOffset = aFrame->GetContentOffset();
+ const nsTextFragment* frag = aFrame->TextFragment();
+ while (frameContentOffset < aContentEndOffset &&
+ iter.IsOriginalCharSkipped()) {
+ if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) {
+ return true;
+ }
+ ++frameContentOffset;
+ iter.AdvanceOriginal(1);
+ }
+ return false;
+}
+
+void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
+ const void* aTextPtr) {
+ using mozilla::intl::LineBreakRule;
+ using mozilla::intl::WordBreakRule;
+
+ // textruns have uniform language
+ const nsStyleFont* styleFont = mMappedFlows[0].mStartFrame->StyleFont();
+ // We should only use a language for hyphenation if it was specified
+ // explicitly.
+ nsAtom* hyphenationLanguage =
+ styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
+ // We keep this pointed at the skip-chars data for the current mappedFlow.
+ // This lets us cheaply check whether the flow has compressed initial
+ // whitespace...
+ gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
+
+ for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
+ MappedFlow* mappedFlow = &mMappedFlows[i];
+ // The CSS word-break value may change within a word, so we reset it for
+ // each MappedFlow. The line-breaker will flush its text if the property
+ // actually changes.
+ const auto* styleText = mappedFlow->mStartFrame->StyleText();
+ auto wordBreak = styleText->EffectiveWordBreak();
+ switch (wordBreak) {
+ case StyleWordBreak::BreakAll:
+ mLineBreaker.SetWordBreak(WordBreakRule::BreakAll);
+ break;
+ case StyleWordBreak::KeepAll:
+ mLineBreaker.SetWordBreak(WordBreakRule::KeepAll);
+ break;
+ case StyleWordBreak::Normal:
+ default:
+ MOZ_ASSERT(wordBreak == StyleWordBreak::Normal);
+ mLineBreaker.SetWordBreak(WordBreakRule::Normal);
+ break;
+ }
+ switch (styleText->mLineBreak) {
+ case StyleLineBreak::Auto:
+ mLineBreaker.SetStrictness(LineBreakRule::Auto);
+ break;
+ case StyleLineBreak::Normal:
+ mLineBreaker.SetStrictness(LineBreakRule::Normal);
+ break;
+ case StyleLineBreak::Loose:
+ mLineBreaker.SetStrictness(LineBreakRule::Loose);
+ break;
+ case StyleLineBreak::Strict:
+ mLineBreaker.SetStrictness(LineBreakRule::Strict);
+ break;
+ case StyleLineBreak::Anywhere:
+ mLineBreaker.SetStrictness(LineBreakRule::Anywhere);
+ break;
+ }
+
+ uint32_t offset = iter.GetSkippedOffset();
+ gfxSkipCharsIterator iterNext = iter;
+ iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
+ mappedFlow->mStartFrame->GetContentOffset());
+
+ UniquePtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
+ MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset));
+
+ uint32_t length = iterNext.GetSkippedOffset() - offset;
+ uint32_t flags = 0;
+ nsIFrame* initialBreakController =
+ mappedFlow->mAncestorControllingInitialBreak;
+ if (!initialBreakController) {
+ initialBreakController = mLineContainer;
+ }
+ if (!initialBreakController->StyleText()->WhiteSpaceCanWrap(
+ initialBreakController)) {
+ flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
+ }
+ nsTextFrame* startFrame = mappedFlow->mStartFrame;
+ const nsStyleText* textStyle = startFrame->StyleText();
+ if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
+ flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
+ }
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks) {
+ flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
+ }
+ if (textStyle->mTextTransform.case_ == StyleTextTransformCase::Capitalize) {
+ flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
+ }
+ if (textStyle->mHyphens == StyleHyphens::Auto &&
+ textStyle->mLineBreak != StyleLineBreak::Anywhere) {
+ flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
+ }
+
+ if (HasCompressedLeadingWhitespace(startFrame, textStyle,
+ mappedFlow->GetContentEnd(), iter)) {
+ mLineBreaker.AppendInvisibleWhitespace(flags);
+ }
+
+ if (length > 0) {
+ BreakSink* sink = mSkipIncompleteTextRuns ? nullptr : (*breakSink).get();
+ if (mDoubleByteText) {
+ const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
+ mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
+ flags, sink);
+ } else {
+ const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
+ mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
+ flags, sink);
+ }
+ }
+
+ iter = iterNext;
+ }
+}
+
+static bool MayCharacterHaveEmphasisMark(uint32_t aCh) {
+ auto category = unicode::GetGeneralCategory(aCh);
+ // Comparing an unsigned variable against zero is a compile error,
+ // so we use static assert here to ensure we really don't need to
+ // compare it with the given constant.
+ static_assert(std::is_unsigned_v<decltype(category)> &&
+ HB_UNICODE_GENERAL_CATEGORY_CONTROL == 0,
+ "if this constant is not zero, or category is signed, "
+ "we need to explicitly do the comparison below");
+ return !(category <= HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED ||
+ (category >= HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR &&
+ category <= HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR));
+}
+
+static bool MayCharacterHaveEmphasisMark(uint8_t aCh) {
+ // 0x00~0x1f and 0x7f~0x9f are in category Cc
+ // 0x20 and 0xa0 are in category Zs
+ bool result = !(aCh <= 0x20 || (aCh >= 0x7f && aCh <= 0xa0));
+ MOZ_ASSERT(result == MayCharacterHaveEmphasisMark(uint32_t(aCh)),
+ "result for uint8_t should match result for uint32_t");
+ return result;
+}
+
+void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun,
+ const void* aTextPtr) {
+ if (!mDoubleByteText) {
+ auto text = reinterpret_cast<const uint8_t*>(aTextPtr);
+ for (auto i : IntegerRange(aTextRun->GetLength())) {
+ if (!MayCharacterHaveEmphasisMark(text[i])) {
+ aTextRun->SetNoEmphasisMark(i);
+ }
+ }
+ } else {
+ auto text = reinterpret_cast<const char16_t*>(aTextPtr);
+ auto length = aTextRun->GetLength();
+ for (size_t i = 0; i < length; ++i) {
+ if (i + 1 < length && NS_IS_SURROGATE_PAIR(text[i], text[i + 1])) {
+ uint32_t ch = SURROGATE_TO_UCS4(text[i], text[i + 1]);
+ if (!MayCharacterHaveEmphasisMark(ch)) {
+ aTextRun->SetNoEmphasisMark(i);
+ aTextRun->SetNoEmphasisMark(i + 1);
+ }
+ ++i;
+ } else {
+ if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) {
+ aTextRun->SetNoEmphasisMark(i);
+ }
+ }
+ }
+ }
+}
+
+// Find the flow corresponding to aContent in aUserData
+static inline TextRunMappedFlow* FindFlowForContent(
+ TextRunUserData* aUserData, nsIContent* aContent,
+ TextRunMappedFlow* userMappedFlows) {
+ // Find the flow that contains us
+ int32_t i = aUserData->mLastFlowIndex;
+ int32_t delta = 1;
+ int32_t sign = 1;
+ // Search starting at the current position and examine close-by
+ // positions first, moving further and further away as we go.
+ while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
+ TextRunMappedFlow* flow = &userMappedFlows[i];
+ if (flow->mStartFrame->GetContent() == aContent) {
+ return flow;
+ }
+
+ i += delta;
+ sign = -sign;
+ delta = -delta + sign;
+ }
+
+ // We ran into an array edge. Add |delta| to |i| once more to get
+ // back to the side where we still need to search, then step in
+ // the |sign| direction.
+ i += delta;
+ if (sign > 0) {
+ for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
+ TextRunMappedFlow* flow = &userMappedFlows[i];
+ if (flow->mStartFrame->GetContent() == aContent) {
+ return flow;
+ }
+ }
+ } else {
+ for (; i >= 0; --i) {
+ TextRunMappedFlow* flow = &userMappedFlows[i];
+ if (flow->mStartFrame->GetContent() == aContent) {
+ return flow;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+void BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun,
+ float aInflation) {
+ for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
+ MappedFlow* mappedFlow = &mMappedFlows[i];
+ nsTextFrame* startFrame = mappedFlow->mStartFrame;
+ nsTextFrame* endFrame = mappedFlow->mEndFrame;
+ nsTextFrame* f;
+ for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) {
+#ifdef DEBUG_roc
+ if (f->GetTextRun(mWhichTextRun)) {
+ gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
+ if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) {
+ NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
+ }
+ } else {
+ auto userData =
+ static_cast<TextRunUserData*>(aTextRun->GetUserData());
+ TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
+ if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
+ userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
+ mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) {
+ NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
+ }
+ }
+ }
+#endif
+
+ gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
+ if (oldTextRun) {
+ nsTextFrame* firstFrame = nullptr;
+ uint32_t startOffset = 0;
+ if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ firstFrame = GetFrameForSimpleFlow(oldTextRun);
+ } else {
+ auto userData =
+ static_cast<TextRunUserData*>(oldTextRun->GetUserData());
+ TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun);
+ firstFrame = userMappedFlows[0].mStartFrame;
+ if (MOZ_UNLIKELY(f != firstFrame)) {
+ TextRunMappedFlow* flow =
+ FindFlowForContent(userData, f->GetContent(), userMappedFlows);
+ if (flow) {
+ startOffset = flow->mDOMOffsetToBeforeTransformOffset;
+ } else {
+ NS_ERROR("Can't find flow containing frame 'f'");
+ }
+ }
+ }
+
+ // Optimization: if |f| is the first frame in the flow then there are no
+ // prev-continuations that use |oldTextRun|.
+ nsTextFrame* clearFrom = nullptr;
+ if (MOZ_UNLIKELY(f != firstFrame)) {
+ // If all the frames in the mapped flow starting at |f| (inclusive)
+ // are empty then we let the prev-continuations keep the old text run.
+ gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset,
+ f->GetContentOffset());
+ uint32_t textRunOffset =
+ iter.ConvertOriginalToSkipped(f->GetContentOffset());
+ clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
+ }
+ f->ClearTextRun(clearFrom, mWhichTextRun);
+
+#ifdef DEBUG
+ if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
+ // oldTextRun was destroyed - assert that we don't reference it.
+ for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
+ NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
+ "destroyed text run is still in use");
+ }
+ }
+#endif
+ }
+ f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
+ }
+ // Set this bit now; we can't set it any earlier because
+ // f->ClearTextRun() might clear it out.
+ nsFrameState whichTextRunState =
+ startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
+ ? TEXT_IN_TEXTRUN_USER_DATA
+ : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
+ startFrame->AddStateBits(whichTextRunState);
+ }
+}
+
+NS_QUERYFRAME_HEAD(nsTextFrame)
+ NS_QUERYFRAME_ENTRY(nsTextFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
+
+gfxSkipCharsIterator nsTextFrame::EnsureTextRun(
+ TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget,
+ nsIFrame* aLineContainer, const nsLineList::iterator* aLine,
+ uint32_t* aFlowEndInTextRun) {
+ gfxTextRun* textRun = GetTextRun(aWhichTextRun);
+ if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) {
+ RefPtr<DrawTarget> refDT = aRefDrawTarget;
+ if (!refDT) {
+ refDT = CreateReferenceDrawTarget(this);
+ }
+ if (refDT) {
+ BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun);
+ }
+ textRun = GetTextRun(aWhichTextRun);
+ if (!textRun) {
+ // A text run was not constructed for this frame. This is bad. The caller
+ // will check mTextRun.
+ return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(),
+ 0);
+ }
+ TabWidthStore* tabWidths = GetProperty(TabWidthProperty());
+ if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
+ RemoveProperty(TabWidthProperty());
+ }
+ }
+
+ if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
+ if (aFlowEndInTextRun) {
+ *aFlowEndInTextRun = textRun->GetLength();
+ }
+ return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
+ }
+
+ auto userData = static_cast<TextRunUserData*>(textRun->GetUserData());
+ TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun);
+ TextRunMappedFlow* flow =
+ FindFlowForContent(userData, mContent, userMappedFlows);
+ if (flow) {
+ // Since textruns can only contain one flow for a given content element,
+ // this must be our flow.
+ uint32_t flowIndex = flow - userMappedFlows;
+ userData->mLastFlowIndex = flowIndex;
+ gfxSkipCharsIterator iter(textRun->GetSkipChars(),
+ flow->mDOMOffsetToBeforeTransformOffset,
+ mContentOffset);
+ if (aFlowEndInTextRun) {
+ if (flowIndex + 1 < userData->mMappedFlowCount) {
+ gfxSkipCharsIterator end(textRun->GetSkipChars());
+ *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
+ flow[1].mStartFrame->GetContentOffset() +
+ flow[1].mDOMOffsetToBeforeTransformOffset);
+ } else {
+ *aFlowEndInTextRun = textRun->GetLength();
+ }
+ }
+ return iter;
+ }
+
+ NS_ERROR("Can't find flow containing this frame???");
+ return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
+}
+
+static uint32_t GetEndOfTrimmedText(const nsTextFragment* aFrag,
+ const nsStyleText* aStyleText,
+ uint32_t aStart, uint32_t aEnd,
+ gfxSkipCharsIterator* aIterator,
+ bool aAllowHangingWS = false) {
+ aIterator->SetSkippedOffset(aEnd);
+ while (aIterator->GetSkippedOffset() > aStart) {
+ aIterator->AdvanceSkipped(-1);
+ if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText,
+ aAllowHangingWS))
+ return aIterator->GetSkippedOffset() + 1;
+ }
+ return aStart;
+}
+
+nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets(
+ const nsTextFragment* aFrag, TrimmedOffsetFlags aFlags) const {
+ NS_ASSERTION(mTextRun, "Need textrun here");
+ if (!(aFlags & TrimmedOffsetFlags::NotPostReflow)) {
+ // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
+ // to be set correctly. If our parent wasn't reflowed due to the frame
+ // tree being too deep then the return value doesn't matter.
+ NS_ASSERTION(
+ !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
+ GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
+ "Can only call this on frames that have been reflowed");
+ NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW),
+ "Can only call this on frames that are not being reflowed");
+ }
+
+ TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()};
+ const nsStyleText* textStyle = StyleText();
+ // Note that pre-line newlines should still allow us to trim spaces
+ // for display
+ if (textStyle->WhiteSpaceIsSignificant()) {
+ return offsets;
+ }
+
+ if (!(aFlags & TrimmedOffsetFlags::NoTrimBefore) &&
+ ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
+ HasAnyStateBits(TEXT_START_OF_LINE))) {
+ int32_t whitespaceCount =
+ GetTrimmableWhitespaceCount(aFrag, offsets.mStart, offsets.mLength, 1);
+ offsets.mStart += whitespaceCount;
+ offsets.mLength -= whitespaceCount;
+ }
+
+ if (!(aFlags & TrimmedOffsetFlags::NoTrimAfter) &&
+ ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
+ HasAnyStateBits(TEXT_END_OF_LINE))) {
+ // This treats a trailing 'pre-line' newline as trimmable. That's fine,
+ // it's actually what we want since we want whitespace before it to
+ // be trimmed.
+ int32_t whitespaceCount = GetTrimmableWhitespaceCount(
+ aFrag, offsets.GetEnd() - 1, offsets.mLength, -1);
+ offsets.mLength -= whitespaceCount;
+ }
+ return offsets;
+}
+
+static bool IsJustifiableCharacter(const nsStyleText* aTextStyle,
+ const nsTextFragment* aFrag, int32_t aPos,
+ bool aLangIsCJ) {
+ NS_ASSERTION(aPos >= 0, "negative position?!");
+
+ StyleTextJustify justifyStyle = aTextStyle->mTextJustify;
+ if (justifyStyle == StyleTextJustify::None) {
+ return false;
+ }
+
+ const char16_t ch = aFrag->CharAt(AssertedCast<uint32_t>(aPos));
+ if (ch == '\n' || ch == '\t' || ch == '\r') {
+ return !aTextStyle->WhiteSpaceIsSignificant();
+ }
+ if (ch == ' ' || ch == CH_NBSP) {
+ // Don't justify spaces that are combined with diacriticals
+ if (!aFrag->Is2b()) {
+ return true;
+ }
+ return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
+ aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
+ }
+
+ if (justifyStyle == StyleTextJustify::InterCharacter) {
+ return true;
+ } else if (justifyStyle == StyleTextJustify::InterWord) {
+ return false;
+ }
+
+ // text-justify: auto
+ if (ch < 0x2150u) {
+ return false;
+ }
+ if (aLangIsCJ) {
+ if ( // Number Forms, Arrows, Mathematical Operators
+ (0x2150u <= ch && ch <= 0x22ffu) ||
+ // Enclosed Alphanumerics
+ (0x2460u <= ch && ch <= 0x24ffu) ||
+ // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
+ (0x2580u <= ch && ch <= 0x27bfu) ||
+ // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
+ // Miscellaneous Mathematical Symbols-B,
+ // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows
+ (0x27f0u <= ch && ch <= 0x2bffu) ||
+ // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic
+ // Description Characters, CJK Symbols and Punctuation, Hiragana,
+ // Katakana, Bopomofo
+ (0x2e80u <= ch && ch <= 0x312fu) ||
+ // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
+ // Enclosed CJK Letters and Months, CJK Compatibility,
+ // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
+ // CJK Unified Ideographs, Yi Syllables, Yi Radicals
+ (0x3190u <= ch && ch <= 0xabffu) ||
+ // CJK Compatibility Ideographs
+ (0xf900u <= ch && ch <= 0xfaffu) ||
+ // Halfwidth and Fullwidth Forms (a part)
+ (0xff5eu <= ch && ch <= 0xff9fu)) {
+ return true;
+ }
+ if (NS_IS_HIGH_SURROGATE(ch)) {
+ if (char32_t u = aFrag->ScalarValueAt(AssertedCast<uint32_t>(aPos))) {
+ // CJK Unified Ideographs Extension B,
+ // CJK Unified Ideographs Extension C,
+ // CJK Unified Ideographs Extension D,
+ // CJK Compatibility Ideographs Supplement
+ if (0x20000u <= u && u <= 0x2ffffu) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+void nsTextFrame::ClearMetrics(ReflowOutput& aMetrics) {
+ aMetrics.ClearSize();
+ aMetrics.SetBlockStartAscent(0);
+ mAscent = 0;
+
+ AddStateBits(TEXT_NO_RENDERED_GLYPHS);
+}
+
+static int32_t FindChar(const nsTextFragment* frag, int32_t aOffset,
+ int32_t aLength, char16_t ch) {
+ int32_t i = 0;
+ if (frag->Is2b()) {
+ const char16_t* str = frag->Get2b() + aOffset;
+ for (; i < aLength; ++i) {
+ if (*str == ch) {
+ return i + aOffset;
+ }
+ ++str;
+ }
+ } else {
+ if (uint16_t(ch) <= 0xFF) {
+ const char* str = frag->Get1b() + aOffset;
+ const void* p = memchr(str, ch, aLength);
+ if (p) {
+ return (static_cast<const char*>(p) - str) + aOffset;
+ }
+ }
+ }
+ return -1;
+}
+
+static bool IsChineseOrJapanese(const nsTextFrame* aFrame) {
+ if (aFrame->ShouldSuppressLineBreak()) {
+ // Always treat ruby as CJ language so that those characters can
+ // be expanded properly even when surrounded by other language.
+ return true;
+ }
+
+ nsAtom* language = aFrame->StyleFont()->mLanguage;
+ if (!language) {
+ return false;
+ }
+ return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") ||
+ nsStyleUtil::MatchesLanguagePrefix(language, u"zh");
+}
+
+#ifdef DEBUG
+static bool IsInBounds(const gfxSkipCharsIterator& aStart,
+ int32_t aContentLength, gfxTextRun::Range aRange) {
+ if (aStart.GetSkippedOffset() > aRange.start) {
+ return false;
+ }
+ if (aContentLength == INT32_MAX) {
+ return true;
+ }
+ gfxSkipCharsIterator iter(aStart);
+ iter.AdvanceOriginal(aContentLength);
+ return iter.GetSkippedOffset() >= aRange.end;
+}
+#endif
+
+nsTextFrame::PropertyProvider::PropertyProvider(
+ gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
+ const nsTextFragment* aFrag, nsTextFrame* aFrame,
+ const gfxSkipCharsIterator& aStart, int32_t aLength,
+ nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs,
+ nsTextFrame::TextRunType aWhichTextRun)
+ : mTextRun(aTextRun),
+ mFontGroup(nullptr),
+ mTextStyle(aTextStyle),
+ mFrag(aFrag),
+ mLineContainer(aLineContainer),
+ mFrame(aFrame),
+ mStart(aStart),
+ mTempIterator(aStart),
+ mTabWidths(nullptr),
+ mTabWidthsAnalyzedLimit(0),
+ mLength(aLength),
+ mWordSpacing(WordSpacing(aFrame, mTextRun, *aTextStyle)),
+ mLetterSpacing(LetterSpacing(aFrame, *aTextStyle)),
+ mMinTabAdvance(-1.0),
+ mHyphenWidth(-1),
+ mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
+ mJustificationArrayStart(0),
+ mReflowing(true),
+ mWhichTextRun(aWhichTextRun) {
+ NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
+}
+
+nsTextFrame::PropertyProvider::PropertyProvider(
+ nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
+ nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics)
+ : mTextRun(aFrame->GetTextRun(aWhichTextRun)),
+ mFontGroup(nullptr),
+ mFontMetrics(aFontMetrics),
+ mTextStyle(aFrame->StyleText()),
+ mFrag(aFrame->TextFragment()),
+ mLineContainer(nullptr),
+ mFrame(aFrame),
+ mStart(aStart),
+ mTempIterator(aStart),
+ mTabWidths(nullptr),
+ mTabWidthsAnalyzedLimit(0),
+ mLength(aFrame->GetContentLength()),
+ mWordSpacing(WordSpacing(aFrame, mTextRun, *mTextStyle)),
+ mLetterSpacing(LetterSpacing(aFrame, *mTextStyle)),
+ mMinTabAdvance(-1.0),
+ mHyphenWidth(-1),
+ mOffsetFromBlockOriginForTabs(0),
+ mJustificationArrayStart(0),
+ mReflowing(false),
+ mWhichTextRun(aWhichTextRun) {
+ NS_ASSERTION(mTextRun, "Textrun not initialized!");
+}
+
+gfx::ShapedTextFlags nsTextFrame::PropertyProvider::GetShapedTextFlags() const {
+ return nsLayoutUtils::GetTextRunOrientFlagsForStyle(mFrame->Style());
+}
+
+already_AddRefed<DrawTarget> nsTextFrame::PropertyProvider::GetDrawTarget()
+ const {
+ return CreateReferenceDrawTarget(GetFrame());
+}
+
+gfxFloat nsTextFrame::PropertyProvider::MinTabAdvance() const {
+ if (mMinTabAdvance < 0.0) {
+ mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun);
+ }
+ return mMinTabAdvance;
+}
+
+/**
+ * Finds the offset of the first character of the cluster containing aPos
+ */
+static void FindClusterStart(const gfxTextRun* aTextRun, int32_t aOriginalStart,
+ gfxSkipCharsIterator* aPos) {
+ while (aPos->GetOriginalOffset() > aOriginalStart) {
+ if (aPos->IsOriginalCharSkipped() ||
+ aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
+ break;
+ }
+ aPos->AdvanceOriginal(-1);
+ }
+}
+
+/**
+ * Finds the offset of the last character of the cluster containing aPos.
+ * If aAllowSplitLigature is false, we also check for a ligature-group
+ * start.
+ */
+static void FindClusterEnd(const gfxTextRun* aTextRun, int32_t aOriginalEnd,
+ gfxSkipCharsIterator* aPos,
+ bool aAllowSplitLigature = true) {
+ MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,
+ "character outside string");
+
+ aPos->AdvanceOriginal(1);
+ while (aPos->GetOriginalOffset() < aOriginalEnd) {
+ if (aPos->IsOriginalCharSkipped() ||
+ (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
+ (aAllowSplitLigature ||
+ aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
+ break;
+ }
+ aPos->AdvanceOriginal(1);
+ }
+ aPos->AdvanceOriginal(-1);
+}
+
+// Get the line number of aFrame in the lines referenced by aLineIter, if
+// known (returning -1 if we don't find it).
+static int32_t GetFrameLineNum(nsIFrame* aFrame, nsILineIterator* aLineIter) {
+ if (!aLineIter) {
+ return -1;
+ }
+ int32_t n = aLineIter->FindLineContaining(aFrame);
+ if (n >= 0) {
+ return n;
+ }
+ // If we didn't find the frame directly, but its parent is an inline,
+ // we want the line that the inline ancestor is on.
+ nsIFrame* ancestor = aFrame->GetParent();
+ while (ancestor && ancestor->IsInlineFrame()) {
+ n = aLineIter->FindLineContaining(ancestor);
+ if (n >= 0) {
+ return n;
+ }
+ ancestor = ancestor->GetParent();
+ }
+ return -1;
+}
+
+// Get the position of the first preserved newline in aFrame, if any,
+// returning -1 if none.
+static int32_t FindFirstNewlinePosition(const nsTextFrame* aFrame) {
+ MOZ_ASSERT(aFrame->StyleText()->NewlineIsSignificantStyle(),
+ "how did the HasNewline flag get set?");
+ const auto* textFragment = aFrame->TextFragment();
+ for (auto i = aFrame->GetContentOffset(); i < aFrame->GetContentEnd(); ++i) {
+ if (textFragment->CharAt(i) == '\n') {
+ return i;
+ }
+ }
+ return -1;
+}
+
+// Get the position of the last preserved tab in aFrame that is before the
+// preserved newline at aNewlinePos.
+// Passing -1 for aNewlinePos means there is no preserved newline, so we look
+// for the last preserved tab in the whole content.
+// Returns -1 if no such preserved tab is present.
+static int32_t FindLastTabPositionBeforeNewline(const nsTextFrame* aFrame,
+ int32_t aNewlinePos) {
+ // We only call this if white-space is not being collapsed.
+ MOZ_ASSERT(aFrame->StyleText()->WhiteSpaceIsSignificant(),
+ "how did the HasTab flag get set?");
+ const auto* textFragment = aFrame->TextFragment();
+ // If a non-negative newline position was given, we only need to search the
+ // text before that offset.
+ for (auto i = aNewlinePos < 0 ? aFrame->GetContentEnd() : aNewlinePos;
+ i > aFrame->GetContentOffset(); --i) {
+ if (textFragment->CharAt(i - 1) == '\t') {
+ return i;
+ }
+ }
+ return -1;
+}
+
+// Look for preserved tab or newline in the given frame or its following
+// siblings on the same line, to determine whether justification should be
+// suppressed in order to avoid disrupting tab-stop positions.
+// Returns the first such preserved whitespace char, or 0 if none found.
+static char NextPreservedWhiteSpaceOnLine(nsIFrame* aSibling,
+ nsILineIterator* aLineIter,
+ int32_t aLineNum) {
+ while (aSibling) {
+ // If we find a <br>, treat it like a newline.
+ if (aSibling->IsBrFrame()) {
+ return '\n';
+ }
+ // If we've moved on to a later line, stop searching.
+ if (GetFrameLineNum(aSibling, aLineIter) > aLineNum) {
+ return 0;
+ }
+ // If we encounter an inline frame, recurse into it.
+ if (aSibling->IsInlineFrame()) {
+ auto* child = aSibling->PrincipalChildList().FirstChild();
+ char result = NextPreservedWhiteSpaceOnLine(child, aLineIter, aLineNum);
+ if (result) {
+ return result;
+ }
+ }
+ // If we have a text frame, and whitespace is not collapsed, we need to
+ // check its contents.
+ if (aSibling->IsTextFrame()) {
+ const auto* textStyle = aSibling->StyleText();
+ if (textStyle->WhiteSpaceOrNewlineIsSignificant()) {
+ const auto* textFrame = static_cast<nsTextFrame*>(aSibling);
+ const auto* textFragment = textFrame->TextFragment();
+ for (auto i = textFrame->GetContentOffset();
+ i < textFrame->GetContentEnd(); ++i) {
+ const char16_t ch = textFragment->CharAt(i);
+ if (ch == '\n' && textStyle->NewlineIsSignificantStyle()) {
+ return '\n';
+ }
+ if (ch == '\t' && textStyle->WhiteSpaceIsSignificant()) {
+ return '\t';
+ }
+ }
+ }
+ }
+ aSibling = aSibling->GetNextSibling();
+ }
+ return 0;
+}
+
+static bool HasPreservedTabInFollowingSiblingOnLine(nsTextFrame* aFrame) {
+ bool foundTab = false;
+
+ nsIFrame* lineContainer = FindLineContainer(aFrame);
+ nsILineIterator* iter = lineContainer->GetLineIterator();
+ int32_t line = GetFrameLineNum(aFrame, iter);
+ char ws = NextPreservedWhiteSpaceOnLine(aFrame->GetNextSibling(), iter, line);
+ if (ws == '\t') {
+ foundTab = true;
+ } else if (!ws) {
+ // Didn't find a preserved tab or newline in our siblings; if our parent
+ // (and its parent, etc) is an inline, we need to look at their following
+ // siblings, too, as long as they're on the same line.
+ const nsIFrame* maybeInline = aFrame->GetParent();
+ while (maybeInline && maybeInline->IsInlineFrame()) {
+ ws = NextPreservedWhiteSpaceOnLine(maybeInline->GetNextSibling(), iter,
+ line);
+ if (ws == '\t') {
+ foundTab = true;
+ break;
+ }
+ if (ws == '\n') {
+ break;
+ }
+ maybeInline = maybeInline->GetParent();
+ }
+ }
+
+ // We called lineContainer->GetLineIterator() above, but we mustn't
+ // allow a block frame to retain this iterator if we're currently in
+ // reflow, as it will become invalid as the line list is reflowed.
+ if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) &&
+ lineContainer->IsBlockFrameOrSubclass()) {
+ static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator();
+ }
+
+ return foundTab;
+}
+
+JustificationInfo nsTextFrame::PropertyProvider::ComputeJustification(
+ Range aRange, nsTArray<JustificationAssignment>* aAssignments) {
+ JustificationInfo info;
+
+ // Horizontal-in-vertical frame is orthogonal to the line, so it
+ // doesn't actually include any justification opportunity inside.
+ // The spec says such frame should be treated as a U+FFFC. Since we
+ // do not insert justification opportunities on the sides of that
+ // character, the sides of this frame are not justifiable either.
+ if (mFrame->Style()->IsTextCombined()) {
+ return info;
+ }
+
+ int32_t lastTab = -1;
+ if (StaticPrefs::layout_css_text_align_justify_only_after_last_tab()) {
+ // If there is a preserved tab on the line, we don't apply justification
+ // until we're past its position.
+ if (mTextStyle->WhiteSpaceIsSignificant()) {
+ // If there is a preserved newline within the text, we don't need to look
+ // beyond this frame, as following frames will not be on the same line.
+ int32_t newlinePos =
+ (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasNewline)
+ ? FindFirstNewlinePosition(mFrame)
+ : -1;
+ if (newlinePos < 0) {
+ // There's no preserved newline within this frame; if there's a tab
+ // in a later sibling frame on the same line, we won't apply any
+ // justification to this one.
+ if (HasPreservedTabInFollowingSiblingOnLine(mFrame)) {
+ return info;
+ }
+ }
+
+ if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) {
+ // Find last tab character in the content; we won't justify anything
+ // before that position, so that tab alignment remains correct.
+ lastTab = FindLastTabPositionBeforeNewline(mFrame, newlinePos);
+ }
+ }
+ }
+
+ bool isCJ = IsChineseOrJapanese(mFrame);
+ nsSkipCharsRunIterator run(
+ mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
+ run.SetOriginalOffset(aRange.start);
+ mJustificationArrayStart = run.GetSkippedOffset();
+
+ nsTArray<JustificationAssignment> assignments;
+ assignments.SetCapacity(aRange.Length());
+ while (run.NextRun()) {
+ uint32_t originalOffset = run.GetOriginalOffset();
+ uint32_t skippedOffset = run.GetSkippedOffset();
+ uint32_t length = run.GetRunLength();
+ assignments.SetLength(skippedOffset + length - mJustificationArrayStart);
+
+ gfxSkipCharsIterator iter = run.GetPos();
+ for (uint32_t i = 0; i < length; ++i) {
+ uint32_t offset = originalOffset + i;
+ if (!IsJustifiableCharacter(mTextStyle, mFrag, offset, isCJ) ||
+ (lastTab >= 0 && offset <= uint32_t(lastTab))) {
+ continue;
+ }
+
+ iter.SetOriginalOffset(offset);
+
+ FindClusterStart(mTextRun, originalOffset, &iter);
+ uint32_t firstCharOffset = iter.GetSkippedOffset();
+ uint32_t firstChar = firstCharOffset > mJustificationArrayStart
+ ? firstCharOffset - mJustificationArrayStart
+ : 0;
+ if (!firstChar) {
+ info.mIsStartJustifiable = true;
+ } else {
+ auto& assign = assignments[firstChar];
+ auto& prevAssign = assignments[firstChar - 1];
+ if (prevAssign.mGapsAtEnd) {
+ prevAssign.mGapsAtEnd = 1;
+ assign.mGapsAtStart = 1;
+ } else {
+ assign.mGapsAtStart = 2;
+ info.mInnerOpportunities++;
+ }
+ }
+
+ FindClusterEnd(mTextRun, originalOffset + length, &iter);
+ uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
+ // Assign the two gaps temporary to the last char. If the next cluster is
+ // justifiable as well, one of the gaps will be removed by code above.
+ assignments[lastChar].mGapsAtEnd = 2;
+ info.mInnerOpportunities++;
+
+ // Skip the whole cluster
+ i = iter.GetOriginalOffset() - originalOffset;
+ }
+ }
+
+ if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) {
+ // We counted the expansion opportunity after the last character,
+ // but it is not an inner opportunity.
+ MOZ_ASSERT(info.mInnerOpportunities > 0);
+ info.mInnerOpportunities--;
+ info.mIsEndJustifiable = true;
+ }
+
+ if (aAssignments) {
+ *aAssignments = std::move(assignments);
+ }
+ return info;
+}
+
+// aStart, aLength in transformed string offsets
+void nsTextFrame::PropertyProvider::GetSpacing(Range aRange,
+ Spacing* aSpacing) const {
+ GetSpacingInternal(
+ aRange, aSpacing,
+ !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab));
+}
+
+static bool CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset,
+ bool aNewlineIsSignificant) {
+ const auto* g = aTextRun->GetCharacterGlyphs();
+ MOZ_ASSERT(aOffset < aTextRun->GetLength());
+ if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) {
+ return false;
+ }
+ if (aOffset + 1 >= aTextRun->GetLength()) {
+ return true;
+ }
+ return g[aOffset + 1].IsClusterStart() &&
+ g[aOffset + 1].IsLigatureGroupStart() &&
+ !g[aOffset].CharIsFormattingControl() && !g[aOffset].CharIsTab();
+}
+
+static gfxFloat ComputeTabWidthAppUnits(const nsIFrame* aFrame) {
+ const auto& tabSize = aFrame->StyleText()->mTabSize;
+ if (tabSize.IsLength()) {
+ nscoord w = tabSize.length._0.ToAppUnits();
+ MOZ_ASSERT(w >= 0);
+ return w;
+ }
+
+ MOZ_ASSERT(tabSize.IsNumber());
+ gfxFloat spaces = tabSize.number._0;
+ MOZ_ASSERT(spaces >= 0);
+
+ const nsIFrame* cb = aFrame->GetContainingBlock(0, aFrame->StyleDisplay());
+ const auto* styleText = cb->StyleText();
+
+ // Round the space width when converting to appunits the same way textruns do.
+ // We don't use GetFirstFontMetrics here because that may return a font that
+ // does not actually have the <space> character, yet is considered the "first
+ // available font" per CSS Fonts. Here, we want the font that would be used
+ // to render <space>, even if that means looking further down the font-family
+ // list.
+ RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(cb, 1.0f);
+ bool vertical = cb->GetWritingMode().IsCentralBaseline();
+ RefPtr font = fm->GetThebesFontGroup()->GetFirstValidFont(' ');
+ auto metrics = font->GetMetrics(vertical ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal);
+ nscoord spaceWidth = nscoord(
+ NS_round(metrics.spaceWidth * cb->PresContext()->AppUnitsPerDevPixel()));
+ return spaces * (spaceWidth + styleText->mLetterSpacing.ToAppUnits() +
+ styleText->mWordSpacing.Resolve(spaceWidth));
+}
+
+void nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange,
+ Spacing* aSpacing,
+ bool aIgnoreTabs) const {
+ MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
+
+ uint32_t index;
+ for (index = 0; index < aRange.Length(); ++index) {
+ aSpacing[index].mBefore = 0.0;
+ aSpacing[index].mAfter = 0.0;
+ }
+
+ if (mFrame->Style()->IsTextCombined()) {
+ return;
+ }
+
+ // Find our offset into the original+transformed string
+ gfxSkipCharsIterator start(mStart);
+ start.SetSkippedOffset(aRange.start);
+
+ // First, compute the word and letter spacing
+ if (mWordSpacing || mLetterSpacing) {
+ // Iterate over non-skipped characters
+ nsSkipCharsRunIterator run(
+ start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
+ bool newlineIsSignificant = mTextStyle->NewlineIsSignificant(mFrame);
+ while (run.NextRun()) {
+ uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
+ gfxSkipCharsIterator iter = run.GetPos();
+ for (int32_t i = 0; i < run.GetRunLength(); ++i) {
+ if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i,
+ newlineIsSignificant)) {
+ // End of a cluster, not in a ligature: put letter-spacing after it
+ aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
+ }
+ if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(), mFrame,
+ mTextStyle)) {
+ // It kinda sucks, but space characters can be part of clusters,
+ // and even still be whitespace (I think!)
+ iter.SetSkippedOffset(run.GetSkippedOffset() + i);
+ FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
+ &iter);
+ uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
+ aSpacing[runOffset].mAfter += mWordSpacing;
+ }
+ }
+ }
+ }
+
+ // Now add tab spacing, if there is any
+ if (!aIgnoreTabs) {
+ gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame);
+ if (tabWidth > 0) {
+ CalcTabWidths(aRange, tabWidth);
+ if (mTabWidths) {
+ mTabWidths->ApplySpacing(aSpacing,
+ aRange.start - mStart.GetSkippedOffset(),
+ aRange.Length());
+ }
+ }
+ }
+
+ // Now add in justification spacing
+ if (mJustificationSpacings.Length() > 0) {
+ // If there is any spaces trimmed at the end, aStart + aLength may
+ // be larger than the flags array. When that happens, we can simply
+ // ignore those spaces.
+ auto arrayEnd = mJustificationArrayStart +
+ static_cast<uint32_t>(mJustificationSpacings.Length());
+ auto end = std::min(aRange.end, arrayEnd);
+ MOZ_ASSERT(aRange.start >= mJustificationArrayStart);
+ for (auto i = aRange.start; i < end; i++) {
+ const auto& spacing =
+ mJustificationSpacings[i - mJustificationArrayStart];
+ uint32_t offset = i - aRange.start;
+ aSpacing[offset].mBefore += spacing.mBefore;
+ aSpacing[offset].mAfter += spacing.mAfter;
+ }
+ }
+}
+
+// aX and the result are in whole appunits.
+static gfxFloat AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth,
+ gfxFloat aMinAdvance) {
+ // Advance aX to the next multiple of aTabWidth. We must advance
+ // by at least aMinAdvance.
+ gfxFloat nextPos = aX + aMinAdvance;
+ return aTabWidth > 0.0 ? ceil(nextPos / aTabWidth) * aTabWidth : nextPos;
+}
+
+void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange,
+ gfxFloat aTabWidth) const {
+ MOZ_ASSERT(aTabWidth > 0);
+
+ if (!mTabWidths) {
+ if (mReflowing && !mLineContainer) {
+ // Intrinsic width computation does its own tab processing. We
+ // just don't do anything here.
+ return;
+ }
+ if (!mReflowing) {
+ mTabWidths = mFrame->GetProperty(TabWidthProperty());
+#ifdef DEBUG
+ // If we're not reflowing, we should have already computed the
+ // tab widths; check that they're available as far as the last
+ // tab character present (if any)
+ for (uint32_t i = aRange.end; i > aRange.start; --i) {
+ if (mTextRun->CharIsTab(i - 1)) {
+ uint32_t startOffset = mStart.GetSkippedOffset();
+ NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
+ "Precomputed tab widths are missing!");
+ break;
+ }
+ }
+#endif
+ return;
+ }
+ }
+
+ uint32_t startOffset = mStart.GetSkippedOffset();
+ MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset");
+ MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end");
+ uint32_t tabsEnd =
+ (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
+ if (tabsEnd < aRange.end) {
+ NS_ASSERTION(mReflowing,
+ "We need precomputed tab widths, but don't have enough.");
+
+ for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
+ Spacing spacing;
+ GetSpacingInternal(Range(i, i + 1), &spacing, true);
+ mOffsetFromBlockOriginForTabs += spacing.mBefore;
+
+ if (!mTextRun->CharIsTab(i)) {
+ if (mTextRun->IsClusterStart(i)) {
+ uint32_t clusterEnd = i + 1;
+ while (clusterEnd < mTextRun->GetLength() &&
+ !mTextRun->IsClusterStart(clusterEnd)) {
+ ++clusterEnd;
+ }
+ mOffsetFromBlockOriginForTabs +=
+ mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
+ }
+ } else {
+ if (!mTabWidths) {
+ mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
+ mFrame->SetProperty(TabWidthProperty(), mTabWidths);
+ }
+ double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
+ aTabWidth, MinTabAdvance());
+ mTabWidths->mWidths.AppendElement(
+ TabWidth(i - startOffset,
+ NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
+ mOffsetFromBlockOriginForTabs = nextTab;
+ }
+
+ mOffsetFromBlockOriginForTabs += spacing.mAfter;
+ }
+
+ if (mTabWidths) {
+ mTabWidths->mLimit = aRange.end - startOffset;
+ }
+ }
+
+ if (!mTabWidths) {
+ // Delete any stale property that may be left on the frame
+ mFrame->RemoveProperty(TabWidthProperty());
+ mTabWidthsAnalyzedLimit =
+ std::max(mTabWidthsAnalyzedLimit, aRange.end - startOffset);
+ }
+}
+
+gfxFloat nsTextFrame::PropertyProvider::GetHyphenWidth() const {
+ if (mHyphenWidth < 0) {
+ const auto& hyphenateChar = mTextStyle->mHyphenateCharacter;
+ if (hyphenateChar.IsAuto()) {
+ mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
+ } else {
+ RefPtr<gfxTextRun> hyphRun = GetHyphenTextRun(mFrame, nullptr);
+ mHyphenWidth = hyphRun ? hyphRun->GetAdvanceWidth() : 0;
+ }
+ }
+ return mHyphenWidth + mLetterSpacing;
+}
+
+static inline bool IS_HYPHEN(char16_t u) {
+ return u == char16_t('-') || // HYPHEN-MINUS
+ u == 0x058A || // ARMENIAN HYPHEN
+ u == 0x2010 || // HYPHEN
+ u == 0x2012 || // FIGURE DASH
+ u == 0x2013; // EN DASH
+}
+
+void nsTextFrame::PropertyProvider::GetHyphenationBreaks(
+ Range aRange, HyphenType* aBreakBefore) const {
+ MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
+ MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
+
+ if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
+ mTextStyle->mHyphens == StyleHyphens::None) {
+ memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
+ aRange.Length() * sizeof(HyphenType));
+ return;
+ }
+
+ // Iterate through the original-string character runs
+ nsSkipCharsRunIterator run(
+ mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
+ run.SetSkippedOffset(aRange.start);
+ // We need to visit skipped characters so that we can detect SHY
+ run.SetVisitSkipped();
+
+ int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
+ bool allowHyphenBreakBeforeNextChar =
+ prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
+ prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
+ mFrag->CharAt(AssertedCast<uint32_t>(prevTrailingCharOffset)) == CH_SHY;
+
+ while (run.NextRun()) {
+ NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
+ if (run.IsSkipped()) {
+ // Check if there's a soft hyphen which would let us hyphenate before
+ // the next non-skipped character. Don't look at soft hyphens followed
+ // by other skipped characters, we won't use them.
+ allowHyphenBreakBeforeNextChar =
+ mFrag->CharAt(AssertedCast<uint32_t>(
+ run.GetOriginalOffset() + run.GetRunLength() - 1)) == CH_SHY;
+ } else {
+ int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
+ memset(aBreakBefore + runOffsetInSubstring,
+ static_cast<uint8_t>(HyphenType::None),
+ run.GetRunLength() * sizeof(HyphenType));
+ // Don't allow hyphen breaks at the start of the line
+ aBreakBefore[runOffsetInSubstring] =
+ allowHyphenBreakBeforeNextChar &&
+ (!mFrame->HasAnyStateBits(TEXT_START_OF_LINE) ||
+ run.GetSkippedOffset() > mStart.GetSkippedOffset())
+ ? HyphenType::Soft
+ : HyphenType::None;
+ allowHyphenBreakBeforeNextChar = false;
+ }
+ }
+
+ if (mTextStyle->mHyphens == StyleHyphens::Auto) {
+ gfxSkipCharsIterator skipIter(mStart);
+ for (uint32_t i = 0; i < aRange.Length(); ++i) {
+ if (IS_HYPHEN(mFrag->CharAt(AssertedCast<uint32_t>(
+ skipIter.ConvertSkippedToOriginal(aRange.start + i))))) {
+ if (i < aRange.Length() - 1) {
+ aBreakBefore[i + 1] = HyphenType::Explicit;
+ }
+ continue;
+ }
+
+ if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
+ aBreakBefore[i] == HyphenType::None) {
+ aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
+ }
+ }
+ }
+}
+
+void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter) {
+ nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
+ mFrag, (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::Default
+ : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter));
+ mStart.SetOriginalOffset(trimmed.mStart);
+ mLength = trimmed.mLength;
+ SetupJustificationSpacing(true);
+}
+
+void nsTextFrame::PropertyProvider::InitializeForMeasure() {
+ nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
+ mFrag, nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
+ mStart.SetOriginalOffset(trimmed.mStart);
+ mLength = trimmed.mLength;
+ SetupJustificationSpacing(false);
+}
+
+void nsTextFrame::PropertyProvider::SetupJustificationSpacing(
+ bool aPostReflow) {
+ MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
+
+ if (!mFrame->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED)) {
+ return;
+ }
+
+ gfxSkipCharsIterator start(mStart), end(mStart);
+ // We can't just use our mLength here; when InitializeForDisplay is
+ // called with false for aTrimAfter, we still shouldn't be assigning
+ // justification space to any trailing whitespace.
+ nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
+ mFrag, (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
+ : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
+ end.AdvanceOriginal(trimmed.mLength);
+ gfxSkipCharsIterator realEnd(end);
+
+ Range range(uint32_t(start.GetOriginalOffset()),
+ uint32_t(end.GetOriginalOffset()));
+ nsTArray<JustificationAssignment> assignments;
+ JustificationInfo info = ComputeJustification(range, &assignments);
+
+ auto assign = mFrame->GetJustificationAssignment();
+ auto totalGaps = JustificationUtils::CountGaps(info, assign);
+ if (!totalGaps || assignments.IsEmpty()) {
+ // Nothing to do, nothing is justifiable and we shouldn't have any
+ // justification space assigned
+ return;
+ }
+
+ // Remember that textrun measurements are in the run's orientation,
+ // so its advance "width" is actually a height in vertical writing modes,
+ // corresponding to the inline-direction of the frame.
+ gfxFloat naturalWidth = mTextRun->GetAdvanceWidth(
+ Range(mStart.GetSkippedOffset(), realEnd.GetSkippedOffset()), this);
+ if (mFrame->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
+ naturalWidth += GetHyphenWidth();
+ }
+ nscoord totalSpacing = mFrame->ISize() - naturalWidth;
+ if (totalSpacing <= 0) {
+ // No space available
+ return;
+ }
+
+ assignments[0].mGapsAtStart = assign.mGapsAtStart;
+ assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
+
+ MOZ_ASSERT(mJustificationSpacings.IsEmpty());
+ JustificationApplicationState state(totalGaps, totalSpacing);
+ mJustificationSpacings.SetCapacity(assignments.Length());
+ for (const JustificationAssignment& assign : assignments) {
+ Spacing* spacing = mJustificationSpacings.AppendElement();
+ spacing->mBefore = state.Consume(assign.mGapsAtStart);
+ spacing->mAfter = state.Consume(assign.mGapsAtEnd);
+ }
+}
+
+void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const {
+ if (!mFontMetrics) {
+ if (mWhichTextRun == nsTextFrame::eInflated) {
+ mFontMetrics = mFrame->InflatedFontMetrics();
+ } else {
+ mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
+ }
+ }
+ mFontGroup = mFontMetrics->GetThebesFontGroup();
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsTextFrame::AccessibleType() {
+ if (IsEmpty()) {
+ RenderedText text =
+ GetRenderedText(0, UINT32_MAX, TextOffsetType::OffsetsInContentText,
+ TrailingWhitespace::DontTrim);
+ if (text.mString.IsEmpty()) {
+ return a11y::eNoType;
+ }
+ }
+
+ return a11y::eTextLeafType;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+void nsTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
+ MOZ_ASSERT(aContent->IsText(), "Bogus content!");
+
+ // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
+ // might be invalid if the content was modified while there was no frame
+ if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
+ aContent->RemoveProperty(nsGkAtoms::newline);
+ aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
+ }
+ if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+ aContent->RemoveProperty(nsGkAtoms::flowlength);
+ aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+ }
+
+ // Since our content has a frame now, this flag is no longer needed.
+ aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
+
+ // We're not a continuing frame.
+ // mContentOffset = 0; not necessary since we get zeroed out at init
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+void nsTextFrame::ClearFrameOffsetCache() {
+ // See if we need to remove ourselves from the offset cache
+ if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE)) {
+ nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
+ if (primaryFrame) {
+ // The primary frame might be null here. For example,
+ // nsLineBox::DeleteLineList just destroys the frames in order, which
+ // means that the primary frame is already dead if we're a continuing text
+ // frame, in which case, all of its properties are gone, and we don't need
+ // to worry about deleting this property here.
+ primaryFrame->RemoveProperty(OffsetToFrameProperty());
+ }
+ RemoveStateBits(TEXT_IN_OFFSET_CACHE);
+ }
+}
+
+void nsTextFrame::Destroy(DestroyContext& aContext) {
+ ClearFrameOffsetCache();
+
+ // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
+ // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
+ // type might be changing. Not clear whether it's worth it.
+ ClearTextRuns();
+ if (mNextContinuation) {
+ mNextContinuation->SetPrevInFlow(nullptr);
+ }
+ // Let the base class destroy the frame
+ nsIFrame::Destroy(aContext);
+}
+
+nsTArray<nsTextFrame*>* nsTextFrame::GetContinuations() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Only for use on the primary frame, which has no prev-continuation.
+ MOZ_ASSERT(!GetPrevContinuation());
+ if (!mNextContinuation) {
+ return nullptr;
+ }
+ if (mPropertyFlags & PropertyFlags::Continuations) {
+ return GetProperty(ContinuationsProperty());
+ }
+ size_t count = 0;
+ for (nsIFrame* f = this; f; f = f->GetNextContinuation()) {
+ ++count;
+ }
+ auto* continuations = new nsTArray<nsTextFrame*>;
+ if (continuations->SetCapacity(count, fallible)) {
+ for (nsTextFrame* f = this; f;
+ f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
+ continuations->AppendElement(f);
+ }
+ } else {
+ delete continuations;
+ continuations = nullptr;
+ }
+ AddProperty(ContinuationsProperty(), continuations);
+ mPropertyFlags |= PropertyFlags::Continuations;
+ return continuations;
+}
+
+class nsContinuingTextFrame final : public nsTextFrame {
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
+
+ friend nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) final;
+
+ void Destroy(DestroyContext&) override;
+
+ nsTextFrame* GetPrevContinuation() const final { return mPrevContinuation; }
+
+ void SetPrevContinuation(nsIFrame* aPrevContinuation) final {
+ NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),
+ "setting a prev continuation with incorrect type!");
+ NS_ASSERTION(
+ !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
+ "creating a loop in continuation chain!");
+ mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation);
+ RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ UpdateCachedContinuations();
+ }
+
+ nsTextFrame* GetPrevInFlow() const final {
+ return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation
+ : nullptr;
+ }
+
+ void SetPrevInFlow(nsIFrame* aPrevInFlow) final {
+ NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),
+ "setting a prev in flow with incorrect type!");
+ NS_ASSERTION(
+ !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
+ "creating a loop in continuation chain!");
+ mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow);
+ AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ UpdateCachedContinuations();
+ }
+
+ // Call this helper to update cache after mPrevContinuation is changed.
+ void UpdateCachedContinuations() {
+ nsTextFrame* prevFirst = mFirstContinuation;
+ if (mPrevContinuation) {
+ mFirstContinuation = mPrevContinuation->FirstContinuation();
+ if (mFirstContinuation) {
+ mFirstContinuation->ClearCachedContinuations();
+ }
+ } else {
+ mFirstContinuation = nullptr;
+ }
+ if (mFirstContinuation != prevFirst) {
+ if (prevFirst) {
+ prevFirst->ClearCachedContinuations();
+ }
+ auto* f = static_cast<nsContinuingTextFrame*>(mNextContinuation);
+ while (f) {
+ f->mFirstContinuation = mFirstContinuation;
+ f = static_cast<nsContinuingTextFrame*>(f->mNextContinuation);
+ }
+ }
+ }
+
+ nsIFrame* FirstInFlow() const final;
+ nsTextFrame* FirstContinuation() const final {
+#if DEBUG
+ // If we have a prev-continuation pointer, then our first-continuation
+ // must be the same as that frame's.
+ if (mPrevContinuation) {
+ // If there's a prev-prev, then we can safely cast mPrevContinuation to
+ // an nsContinuingTextFrame and access its mFirstContinuation pointer
+ // directly, to avoid recursively calling FirstContinuation(), leading
+ // to exponentially-slow behavior in the assertion.
+ if (mPrevContinuation->GetPrevContinuation()) {
+ auto* prev = static_cast<nsContinuingTextFrame*>(mPrevContinuation);
+ MOZ_ASSERT(mFirstContinuation == prev->mFirstContinuation);
+ } else {
+ MOZ_ASSERT(mFirstContinuation ==
+ mPrevContinuation->FirstContinuation());
+ }
+ } else {
+ MOZ_ASSERT(!mFirstContinuation);
+ }
+#endif
+ return mFirstContinuation;
+ };
+
+ void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) final;
+ void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) final;
+
+ protected:
+ explicit nsContinuingTextFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsTextFrame(aStyle, aPresContext, kClassID) {}
+
+ nsTextFrame* mPrevContinuation = nullptr;
+ nsTextFrame* mFirstContinuation = nullptr;
+};
+
+void nsContinuingTextFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
+
+ // Hook the frame into the flow
+ nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
+ nsTextFrame* nextContinuation = prev->GetNextContinuation();
+ SetPrevInFlow(aPrevInFlow);
+ aPrevInFlow->SetNextInFlow(this);
+
+ // NOTE: bypassing nsTextFrame::Init!!!
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
+ NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
+ "Creating ContinuingTextFrame, but there is no more content");
+ if (prev->Style() != Style()) {
+ // We're taking part of prev's text, and its style may be different
+ // so clear its textrun which may no longer be valid (and don't set ours)
+ prev->ClearTextRuns();
+ } else {
+ float inflation = prev->GetFontSizeInflation();
+ SetFontSizeInflation(inflation);
+ mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
+ if (inflation != 1.0f) {
+ gfxTextRun* uninflatedTextRun =
+ prev->GetTextRun(nsTextFrame::eNotInflated);
+ if (uninflatedTextRun) {
+ SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
+ }
+ }
+ }
+ if (aPrevInFlow->HasAnyStateBits(NS_FRAME_IS_BIDI)) {
+ FrameBidiData bidiData = aPrevInFlow->GetBidiData();
+ bidiData.precedingControl = kBidiLevelNone;
+ SetProperty(BidiDataProperty(), bidiData);
+
+ if (nextContinuation) {
+ SetNextContinuation(nextContinuation);
+ nextContinuation->SetPrevContinuation(this);
+ // Adjust next-continuations' content offset as needed.
+ while (nextContinuation &&
+ nextContinuation->GetContentOffset() < mContentOffset) {
+#ifdef DEBUG
+ FrameBidiData nextBidiData = nextContinuation->GetBidiData();
+ NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&
+ bidiData.baseLevel == nextBidiData.baseLevel,
+ "stealing text from different type of BIDI continuation");
+ MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,
+ "There shouldn't be any virtual bidi formatting character "
+ "between continuations");
+#endif
+ nextContinuation->mContentOffset = mContentOffset;
+ nextContinuation = nextContinuation->GetNextContinuation();
+ }
+ }
+ AddStateBits(NS_FRAME_IS_BIDI);
+ } // prev frame is bidi
+}
+
+void nsContinuingTextFrame::Destroy(DestroyContext& aContext) {
+ ClearFrameOffsetCache();
+
+ // The text associated with this frame will become associated with our
+ // prev-continuation. If that means the text has changed style, then
+ // we need to wipe out the text run for the text.
+ // Note that mPrevContinuation can be null if we're destroying the whole
+ // frame chain from the start to the end.
+ // If this frame is mentioned in the userData for a textrun (say
+ // because there's a direction change at the start of this frame), then
+ // we have to clear the textrun because we're going away and the
+ // textrun had better not keep a dangling reference to us.
+ if (IsInTextRunUserData() ||
+ (mPrevContinuation && mPrevContinuation->Style() != Style())) {
+ ClearTextRuns();
+ // Clear the previous continuation's text run also, so that it can rebuild
+ // the text run to include our text.
+ if (mPrevContinuation) {
+ mPrevContinuation->ClearTextRuns();
+ }
+ }
+ nsSplittableFrame::RemoveFromFlow(this);
+ // Let the base class destroy the frame
+ nsIFrame::Destroy(aContext);
+}
+
+nsIFrame* nsContinuingTextFrame::FirstInFlow() const {
+ // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
+ nsIFrame *firstInFlow,
+ *previous = const_cast<nsIFrame*>(static_cast<const nsIFrame*>(this));
+ do {
+ firstInFlow = previous;
+ previous = firstInFlow->GetPrevInFlow();
+ } while (previous);
+ MOZ_ASSERT(firstInFlow, "post-condition failed");
+ return firstInFlow;
+}
+
+// XXX Do we want to do all the work for the first-in-flow or do the
+// work for each part? (Be careful of first-letter / first-line, though,
+// especially first-line!) Doing all the work on the first-in-flow has
+// the advantage of avoiding the potential for incremental reflow bugs,
+// but depends on our maintining the frame tree in reasonable ways even
+// for edge cases (block-within-inline splits, nextBidi, etc.)
+
+// XXX We really need to make :first-letter happen during frame
+// construction.
+
+// Needed for text frames in XUL.
+/* virtual */
+nscoord nsTextFrame::GetMinISize(gfxContext* aRenderingContext) {
+ return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
+}
+
+// Needed for text frames in XUL.
+/* virtual */
+nscoord nsTextFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
+}
+
+/* virtual */
+void nsContinuingTextFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) {
+ // Do nothing, since the first-in-flow accounts for everything.
+}
+
+/* virtual */
+void nsContinuingTextFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) {
+ // Do nothing, since the first-in-flow accounts for everything.
+}
+
+//----------------------------------------------------------------------
+
+#if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
+static void VerifyNotDirty(nsFrameState state) {
+ bool isZero = state & NS_FRAME_FIRST_REFLOW;
+ bool isDirty = state & NS_FRAME_IS_DIRTY;
+ if (!isZero && isDirty) {
+ NS_WARNING("internal offsets may be out-of-sync");
+ }
+}
+# define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state)
+#else
+# define DEBUG_VERIFY_NOT_DIRTY(state)
+#endif
+
+nsIFrame* NS_NewTextFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsTextFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
+
+nsIFrame* NS_NewContinuingTextFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsContinuingTextFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
+
+nsTextFrame::~nsTextFrame() = default;
+
+nsIFrame::Cursor nsTextFrame::GetCursor(const nsPoint& aPoint) {
+ StyleCursorKind kind = StyleUI()->Cursor().keyword;
+ if (kind == StyleCursorKind::Auto) {
+ if (!IsSelectable(nullptr)) {
+ kind = StyleCursorKind::Default;
+ } else {
+ kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText
+ : StyleCursorKind::Text;
+ }
+ }
+ return Cursor{kind, AllowCustomCursorImage::Yes};
+}
+
+nsTextFrame* nsTextFrame::LastInFlow() const {
+ nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
+ while (lastInFlow->GetNextInFlow()) {
+ lastInFlow = lastInFlow->GetNextInFlow();
+ }
+ MOZ_ASSERT(lastInFlow, "post-condition failed");
+ return lastInFlow;
+}
+
+nsTextFrame* nsTextFrame::LastContinuation() const {
+ nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
+ while (lastContinuation->mNextContinuation) {
+ lastContinuation = lastContinuation->mNextContinuation;
+ }
+ MOZ_ASSERT(lastContinuation, "post-condition failed");
+ return lastContinuation;
+}
+
+bool nsTextFrame::ShouldSuppressLineBreak() const {
+ // If the parent frame of the text frame is ruby content box, it must
+ // suppress line break inside. This check is necessary, because when
+ // a whitespace is only contained by pseudo ruby frames, its style
+ // context won't have SuppressLineBreak bit set.
+ if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) {
+ return true;
+ }
+ return Style()->ShouldSuppressLineBreak();
+}
+
+void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ InvalidateSelectionState();
+
+ if (IsInSVGTextSubtree()) {
+ nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
+ GetParent(), LayoutFrameType::SVGText);
+ svgTextFrame->InvalidateFrame();
+ return;
+ }
+ nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
+}
+
+void nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ InvalidateSelectionState();
+
+ if (IsInSVGTextSubtree()) {
+ nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
+ GetParent(), LayoutFrameType::SVGText);
+ svgTextFrame->InvalidateFrame();
+ return;
+ }
+ nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
+ aRebuildDisplayItems);
+}
+
+gfxTextRun* nsTextFrame::GetUninflatedTextRun() const {
+ return GetProperty(UninflatedTextRunProperty());
+}
+
+void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
+ float aInflation) {
+ NS_ASSERTION(aTextRun, "must have text run");
+
+ // Our inflated text run is always stored in mTextRun. In the cases
+ // where our current inflation is not 1.0, however, we store two text
+ // runs, and the uninflated one goes in a frame property. We never
+ // store a single text run in both.
+ if (aWhichTextRun == eInflated) {
+ if (HasFontSizeInflation() && aInflation == 1.0f) {
+ // FIXME: Probably shouldn't do this within each SetTextRun
+ // method, but it doesn't hurt.
+ ClearTextRun(nullptr, nsTextFrame::eNotInflated);
+ }
+ SetFontSizeInflation(aInflation);
+ } else {
+ MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation");
+ if (HasFontSizeInflation()) {
+ // Setting the property will not automatically increment the textrun's
+ // reference count, so we need to do it here.
+ aTextRun->AddRef();
+ SetProperty(UninflatedTextRunProperty(), aTextRun);
+ return;
+ }
+ // fall through to setting mTextRun
+ }
+
+ mTextRun = aTextRun;
+
+ // FIXME: Add assertions testing the relationship between
+ // GetFontSizeInflation() and whether we have an uninflated text run
+ // (but be aware that text runs can go away).
+}
+
+bool nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) {
+ if (aTextRun == mTextRun) {
+ mTextRun = nullptr;
+ mFontMetrics = nullptr;
+ return true;
+ }
+ if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION) &&
+ GetProperty(UninflatedTextRunProperty()) == aTextRun) {
+ RemoveProperty(UninflatedTextRunProperty());
+ return true;
+ }
+ return false;
+}
+
+void nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
+ TextRunType aWhichTextRun) {
+ RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
+ if (!textRun) {
+ return;
+ }
+
+ if (aWhichTextRun == nsTextFrame::eInflated) {
+ mFontMetrics = nullptr;
+ }
+
+ DebugOnly<bool> checkmTextrun = textRun == mTextRun;
+ UnhookTextRunFromFrames(textRun, aStartContinuation);
+ MOZ_ASSERT(checkmTextrun ? !mTextRun
+ : !GetProperty(UninflatedTextRunProperty()));
+}
+
+void nsTextFrame::DisconnectTextRuns() {
+ MOZ_ASSERT(!IsInTextRunUserData(),
+ "Textrun mentions this frame in its user data so we can't just "
+ "disconnect");
+ mTextRun = nullptr;
+ if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION)) {
+ RemoveProperty(UninflatedTextRunProperty());
+ }
+}
+
+void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength) {
+ MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree());
+
+ MarkIntrinsicISizesDirty();
+
+ // This is to avoid making a new Reflow request in CharacterDataChanged:
+ for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
+ f->MarkSubtreeDirty();
+ f->mReflowRequestedForCharDataChange = true;
+ }
+
+ // Pretend that all the text changed.
+ CharacterDataChangeInfo info;
+ info.mAppend = false;
+ info.mChangeStart = 0;
+ info.mChangeEnd = aOldLength;
+ info.mReplaceLength = GetContent()->TextLength();
+ CharacterDataChanged(info);
+}
+
+nsresult nsTextFrame::CharacterDataChanged(
+ const CharacterDataChangeInfo& aInfo) {
+ if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
+ mContent->RemoveProperty(nsGkAtoms::newline);
+ mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
+ }
+ if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+ mContent->RemoveProperty(nsGkAtoms::flowlength);
+ mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+ }
+
+ // Find the first frame whose text has changed. Frames that are entirely
+ // before the text change are completely unaffected.
+ nsTextFrame* next;
+ nsTextFrame* textFrame = this;
+ while (true) {
+ next = textFrame->GetNextContinuation();
+ if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart)) {
+ break;
+ }
+ textFrame = next;
+ }
+
+ int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength;
+
+ // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
+ // had already received an earlier FrameNeedsReflow call).
+ // (For subsequent frames with this same parent, we can just set their
+ // dirty bit without bothering to call FrameNeedsReflow again.)
+ nsIFrame* lastDirtiedFrameParent = nullptr;
+
+ mozilla::PresShell* presShell = PresShell();
+ do {
+ // textFrame contained deleted text (or the insertion point,
+ // if this was a pure insertion).
+ textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS);
+ textFrame->ClearTextRuns();
+
+ nsIFrame* parentOfTextFrame = textFrame->GetParent();
+ bool areAncestorsAwareOfReflowRequest = false;
+ if (lastDirtiedFrameParent == parentOfTextFrame) {
+ // An earlier iteration of this loop already called
+ // FrameNeedsReflow for a sibling of |textFrame|.
+ areAncestorsAwareOfReflowRequest = true;
+ } else {
+ lastDirtiedFrameParent = parentOfTextFrame;
+ }
+
+ if (textFrame->mReflowRequestedForCharDataChange) {
+ // We already requested a reflow for this frame; nothing to do.
+ MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),
+ "mReflowRequestedForCharDataChange should only be set "
+ "on dirty frames");
+ } else {
+ // Make sure textFrame is queued up for a reflow. Also set a flag so we
+ // don't waste time doing this again in repeated calls to this method.
+ textFrame->mReflowRequestedForCharDataChange = true;
+ if (!areAncestorsAwareOfReflowRequest) {
+ // Ask the parent frame to reflow me.
+ presShell->FrameNeedsReflow(
+ textFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+ } else {
+ // We already called FrameNeedsReflow on behalf of an earlier sibling,
+ // so we can just mark this frame as dirty and don't need to bother
+ // telling its ancestors.
+ // Note: if the parent is a block, we're cheating here because we should
+ // be marking our line dirty, but we're not. nsTextFrame::SetLength will
+ // do that when it gets called during reflow.
+ textFrame->MarkSubtreeDirty();
+ }
+ }
+ textFrame->InvalidateFrame();
+
+ // Below, frames that start after the deleted text will be adjusted so that
+ // their offsets move with the trailing unchanged text. If this change
+ // deletes more text than it inserts, those frame offsets will decrease.
+ // We need to maintain the invariant that mContentOffset is non-decreasing
+ // along the continuation chain. So we need to ensure that frames that
+ // started in the deleted text are all still starting before the
+ // unchanged text.
+ if (textFrame->mContentOffset > endOfChangedText) {
+ textFrame->mContentOffset = endOfChangedText;
+ }
+
+ textFrame = textFrame->GetNextContinuation();
+ } while (textFrame &&
+ textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd));
+
+ // This is how much the length of the string changed by --- i.e.,
+ // how much the trailing unchanged text moved.
+ int32_t sizeChange =
+ aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd;
+
+ if (sizeChange) {
+ // Fix the offsets of the text frames that start in the trailing
+ // unchanged text.
+ while (textFrame) {
+ textFrame->mContentOffset += sizeChange;
+ // XXX we could rescue some text runs by adjusting their user data
+ // to reflect the change in DOM offsets
+ textFrame->ClearTextRuns();
+ textFrame = textFrame->GetNextContinuation();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty, float)
+
+float nsTextFrame::GetTextCombineScaleFactor(nsTextFrame* aFrame) {
+ float factor = aFrame->GetProperty(TextCombineScaleFactorProperty());
+ return factor ? factor : 1.0f;
+}
+
+void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) {
+ return;
+ }
+
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
+
+ const nsStyleText* st = StyleText();
+ bool isTextTransparent =
+ NS_GET_A(st->mWebkitTextFillColor.CalcColor(this)) == 0 &&
+ NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this)) == 0;
+ if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) ||
+ (isTextTransparent && !StyleText()->HasTextShadow())) &&
+ aBuilder->IsForPainting() && !IsInSVGTextSubtree()) {
+ if (!IsSelected()) {
+ TextDecorations textDecs;
+ GetTextDecorations(PresContext(), eResolvedColors, textDecs);
+ if (!textDecs.HasDecorationLines()) {
+ if (auto* currentPresContext = aBuilder->CurrentPresContext()) {
+ currentPresContext->SetBuiltInvisibleText();
+ }
+ return;
+ }
+ }
+ }
+
+ aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this);
+}
+
+UniquePtr<SelectionDetails> nsTextFrame::GetSelectionDetails() {
+ const nsFrameSelection* frameSelection = GetConstFrameSelection();
+ if (frameSelection->IsInTableSelectionMode()) {
+ return nullptr;
+ }
+ UniquePtr<SelectionDetails> details = frameSelection->LookUpSelection(
+ mContent, GetContentOffset(), GetContentLength(), false);
+ for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
+ sd->mStart += mContentOffset;
+ sd->mEnd += mContentOffset;
+ }
+ return details;
+}
+
+static void PaintSelectionBackground(
+ DrawTarget& aDrawTarget, nscolor aColor, const LayoutDeviceRect& aDirtyRect,
+ const LayoutDeviceRect& aRect, nsTextFrame::DrawPathCallbacks* aCallbacks) {
+ Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect();
+ MaybeSnapToDevicePixels(rect, aDrawTarget);
+
+ if (aCallbacks) {
+ aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget);
+ } else {
+ ColorPattern color(ToDeviceColor(aColor));
+ aDrawTarget.FillRect(rect, color);
+ }
+}
+
+// Attempt to get the LineBaselineOffset property of aChildFrame
+// If not set, calculate this value for all child frames of aBlockFrame
+static nscoord LazyGetLineBaselineOffset(nsIFrame* aChildFrame,
+ nsBlockFrame* aBlockFrame) {
+ bool offsetFound;
+ nscoord offset =
+ aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound);
+
+ if (!offsetFound) {
+ for (const auto& line : aBlockFrame->Lines()) {
+ if (line.IsInline()) {
+ int32_t n = line.GetChildCount();
+ nscoord lineBaseline = line.BStart() + line.GetLogicalAscent();
+ for (auto* lineFrame = line.mFirstChild; n > 0;
+ lineFrame = lineFrame->GetNextSibling(), --n) {
+ offset = lineBaseline - lineFrame->GetNormalPosition().y;
+ lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset);
+ }
+ }
+ }
+ return aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(),
+ &offsetFound);
+ } else {
+ return offset;
+ }
+}
+
+static bool IsUnderlineRight(const ComputedStyle& aStyle) {
+ // Check for 'left' or 'right' explicitly specified in the property;
+ // if neither is there, we use auto positioning based on lang.
+ const auto position = aStyle.StyleText()->mTextUnderlinePosition;
+ if (position.IsLeft()) {
+ return false;
+ }
+ if (position.IsRight()) {
+ return true;
+ }
+ // If neither 'left' nor 'right' was specified, check the language.
+ nsAtom* langAtom = aStyle.StyleFont()->mLanguage;
+ if (!langAtom) {
+ return false;
+ }
+ nsDependentAtomString langStr(langAtom);
+ return (StringBeginsWith(langStr, u"ja"_ns) ||
+ StringBeginsWith(langStr, u"ko"_ns)) &&
+ (langStr.Length() == 2 || langStr[2] == '-');
+}
+
+void nsTextFrame::GetTextDecorations(
+ nsPresContext* aPresContext,
+ nsTextFrame::TextDecorationColorResolution aColorResolution,
+ nsTextFrame::TextDecorations& aDecorations) {
+ const nsCompatibility compatMode = aPresContext->CompatibilityMode();
+
+ bool useOverride = false;
+ nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
+
+ bool nearestBlockFound = false;
+ // Use writing mode of parent frame for orthogonal text frame to work.
+ // See comment in nsTextFrame::DrawTextRunAndDecorations.
+ WritingMode wm = GetParent()->GetWritingMode();
+ bool vertical = wm.IsVertical();
+
+ nscoord ascent = GetLogicalBaseline(wm);
+ // physicalBlockStartOffset represents the offset from our baseline
+ // to f's physical block start, which is top in horizontal writing
+ // mode, and left in vertical writing modes, in our coordinate space.
+ // This physical block start is logical block start in most cases,
+ // but for vertical-rl, it is logical block end, and consequently in
+ // that case, it starts from the descent instead of ascent.
+ nscoord physicalBlockStartOffset =
+ wm.IsVerticalRL() ? GetSize().width - ascent : ascent;
+ // baselineOffset represents the offset from our baseline to f's baseline or
+ // the nearest block's baseline, in our coordinate space, whichever is closest
+ // during the particular iteration
+ nscoord baselineOffset = 0;
+
+ for (nsIFrame *f = this, *fChild = nullptr; f;
+ fChild = f, f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
+ ComputedStyle* const context = f->Style();
+ if (!context->HasTextDecorationLines()) {
+ break;
+ }
+
+ if (context->GetPseudoType() == PseudoStyleType::marker &&
+ (context->StyleList()->mListStylePosition ==
+ StyleListStylePosition::Outside ||
+ !context->StyleDisplay()->IsInlineOutsideStyle())) {
+ // Outside ::marker pseudos, and inside markers that aren't inlines, don't
+ // have text decorations.
+ break;
+ }
+
+ const nsStyleTextReset* const styleTextReset = context->StyleTextReset();
+ const StyleTextDecorationLine textDecorations =
+ styleTextReset->mTextDecorationLine;
+
+ if (!useOverride &&
+ (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) {
+ // This handles the <a href="blah.html"><font color="green">La
+ // la la</font></a> case. The link underline should be green.
+ useOverride = true;
+ overrideColor =
+ nsLayoutUtils::GetColor(f, &nsStyleTextReset::mTextDecorationColor);
+ }
+
+ nsBlockFrame* fBlock = do_QueryFrame(f);
+ const bool firstBlock = !nearestBlockFound && fBlock;
+
+ // Not updating positions once we hit a parent block is equivalent to
+ // the CSS 2.1 spec that blocks should propagate decorations down to their
+ // children (albeit the style should be preserved)
+ // However, if we're vertically aligned within a block, then we need to
+ // recover the correct baseline from the line by querying the FrameProperty
+ // that should be set (see nsLineLayout::VerticalAlignLine).
+ if (firstBlock) {
+ // At this point, fChild can't be null since TextFrames can't be blocks
+ Maybe<StyleVerticalAlignKeyword> verticalAlign =
+ fChild->VerticalAlignEnum();
+ if (verticalAlign != Some(StyleVerticalAlignKeyword::Baseline)) {
+ // Since offset is the offset in the child's coordinate space, we have
+ // to undo the accumulation to bring the transform out of the block's
+ // coordinate space
+ const nscoord lineBaselineOffset =
+ LazyGetLineBaselineOffset(fChild, fBlock);
+
+ baselineOffset = physicalBlockStartOffset - lineBaselineOffset -
+ (vertical ? fChild->GetNormalPosition().x
+ : fChild->GetNormalPosition().y);
+ }
+ } else if (!nearestBlockFound) {
+ // offset here is the offset from f's baseline to f's top/left
+ // boundary. It's descent for vertical-rl, and ascent otherwise.
+ nscoord offset = wm.IsVerticalRL()
+ ? f->GetSize().width - f->GetLogicalBaseline(wm)
+ : f->GetLogicalBaseline(wm);
+ baselineOffset = physicalBlockStartOffset - offset;
+ }
+
+ nearestBlockFound = nearestBlockFound || firstBlock;
+ physicalBlockStartOffset +=
+ vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
+
+ const auto style = styleTextReset->mTextDecorationStyle;
+ if (textDecorations) {
+ nscolor color;
+ if (useOverride) {
+ color = overrideColor;
+ } else if (IsInSVGTextSubtree()) {
+ // XXX We might want to do something with text-decoration-color when
+ // painting SVG text, but it's not clear what we should do. We
+ // at least need SVG text decorations to paint with 'fill' if
+ // text-decoration-color has its initial value currentColor.
+ // We could choose to interpret currentColor as "currentFill"
+ // for SVG text, and have e.g. text-decoration-color:red to
+ // override the fill paint of the decoration.
+ color = aColorResolution == eResolvedColors
+ ? nsLayoutUtils::GetColor(f, &nsStyleSVG::mFill)
+ : NS_SAME_AS_FOREGROUND_COLOR;
+ } else {
+ color =
+ nsLayoutUtils::GetColor(f, &nsStyleTextReset::mTextDecorationColor);
+ }
+
+ bool swapUnderlineAndOverline =
+ wm.IsCentralBaseline() && IsUnderlineRight(*context);
+ const auto kUnderline = swapUnderlineAndOverline
+ ? StyleTextDecorationLine::OVERLINE
+ : StyleTextDecorationLine::UNDERLINE;
+ const auto kOverline = swapUnderlineAndOverline
+ ? StyleTextDecorationLine::UNDERLINE
+ : StyleTextDecorationLine::OVERLINE;
+
+ const nsStyleText* const styleText = context->StyleText();
+ if (textDecorations & kUnderline) {
+ aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration(
+ f, baselineOffset, styleText->mTextUnderlinePosition,
+ styleText->mTextUnderlineOffset,
+ styleTextReset->mTextDecorationThickness, color, style));
+ }
+ if (textDecorations & kOverline) {
+ aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration(
+ f, baselineOffset, styleText->mTextUnderlinePosition,
+ styleText->mTextUnderlineOffset,
+ styleTextReset->mTextDecorationThickness, color, style));
+ }
+ if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) {
+ aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration(
+ f, baselineOffset, styleText->mTextUnderlinePosition,
+ styleText->mTextUnderlineOffset,
+ styleTextReset->mTextDecorationThickness, color, style));
+ }
+ }
+
+ // In all modes, if we're on an inline-block/table/grid/flex (or
+ // -moz-inline-box), we're done.
+ // If we're on a ruby frame other than ruby text container, we
+ // should continue.
+ mozilla::StyleDisplay display = f->GetDisplay();
+ if (!display.IsInlineFlow() &&
+ (!display.IsRuby() ||
+ display == mozilla::StyleDisplay::RubyTextContainer) &&
+ display.IsInlineOutside()) {
+ break;
+ }
+
+ // In quirks mode, if we're on an HTML table element, we're done.
+ if (compatMode == eCompatibility_NavQuirks &&
+ f->GetContent()->IsHTMLElement(nsGkAtoms::table)) {
+ break;
+ }
+
+ // If we're on an absolutely-positioned element or a floating
+ // element, we're done.
+ if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
+ break;
+ }
+
+ // If we're an outer <svg> element, which is classified as an atomic
+ // inline-level element, we're done.
+ if (f->IsSVGOuterSVGFrame()) {
+ break;
+ }
+ }
+}
+
+static float GetInflationForTextDecorations(nsIFrame* aFrame,
+ nscoord aInflationMinFontSize) {
+ if (aFrame->IsInSVGTextSubtree()) {
+ auto* container =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
+ MOZ_ASSERT(container);
+ return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
+ }
+ return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
+}
+
+struct EmphasisMarkInfo {
+ RefPtr<gfxTextRun> textRun;
+ gfxFloat advance;
+ gfxFloat baselineOffset;
+};
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
+
+static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle& aStyle,
+ nsAString& aOut) {
+ MOZ_ASSERT(!aStyle.IsNone());
+ if (aStyle.IsString()) {
+ nsDependentCSubstring string = aStyle.AsString().AsString();
+ AppendUTF8toUTF16(string, aOut);
+ return;
+ }
+ const auto& keyword = aStyle.AsKeyword();
+ const bool fill = keyword.fill == StyleTextEmphasisFillMode::Filled;
+ switch (keyword.shape) {
+ case StyleTextEmphasisShapeKeyword::Dot:
+ return aOut.AppendLiteral(fill ? u"\u2022" : u"\u25e6");
+ case StyleTextEmphasisShapeKeyword::Circle:
+ return aOut.AppendLiteral(fill ? u"\u25cf" : u"\u25cb");
+ case StyleTextEmphasisShapeKeyword::DoubleCircle:
+ return aOut.AppendLiteral(fill ? u"\u25c9" : u"\u25ce");
+ case StyleTextEmphasisShapeKeyword::Triangle:
+ return aOut.AppendLiteral(fill ? u"\u25b2" : u"\u25b3");
+ case StyleTextEmphasisShapeKeyword::Sesame:
+ return aOut.AppendLiteral(fill ? u"\ufe45" : u"\ufe46");
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape");
+ }
+}
+
+static already_AddRefed<gfxTextRun> GenerateTextRunForEmphasisMarks(
+ nsTextFrame* aFrame, gfxFontGroup* aFontGroup,
+ ComputedStyle* aComputedStyle, const nsStyleText* aStyleText) {
+ nsAutoString string;
+ ComputeTextEmphasisStyleString(aStyleText->mTextEmphasisStyle, string);
+
+ RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
+ auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
+ gfx::ShapedTextFlags flags =
+ nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
+ if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
+ // The emphasis marks should always be rendered upright per spec.
+ flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ }
+ return aFontGroup->MakeTextRun<char16_t>(string.get(), string.Length(), dt,
+ appUnitsPerDevUnit, flags,
+ nsTextFrameUtils::Flags(), nullptr);
+}
+
+static nsRubyFrame* FindFurthestInlineRubyAncestor(nsTextFrame* aFrame) {
+ nsRubyFrame* rubyFrame = nullptr;
+ for (nsIFrame* frame = aFrame->GetParent();
+ frame && frame->IsLineParticipant(); frame = frame->GetParent()) {
+ if (frame->IsRubyFrame()) {
+ rubyFrame = static_cast<nsRubyFrame*>(frame);
+ }
+ }
+ return rubyFrame;
+}
+
+nsRect nsTextFrame::UpdateTextEmphasis(WritingMode aWM,
+ PropertyProvider& aProvider) {
+ const nsStyleText* styleText = StyleText();
+ if (!styleText->HasEffectiveTextEmphasis()) {
+ RemoveProperty(EmphasisMarkProperty());
+ return nsRect();
+ }
+
+ ComputedStyle* computedStyle = Style();
+ bool isTextCombined = computedStyle->IsTextCombined();
+ if (isTextCombined) {
+ computedStyle = GetParent()->Style();
+ }
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
+ computedStyle, PresContext(), GetFontSizeInflation());
+ EmphasisMarkInfo* info = new EmphasisMarkInfo;
+ info->textRun = GenerateTextRunForEmphasisMarks(
+ this, fm->GetThebesFontGroup(), computedStyle, styleText);
+ info->advance = info->textRun->GetAdvanceWidth();
+
+ // Calculate the baseline offset
+ LogicalSide side = styleText->TextEmphasisSide(aWM);
+ LogicalSize frameSize = GetLogicalSize(aWM);
+ // The overflow rect is inflated in the inline direction by half
+ // advance of the emphasis mark on each side, so that even if a mark
+ // is drawn for a zero-width character, it won't be clipped.
+ LogicalRect overflowRect(aWM, -info->advance / 2,
+ /* BStart to be computed below */ 0,
+ frameSize.ISize(aWM) + info->advance,
+ fm->MaxAscent() + fm->MaxDescent());
+ RefPtr<nsFontMetrics> baseFontMetrics =
+ isTextCombined
+ ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
+ : do_AddRef(aProvider.GetFontMetrics());
+ // When the writing mode is vertical-lr the line is inverted, and thus
+ // the ascent and descent are swapped.
+ nscoord absOffset = (side == eLogicalSideBStart) != aWM.IsLineInverted()
+ ? baseFontMetrics->MaxAscent() + fm->MaxDescent()
+ : baseFontMetrics->MaxDescent() + fm->MaxAscent();
+ RubyBlockLeadings leadings;
+ if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) {
+ leadings = ruby->GetBlockLeadings();
+ }
+ if (side == eLogicalSideBStart) {
+ info->baselineOffset = -absOffset - leadings.mStart;
+ overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart;
+ } else {
+ MOZ_ASSERT(side == eLogicalSideBEnd);
+ info->baselineOffset = absOffset + leadings.mEnd;
+ overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd;
+ }
+ // If text combined, fix the gap between the text frame and its parent.
+ if (isTextCombined) {
+ nscoord gap = (baseFontMetrics->MaxHeight() - frameSize.BSize(aWM)) / 2;
+ overflowRect.BStart(aWM) += gap * (side == eLogicalSideBStart ? -1 : 1);
+ }
+
+ SetProperty(EmphasisMarkProperty(), info);
+ return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
+}
+
+// helper function for implementing text-decoration-thickness
+// https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property
+// Returns the thickness in device pixels.
+static gfxFloat ComputeDecorationLineThickness(
+ const StyleTextDecorationLength& aThickness, const gfxFloat aAutoValue,
+ const gfxFont::Metrics& aFontMetrics, const gfxFloat aAppUnitsPerDevPixel,
+ const nsIFrame* aFrame) {
+ if (aThickness.IsAuto()) {
+ return aAutoValue;
+ }
+
+ if (aThickness.IsFromFont()) {
+ return aFontMetrics.underlineSize;
+ }
+ auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
+ return aThickness.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
+}
+
+// Helper function for implementing text-underline-offset and -position
+// https://drafts.csswg.org/css-text-decor-4/#underline-offset
+// Returns the offset in device pixels.
+static gfxFloat ComputeDecorationLineOffset(
+ StyleTextDecorationLine aLineType,
+ const StyleTextUnderlinePosition& aPosition,
+ const LengthPercentageOrAuto& aOffset, const gfxFont::Metrics& aFontMetrics,
+ const gfxFloat aAppUnitsPerDevPixel, const nsIFrame* aFrame,
+ bool aIsCentralBaseline, bool aSwappedUnderline) {
+ // Em value to use if we need to resolve a percentage length.
+ auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
+ // If we're in vertical-upright typographic mode, we need to compute the
+ // offset of the decoration line from the default central baseline.
+ if (aIsCentralBaseline) {
+ // Line-through simply goes at the (central) baseline.
+ if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
+ return 0;
+ }
+
+ // Compute "zero position" for the under- or overline.
+ gfxFloat zeroPos = 0.5 * aFontMetrics.emHeight;
+
+ // aOffset applies to underline only; for overline (or offset:auto) we use
+ // a somewhat arbitrary offset of half the font's (horziontal-mode) value
+ // for underline-offset, to get a little bit of separation between glyph
+ // edges and the line in typical cases.
+ // If we have swapped under-/overlines for text-underline-position:right,
+ // we need to take account of this to determine which decoration lines are
+ // "real" underlines which should respect the text-underline-* values.
+ bool isUnderline =
+ (aLineType == StyleTextDecorationLine::UNDERLINE) != aSwappedUnderline;
+ gfxFloat offset =
+ isUnderline && !aOffset.IsAuto()
+ ? aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel
+ : aFontMetrics.underlineOffset * -0.5;
+
+ // Direction of the decoration line's offset from the central baseline.
+ gfxFloat dir = aLineType == StyleTextDecorationLine::OVERLINE ? 1.0 : -1.0;
+ return dir * (zeroPos + offset);
+ }
+
+ // Compute line offset for horizontal typographic mode.
+ if (aLineType == StyleTextDecorationLine::UNDERLINE) {
+ if (aPosition.IsFromFont()) {
+ gfxFloat zeroPos = aFontMetrics.underlineOffset;
+ gfxFloat offset =
+ aOffset.IsAuto()
+ ? 0
+ : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
+ return zeroPos - offset;
+ }
+
+ if (aPosition.IsUnder()) {
+ gfxFloat zeroPos = -aFontMetrics.maxDescent;
+ gfxFloat offset =
+ aOffset.IsAuto()
+ ? -0.5 * aFontMetrics.underlineOffset
+ : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
+ return zeroPos - offset;
+ }
+
+ // text-underline-position must be 'auto', so zero position is the
+ // baseline and 'auto' offset will apply the font's underline-offset.
+ //
+ // If offset is `auto`, we clamp the offset (in horizontal typographic mode)
+ // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate
+ // skip-ink issues with fonts that leave the underlineOffset field as zero.
+ MOZ_ASSERT(aPosition.IsAuto());
+ return aOffset.IsAuto() ? std::min(aFontMetrics.underlineOffset,
+ -aFontMetrics.emHeight / 16.0)
+ : -aOffset.AsLengthPercentage().Resolve(em) /
+ aAppUnitsPerDevPixel;
+ }
+
+ if (aLineType == StyleTextDecorationLine::OVERLINE) {
+ return aFontMetrics.maxAscent;
+ }
+
+ if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
+ return aFontMetrics.strikeoutOffset;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("unknown decoration line type");
+ return 0;
+}
+
+void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
+ nsIFrame* aBlock,
+ PropertyProvider& aProvider,
+ nsRect* aInkOverflowRect,
+ bool aIncludeTextDecorations,
+ bool aIncludeShadows) {
+ const WritingMode wm = GetWritingMode();
+ bool verticalRun = mTextRun->IsVertical();
+ const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
+
+ if (IsFloatingFirstLetterChild()) {
+ bool inverted = wm.IsLineInverted();
+ // The underline/overline drawable area must be contained in the overflow
+ // rect when this is in floating first letter frame at *both* modes.
+ // In this case, aBlock is the ::first-letter frame.
+ auto decorationStyle =
+ aBlock->Style()->StyleTextReset()->mTextDecorationStyle;
+ // If the style is none, let's include decoration line rect as solid style
+ // since changing the style from none to solid/dotted/dashed doesn't cause
+ // reflow.
+ if (decorationStyle == StyleTextDecorationStyle::None) {
+ decorationStyle = StyleTextDecorationStyle::Solid;
+ }
+ nsCSSRendering::DecorationRectParams params;
+
+ bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
+ nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
+ RefPtr<gfxFont> font =
+ fontMetrics->GetThebesFontGroup()->GetFirstValidFont();
+ const gfxFont::Metrics& metrics =
+ font->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal);
+
+ params.defaultLineThickness = metrics.underlineSize;
+ params.lineSize.height = ComputeDecorationLineThickness(
+ aBlock->Style()->StyleTextReset()->mTextDecorationThickness,
+ params.defaultLineThickness, metrics, appUnitsPerDevUnit, this);
+
+ const auto* styleText = aBlock->StyleText();
+ bool swapUnderline =
+ wm.IsCentralBaseline() && IsUnderlineRight(*aBlock->Style());
+ params.offset = ComputeDecorationLineOffset(
+ StyleTextDecorationLine::UNDERLINE, styleText->mTextUnderlinePosition,
+ styleText->mTextUnderlineOffset, metrics, appUnitsPerDevUnit, this,
+ wm.IsCentralBaseline(), swapUnderline);
+
+ nscoord maxAscent =
+ inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent();
+
+ Float gfxWidth =
+ (verticalRun ? aInkOverflowRect->height : aInkOverflowRect->width) /
+ appUnitsPerDevUnit;
+ params.lineSize.width = gfxWidth;
+ params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
+ params.style = decorationStyle;
+ params.vertical = verticalRun;
+ params.sidewaysLeft = mTextRun->IsSidewaysLeft();
+ params.decoration = StyleTextDecorationLine::UNDERLINE;
+ nsRect underlineRect =
+ nsCSSRendering::GetTextDecorationRect(aPresContext, params);
+
+ // TODO(jfkthame):
+ // Should we actually be calling ComputeDecorationLineOffset again here?
+ params.offset = maxAscent / appUnitsPerDevUnit;
+ params.decoration = StyleTextDecorationLine::OVERLINE;
+ nsRect overlineRect =
+ nsCSSRendering::GetTextDecorationRect(aPresContext, params);
+
+ aInkOverflowRect->UnionRect(*aInkOverflowRect, underlineRect);
+ aInkOverflowRect->UnionRect(*aInkOverflowRect, overlineRect);
+
+ // XXX If strikeoutSize is much thicker than the underlineSize, it may
+ // cause overflowing from the overflow rect. However, such case
+ // isn't realistic, we don't need to compute it now.
+ }
+ if (aIncludeTextDecorations) {
+ // Use writing mode of parent frame for orthogonal text frame to
+ // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
+ WritingMode parentWM = GetParent()->GetWritingMode();
+ bool verticalDec = parentWM.IsVertical();
+ bool useVerticalMetrics =
+ verticalDec != verticalRun
+ ? verticalDec
+ : verticalRun && mTextRun->UseCenterBaseline();
+
+ // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
+ // style and position, they can be drawn at virtually any y-offset, so
+ // maxima and minima are required to reliably generate the rectangle for
+ // them
+ TextDecorations textDecs;
+ GetTextDecorations(aPresContext, eResolvedColors, textDecs);
+ if (textDecs.HasDecorationLines()) {
+ nscoord inflationMinFontSize =
+ nsLayoutUtils::InflationMinFontSizeFor(aBlock);
+
+ const nscoord measure = verticalDec ? GetSize().height : GetSize().width;
+ gfxFloat gfxWidth = measure / appUnitsPerDevUnit;
+ gfxFloat ascent =
+ gfxFloat(GetLogicalBaseline(parentWM)) / appUnitsPerDevUnit;
+ nscoord frameBStart = 0;
+ if (parentWM.IsVerticalRL()) {
+ frameBStart = GetSize().width;
+ ascent = -ascent;
+ }
+
+ nsCSSRendering::DecorationRectParams params;
+ params.lineSize = Size(gfxWidth, 0);
+ params.ascent = ascent;
+ params.vertical = verticalDec;
+ params.sidewaysLeft = mTextRun->IsSidewaysLeft();
+
+ nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
+ typedef gfxFont::Metrics Metrics;
+ auto accumulateDecorationRect =
+ [&](const LineDecoration& dec, gfxFloat Metrics::*lineSize,
+ mozilla::StyleTextDecorationLine lineType) {
+ params.style = dec.mStyle;
+ // If the style is solid, let's include decoration line rect of
+ // solid style since changing the style from none to
+ // solid/dotted/dashed doesn't cause reflow.
+ if (params.style == StyleTextDecorationStyle::None) {
+ params.style = StyleTextDecorationStyle::Solid;
+ }
+
+ float inflation = GetInflationForTextDecorations(
+ dec.mFrame, inflationMinFontSize);
+ const Metrics metrics =
+ GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
+ useVerticalMetrics);
+
+ params.defaultLineThickness = metrics.*lineSize;
+ params.lineSize.height = ComputeDecorationLineThickness(
+ dec.mTextDecorationThickness, params.defaultLineThickness,
+ metrics, appUnitsPerDevUnit, this);
+
+ bool swapUnderline =
+ parentWM.IsCentralBaseline() && IsUnderlineRight(*Style());
+ params.offset = ComputeDecorationLineOffset(
+ lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset,
+ metrics, appUnitsPerDevUnit, this, parentWM.IsCentralBaseline(),
+ swapUnderline);
+
+ const nsRect decorationRect =
+ nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
+ (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
+ : nsPoint(0, -dec.mBaselineOffset));
+
+ if (verticalDec) {
+ topOrLeft = std::min(decorationRect.x, topOrLeft);
+ bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
+ } else {
+ topOrLeft = std::min(decorationRect.y, topOrLeft);
+ bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
+ }
+ };
+
+ // Below we loop through all text decorations and compute the rectangle
+ // containing all of them, in this frame's coordinate space
+ params.decoration = StyleTextDecorationLine::UNDERLINE;
+ for (const LineDecoration& dec : textDecs.mUnderlines) {
+ accumulateDecorationRect(dec, &Metrics::underlineSize,
+ params.decoration);
+ }
+ params.decoration = StyleTextDecorationLine::OVERLINE;
+ for (const LineDecoration& dec : textDecs.mOverlines) {
+ accumulateDecorationRect(dec, &Metrics::underlineSize,
+ params.decoration);
+ }
+ params.decoration = StyleTextDecorationLine::LINE_THROUGH;
+ for (const LineDecoration& dec : textDecs.mStrikes) {
+ accumulateDecorationRect(dec, &Metrics::strikeoutSize,
+ params.decoration);
+ }
+
+ aInkOverflowRect->UnionRect(
+ *aInkOverflowRect,
+ verticalDec
+ ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
+ : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
+ }
+
+ aInkOverflowRect->UnionRect(*aInkOverflowRect,
+ UpdateTextEmphasis(parentWM, aProvider));
+ }
+
+ // text-stroke overflows: add half of text-stroke-width on all sides
+ nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
+ if (textStrokeWidth > 0) {
+ // Inflate rect by stroke-width/2; we add an extra pixel to allow for
+ // antialiasing, rounding errors, etc.
+ nsRect strokeRect = *aInkOverflowRect;
+ strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit);
+ aInkOverflowRect->UnionRect(*aInkOverflowRect, strokeRect);
+ }
+
+ // Text-shadow overflows
+ if (aIncludeShadows) {
+ nsRect shadowRect =
+ nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect, this);
+ aInkOverflowRect->UnionRect(*aInkOverflowRect, shadowRect);
+ }
+
+ // When this frame is not selected, the text-decoration area must be in
+ // frame bounds.
+ if (!IsSelected() ||
+ !CombineSelectionUnderlineRect(aPresContext, *aInkOverflowRect))
+ return;
+ AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
+}
+
+nscoord nsTextFrame::ComputeLineHeight() const {
+ return ReflowInput::CalcLineHeight(*Style(), PresContext(), GetContent(),
+ NS_UNCONSTRAINEDSIZE,
+ GetFontSizeInflation());
+}
+
+gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline(
+ nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) {
+ const gfxFloat lineHeight =
+ gfxFloat(ComputeLineHeight()) / aPresContext->AppUnitsPerDevPixel();
+ if (lineHeight <= aFontMetrics.maxHeight) {
+ return aFontMetrics.maxDescent;
+ }
+ return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
+}
+
+// Make sure this stays in sync with DrawSelectionDecorations below
+static const SelectionTypeMask kSelectionTypesWithDecorations =
+ ToSelectionTypeMask(SelectionType::eSpellCheck) |
+ ToSelectionTypeMask(SelectionType::eURLStrikeout) |
+ ToSelectionTypeMask(SelectionType::eIMERawClause) |
+ ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) |
+ ToSelectionTypeMask(SelectionType::eIMEConvertedClause) |
+ ToSelectionTypeMask(SelectionType::eIMESelectedClause);
+
+/* static */
+gfxFloat nsTextFrame::ComputeSelectionUnderlineHeight(
+ nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics,
+ SelectionType aSelectionType) {
+ switch (aSelectionType) {
+ case SelectionType::eIMERawClause:
+ case SelectionType::eIMESelectedRawClause:
+ case SelectionType::eIMEConvertedClause:
+ case SelectionType::eIMESelectedClause:
+ return aFontMetrics.underlineSize;
+ case SelectionType::eSpellCheck: {
+ // The thickness of the spellchecker underline shouldn't honor the font
+ // metrics. It should be constant pixels value which is decided from the
+ // default font size. Note that if the actual font size is smaller than
+ // the default font size, we should use the actual font size because the
+ // computed value from the default font size can be too thick for the
+ // current font size.
+ Length defaultFontSize =
+ aPresContext->Document()
+ ->GetFontPrefsForLang(nullptr)
+ ->GetDefaultFont(StyleGenericFontFamily::None)
+ ->size;
+ int32_t zoomedFontSize = aPresContext->CSSPixelsToDevPixels(
+ nsStyleFont::ZoomText(*aPresContext->Document(), defaultFontSize)
+ .ToCSSPixels());
+ gfxFloat fontSize =
+ std::min(gfxFloat(zoomedFontSize), aFontMetrics.emHeight);
+ fontSize = std::max(fontSize, 1.0);
+ return ceil(fontSize / 20);
+ }
+ default:
+ NS_WARNING("Requested underline style is not valid");
+ return aFontMetrics.underlineSize;
+ }
+}
+
+enum class DecorationType { Normal, Selection };
+struct nsTextFrame::PaintDecorationLineParams
+ : nsCSSRendering::DecorationRectParams {
+ gfxContext* context = nullptr;
+ LayoutDeviceRect dirtyRect;
+ Point pt;
+ const nscolor* overrideColor = nullptr;
+ nscolor color = NS_RGBA(0, 0, 0, 0);
+ gfxFloat icoordInFrame = 0.0f;
+ gfxFloat baselineOffset = 0.0f;
+ DecorationType decorationType = DecorationType::Normal;
+ DrawPathCallbacks* callbacks = nullptr;
+};
+
+void nsTextFrame::PaintDecorationLine(
+ const PaintDecorationLineParams& aParams) {
+ nsCSSRendering::PaintDecorationLineParams params;
+ static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
+ params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
+ params.pt = aParams.pt;
+ params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
+ params.icoordInFrame = Float(aParams.icoordInFrame);
+ params.baselineOffset = Float(aParams.baselineOffset);
+ if (aParams.callbacks) {
+ Rect path = nsCSSRendering::DecorationLineToPath(params);
+ if (aParams.decorationType == DecorationType::Normal) {
+ aParams.callbacks->PaintDecorationLine(path, params.color);
+ } else {
+ aParams.callbacks->PaintSelectionDecorationLine(path, params.color);
+ }
+ } else {
+ nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(),
+ params);
+ }
+}
+
+static StyleTextDecorationStyle ToStyleLineStyle(const TextRangeStyle& aStyle) {
+ switch (aStyle.mLineStyle) {
+ case TextRangeStyle::LineStyle::None:
+ return StyleTextDecorationStyle::None;
+ case TextRangeStyle::LineStyle::Solid:
+ return StyleTextDecorationStyle::Solid;
+ case TextRangeStyle::LineStyle::Dotted:
+ return StyleTextDecorationStyle::Dotted;
+ case TextRangeStyle::LineStyle::Dashed:
+ return StyleTextDecorationStyle::Dashed;
+ case TextRangeStyle::LineStyle::Double:
+ return StyleTextDecorationStyle::Double;
+ case TextRangeStyle::LineStyle::Wavy:
+ return StyleTextDecorationStyle::Wavy;
+ }
+ MOZ_ASSERT_UNREACHABLE("Invalid line style");
+ return StyleTextDecorationStyle::None;
+}
+
+/**
+ * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
+ * about drawing text decoration for selections.
+ */
+void nsTextFrame::DrawSelectionDecorations(
+ gfxContext* aContext, const LayoutDeviceRect& aDirtyRect,
+ SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
+ const TextRangeStyle& aRangeStyle, const Point& aPt,
+ gfxFloat aICoordInFrame, gfxFloat aWidth, gfxFloat aAscent,
+ const gfxFont::Metrics& aFontMetrics, DrawPathCallbacks* aCallbacks,
+ bool aVertical, StyleTextDecorationLine aDecoration) {
+ PaintDecorationLineParams params;
+ params.context = aContext;
+ params.dirtyRect = aDirtyRect;
+ params.pt = aPt;
+ params.lineSize.width = aWidth;
+ params.ascent = aAscent;
+ params.decoration = aDecoration;
+ params.decorationType = DecorationType::Selection;
+ params.callbacks = aCallbacks;
+ params.vertical = aVertical;
+ params.sidewaysLeft = mTextRun->IsSidewaysLeft();
+ params.descentLimit = ComputeDescentLimitForSelectionUnderline(
+ aTextPaintStyle.PresContext(), aFontMetrics);
+
+ float relativeSize;
+ const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
+ const gfxFloat appUnitsPerDevPixel =
+ aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
+
+ const WritingMode wm = GetWritingMode();
+ switch (aSelectionType) {
+ case SelectionType::eIMERawClause:
+ case SelectionType::eIMESelectedRawClause:
+ case SelectionType::eIMEConvertedClause:
+ case SelectionType::eIMESelectedClause:
+ case SelectionType::eSpellCheck:
+ case SelectionType::eHighlight: {
+ auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
+ aSelectionType);
+ bool weDefineSelectionUnderline =
+ aTextPaintStyle.GetSelectionUnderlineForPaint(
+ index, &params.color, &relativeSize, &params.style);
+ params.defaultLineThickness = ComputeSelectionUnderlineHeight(
+ aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
+ params.lineSize.height = ComputeDecorationLineThickness(
+ decThickness, params.defaultLineThickness, aFontMetrics,
+ appUnitsPerDevPixel, this);
+
+ bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
+ const auto* styleText = StyleText();
+ params.offset = ComputeDecorationLineOffset(
+ aDecoration, styleText->mTextUnderlinePosition,
+ styleText->mTextUnderlineOffset, aFontMetrics, appUnitsPerDevPixel,
+ this, wm.IsCentralBaseline(), swapUnderline);
+
+ bool isIMEType = aSelectionType != SelectionType::eSpellCheck &&
+ aSelectionType != SelectionType::eHighlight;
+
+ if (isIMEType) {
+ // IME decoration lines should not be drawn on the both ends, i.e., we
+ // need to cut both edges of the decoration lines. Because same style
+ // IME selections can adjoin, but the users need to be able to know
+ // where are the boundaries of the selections.
+ //
+ // X: underline
+ //
+ // IME selection #1 IME selection #2 IME selection #3
+ // | | |
+ // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
+ // +---------------------+----------------------+--------------------
+ // ^ ^ ^ ^ ^
+ // gap gap gap
+ params.pt.x += 1.0;
+ params.lineSize.width -= 2.0;
+ }
+ if (isIMEType && aRangeStyle.IsDefined()) {
+ // If IME defines the style, that should override our definition.
+ if (aRangeStyle.IsLineStyleDefined()) {
+ if (aRangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
+ return;
+ }
+ params.style = ToStyleLineStyle(aRangeStyle);
+ relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
+ } else if (!weDefineSelectionUnderline) {
+ // There is no underline style definition.
+ return;
+ }
+ // If underline color is defined and that doesn't depend on the
+ // foreground color, we should use the color directly.
+ if (aRangeStyle.IsUnderlineColorDefined() &&
+ (!aRangeStyle.IsForegroundColorDefined() ||
+ aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
+ params.color = aRangeStyle.mUnderlineColor;
+ }
+ // If foreground color or background color is defined, the both colors
+ // are computed by GetSelectionTextColors(). Then, we should use its
+ // foreground color always. The color should have sufficient contrast
+ // with the background color.
+ else if (aRangeStyle.IsForegroundColorDefined() ||
+ aRangeStyle.IsBackgroundColorDefined()) {
+ nscolor bg;
+ GetSelectionTextColors(aSelectionType, nullptr, aTextPaintStyle,
+ aRangeStyle, &params.color, &bg);
+ }
+ // Otherwise, use the foreground color of the frame.
+ else {
+ params.color = aTextPaintStyle.GetTextColor();
+ }
+ } else if (!weDefineSelectionUnderline) {
+ // IME doesn't specify the selection style and we don't define selection
+ // underline.
+ return;
+ }
+ break;
+ }
+ case SelectionType::eURLStrikeout: {
+ nscoord inflationMinFontSize =
+ nsLayoutUtils::InflationMinFontSizeFor(this);
+ float inflation =
+ GetInflationForTextDecorations(this, inflationMinFontSize);
+ const gfxFont::Metrics metrics =
+ GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
+
+ relativeSize = 2.0f;
+ aTextPaintStyle.GetURLSecondaryColor(&params.color);
+ params.style = StyleTextDecorationStyle::Solid;
+ params.defaultLineThickness = metrics.strikeoutSize;
+ params.lineSize.height = ComputeDecorationLineThickness(
+ decThickness, params.defaultLineThickness, metrics,
+ appUnitsPerDevPixel, this);
+ // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode!
+ params.offset = metrics.strikeoutOffset + 0.5;
+ params.decoration = StyleTextDecorationLine::LINE_THROUGH;
+ break;
+ }
+ default:
+ NS_WARNING("Requested selection decorations when there aren't any");
+ return;
+ }
+ params.lineSize.height *= relativeSize;
+ params.defaultLineThickness *= relativeSize;
+ params.icoordInFrame =
+ (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame;
+ PaintDecorationLine(params);
+}
+
+/* static */
+bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
+ nsAtom* aHighlightName,
+ nsTextPaintStyle& aTextPaintStyle,
+ const TextRangeStyle& aRangeStyle,
+ nscolor* aForeground,
+ nscolor* aBackground) {
+ switch (aSelectionType) {
+ case SelectionType::eNormal:
+ return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
+ case SelectionType::eFind:
+ aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
+ return true;
+ case SelectionType::eHighlight: {
+ // Intentionally not short-cutting here because the called methods have
+ // side-effects that affect outparams.
+ bool hasForeground = aTextPaintStyle.GetCustomHighlightTextColor(
+ aHighlightName, aForeground);
+ bool hasBackground = aTextPaintStyle.GetCustomHighlightBackgroundColor(
+ aHighlightName, aBackground);
+ return hasForeground || hasBackground;
+ }
+ case SelectionType::eURLSecondary:
+ aTextPaintStyle.GetURLSecondaryColor(aForeground);
+ *aBackground = NS_RGBA(0, 0, 0, 0);
+ return true;
+ case SelectionType::eIMERawClause:
+ case SelectionType::eIMESelectedRawClause:
+ case SelectionType::eIMEConvertedClause:
+ case SelectionType::eIMESelectedClause:
+ if (aRangeStyle.IsDefined()) {
+ if (!aRangeStyle.IsForegroundColorDefined() &&
+ !aRangeStyle.IsBackgroundColorDefined()) {
+ *aForeground = aTextPaintStyle.GetTextColor();
+ *aBackground = NS_RGBA(0, 0, 0, 0);
+ return false;
+ }
+ if (aRangeStyle.IsForegroundColorDefined()) {
+ *aForeground = aRangeStyle.mForegroundColor;
+ if (aRangeStyle.IsBackgroundColorDefined()) {
+ *aBackground = aRangeStyle.mBackgroundColor;
+ } else {
+ // If foreground color is defined but background color isn't
+ // defined, we can guess that IME must expect that the background
+ // color is system's default field background color.
+ *aBackground = aTextPaintStyle.GetSystemFieldBackgroundColor();
+ }
+ } else { // aRangeStyle.IsBackgroundColorDefined() is true
+ *aBackground = aRangeStyle.mBackgroundColor;
+ // If background color is defined but foreground color isn't defined,
+ // we can assume that IME must expect that the foreground color is
+ // same as system's field text color.
+ *aForeground = aTextPaintStyle.GetSystemFieldForegroundColor();
+ }
+ return true;
+ }
+ aTextPaintStyle.GetIMESelectionColors(
+ nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
+ aSelectionType),
+ aForeground, aBackground);
+ return true;
+ default:
+ *aForeground = aTextPaintStyle.GetTextColor();
+ *aBackground = NS_RGBA(0, 0, 0, 0);
+ return false;
+ }
+}
+
+/**
+ * This sets *aShadows to the appropriate shadows, if any, for the given
+ * type of selection.
+ * If text-shadow was not specified, *aShadows is left untouched.
+ */
+void nsTextFrame::GetSelectionTextShadow(
+ SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
+ Span<const StyleSimpleShadow>* aShadows) {
+ if (aSelectionType != SelectionType::eNormal) {
+ return;
+ }
+ aTextPaintStyle.GetSelectionShadow(aShadows);
+}
+
+/**
+ * This class lets us iterate over chunks of text recorded in an array of
+ * resolved selection ranges, observing cluster boundaries, in content order,
+ * maintaining the current x-offset as we go, and telling whether the text
+ * chunk has a hyphen after it or not.
+ * In addition to returning the selected chunks, the iterator is responsible
+ * to interpolate unselected chunks in any gaps between them.
+ * The caller is responsible for actually computing the advance width of each
+ * chunk.
+ */
+class MOZ_STACK_CLASS SelectionRangeIterator {
+ using PropertyProvider = nsTextFrame::PropertyProvider;
+ using CombinedSelectionRange = nsTextFrame::PriorityOrderedSelectionsForRange;
+
+ public:
+ // aSelectionRanges and aRange are according to the original string.
+ SelectionRangeIterator(
+ const nsTArray<CombinedSelectionRange>& aSelectionRanges,
+ gfxTextRun::Range aRange, PropertyProvider& aProvider,
+ gfxTextRun* aTextRun, gfxFloat aXOffset);
+
+ bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
+ gfxFloat* aHyphenWidth,
+ nsTArray<SelectionType>& aSelectionType,
+ nsTArray<RefPtr<nsAtom>>& aHighlightName,
+ nsTArray<TextRangeStyle>& aStyle);
+
+ void UpdateWithAdvance(gfxFloat aAdvance) {
+ mXOffset += aAdvance * mTextRun->GetDirection();
+ }
+
+ private:
+ const nsTArray<CombinedSelectionRange>& mSelectionRanges;
+ PropertyProvider& mProvider;
+ gfxTextRun* mTextRun;
+ gfxSkipCharsIterator mIterator;
+ gfxTextRun::Range mOriginalRange;
+ gfxFloat mXOffset;
+ uint32_t mIndex;
+};
+
+SelectionRangeIterator::SelectionRangeIterator(
+ const nsTArray<nsTextFrame::PriorityOrderedSelectionsForRange>&
+ aSelectionRanges,
+ gfxTextRun::Range aRange, PropertyProvider& aProvider, gfxTextRun* aTextRun,
+ gfxFloat aXOffset)
+ : mSelectionRanges(aSelectionRanges),
+ mProvider(aProvider),
+ mTextRun(aTextRun),
+ mIterator(aProvider.GetStart()),
+ mOriginalRange(aRange),
+ mXOffset(aXOffset),
+ mIndex(0) {
+ mIterator.SetOriginalOffset(int32_t(aRange.start));
+}
+
+bool SelectionRangeIterator::GetNextSegment(
+ gfxFloat* aXOffset, gfxTextRun::Range* aRange, gfxFloat* aHyphenWidth,
+ nsTArray<SelectionType>& aSelectionType,
+ nsTArray<RefPtr<nsAtom>>& aHighlightName,
+ nsTArray<TextRangeStyle>& aStyle) {
+ if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end)) {
+ return false;
+ }
+
+ uint32_t runOffset = mIterator.GetSkippedOffset();
+ uint32_t segmentEnd = mOriginalRange.end;
+
+ aSelectionType.Clear();
+ aHighlightName.Clear();
+ aStyle.Clear();
+
+ if (mIndex == mSelectionRanges.Length() ||
+ mIterator.GetOriginalOffset() <
+ int32_t(mSelectionRanges[mIndex].mRange.start)) {
+ // There's an unselected segment before the next range (or at the end).
+ aSelectionType.AppendElement(SelectionType::eNone);
+ aHighlightName.AppendElement();
+ aStyle.AppendElement(TextRangeStyle());
+ if (mIndex < mSelectionRanges.Length()) {
+ segmentEnd = mSelectionRanges[mIndex].mRange.start;
+ }
+ } else {
+ // Get the selection details for the next segment, and increment index.
+ for (const SelectionDetails* sdptr :
+ mSelectionRanges[mIndex].mSelectionRanges) {
+ aSelectionType.AppendElement(sdptr->mSelectionType);
+ aHighlightName.AppendElement(sdptr->mHighlightData.mHighlightName);
+ aStyle.AppendElement(sdptr->mTextRangeStyle);
+ }
+ segmentEnd = mSelectionRanges[mIndex].mRange.end;
+ ++mIndex;
+ }
+
+ // Advance iterator to the end of the segment.
+ mIterator.SetOriginalOffset(int32_t(segmentEnd));
+
+ // Further advance if necessary to a cluster boundary.
+ while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
+ !mIterator.IsOriginalCharSkipped() &&
+ !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
+ mIterator.AdvanceOriginal(1);
+ }
+
+ aRange->start = runOffset;
+ aRange->end = mIterator.GetSkippedOffset();
+ *aXOffset = mXOffset;
+ *aHyphenWidth = 0;
+ if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
+ mProvider.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
+ *aHyphenWidth = mProvider.GetHyphenWidth();
+ }
+
+ return true;
+}
+
+static void AddHyphenToMetrics(nsTextFrame* aTextFrame, bool aIsRightToLeft,
+ gfxTextRun::Metrics* aMetrics,
+ gfxFont::BoundingBoxType aBoundingBoxType,
+ DrawTarget* aDrawTarget) {
+ // Fix up metrics to include hyphen
+ RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(aTextFrame, aDrawTarget);
+ if (!hyphenTextRun) {
+ return;
+ }
+
+ gfxTextRun::Metrics hyphenMetrics =
+ hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
+ if (aTextFrame->GetWritingMode().IsLineInverted()) {
+ hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
+ }
+ aMetrics->CombineWith(hyphenMetrics, aIsRightToLeft);
+}
+
+void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
+ const StyleSimpleShadow& aShadowDetails,
+ gfxRect& aBoundingBox, uint32_t aBlurFlags) {
+ AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS);
+
+ nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(),
+ aShadowDetails.vertical.ToAppUnits());
+ nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0);
+
+ nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor);
+
+ if (auto* textDrawer = aParams.context->GetTextDrawer()) {
+ wr::Shadow wrShadow;
+
+ wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x),
+ PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)};
+
+ wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
+ wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
+
+ bool inflate = true;
+ textDrawer->AppendShadow(wrShadow, inflate);
+ return;
+ }
+
+ // This rect is the box which is equivalent to where the shadow will be
+ // painted. The origin of aBoundingBox is the text baseline left, so we must
+ // translate it by that much in order to make the origin the top-left corner
+ // of the text bounding box. Note that aLeftSideOffset is line-left, so
+ // actually means top offset in vertical writing modes.
+ gfxRect shadowGfxRect;
+ WritingMode wm = GetWritingMode();
+ if (wm.IsVertical()) {
+ shadowGfxRect = aBoundingBox;
+ if (wm.IsVerticalRL()) {
+ // for vertical-RL, reverse direction of x-coords of bounding box
+ shadowGfxRect.x = -shadowGfxRect.XMost();
+ }
+ shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
+ aParams.framePt.y + aParams.leftSideOffset);
+ } else {
+ shadowGfxRect =
+ aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
+ aParams.textBaselinePt.y);
+ }
+ Point shadowGfxOffset(shadowOffset.x, shadowOffset.y);
+ shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y);
+
+ nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
+ NSToCoordRound(shadowGfxRect.Y()),
+ NSToCoordRound(shadowGfxRect.Width()),
+ NSToCoordRound(shadowGfxRect.Height()));
+
+ nsContextBoxBlur contextBoxBlur;
+ const auto A2D = PresContext()->AppUnitsPerDevPixel();
+ gfxContext* shadowContext =
+ contextBoxBlur.Init(shadowRect, 0, blurRadius, A2D, aParams.context,
+ LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D),
+ nullptr, aBlurFlags);
+ if (!shadowContext) {
+ return;
+ }
+
+ aParams.context->Save();
+ aParams.context->SetColor(sRGBColor::FromABGR(shadowColor));
+
+ // Draw the text onto our alpha-only surface to capture the alpha values.
+ // Remember that the box blur context has a device offset on it, so we don't
+ // need to translate any coordinates to fit on the surface.
+ gfxFloat advanceWidth;
+ nsTextPaintStyle textPaintStyle(this);
+ DrawTextParams params(shadowContext, PresContext()->FontPaletteCache());
+ params.advanceWidth = &advanceWidth;
+ params.dirtyRect = aParams.dirtyRect;
+ params.framePt = aParams.framePt + shadowGfxOffset;
+ params.provider = aParams.provider;
+ params.textStyle = &textPaintStyle;
+ params.textColor =
+ aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
+ params.clipEdges = aParams.clipEdges;
+ params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
+ // Multi-color shadow is not allowed, so we use the same color of the text
+ // color.
+ params.decorationOverrideColor = &params.textColor;
+ params.fontPalette = StyleFont()->GetFontPaletteAtom();
+
+ DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params);
+
+ contextBoxBlur.DoPaint();
+ aParams.context->Restore();
+}
+
+/* static */
+SelectionTypeMask nsTextFrame::CreateSelectionRangeList(
+ const SelectionDetails* aDetails, SelectionType aSelectionType,
+ const PaintTextSelectionParams& aParams,
+ nsTArray<SelectionRange>& aSelectionRanges, bool* aAnyBackgrounds) {
+ SelectionTypeMask allTypes = 0;
+ bool anyBackgrounds = false;
+
+ uint32_t priorityOfInsertionOrder = 0;
+ for (const SelectionDetails* sd = aDetails; sd; sd = sd->mNext.get()) {
+ MOZ_ASSERT(sd->mStart >= 0 && sd->mEnd >= 0); // XXX make unsigned?
+ uint32_t start = std::max(aParams.contentRange.start, uint32_t(sd->mStart));
+ uint32_t end = std::min(aParams.contentRange.end, uint32_t(sd->mEnd));
+ if (start < end) {
+ // The PaintTextWithSelectionColors caller passes SelectionType::eNone,
+ // so we collect all selections that set colors, and prioritize them
+ // according to selection type (lower types take precedence).
+ if (aSelectionType == SelectionType::eNone) {
+ allTypes |= ToSelectionTypeMask(sd->mSelectionType);
+ // Ignore selections that don't set colors.
+ nscolor foreground(0), background(0);
+ if (GetSelectionTextColors(sd->mSelectionType,
+ sd->mHighlightData.mHighlightName,
+ *aParams.textPaintStyle, sd->mTextRangeStyle,
+ &foreground, &background)) {
+ if (NS_GET_A(background) > 0) {
+ anyBackgrounds = true;
+ }
+ aSelectionRanges.AppendElement(
+ SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
+ }
+ } else if (sd->mSelectionType == aSelectionType) {
+ // The PaintSelectionTextDecorations caller passes a specific type,
+ // so we include only ranges of that type, and keep them in order
+ // so that later ones take precedence over earlier.
+ aSelectionRanges.AppendElement(
+ SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
+ }
+ }
+ }
+ if (aAnyBackgrounds) {
+ *aAnyBackgrounds = anyBackgrounds;
+ }
+ return allTypes;
+}
+
+/* static */
+void nsTextFrame::CombineSelectionRanges(
+ const nsTArray<SelectionRange>& aSelectionRanges,
+ nsTArray<PriorityOrderedSelectionsForRange>& aCombinedSelectionRanges) {
+ struct SelectionRangeEndCmp {
+ bool Equals(const SelectionRange* a, const SelectionRange* b) const {
+ return a->mRange.end == b->mRange.end;
+ }
+ bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
+ return a->mRange.end < b->mRange.end;
+ }
+ };
+
+ struct SelectionRangePriorityCmp {
+ bool Equals(const SelectionRange* a, const SelectionRange* b) const {
+ const SelectionDetails* aDetails = a->mDetails;
+ const SelectionDetails* bDetails = b->mDetails;
+ if (aDetails->mSelectionType != bDetails->mSelectionType) {
+ return false;
+ }
+ if (aDetails->mSelectionType != SelectionType::eHighlight) {
+ return a->mPriority == b->mPriority;
+ }
+ if (aDetails->mHighlightData.mHighlight->Priority() !=
+ bDetails->mHighlightData.mHighlight->Priority()) {
+ return false;
+ }
+ return a->mPriority == b->mPriority;
+ }
+
+ bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
+ if (a->mDetails->mSelectionType != b->mDetails->mSelectionType) {
+ // Even though this looks counter-intuitive,
+ // this is intended, as values in `SelectionType` are inverted:
+ // a lower value indicates a higher priority.
+ return a->mDetails->mSelectionType > b->mDetails->mSelectionType;
+ }
+
+ if (a->mDetails->mSelectionType != SelectionType::eHighlight) {
+ // for non-highlights, the selection which was added later
+ // has a higher priority.
+ return a->mPriority < b->mPriority;
+ }
+
+ if (a->mDetails->mHighlightData.mHighlight->Priority() !=
+ b->mDetails->mHighlightData.mHighlight->Priority()) {
+ // For highlights, first compare the priorities set by the user.
+ return a->mDetails->mHighlightData.mHighlight->Priority() <
+ b->mDetails->mHighlightData.mHighlight->Priority();
+ }
+ // only if the user priorities are equal, let the highlight that was added
+ // later take precedence.
+ return a->mPriority < b->mPriority;
+ }
+ };
+
+ uint32_t currentOffset = 0;
+ AutoTArray<const SelectionRange*, 1> activeSelectionsForCurrentSegment;
+ size_t rangeIndex = 0;
+
+ // Divide the given selection ranges into segments which share the same
+ // set of selections.
+ // The following algorithm iterates `aSelectionRanges`, assuming
+ // that its elements are sorted by their start offset.
+ // Each time a new selection starts, it is pushed into an array of
+ // "currently present" selections, sorted by their *end* offset.
+ // For each iteration the next segment end offset is determined,
+ // which is either the start offset of the next selection or
+ // the next end offset of all "currently present" selections
+ // (which is always the first element of the array because of its order).
+ // Then, a `CombinedSelectionRange` can be constructed, which describes
+ // the text segment until its end offset (as determined above), and contains
+ // all elements of the "currently present" selection list, now sorted
+ // by their priority.
+ // If a range ends at the given offset, it is removed from the array.
+ while (rangeIndex < aSelectionRanges.Length() ||
+ !activeSelectionsForCurrentSegment.IsEmpty()) {
+ uint32_t currentSegmentEndOffset =
+ activeSelectionsForCurrentSegment.IsEmpty()
+ ? -1
+ : activeSelectionsForCurrentSegment[0]->mRange.end;
+ uint32_t nextRangeStartOffset =
+ rangeIndex < aSelectionRanges.Length()
+ ? aSelectionRanges[rangeIndex].mRange.start
+ : -1;
+ uint32_t nextOffset =
+ std::min(currentSegmentEndOffset, nextRangeStartOffset);
+ if (!activeSelectionsForCurrentSegment.IsEmpty() &&
+ currentOffset != nextOffset) {
+ auto activeSelectionRangesSortedByPriority =
+ activeSelectionsForCurrentSegment.Clone();
+ activeSelectionRangesSortedByPriority.Sort(SelectionRangePriorityCmp());
+
+ AutoTArray<const SelectionDetails*, 1> selectionDetails;
+ selectionDetails.SetCapacity(
+ activeSelectionRangesSortedByPriority.Length());
+ // ensure that overlapping highlights which have the same name
+ // are only added once. If added each time, they would be painted
+ // several times (see wpt
+ // /css/css-highlight-api/painting/custom-highlight-painting-003.html)
+ // Comparing the highlight name with the previous one is
+ // sufficient here because selections are already sorted
+ // in a way that ensures that highlights of the same name are
+ // grouped together.
+ nsAtom* currentHighlightName = nullptr;
+ for (const auto* selectionRange : activeSelectionRangesSortedByPriority) {
+ if (selectionRange->mDetails->mSelectionType ==
+ SelectionType::eHighlight) {
+ if (selectionRange->mDetails->mHighlightData.mHighlightName ==
+ currentHighlightName) {
+ continue;
+ }
+ currentHighlightName =
+ selectionRange->mDetails->mHighlightData.mHighlightName;
+ }
+ selectionDetails.AppendElement(selectionRange->mDetails);
+ }
+ aCombinedSelectionRanges.AppendElement(PriorityOrderedSelectionsForRange{
+ std::move(selectionDetails), {currentOffset, nextOffset}});
+ }
+ currentOffset = nextOffset;
+
+ if (nextRangeStartOffset < currentSegmentEndOffset) {
+ activeSelectionsForCurrentSegment.InsertElementSorted(
+ &aSelectionRanges[rangeIndex++], SelectionRangeEndCmp());
+ } else {
+ activeSelectionsForCurrentSegment.RemoveElementAt(0);
+ }
+ }
+}
+
+SelectionTypeMask nsTextFrame::ResolveSelections(
+ const PaintTextSelectionParams& aParams, const SelectionDetails* aDetails,
+ nsTArray<PriorityOrderedSelectionsForRange>& aResult,
+ SelectionType aSelectionType, bool* aAnyBackgrounds) const {
+ AutoTArray<SelectionRange, 4> selectionRanges;
+
+ SelectionTypeMask allTypes = CreateSelectionRangeList(
+ aDetails, aSelectionType, aParams, selectionRanges, aAnyBackgrounds);
+
+ if (selectionRanges.IsEmpty()) {
+ return allTypes;
+ }
+
+ struct SelectionRangeStartCmp {
+ bool Equals(const SelectionRange& a, const SelectionRange& b) const {
+ return a.mRange.start == b.mRange.start;
+ }
+ bool LessThan(const SelectionRange& a, const SelectionRange& b) const {
+ return a.mRange.start < b.mRange.start;
+ }
+ };
+ selectionRanges.Sort(SelectionRangeStartCmp());
+
+ CombineSelectionRanges(selectionRanges, aResult);
+
+ return allTypes;
+}
+
+// Paints selection backgrounds and text in the correct colors. Also computes
+// aAllSelectionTypeMask, the union of all selection types that are applying to
+// this text.
+bool nsTextFrame::PaintTextWithSelectionColors(
+ const PaintTextSelectionParams& aParams,
+ const UniquePtr<SelectionDetails>& aDetails,
+ SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges) {
+ bool anyBackgrounds = false;
+ AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
+
+ *aAllSelectionTypeMask =
+ ResolveSelections(aParams, aDetails.get(), selectionRanges,
+ SelectionType::eNone, &anyBackgrounds);
+ bool vertical = mTextRun->IsVertical();
+ const gfxFloat startIOffset =
+ vertical ? aParams.textBaselinePt.y - aParams.framePt.y
+ : aParams.textBaselinePt.x - aParams.framePt.x;
+ gfxFloat iOffset, hyphenWidth;
+ Range range; // in transformed string
+
+ const gfxTextRun::Range& contentRange = aParams.contentRange;
+ auto* textDrawer = aParams.context->GetTextDrawer();
+
+ if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
+ int32_t appUnitsPerDevPixel =
+ aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
+ SelectionRangeIterator iterator(selectionRanges, contentRange,
+ *aParams.provider, mTextRun, startIOffset);
+ AutoTArray<SelectionType, 1> selectionTypes;
+ AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
+ AutoTArray<TextRangeStyle, 1> rangeStyles;
+ while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
+ selectionTypes, highlightNames,
+ rangeStyles)) {
+ nscolor foreground(0), background(0);
+ gfxFloat advance =
+ hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
+ nsRect bgRect;
+ gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
+ if (vertical) {
+ bgRect = nsRect(nscoord(aParams.framePt.x),
+ nscoord(aParams.framePt.y + offs), GetSize().width,
+ nscoord(advance));
+ } else {
+ bgRect = nsRect(nscoord(aParams.framePt.x + offs),
+ nscoord(aParams.framePt.y), nscoord(advance),
+ GetSize().height);
+ }
+
+ LayoutDeviceRect selectionRect =
+ LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
+ // The elements in `selectionTypes` are ordered ascending by their
+ // priority. To account for non-opaque overlapping selections, all
+ // selection backgrounds are painted.
+ for (size_t index = 0; index < selectionTypes.Length(); ++index) {
+ GetSelectionTextColors(selectionTypes[index], highlightNames[index],
+ *aParams.textPaintStyle, rangeStyles[index],
+ &foreground, &background);
+
+ // Draw background color
+ if (NS_GET_A(background) > 0) {
+ if (textDrawer) {
+ textDrawer->AppendSelectionRect(selectionRect,
+ ToDeviceColor(background));
+ } else {
+ PaintSelectionBackground(*aParams.context->GetDrawTarget(),
+ background, aParams.dirtyRect,
+ selectionRect, aParams.callbacks);
+ }
+ }
+ }
+ iterator.UpdateWithAdvance(advance);
+ }
+ }
+
+ gfxFloat advance;
+ DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
+ params.dirtyRect = aParams.dirtyRect;
+ params.framePt = aParams.framePt;
+ params.provider = aParams.provider;
+ params.textStyle = aParams.textPaintStyle;
+ params.clipEdges = &aClipEdges;
+ params.advanceWidth = &advance;
+ params.callbacks = aParams.callbacks;
+ params.glyphRange = aParams.glyphRange;
+ params.fontPalette = StyleFont()->GetFontPaletteAtom();
+ params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
+
+ PaintShadowParams shadowParams(aParams);
+ shadowParams.provider = aParams.provider;
+ shadowParams.clipEdges = &aClipEdges;
+
+ // Draw text
+ const nsStyleText* textStyle = StyleText();
+ SelectionRangeIterator iterator(selectionRanges, contentRange,
+ *aParams.provider, mTextRun, startIOffset);
+ AutoTArray<SelectionType, 1> selectionTypes;
+ AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
+ AutoTArray<TextRangeStyle, 1> rangeStyles;
+ while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, selectionTypes,
+ highlightNames, rangeStyles)) {
+ nscolor foreground(0), background(0);
+ if (aParams.IsGenerateTextMask()) {
+ foreground = NS_RGBA(0, 0, 0, 255);
+ } else {
+ nscolor tmpForeground(0);
+ bool colorHasBeenSet = false;
+ for (size_t index = 0; index < selectionTypes.Length(); ++index) {
+ if (selectionTypes[index] == SelectionType::eHighlight) {
+ if (aParams.textPaintStyle->GetCustomHighlightTextColor(
+ highlightNames[index], &tmpForeground)) {
+ foreground = tmpForeground;
+ colorHasBeenSet = true;
+ }
+
+ } else {
+ GetSelectionTextColors(selectionTypes[index], highlightNames[index],
+ *aParams.textPaintStyle, rangeStyles[index],
+ &foreground, &background);
+ colorHasBeenSet = true;
+ }
+ }
+ if (!colorHasBeenSet) {
+ foreground = tmpForeground;
+ }
+ }
+
+ gfx::Point textBaselinePt =
+ vertical
+ ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset)
+ : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
+
+ // Determine what shadow, if any, to draw - either from textStyle
+ // or from the ::-moz-selection pseudo-class if specified there
+ Span<const StyleSimpleShadow> shadows = textStyle->mTextShadow.AsSpan();
+ for (auto selectionType : selectionTypes) {
+ GetSelectionTextShadow(selectionType, *aParams.textPaintStyle, &shadows);
+ }
+ if (!shadows.IsEmpty()) {
+ nscoord startEdge = iOffset;
+ if (mTextRun->IsInlineReversed()) {
+ startEdge -=
+ hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
+ }
+ shadowParams.range = range;
+ shadowParams.textBaselinePt = textBaselinePt;
+ shadowParams.foregroundColor = foreground;
+ shadowParams.leftSideOffset = startEdge;
+ PaintShadows(shadows, shadowParams);
+ }
+
+ // Draw text segment
+ params.textColor = foreground;
+ params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
+ params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
+ params.drawSoftHyphen = hyphenWidth > 0;
+ DrawText(range, textBaselinePt, params);
+ advance += hyphenWidth;
+ iterator.UpdateWithAdvance(advance);
+ }
+ return true;
+}
+
+void nsTextFrame::PaintTextSelectionDecorations(
+ const PaintTextSelectionParams& aParams,
+ const UniquePtr<SelectionDetails>& aDetails, SelectionType aSelectionType) {
+ // Hide text decorations if we're currently hiding @font-face fallback text
+ if (aParams.provider->GetFontGroup()->ShouldSkipDrawing()) {
+ return;
+ }
+
+ AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
+ ResolveSelections(aParams, aDetails.get(), selectionRanges, aSelectionType);
+
+ RefPtr<gfxFont> firstFont =
+ aParams.provider->GetFontGroup()->GetFirstValidFont();
+ bool verticalRun = mTextRun->IsVertical();
+ bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
+ bool rightUnderline = useVerticalMetrics && IsUnderlineRight(*Style());
+ const auto kDecoration = rightUnderline ? StyleTextDecorationLine::OVERLINE
+ : StyleTextDecorationLine::UNDERLINE;
+ gfxFont::Metrics decorationMetrics(
+ firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal));
+ decorationMetrics.underlineOffset =
+ aParams.provider->GetFontGroup()->GetUnderlineOffset();
+
+ const gfxTextRun::Range& contentRange = aParams.contentRange;
+ gfxFloat startIOffset = verticalRun
+ ? aParams.textBaselinePt.y - aParams.framePt.y
+ : aParams.textBaselinePt.x - aParams.framePt.x;
+ SelectionRangeIterator iterator(selectionRanges, contentRange,
+ *aParams.provider, mTextRun, startIOffset);
+ gfxFloat iOffset, hyphenWidth;
+ Range range;
+ int32_t app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
+ // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
+ Point pt;
+ if (verticalRun) {
+ pt.x = (aParams.textBaselinePt.x - mAscent) / app;
+ } else {
+ pt.y = (aParams.textBaselinePt.y - mAscent) / app;
+ }
+ AutoTArray<SelectionType, 1> nextSelectionTypes;
+ AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
+ AutoTArray<TextRangeStyle, 1> selectedStyles;
+
+ while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
+ nextSelectionTypes, highlightNames,
+ selectedStyles)) {
+ gfxFloat advance =
+ hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
+ for (size_t index = 0; index < nextSelectionTypes.Length(); ++index) {
+ if (nextSelectionTypes[index] == aSelectionType) {
+ if (verticalRun) {
+ pt.y = (aParams.framePt.y + iOffset -
+ (mTextRun->IsInlineReversed() ? advance : 0)) /
+ app;
+ } else {
+ pt.x = (aParams.framePt.x + iOffset -
+ (mTextRun->IsInlineReversed() ? advance : 0)) /
+ app;
+ }
+ gfxFloat width = Abs(advance) / app;
+ gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
+ DrawSelectionDecorations(aParams.context, aParams.dirtyRect,
+ aSelectionType, *aParams.textPaintStyle,
+ selectedStyles[index], pt, xInFrame, width,
+ mAscent / app, decorationMetrics,
+ aParams.callbacks, verticalRun, kDecoration);
+ }
+ }
+ iterator.UpdateWithAdvance(advance);
+ }
+}
+
+bool nsTextFrame::PaintTextWithSelection(
+ const PaintTextSelectionParams& aParams, const ClipEdges& aClipEdges) {
+ NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path");
+
+ UniquePtr<SelectionDetails> details = GetSelectionDetails();
+ if (!details) {
+ return false;
+ }
+
+ SelectionTypeMask allSelectionTypeMask;
+ if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask,
+ aClipEdges)) {
+ return false;
+ }
+ // Iterate through just the selection rawSelectionTypes that paint decorations
+ // and paint decorations for any that actually occur in this frame. Paint
+ // higher-numbered selection rawSelectionTypes below lower-numered ones on the
+ // general principal that lower-numbered selections are higher priority.
+ allSelectionTypeMask &= kSelectionTypesWithDecorations;
+ MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,
+ "The following for loop assumes that the first item of "
+ "kPresentSelectionTypes is SelectionType::eNormal");
+ for (size_t i = ArrayLength(kPresentSelectionTypes) - 1; i >= 1; --i) {
+ SelectionType selectionType = kPresentSelectionTypes[i];
+ if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) {
+ // There is some selection of this selectionType. Try to paint its
+ // decorations (there might not be any for this type but that's OK,
+ // PaintTextSelectionDecorations will exit early).
+ PaintTextSelectionDecorations(aParams, details, selectionType);
+ }
+ }
+
+ return true;
+}
+
+void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
+ const gfx::Point& aTextBaselinePt,
+ const gfx::Point& aFramePt, Range aRange,
+ const nscolor* aDecorationOverrideColor,
+ PropertyProvider* aProvider) {
+ const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
+ if (!info) {
+ return;
+ }
+
+ bool isTextCombined = Style()->IsTextCombined();
+ if (isTextCombined && !aWM.IsVertical()) {
+ // XXX This only happens when the parent is display:contents with an
+ // orthogonal writing mode. This should be rare, and don't have use
+ // cases, so we don't care. It is non-trivial to implement a sane
+ // behavior for that case: if you treat the text as not combined,
+ // the marks would spread wider than the text (which is rendered as
+ // combined); if you try to draw a single mark, selecting part of
+ // the text could dynamically create multiple new marks.
+ NS_WARNING("Give up on combined text with horizontal wm");
+ return;
+ }
+ nscolor color =
+ aDecorationOverrideColor
+ ? *aDecorationOverrideColor
+ : nsLayoutUtils::GetColor(this, &nsStyleText::mTextEmphasisColor);
+ aContext->SetColor(sRGBColor::FromABGR(color));
+ gfx::Point pt;
+ if (!isTextCombined) {
+ pt = aTextBaselinePt;
+ } else {
+ MOZ_ASSERT(aWM.IsVertical());
+ pt = aFramePt;
+ if (aWM.IsVerticalRL()) {
+ pt.x += GetSize().width - GetLogicalBaseline(aWM);
+ } else {
+ pt.x += GetLogicalBaseline(aWM);
+ }
+ }
+ if (!aWM.IsVertical()) {
+ pt.y += info->baselineOffset;
+ } else {
+ if (aWM.IsVerticalRL()) {
+ pt.x -= info->baselineOffset;
+ } else {
+ pt.x += info->baselineOffset;
+ }
+ }
+ if (!isTextCombined) {
+ mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance,
+ pt, aRange, aProvider,
+ PresContext()->FontPaletteCache());
+ } else {
+ pt.y += (GetSize().height - info->advance) / 2;
+ gfxTextRun::DrawParams params(aContext, PresContext()->FontPaletteCache());
+ info->textRun->Draw(Range(info->textRun.get()), pt, params);
+ }
+}
+
+nscolor nsTextFrame::GetCaretColorAt(int32_t aOffset) {
+ MOZ_ASSERT(aOffset >= 0, "aOffset must be positive");
+
+ nscolor result = nsIFrame::GetCaretColorAt(aOffset);
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
+ int32_t contentOffset = provider.GetStart().GetOriginalOffset();
+ int32_t contentLength = provider.GetOriginalLength();
+ MOZ_ASSERT(
+ aOffset >= contentOffset && aOffset <= contentOffset + contentLength,
+ "aOffset must be in the frame's range");
+
+ int32_t offsetInFrame = aOffset - contentOffset;
+ if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
+ return result;
+ }
+
+ bool isSolidTextColor = true;
+ if (IsInSVGTextSubtree()) {
+ const nsStyleSVG* style = StyleSVG();
+ if (!style->mFill.kind.IsNone() && !style->mFill.kind.IsColor()) {
+ isSolidTextColor = false;
+ }
+ }
+
+ nsTextPaintStyle textPaintStyle(this);
+ textPaintStyle.SetResolveColors(isSolidTextColor);
+ UniquePtr<SelectionDetails> details = GetSelectionDetails();
+ SelectionType selectionType = SelectionType::eNone;
+ for (SelectionDetails* sdptr = details.get(); sdptr;
+ sdptr = sdptr->mNext.get()) {
+ int32_t start = std::max(0, sdptr->mStart - contentOffset);
+ int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
+ if (start <= offsetInFrame && offsetInFrame < end &&
+ (selectionType == SelectionType::eNone ||
+ sdptr->mSelectionType < selectionType)) {
+ nscolor foreground, background;
+ if (GetSelectionTextColors(sdptr->mSelectionType,
+ sdptr->mHighlightData.mHighlightName,
+ textPaintStyle, sdptr->mTextRangeStyle,
+ &foreground, &background)) {
+ if (!isSolidTextColor && NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
+ result = NS_RGBA(0, 0, 0, 255);
+ } else {
+ result = foreground;
+ }
+ selectionType = sdptr->mSelectionType;
+ }
+ }
+ }
+
+ return result;
+}
+
+static gfxTextRun::Range ComputeTransformedRange(
+ nsTextFrame::PropertyProvider& aProvider) {
+ gfxSkipCharsIterator iter(aProvider.GetStart());
+ uint32_t start = iter.GetSkippedOffset();
+ iter.AdvanceOriginal(aProvider.GetOriginalLength());
+ return gfxTextRun::Range(start, iter.GetSkippedOffset());
+}
+
+bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
+ nscoord aVisIEndEdge,
+ nscoord* aSnappedStartEdge,
+ nscoord* aSnappedEndEdge) {
+ // We need a *reference* rendering context (not one that might have a
+ // transform), so we don't have a rendering context argument.
+ // XXX get the block and line passed to us somehow! This is slow!
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return false;
+ }
+
+ PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
+ // Trim trailing whitespace
+ provider.InitializeForDisplay(true);
+
+ Range range = ComputeTransformedRange(provider);
+ uint32_t startOffset = range.start;
+ uint32_t maxLength = range.Length();
+ return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
+ &startOffset, &maxLength, aSnappedStartEdge,
+ aSnappedEndEdge);
+}
+
+static uint32_t GetClusterLength(const gfxTextRun* aTextRun,
+ uint32_t aStartOffset, uint32_t aMaxLength) {
+ uint32_t clusterLength = 0;
+ while (++clusterLength < aMaxLength) {
+ if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
+ return clusterLength;
+ }
+ }
+ return aMaxLength;
+}
+
+bool nsTextFrame::MeasureCharClippedText(
+ PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge,
+ uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge,
+ nscoord* aSnappedEndEdge) {
+ *aSnappedStartEdge = 0;
+ *aSnappedEndEdge = 0;
+ if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) {
+ return true;
+ }
+
+ uint32_t offset = *aStartOffset;
+ uint32_t maxLength = *aMaxLength;
+ const nscoord frameISize = ISize();
+ const bool rtl = mTextRun->IsRightToLeft();
+ gfxFloat advanceWidth = 0;
+ const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
+ if (startEdge > 0) {
+ const gfxFloat maxAdvance = gfxFloat(startEdge);
+ while (maxLength > 0) {
+ uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
+ advanceWidth += mTextRun->GetAdvanceWidth(
+ Range(offset, offset + clusterLength), &aProvider);
+ maxLength -= clusterLength;
+ offset += clusterLength;
+ if (advanceWidth >= maxAdvance) {
+ break;
+ }
+ }
+ nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
+ *snappedStartEdge = NSToCoordFloor(advanceWidth);
+ *aStartOffset = offset;
+ }
+
+ const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
+ if (endEdge > 0) {
+ const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
+ while (maxLength > 0) {
+ uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
+ gfxFloat nextAdvance =
+ advanceWidth + mTextRun->GetAdvanceWidth(
+ Range(offset, offset + clusterLength), &aProvider);
+ if (nextAdvance > maxAdvance) {
+ break;
+ }
+ // This cluster fits, include it.
+ advanceWidth = nextAdvance;
+ maxLength -= clusterLength;
+ offset += clusterLength;
+ }
+ maxLength = offset - *aStartOffset;
+ nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
+ *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
+ }
+ *aMaxLength = maxLength;
+ return maxLength != 0;
+}
+
+void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows,
+ const PaintShadowParams& aParams) {
+ if (aShadows.IsEmpty()) {
+ return;
+ }
+
+ gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText(
+ aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider);
+ if (GetWritingMode().IsLineInverted()) {
+ std::swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
+ shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
+ }
+ if (HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
+ AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &shadowMetrics,
+ gfxFont::LOOSE_INK_EXTENTS,
+ aParams.context->GetDrawTarget());
+ }
+ // Add bounds of text decorations
+ gfxRect decorationRect(0, -shadowMetrics.mAscent, shadowMetrics.mAdvanceWidth,
+ shadowMetrics.mAscent + shadowMetrics.mDescent);
+ shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
+ decorationRect);
+
+ // If the textrun uses any color or SVG fonts, we need to force use of a mask
+ // for shadow rendering even if blur radius is zero.
+ // Force disable hardware acceleration for text shadows since it's usually
+ // more expensive than just doing it on the CPU.
+ uint32_t blurFlags = nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR;
+ uint32_t numGlyphRuns;
+ const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
+ while (numGlyphRuns-- > 0) {
+ if (run->mFont->AlwaysNeedsMaskForShadow()) {
+ blurFlags |= nsContextBoxBlur::FORCE_MASK;
+ break;
+ }
+ run++;
+ }
+
+ if (mTextRun->IsVertical()) {
+ std::swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
+ std::swap(shadowMetrics.mBoundingBox.width,
+ shadowMetrics.mBoundingBox.height);
+ }
+
+ for (const auto& shadow : Reversed(aShadows)) {
+ PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags);
+ }
+}
+
+void nsTextFrame::PaintText(const PaintTextParams& aParams,
+ const nscoord aVisIStartEdge,
+ const nscoord aVisIEndEdge,
+ const nsPoint& aToReferenceFrame,
+ const bool aIsSelected,
+ float aOpacity /* = 1.0f */) {
+#ifdef DEBUG
+ if (IsInSVGTextSubtree()) {
+ auto* container =
+ nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText);
+ MOZ_ASSERT(container);
+ MOZ_ASSERT(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ||
+ !aParams.IsPaintText(),
+ "Expecting IsPaintText to be false for a clipPath");
+ }
+#endif
+
+ // Don't pass in the rendering context here, because we need a
+ // *reference* context and rendering context might have some transform
+ // in it
+ // XXX get the block and line passed to us somehow! This is slow!
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return;
+ }
+
+ PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
+
+ // Trim trailing whitespace, unless we're painting a selection highlight,
+ // which should include trailing spaces if present (bug 1146754).
+ provider.InitializeForDisplay(!aIsSelected);
+
+ const bool reversed = mTextRun->IsInlineReversed();
+ const bool verticalRun = mTextRun->IsVertical();
+ WritingMode wm = GetWritingMode();
+ const float frameWidth = GetSize().width;
+ const float frameHeight = GetSize().height;
+ gfx::Point textBaselinePt;
+ if (verticalRun) {
+ if (wm.IsVerticalLR()) {
+ textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
+ this, aParams.context, nscoord(aParams.framePt.x), mAscent);
+ } else {
+ textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
+ this, aParams.context, nscoord(aParams.framePt.x) + frameWidth,
+ -mAscent);
+ }
+ textBaselinePt.y = reversed ? aParams.framePt.y.value + frameHeight
+ : aParams.framePt.y.value;
+ } else {
+ textBaselinePt =
+ gfx::Point(reversed ? aParams.framePt.x.value + frameWidth
+ : aParams.framePt.x.value,
+ nsLayoutUtils::GetSnappedBaselineY(
+ this, aParams.context, aParams.framePt.y, mAscent));
+ }
+ Range range = ComputeTransformedRange(provider);
+ uint32_t startOffset = range.start;
+ uint32_t maxLength = range.Length();
+ nscoord snappedStartEdge, snappedEndEdge;
+ if (!MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
+ &startOffset, &maxLength, &snappedStartEdge,
+ &snappedEndEdge)) {
+ return;
+ }
+ if (verticalRun) {
+ textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
+ } else {
+ textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
+ }
+ const ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge,
+ snappedEndEdge);
+ nsTextPaintStyle textPaintStyle(this);
+ textPaintStyle.SetResolveColors(!aParams.callbacks);
+
+ // Fork off to the (slower) paint-with-selection path if necessary.
+ if (aIsSelected) {
+ MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
+ gfxSkipCharsIterator tmp(provider.GetStart());
+ Range contentRange(
+ uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
+ uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
+ PaintTextSelectionParams params(aParams);
+ params.textBaselinePt = textBaselinePt;
+ params.provider = &provider;
+ params.contentRange = contentRange;
+ params.textPaintStyle = &textPaintStyle;
+ params.glyphRange = range;
+ if (PaintTextWithSelection(params, clipEdges)) {
+ return;
+ }
+ }
+
+ nscolor foregroundColor = aParams.IsGenerateTextMask()
+ ? NS_RGBA(0, 0, 0, 255)
+ : textPaintStyle.GetTextColor();
+ if (aOpacity != 1.0f) {
+ gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(foregroundColor);
+ gfxColor.a *= aOpacity;
+ foregroundColor = gfxColor.ToABGR();
+ }
+
+ nscolor textStrokeColor = aParams.IsGenerateTextMask()
+ ? NS_RGBA(0, 0, 0, 255)
+ : textPaintStyle.GetWebkitTextStrokeColor();
+ if (aOpacity != 1.0f) {
+ gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(textStrokeColor);
+ gfxColor.a *= aOpacity;
+ textStrokeColor = gfxColor.ToABGR();
+ }
+
+ range = Range(startOffset, startOffset + maxLength);
+ if (aParams.IsPaintText()) {
+ const nsStyleText* textStyle = StyleText();
+ PaintShadowParams shadowParams(aParams);
+ shadowParams.range = range;
+ shadowParams.textBaselinePt = textBaselinePt;
+ shadowParams.leftSideOffset = snappedStartEdge;
+ shadowParams.provider = &provider;
+ shadowParams.foregroundColor = foregroundColor;
+ shadowParams.clipEdges = &clipEdges;
+ PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams);
+ }
+
+ gfxFloat advanceWidth;
+ DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
+ params.dirtyRect = aParams.dirtyRect;
+ params.framePt = aParams.framePt;
+ params.provider = &provider;
+ params.advanceWidth = &advanceWidth;
+ params.textStyle = &textPaintStyle;
+ params.textColor = foregroundColor;
+ params.textStrokeColor = textStrokeColor;
+ params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
+ params.clipEdges = &clipEdges;
+ params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
+ params.contextPaint = aParams.contextPaint;
+ params.callbacks = aParams.callbacks;
+ params.glyphRange = range;
+ params.fontPalette = StyleFont()->GetFontPaletteAtom();
+ params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
+
+ DrawText(range, textBaselinePt, params);
+}
+
+static void DrawTextRun(const gfxTextRun* aTextRun,
+ const gfx::Point& aTextBaselinePt,
+ gfxTextRun::Range aRange,
+ const nsTextFrame::DrawTextRunParams& aParams,
+ nsTextFrame* aFrame) {
+ gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache);
+ params.provider = aParams.provider;
+ params.advanceWidth = aParams.advanceWidth;
+ params.contextPaint = aParams.contextPaint;
+ params.fontPalette = aParams.fontPalette;
+ params.callbacks = aParams.callbacks;
+ params.hasTextShadow = aParams.hasTextShadow;
+ if (aParams.callbacks) {
+ aParams.callbacks->NotifyBeforeText(aParams.textColor);
+ params.drawMode = DrawMode::GLYPH_PATH;
+ aTextRun->Draw(aRange, aTextBaselinePt, params);
+ aParams.callbacks->NotifyAfterText();
+ } else {
+ auto* textDrawer = aParams.context->GetTextDrawer();
+ if (NS_GET_A(aParams.textColor) != 0 || textDrawer ||
+ aParams.textStrokeWidth == 0.0f) {
+ aParams.context->SetColor(sRGBColor::FromABGR(aParams.textColor));
+ } else {
+ params.drawMode = DrawMode::GLYPH_STROKE;
+ }
+
+ if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) &&
+ aParams.textStrokeWidth != 0.0f) {
+ if (textDrawer) {
+ textDrawer->FoundUnsupportedFeature();
+ return;
+ }
+ params.drawMode |= DrawMode::GLYPH_STROKE;
+
+ // Check the paint-order property; if we find stroke before fill,
+ // then change mode to GLYPH_STROKE_UNDERNEATH.
+ uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder;
+ while (paintOrder) {
+ auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
+ switch (component) {
+ case StylePaintOrder::Fill:
+ // Just break the loop, no need to check further
+ paintOrder = 0;
+ break;
+ case StylePaintOrder::Stroke:
+ params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
+ paintOrder = 0;
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
+ case StylePaintOrder::Markers:
+ case StylePaintOrder::Normal:
+ break;
+ }
+ paintOrder >>= kPaintOrderShift;
+ }
+
+ // Use ROUND joins as they are less likely to produce ugly artifacts
+ // when stroking glyphs with sharp angles (see bug 1546985).
+ StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND);
+ params.textStrokeColor = aParams.textStrokeColor;
+ params.strokeOpts = &strokeOpts;
+ aTextRun->Draw(aRange, aTextBaselinePt, params);
+ } else {
+ aTextRun->Draw(aRange, aTextBaselinePt, params);
+ }
+ }
+}
+
+void nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt,
+ const DrawTextRunParams& aParams) {
+ MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
+
+ ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, this);
+
+ if (aParams.drawSoftHyphen) {
+ // Don't use ctx as the context, because we need a reference context here,
+ // ctx may be transformed.
+ DrawTextRunParams params = aParams;
+ params.provider = nullptr;
+ params.advanceWidth = nullptr;
+ RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(this, nullptr);
+ if (hyphenTextRun) {
+ gfx::Point p(aTextBaselinePt);
+ bool vertical = GetWritingMode().IsVertical();
+ // For right-to-left text runs, the soft-hyphen is positioned at the left
+ // of the text.
+ float shift = mTextRun->GetDirection() * (*aParams.advanceWidth);
+ if (vertical) {
+ p.y += shift;
+ } else {
+ p.x += shift;
+ }
+ ::DrawTextRun(hyphenTextRun.get(), p, Range(hyphenTextRun.get()), params,
+ this);
+ }
+ }
+}
+
+void nsTextFrame::DrawTextRunAndDecorations(
+ Range aRange, const gfx::Point& aTextBaselinePt,
+ const DrawTextParams& aParams, const TextDecorations& aDecorations) {
+ const gfxFloat app = aParams.textStyle->PresContext()->AppUnitsPerDevPixel();
+ // Writing mode of parent frame is used because the text frame may
+ // be orthogonal to its parent when text-combine-upright is used or
+ // its parent has "display: contents", and in those cases, we want
+ // to draw the decoration lines according to parents' direction
+ // rather than ours.
+ const WritingMode wm = GetParent()->GetWritingMode();
+ bool verticalDec = wm.IsVertical();
+ bool verticalRun = mTextRun->IsVertical();
+ // If the text run and the decoration is orthogonal, we choose the
+ // metrics for decoration so that decoration line won't be broken.
+ bool useVerticalMetrics = verticalDec != verticalRun
+ ? verticalDec
+ : verticalRun && mTextRun->UseCenterBaseline();
+
+ // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
+ nscoord x = NSToCoordRound(aParams.framePt.x);
+ nscoord y = NSToCoordRound(aParams.framePt.y);
+
+ // 'measure' here is textrun-relative, so for a horizontal run it's the
+ // width, while for a vertical run it's the height of the decoration
+ const nsSize frameSize = GetSize();
+ nscoord measure = verticalDec ? frameSize.height : frameSize.width;
+
+ if (verticalDec) {
+ aParams.clipEdges->Intersect(&y, &measure);
+ } else {
+ aParams.clipEdges->Intersect(&x, &measure);
+ }
+
+ // decSize is a textrun-relative size, so its 'width' field is actually
+ // the run-relative measure, and 'height' will be the line thickness
+ gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app;
+ // The starting edge of the frame in block direction
+ gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
+
+ // In vertical-rl mode, block coordinates are measured from the
+ // right, so we need to adjust here.
+ if (wm.IsVerticalRL()) {
+ frameBStart += frameSize.width;
+ ascent = -ascent;
+ }
+
+ nscoord inflationMinFontSize = nsLayoutUtils::InflationMinFontSizeFor(this);
+
+ PaintDecorationLineParams params;
+ params.context = aParams.context;
+ params.dirtyRect = aParams.dirtyRect;
+ params.overrideColor = aParams.decorationOverrideColor;
+ params.callbacks = aParams.callbacks;
+ params.glyphRange = aParams.glyphRange;
+ params.provider = aParams.provider;
+ // pt is the physical point where the decoration is to be drawn,
+ // relative to the frame; one of its coordinates will be updated below.
+ params.pt = Point(x / app, y / app);
+ Float& bCoord = verticalDec ? params.pt.x.value : params.pt.y.value;
+ params.lineSize = Size(measure / app, 0);
+ params.ascent = ascent;
+ params.vertical = verticalDec;
+ params.sidewaysLeft = mTextRun->IsSidewaysLeft();
+
+ // The matrix of the context may have been altered for text-combine-
+ // upright. However, we want to draw decoration lines unscaled, thus
+ // we need to revert the scaling here.
+ gfxContextMatrixAutoSaveRestore scaledRestorer;
+ if (Style()->IsTextCombined()) {
+ float scaleFactor = GetTextCombineScaleFactor(this);
+ if (scaleFactor != 1.0f) {
+ scaledRestorer.SetContext(aParams.context);
+ gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
+ gfxPoint pt(x / app, y / app);
+ if (GetTextRun(nsTextFrame::eInflated)->IsRightToLeft()) {
+ pt.x += gfxFloat(frameSize.width) / app;
+ }
+ unscaled.PreTranslate(pt)
+ .PreScale(1.0f / scaleFactor, 1.0f)
+ .PreTranslate(-pt);
+ aParams.context->SetMatrixDouble(unscaled);
+ }
+ }
+
+ typedef gfxFont::Metrics Metrics;
+ auto paintDecorationLine = [&](const LineDecoration& dec,
+ gfxFloat Metrics::*lineSize,
+ StyleTextDecorationLine lineType) {
+ if (dec.mStyle == StyleTextDecorationStyle::None) {
+ return;
+ }
+
+ float inflation =
+ GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
+ const Metrics metrics = GetFirstFontMetrics(
+ GetFontGroupForFrame(dec.mFrame, inflation), useVerticalMetrics);
+
+ bCoord = (frameBStart - dec.mBaselineOffset) / app;
+
+ params.color = dec.mColor;
+ params.baselineOffset = dec.mBaselineOffset / app;
+ params.defaultLineThickness = metrics.*lineSize;
+ params.lineSize.height = ComputeDecorationLineThickness(
+ dec.mTextDecorationThickness, params.defaultLineThickness, metrics, app,
+ dec.mFrame);
+
+ bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
+ params.offset = ComputeDecorationLineOffset(
+ lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, metrics,
+ app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline);
+
+ params.style = dec.mStyle;
+ PaintDecorationLine(params);
+ };
+
+ // We create a clip region in order to draw the decoration lines only in the
+ // range of the text. Restricting the draw area prevents the decoration lines
+ // to be drawn multiple times when a part of the text is selected.
+
+ // We skip clipping for the following cases:
+ // - drawing the whole text
+ // - having different orientation of the text and the writing-mode, such as
+ // "text-combine-upright" (Bug 1408825)
+ bool skipClipping =
+ aRange.Length() == mTextRun->GetLength() || verticalDec != verticalRun;
+
+ gfxRect clipRect;
+ if (!skipClipping) {
+ // Get the inline-size according to the specified range.
+ gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider);
+ nsRect visualRect = InkOverflowRect();
+
+ const bool isInlineReversed = mTextRun->IsInlineReversed();
+ if (verticalDec) {
+ clipRect.x = aParams.framePt.x + visualRect.x;
+ clipRect.y = isInlineReversed ? aTextBaselinePt.y.value - clipLength
+ : aTextBaselinePt.y.value;
+ clipRect.width = visualRect.width;
+ clipRect.height = clipLength;
+ } else {
+ clipRect.x = isInlineReversed ? aTextBaselinePt.x.value - clipLength
+ : aTextBaselinePt.x.value;
+ clipRect.y = aParams.framePt.y + visualRect.y;
+ clipRect.width = clipLength;
+ clipRect.height = visualRect.height;
+ }
+
+ clipRect.Scale(1 / app);
+ clipRect.Round();
+ params.context->Clip(clipRect);
+ }
+
+ // Underlines
+ params.decoration = StyleTextDecorationLine::UNDERLINE;
+ for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
+ paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
+ }
+
+ // Overlines
+ params.decoration = StyleTextDecorationLine::OVERLINE;
+ for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
+ paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
+ }
+
+ // Some glyphs and emphasis marks may extend outside the region, so we reset
+ // the clip region here. For an example, italic glyphs.
+ if (!skipClipping) {
+ params.context->PopClip();
+ }
+
+ {
+ gfxContextMatrixAutoSaveRestore unscaledRestorer;
+ if (scaledRestorer.HasMatrix()) {
+ unscaledRestorer.SetContext(aParams.context);
+ aParams.context->SetMatrix(scaledRestorer.Matrix());
+ }
+
+ // CSS 2.1 mandates that text be painted after over/underlines,
+ // and *then* line-throughs
+ DrawTextRun(aRange, aTextBaselinePt, aParams);
+ }
+
+ // Emphasis marks
+ DrawEmphasisMarks(aParams.context, wm, aTextBaselinePt, aParams.framePt,
+ aRange, aParams.decorationOverrideColor, aParams.provider);
+
+ // Re-apply the clip region when the line-through is being drawn.
+ if (!skipClipping) {
+ params.context->Clip(clipRect);
+ }
+
+ // Line-throughs
+ params.decoration = StyleTextDecorationLine::LINE_THROUGH;
+ for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
+ paintDecorationLine(dec, &Metrics::strikeoutSize, params.decoration);
+ }
+
+ if (!skipClipping) {
+ params.context->PopClip();
+ }
+}
+
+void nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt,
+ const DrawTextParams& aParams) {
+ TextDecorations decorations;
+ GetTextDecorations(aParams.textStyle->PresContext(),
+ aParams.callbacks ? eUnresolvedColors : eResolvedColors,
+ decorations);
+
+ // Hide text decorations if we're currently hiding @font-face fallback text
+ const bool drawDecorations =
+ !aParams.provider->GetFontGroup()->ShouldSkipDrawing() &&
+ (decorations.HasDecorationLines() ||
+ StyleText()->HasEffectiveTextEmphasis());
+ if (drawDecorations) {
+ DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations);
+ } else {
+ DrawTextRun(aRange, aTextBaselinePt, aParams);
+ }
+
+ if (auto* textDrawer = aParams.context->GetTextDrawer()) {
+ textDrawer->TerminateShadows();
+ }
+}
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds, nsRect)
+
+nsRect nsTextFrame::WebRenderBounds() {
+ // WR text bounds is just our ink overflow rect but without shadows. So if we
+ // have no shadows, just use the layout bounds.
+ if (!StyleText()->HasTextShadow()) {
+ return InkOverflowRect();
+ }
+ nsRect* cachedBounds = GetProperty(WebRenderTextBounds());
+ if (!cachedBounds) {
+ OverflowAreas overflowAreas;
+ ComputeCustomOverflowInternal(overflowAreas, false);
+ cachedBounds = new nsRect(overflowAreas.InkOverflow());
+ SetProperty(WebRenderTextBounds(), cachedBounds);
+ }
+ return *cachedBounds;
+}
+
+int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) {
+ // get the selection controller
+ nsCOMPtr<nsISelectionController> selectionController;
+ nsresult rv = GetSelectionController(PresContext(),
+ getter_AddRefs(selectionController));
+ if (NS_FAILED(rv) || !selectionController)
+ return nsISelectionController::SELECTION_OFF;
+
+ selectionController->GetSelectionFlags(aSelectionFlags);
+
+ int16_t selectionValue;
+ selectionController->GetDisplaySelection(&selectionValue);
+
+ return selectionValue;
+}
+
+bool nsTextFrame::IsEntirelyWhitespace() const {
+ const nsTextFragment& text = mContent->AsText()->TextFragment();
+ for (uint32_t index = 0; index < text.GetLength(); ++index) {
+ const char16_t ch = text.CharAt(index);
+ if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0xa0) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Compute the longest prefix of text whose width is <= aWidth. Return
+ * the length of the prefix. Also returns the width of the prefix in aFitWidth.
+ */
+static uint32_t CountCharsFit(const gfxTextRun* aTextRun,
+ gfxTextRun::Range aRange, gfxFloat aWidth,
+ nsTextFrame::PropertyProvider* aProvider,
+ gfxFloat* aFitWidth) {
+ uint32_t last = 0;
+ gfxFloat width = 0;
+ for (uint32_t i = 1; i <= aRange.Length(); ++i) {
+ if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
+ gfxTextRun::Range range(aRange.start + last, aRange.start + i);
+ gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
+ if (nextWidth > aWidth) {
+ break;
+ }
+ last = i;
+ width = nextWidth;
+ }
+ }
+ *aFitWidth = width;
+ return last;
+}
+
+nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint(
+ const nsPoint& aPoint) {
+ return GetCharacterOffsetAtFramePointInternal(aPoint, true);
+}
+
+nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePoint(
+ const nsPoint& aPoint) {
+ return GetCharacterOffsetAtFramePointInternal(aPoint, false);
+}
+
+nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePointInternal(
+ const nsPoint& aPoint, bool aForInsertionPoint) {
+ ContentOffsets offsets;
+
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return offsets;
+ }
+
+ PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
+ // Trim leading but not trailing whitespace if possible
+ provider.InitializeForDisplay(false);
+ gfxFloat width =
+ mTextRun->IsVertical()
+ ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
+ : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
+ if (Style()->IsTextCombined()) {
+ width /= GetTextCombineScaleFactor(this);
+ }
+ gfxFloat fitWidth;
+ Range skippedRange = ComputeTransformedRange(provider);
+
+ uint32_t charsFit =
+ CountCharsFit(mTextRun, skippedRange, width, &provider, &fitWidth);
+
+ int32_t selectedOffset;
+ if (charsFit < skippedRange.Length()) {
+ // charsFit characters fitted, but no more could fit. See if we're
+ // more than halfway through the cluster.. If we are, choose the next
+ // cluster.
+ gfxSkipCharsIterator extraCluster(provider.GetStart());
+ extraCluster.AdvanceSkipped(charsFit);
+
+ bool allowSplitLigature = true; // Allow selection of partial ligature...
+
+ // ...but don't let selection/insertion-point split two Regional Indicator
+ // chars that are ligated in the textrun to form a single flag symbol.
+ uint32_t offs = extraCluster.GetOriginalOffset();
+ const nsTextFragment* frag = TextFragment();
+ if (frag->IsHighSurrogateFollowedByLowSurrogateAt(offs) &&
+ gfxFontUtils::IsRegionalIndicator(frag->ScalarValueAt(offs))) {
+ allowSplitLigature = false;
+ if (extraCluster.GetSkippedOffset() > 1 &&
+ !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
+ // CountCharsFit() left us in the middle of the flag; back up over the
+ // first character of the ligature, and adjust fitWidth accordingly.
+ extraCluster.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units
+ fitWidth -= mTextRun->GetAdvanceWidth(
+ Range(extraCluster.GetSkippedOffset(),
+ extraCluster.GetSkippedOffset() + 2),
+ &provider);
+ }
+ }
+
+ gfxSkipCharsIterator extraClusterLastChar(extraCluster);
+ FindClusterEnd(
+ mTextRun,
+ provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
+ &extraClusterLastChar, allowSplitLigature);
+ PropertyProvider::Spacing spacing;
+ Range extraClusterRange(extraCluster.GetSkippedOffset(),
+ extraClusterLastChar.GetSkippedOffset() + 1);
+ gfxFloat charWidth =
+ mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
+ charWidth -= spacing.mBefore + spacing.mAfter;
+ selectedOffset = !aForInsertionPoint ||
+ width <= fitWidth + spacing.mBefore + charWidth / 2
+ ? extraCluster.GetOriginalOffset()
+ : extraClusterLastChar.GetOriginalOffset() + 1;
+ } else {
+ // All characters fitted, we're at (or beyond) the end of the text.
+ // XXX This could be some pathological situation where negative spacing
+ // caused characters to move backwards. We can't really handle that
+ // in the current frame system because frames can't have negative
+ // intrinsic widths.
+ selectedOffset =
+ provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
+ // If we're at the end of a preformatted line which has a terminating
+ // linefeed, we want to reduce the offset by one to make sure that the
+ // selection is placed before the linefeed character.
+ if (HasSignificantTerminalNewline()) {
+ --selectedOffset;
+ }
+ }
+
+ offsets.content = GetContent();
+ offsets.offset = offsets.secondaryOffset = selectedOffset;
+ offsets.associate = mContentOffset == offsets.offset
+ ? CaretAssociationHint::After
+ : CaretAssociationHint::Before;
+ return offsets;
+}
+
+bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
+ nsRect& aRect) {
+ if (aRect.IsEmpty()) {
+ return false;
+ }
+
+ nsRect givenRect = aRect;
+
+ gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this);
+ RefPtr<gfxFont> firstFont = fontGroup->GetFirstValidFont();
+ WritingMode wm = GetWritingMode();
+ bool verticalRun = wm.IsVertical();
+ bool useVerticalMetrics = verticalRun && !wm.IsSideways();
+ const gfxFont::Metrics& metrics =
+ firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal);
+
+ nsCSSRendering::DecorationRectParams params;
+ params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
+
+ params.offset = fontGroup->GetUnderlineOffset();
+
+ TextDecorations textDecs;
+ GetTextDecorations(aPresContext, eResolvedColors, textDecs);
+
+ params.descentLimit =
+ ComputeDescentLimitForSelectionUnderline(aPresContext, metrics);
+ params.vertical = verticalRun;
+
+ if (verticalRun) {
+ EnsureTextRun(nsTextFrame::eInflated);
+ params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false;
+ } else {
+ params.sidewaysLeft = false;
+ }
+
+ UniquePtr<SelectionDetails> details = GetSelectionDetails();
+ for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
+ if (sd->mStart == sd->mEnd ||
+ sd->mSelectionType == SelectionType::eInvalid ||
+ !(ToSelectionTypeMask(sd->mSelectionType) &
+ kSelectionTypesWithDecorations) ||
+ // URL strikeout does not use underline.
+ sd->mSelectionType == SelectionType::eURLStrikeout) {
+ continue;
+ }
+
+ float relativeSize;
+ auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
+ sd->mSelectionType);
+ if (sd->mSelectionType == SelectionType::eSpellCheck) {
+ if (!nsTextPaintStyle::GetSelectionUnderline(
+ this, index, nullptr, &relativeSize, &params.style)) {
+ continue;
+ }
+ } else {
+ // IME selections
+ TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
+ if (rangeStyle.IsDefined()) {
+ if (!rangeStyle.IsLineStyleDefined() ||
+ rangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
+ continue;
+ }
+ params.style = ToStyleLineStyle(rangeStyle);
+ relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
+ } else if (!nsTextPaintStyle::GetSelectionUnderline(
+ this, index, nullptr, &relativeSize, &params.style)) {
+ continue;
+ }
+ }
+ nsRect decorationArea;
+
+ const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
+ params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width);
+ params.defaultLineThickness = ComputeSelectionUnderlineHeight(
+ aPresContext, metrics, sd->mSelectionType);
+
+ params.lineSize.height = ComputeDecorationLineThickness(
+ decThickness, params.defaultLineThickness, metrics,
+ aPresContext->AppUnitsPerDevPixel(), this);
+
+ bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
+ const auto* styleText = StyleText();
+ params.offset = ComputeDecorationLineOffset(
+ textDecs.HasUnderline() ? StyleTextDecorationLine::UNDERLINE
+ : StyleTextDecorationLine::OVERLINE,
+ styleText->mTextUnderlinePosition, styleText->mTextUnderlineOffset,
+ metrics, aPresContext->AppUnitsPerDevPixel(), this,
+ wm.IsCentralBaseline(), swapUnderline);
+
+ relativeSize = std::max(relativeSize, 1.0f);
+ params.lineSize.height *= relativeSize;
+ params.defaultLineThickness *= relativeSize;
+ decorationArea =
+ nsCSSRendering::GetTextDecorationRect(aPresContext, params);
+ aRect.UnionRect(aRect, decorationArea);
+ }
+
+ return !aRect.IsEmpty() && !givenRect.Contains(aRect);
+}
+
+bool nsTextFrame::IsFrameSelected() const {
+ NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
+ "use the public IsSelected() instead");
+ if (mIsSelected == nsTextFrame::SelectionState::Unknown) {
+ const bool isSelected =
+ GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
+ mIsSelected = isSelected ? nsTextFrame::SelectionState::Selected
+ : nsTextFrame::SelectionState::NotSelected;
+ } else {
+#ifdef DEBUG
+ // Assert that the selection caching works.
+ const bool isReallySelected =
+ GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
+ NS_ASSERTION((mIsSelected == nsTextFrame::SelectionState::Selected) ==
+ isReallySelected,
+ "Should have called InvalidateSelectionState()");
+#endif
+ }
+
+ return mIsSelected == nsTextFrame::SelectionState::Selected;
+}
+
+nsTextFrame* nsTextFrame::FindContinuationForOffset(int32_t aOffset) {
+ // Use a continuations array to accelerate finding the first continuation
+ // of interest, if possible.
+ MOZ_ASSERT(!GetPrevContinuation(), "should be called on the primary frame");
+ auto* continuations = GetContinuations();
+ nsTextFrame* f = this;
+ if (continuations) {
+ size_t index;
+ if (BinarySearchIf(
+ *continuations, 0, continuations->Length(),
+ [=](nsTextFrame* aFrame) -> int {
+ return aOffset - aFrame->GetContentOffset();
+ },
+ &index)) {
+ f = (*continuations)[index];
+ } else {
+ f = (*continuations)[index ? index - 1 : 0];
+ }
+ }
+
+ while (f && f->GetContentEnd() <= aOffset) {
+ f = f->GetNextContinuation();
+ }
+
+ return f;
+}
+
+void nsTextFrame::SelectionStateChanged(uint32_t aStart, uint32_t aEnd,
+ bool aSelected,
+ SelectionType aSelectionType) {
+ NS_ASSERTION(!GetPrevContinuation(),
+ "Should only be called for primary frame");
+ DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
+
+ InvalidateSelectionState();
+
+ // Selection is collapsed, which can't affect text frame rendering
+ if (aStart == aEnd) {
+ return;
+ }
+
+ nsTextFrame* f = FindContinuationForOffset(aStart);
+
+ nsPresContext* presContext = PresContext();
+ while (f && f->GetContentOffset() < int32_t(aEnd)) {
+ // We may need to reflow to recompute the overflow area for
+ // spellchecking or IME underline if their underline is thicker than
+ // the normal decoration line.
+ if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) {
+ bool didHaveOverflowingSelection =
+ f->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
+ nsRect r(nsPoint(0, 0), GetSize());
+ if (didHaveOverflowingSelection ||
+ (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) {
+ presContext->PresShell()->FrameNeedsReflow(
+ f, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ }
+ }
+ // Selection might change anything. Invalidate the overflow area.
+ f->InvalidateFrame();
+
+ f = f->GetNextContinuation();
+ }
+}
+
+void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties,
+ int32_t& aInOffset,
+ gfxSkipCharsIterator& aIter) {
+ if (aInOffset < GetContentOffset()) {
+ NS_WARNING("offset before this frame's content");
+ aInOffset = GetContentOffset();
+ } else if (aInOffset > GetContentEnd()) {
+ NS_WARNING("offset after this frame's content");
+ aInOffset = GetContentEnd();
+ }
+
+ int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset();
+ int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength();
+ aInOffset = std::max(aInOffset, trimmedOffset);
+ aInOffset = std::min(aInOffset, trimmedEnd);
+
+ aIter.SetOriginalOffset(aInOffset);
+
+ if (aInOffset < trimmedEnd && !aIter.IsOriginalCharSkipped() &&
+ !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) {
+ // Called for non-cluster boundary
+ FindClusterStart(mTextRun, trimmedOffset, &aIter);
+ }
+}
+
+nsPoint nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter,
+ PropertyProvider& aProperties) {
+ Range range(aProperties.GetStart().GetSkippedOffset(),
+ aIter.GetSkippedOffset());
+ gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties);
+ nscoord iSize = NSToCoordCeilClamped(advance);
+ nsPoint point;
+
+ if (mTextRun->IsVertical()) {
+ point.x = 0;
+ if (mTextRun->IsInlineReversed()) {
+ point.y = mRect.height - iSize;
+ } else {
+ point.y = iSize;
+ }
+ } else {
+ point.y = 0;
+ if (Style()->IsTextCombined()) {
+ iSize *= GetTextCombineScaleFactor(this);
+ }
+ if (mTextRun->IsInlineReversed()) {
+ point.x = mRect.width - iSize;
+ } else {
+ point.x = iSize;
+ }
+ }
+ return point;
+}
+
+nsresult nsTextFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
+ if (!outPoint) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (GetContentLength() <= 0) {
+ outPoint->x = 0;
+ outPoint->y = 0;
+ return NS_OK;
+ }
+
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
+ // Don't trim trailing whitespace, we want the caret to appear in the right
+ // place if it's positioned there
+ properties.InitializeForDisplay(false);
+
+ UpdateIteratorFromOffset(properties, inOffset, iter);
+
+ *outPoint = GetPointFromIterator(iter, properties);
+
+ return NS_OK;
+}
+
+nsresult nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset,
+ int32_t aLength,
+ nsTArray<nsRect>& aRects) {
+ DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (GetContentLength() <= 0) {
+ return NS_OK;
+ }
+
+ if (!mTextRun) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
+ // Don't trim trailing whitespace, we want the caret to appear in the right
+ // place if it's positioned there
+ properties.InitializeForDisplay(false);
+
+ // Initialize iter; this will call FindClusterStart if necessary to align
+ // iter to a cluster boundary.
+ UpdateIteratorFromOffset(properties, aInOffset, iter);
+ nsPoint point = GetPointFromIterator(iter, properties);
+
+ const int32_t kContentEnd = GetContentEnd();
+ const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
+
+ if (aInOffset >= kEndOffset) {
+ return NS_OK;
+ }
+
+ if (!aRects.SetCapacity(aRects.Length() + kEndOffset - aInOffset,
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ do {
+ // We'd like to assert here that |point| matches
+ // |GetPointFromIterator(iter, properties)|, which in principle should be
+ // true; however, testcases with vast dimensions can lead to coordinate
+ // overflow and disrupt the calculations. So we've dropped the assertion
+ // to avoid tripping the fuzzer unnecessarily.
+
+ // Measure to the end of the cluster.
+ nscoord iSize = 0;
+ gfxSkipCharsIterator nextIter(iter);
+ if (aInOffset < kContentEnd) {
+ nextIter.AdvanceOriginal(1);
+ if (!nextIter.IsOriginalCharSkipped() &&
+ !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) &&
+ nextIter.GetOriginalOffset() < kContentEnd) {
+ FindClusterEnd(mTextRun, kContentEnd, &nextIter);
+ }
+
+ gfxFloat advance = mTextRun->GetAdvanceWidth(
+ Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()),
+ &properties);
+ iSize = NSToCoordCeilClamped(advance);
+ }
+
+ // Compute the cluster rect, depending on directionality, and update
+ // point to the origin we'll need for the next cluster.
+ nsRect rect;
+ rect.x = point.x;
+ rect.y = point.y;
+
+ if (mTextRun->IsVertical()) {
+ rect.width = mRect.width;
+ rect.height = iSize;
+ if (mTextRun->IsInlineReversed()) {
+ // The iterator above returns a point with the origin at the
+ // bottom left instead of the top left. Move the origin to the top left
+ // by subtracting the character's height.
+ rect.y -= rect.height;
+ point.y -= iSize;
+ } else {
+ point.y += iSize;
+ }
+ } else {
+ if (Style()->IsTextCombined()) {
+ // The scale factor applies to the inline advance of the glyphs, so it
+ // affects both the rect width and the origin point for the next glyph.
+ iSize *= GetTextCombineScaleFactor(this);
+ }
+ rect.width = iSize;
+ rect.height = mRect.height;
+ if (mTextRun->IsInlineReversed()) {
+ // The iterator above returns a point with the origin at the
+ // top right instead of the top left. Move the origin to the top left by
+ // subtracting the character's width.
+ rect.x -= iSize;
+ point.x -= iSize;
+ } else {
+ point.x += iSize;
+ }
+ }
+
+ // Set the rect for all characters in the cluster.
+ int32_t end = std::min(kEndOffset, nextIter.GetOriginalOffset());
+ while (aInOffset < end) {
+ aRects.AppendElement(rect);
+ aInOffset++;
+ }
+
+ // Advance iter for the next cluster.
+ iter = nextIter;
+ } while (aInOffset < kEndOffset);
+
+ return NS_OK;
+}
+
+nsresult nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
+ bool aHint,
+ int32_t* aOutOffset,
+ nsIFrame** aOutFrame) {
+ DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
+#if 0 // XXXrbs disable due to bug 310227
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY))
+ return NS_ERROR_UNEXPECTED;
+#endif
+
+ NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
+ NS_ASSERTION(aContentOffset >= 0,
+ "Negative content offset, existing code was very broken!");
+ nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
+ if (this != primaryFrame) {
+ // This call needs to happen on the primary frame
+ return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
+ aOutOffset, aOutFrame);
+ }
+
+ nsTextFrame* f = this;
+ int32_t offset = mContentOffset;
+
+ // Try to look up the offset to frame property
+ nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty());
+
+ if (cachedFrame) {
+ f = cachedFrame;
+ offset = f->GetContentOffset();
+
+ f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
+ }
+
+ if ((aContentOffset >= offset) && (aHint || aContentOffset != offset)) {
+ while (true) {
+ nsTextFrame* next = f->GetNextContinuation();
+ if (!next || aContentOffset < next->GetContentOffset()) {
+ break;
+ }
+ if (aContentOffset == next->GetContentOffset()) {
+ if (aHint) {
+ f = next;
+ if (f->GetContentLength() == 0) {
+ continue; // use the last of the empty frames with this offset
+ }
+ }
+ break;
+ }
+ f = next;
+ }
+ } else {
+ while (true) {
+ nsTextFrame* prev = f->GetPrevContinuation();
+ if (!prev || aContentOffset > f->GetContentOffset()) {
+ break;
+ }
+ if (aContentOffset == f->GetContentOffset()) {
+ if (!aHint) {
+ f = prev;
+ if (f->GetContentLength() == 0) {
+ continue; // use the first of the empty frames with this offset
+ }
+ }
+ break;
+ }
+ f = prev;
+ }
+ }
+
+ *aOutOffset = aContentOffset - f->GetContentOffset();
+ *aOutFrame = f;
+
+ // cache the frame we found
+ SetProperty(OffsetToFrameProperty(), f);
+ f->AddStateBits(TEXT_IN_OFFSET_CACHE);
+
+ return NS_OK;
+}
+
+nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward,
+ int32_t* aOffset) {
+ NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),
+ "aOffset out of range");
+
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return CONTINUE_EMPTY;
+ }
+
+ TrimmedOffsets trimmed = GetTrimmedOffsets(TextFragment());
+ // Check whether there are nonskipped characters in the trimmmed range
+ return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
+ iter.ConvertOriginalToSkipped(trimmed.mStart))
+ ? FOUND
+ : CONTINUE;
+}
+
+/**
+ * This class iterates through the clusters before or after the given
+ * aPosition (which is a content offset). You can test each cluster
+ * to see if it's whitespace (as far as selection/caret movement is concerned),
+ * or punctuation, or if there is a word break before the cluster. ("Before"
+ * is interpreted according to aDirection, so if aDirection is -1, "before"
+ * means actually *after* the cluster content.)
+ */
+class MOZ_STACK_CLASS ClusterIterator {
+ public:
+ ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
+ int32_t aDirection, nsString& aContext,
+ bool aTrimSpaces = true);
+
+ bool NextCluster();
+ bool IsInlineWhitespace() const;
+ bool IsNewline() const;
+ bool IsPunctuation() const;
+ bool HaveWordBreakBefore() const { return mHaveWordBreak; }
+
+ // Get the charIndex that corresponds to the "before" side of the current
+ // character, according to the direction of iteration: so for a forward
+ // iterator, this is simply mCharIndex, while for a reverse iterator it will
+ // be mCharIndex + <number of code units in the character>.
+ int32_t GetBeforeOffset() const {
+ MOZ_ASSERT(mCharIndex >= 0);
+ return mDirection < 0 ? GetAfterInternal() : mCharIndex;
+ }
+ // Get the charIndex that corresponds to the "before" side of the current
+ // character, according to the direction of iteration: the opposite side
+ // to what GetBeforeOffset returns.
+ int32_t GetAfterOffset() const {
+ MOZ_ASSERT(mCharIndex >= 0);
+ return mDirection > 0 ? GetAfterInternal() : mCharIndex;
+ }
+
+ private:
+ // Helper for Get{After,Before}Offset; returns the charIndex after the
+ // current position in the text, accounting for surrogate pairs.
+ int32_t GetAfterInternal() const;
+
+ gfxSkipCharsIterator mIterator;
+ // Usually, mFrag is pointer to `dom::CharacterData::mText`. However, if
+ // we're in a password field, this points `mMaskedFrag`.
+ const nsTextFragment* mFrag;
+ // If we're in a password field, this is initialized with mask characters.
+ nsTextFragment mMaskedFrag;
+ nsTextFrame* mTextFrame;
+ int32_t mDirection; // +1 or -1, or 0 to indicate failure
+ int32_t mCharIndex;
+ nsTextFrame::TrimmedOffsets mTrimmed;
+ nsTArray<bool> mWordBreaks;
+ bool mHaveWordBreak;
+};
+
+static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
+ bool aRespectClusters,
+ const gfxTextRun* aTextRun,
+ nsTextFrame* aFrame) {
+ if (aIter.IsOriginalCharSkipped()) {
+ return false;
+ }
+ uint32_t index = aIter.GetSkippedOffset();
+ if (aRespectClusters && !aTextRun->IsClusterStart(index)) {
+ return false;
+ }
+ if (index > 0) {
+ // Check whether the proposed position is in between the two halves of a
+ // surrogate pair, before a Variation Selector character, or within a
+ // ligated emoji sequence; if so, this is not a valid character boundary.
+ // (In the case where we are respecting clusters, we won't actually get
+ // this far because the low surrogate is also marked as non-clusterStart
+ // so we'll return FALSE above.)
+ const uint32_t offs = AssertedCast<uint32_t>(aIter.GetOriginalOffset());
+ const nsTextFragment* frag = aFrame->TextFragment();
+ const char16_t ch = frag->CharAt(offs);
+
+ if (gfxFontUtils::IsVarSelector(ch) ||
+ frag->IsLowSurrogateFollowingHighSurrogateAt(offs) ||
+ (!aTextRun->IsLigatureGroupStart(index) &&
+ (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault ||
+ (unicode::GetEmojiPresentation(ch) == unicode::TextDefault &&
+ offs + 1 < frag->GetLength() &&
+ frag->CharAt(offs + 1) == gfxFontUtils::kUnicodeVS16)))) {
+ return false;
+ }
+
+ // If the proposed position is before a high surrogate, we need to decode
+ // the surrogate pair (if valid) and check the resulting character.
+ if (NS_IS_HIGH_SURROGATE(ch)) {
+ if (const char32_t ucs4 = frag->ScalarValueAt(offs)) {
+ // If the character is a (Plane-14) variation selector,
+ // or an emoji character that is ligated with the previous
+ // character (i.e. part of a Regional-Indicator flag pair,
+ // or an emoji-ZWJ sequence), this is not a valid boundary.
+ if (gfxFontUtils::IsVarSelector(ucs4) ||
+ (!aTextRun->IsLigatureGroupStart(index) &&
+ unicode::GetEmojiPresentation(ucs4) == unicode::EmojiDefault)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
+ int32_t contentLength = GetContentLength();
+ NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
+
+ if (!aOptions.mIgnoreUserStyleAll) {
+ StyleUserSelect selectStyle;
+ Unused << IsSelectable(&selectStyle);
+ if (selectStyle == StyleUserSelect::All) {
+ return CONTINUE_UNSELECTABLE;
+ }
+ }
+
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return CONTINUE_EMPTY;
+ }
+
+ TrimmedOffsets trimmed =
+ GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
+
+ // A negative offset means "end of frame".
+ int32_t startOffset =
+ GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
+
+ if (!aForward) {
+ // If at the beginning of the line, look at the previous continuation
+ for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
+ i >= trimmed.mStart; --i) {
+ iter.SetOriginalOffset(i);
+ if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
+ this)) {
+ *aOffset = i - mContentOffset;
+ return FOUND;
+ }
+ }
+ *aOffset = 0;
+ } else {
+ // If we're at the end of a line, look at the next continuation
+ iter.SetOriginalOffset(startOffset);
+ if (startOffset <= trimmed.GetEnd() &&
+ !(startOffset < trimmed.GetEnd() &&
+ StyleText()->NewlineIsSignificant(this) &&
+ iter.GetSkippedOffset() < mTextRun->GetLength() &&
+ mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
+ for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
+ iter.SetOriginalOffset(i);
+ if (i == trimmed.GetEnd() ||
+ IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
+ this)) {
+ *aOffset = i - mContentOffset;
+ return FOUND;
+ }
+ }
+ }
+ *aOffset = contentLength;
+ }
+
+ return CONTINUE;
+}
+
+bool ClusterIterator::IsInlineWhitespace() const {
+ NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
+ return IsSelectionInlineWhitespace(mFrag, mCharIndex);
+}
+
+bool ClusterIterator::IsNewline() const {
+ NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
+ return IsSelectionNewline(mFrag, mCharIndex);
+}
+
+bool ClusterIterator::IsPunctuation() const {
+ NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
+ const char16_t ch = mFrag->CharAt(AssertedCast<uint32_t>(mCharIndex));
+ return mozilla::IsPunctuationForWordSelect(ch);
+}
+
+int32_t ClusterIterator::GetAfterInternal() const {
+ if (mFrag->IsHighSurrogateFollowedByLowSurrogateAt(
+ AssertedCast<uint32_t>(mCharIndex))) {
+ return mCharIndex + 2;
+ }
+ return mCharIndex + 1;
+}
+
+bool ClusterIterator::NextCluster() {
+ if (!mDirection) {
+ return false;
+ }
+ const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
+
+ mHaveWordBreak = false;
+ while (true) {
+ bool keepGoing = false;
+ if (mDirection > 0) {
+ if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) {
+ return false;
+ }
+ keepGoing = mIterator.IsOriginalCharSkipped() ||
+ mIterator.GetOriginalOffset() < mTrimmed.mStart ||
+ !textRun->IsClusterStart(mIterator.GetSkippedOffset());
+ mCharIndex = mIterator.GetOriginalOffset();
+ mIterator.AdvanceOriginal(1);
+ } else {
+ if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) {
+ // Trimming can skip backward word breakers, see bug 1667138
+ return mHaveWordBreak;
+ }
+ mIterator.AdvanceOriginal(-1);
+ keepGoing = mIterator.IsOriginalCharSkipped() ||
+ mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
+ !textRun->IsClusterStart(mIterator.GetSkippedOffset());
+ mCharIndex = mIterator.GetOriginalOffset();
+ }
+
+ if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
+ mHaveWordBreak = true;
+ }
+ if (!keepGoing) {
+ return true;
+ }
+ }
+}
+
+ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
+ int32_t aDirection, nsString& aContext,
+ bool aTrimSpaces)
+ : mIterator(aTextFrame->EnsureTextRun(nsTextFrame::eInflated)),
+ mTextFrame(aTextFrame),
+ mDirection(aDirection),
+ mCharIndex(-1),
+ mHaveWordBreak(false) {
+ gfxTextRun* textRun = aTextFrame->GetTextRun(nsTextFrame::eInflated);
+ if (!textRun) {
+ mDirection = 0; // signal failure
+ return;
+ }
+
+ mFrag = aTextFrame->TextFragment();
+ // If we're in a password field, some characters may be masked. In such
+ // case, we need to treat each masked character is a mask character since
+ // we shouldn't expose word boundary which is hidden by the masking.
+ if (aTextFrame->GetContent() && mFrag->GetLength() > 0 &&
+ aTextFrame->GetContent()->HasFlag(NS_MAYBE_MASKED) &&
+ (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)) {
+ const char16_t kPasswordMask = TextEditor::PasswordMask();
+ const nsTransformedTextRun* transformedTextRun =
+ static_cast<const nsTransformedTextRun*>(textRun);
+ // Use nsString and not nsAutoString so that we get a nsStringBuffer which
+ // can be just AddRefed in `mMaskedFrag`.
+ nsString maskedText;
+ maskedText.SetCapacity(mFrag->GetLength());
+ for (uint32_t i = 0; i < mFrag->GetLength(); ++i) {
+ mIterator.SetOriginalOffset(i);
+ uint32_t skippedOffset = mIterator.GetSkippedOffset();
+ if (mFrag->IsHighSurrogateFollowedByLowSurrogateAt(i)) {
+ if (transformedTextRun->mStyles[skippedOffset]->mMaskPassword) {
+ maskedText.Append(kPasswordMask);
+ maskedText.Append(kPasswordMask);
+ } else {
+ maskedText.Append(mFrag->CharAt(i));
+ maskedText.Append(mFrag->CharAt(i + 1));
+ }
+ ++i;
+ } else {
+ maskedText.Append(
+ transformedTextRun->mStyles[skippedOffset]->mMaskPassword
+ ? kPasswordMask
+ : mFrag->CharAt(i));
+ }
+ }
+ mMaskedFrag.SetTo(maskedText, mFrag->IsBidi(), true);
+ mFrag = &mMaskedFrag;
+ }
+
+ mIterator.SetOriginalOffset(aPosition);
+ mTrimmed = aTextFrame->GetTrimmedOffsets(
+ mFrag, aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default
+ : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter |
+ nsTextFrame::TrimmedOffsetFlags::NoTrimBefore);
+
+ const uint32_t textOffset =
+ AssertedCast<uint32_t>(aTextFrame->GetContentOffset());
+ const uint32_t textLen =
+ AssertedCast<uint32_t>(aTextFrame->GetContentLength());
+
+ // Allocate an extra element to record the word break at the end of the line
+ // or text run in mWordBreak[textLen].
+ mWordBreaks.AppendElements(textLen + 1);
+ PodZero(mWordBreaks.Elements(), textLen + 1);
+ uint32_t textStart;
+ if (aDirection > 0) {
+ if (aContext.IsEmpty()) {
+ // No previous context, so it must be the start of a line or text run
+ mWordBreaks[0] = true;
+ }
+ textStart = aContext.Length();
+ mFrag->AppendTo(aContext, textOffset, textLen);
+ } else {
+ if (aContext.IsEmpty()) {
+ // No following context, so it must be the end of a line or text run
+ mWordBreaks[textLen] = true;
+ }
+ textStart = 0;
+ nsAutoString str;
+ mFrag->AppendTo(str, textOffset, textLen);
+ aContext.Insert(str, 0);
+ }
+
+ const uint32_t textEnd = textStart + textLen;
+ intl::WordBreakIteratorUtf16 wordBreakIter(aContext);
+ Maybe<uint32_t> nextBreak =
+ wordBreakIter.Seek(textStart > 0 ? textStart - 1 : textStart);
+ while (nextBreak && *nextBreak <= textEnd) {
+ mWordBreaks[*nextBreak - textStart] = true;
+ nextBreak = wordBreakIter.Next();
+ }
+
+ MOZ_ASSERT(textEnd != aContext.Length() || mWordBreaks[textLen],
+ "There should be a word break at the end of a line or text run!");
+}
+
+nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord(
+ bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
+ int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
+ int32_t contentLength = GetContentLength();
+ NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
+
+ StyleUserSelect selectStyle;
+ Unused << IsSelectable(&selectStyle);
+ if (selectStyle == StyleUserSelect::All) {
+ return CONTINUE_UNSELECTABLE;
+ }
+
+ int32_t offset =
+ GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
+ ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext,
+ aTrimSpaces);
+
+ if (!cIter.NextCluster()) {
+ return CONTINUE_EMPTY;
+ }
+
+ do {
+ bool isPunctuation = cIter.IsPunctuation();
+ bool isInlineWhitespace = cIter.IsInlineWhitespace();
+ bool isWhitespace = isInlineWhitespace || cIter.IsNewline();
+ bool isWordBreakBefore = cIter.HaveWordBreakBefore();
+ if (!isWhitespace || isInlineWhitespace) {
+ aState->SetSawInlineCharacter();
+ }
+ if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
+ aState->SetSawBeforeType();
+ aState->Update(isPunctuation, isWhitespace);
+ continue;
+ }
+ // See if we can break before the current cluster
+ if (!aState->mAtStart) {
+ bool canBreak;
+ if (isPunctuation != aState->mLastCharWasPunctuation) {
+ canBreak = BreakWordBetweenPunctuation(aState, aForward, isPunctuation,
+ isWhitespace, aIsKeyboardSelect);
+ } else if (!aState->mLastCharWasWhitespace && !isWhitespace &&
+ !isPunctuation && isWordBreakBefore) {
+ // if both the previous and the current character are not white
+ // space but this can be word break before, we don't need to eat
+ // a white space in this case. This case happens in some languages
+ // that their words are not separated by white spaces. E.g.,
+ // Japanese and Chinese.
+ canBreak = true;
+ } else {
+ canBreak = isWordBreakBefore && aState->mSawBeforeType &&
+ (aWordSelectEatSpace != isWhitespace);
+ }
+ if (canBreak) {
+ *aOffset = cIter.GetBeforeOffset() - mContentOffset;
+ return FOUND;
+ }
+ }
+ aState->Update(isPunctuation, isWhitespace);
+ } while (cIter.NextCluster());
+
+ *aOffset = cIter.GetAfterOffset() - mContentOffset;
+ return CONTINUE;
+}
+
+bool nsTextFrame::HasVisibleText() {
+ // Text in the range is visible if there is at least one character in the
+ // range that is not skipped and is mapped by this frame (which is the primary
+ // frame) or one of its continuations.
+ for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
+ int32_t dummyOffset = 0;
+ if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::pair<int32_t, int32_t> nsTextFrame::GetOffsets() const {
+ return std::make_pair(GetContentOffset(), GetContentEnd());
+}
+
+static int32_t FindEndOfPunctuationRun(const nsTextFragment* aFrag,
+ const gfxTextRun* aTextRun,
+ gfxSkipCharsIterator* aIter,
+ int32_t aOffset, int32_t aStart,
+ int32_t aEnd) {
+ int32_t i;
+
+ for (i = aStart; i < aEnd - aOffset; ++i) {
+ if (nsContentUtils::IsFirstLetterPunctuation(
+ aFrag->ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) {
+ aIter->SetOriginalOffset(aOffset + i);
+ FindClusterEnd(aTextRun, aEnd, aIter);
+ i = aIter->GetOriginalOffset() - aOffset;
+ } else {
+ break;
+ }
+ }
+ return i;
+}
+
+/**
+ * Returns true if this text frame completes the first-letter, false
+ * if it does not contain a true "letter".
+ * If returns true, then it also updates aLength to cover just the first-letter
+ * text.
+ *
+ * XXX :first-letter should be handled during frame construction
+ * (and it has a good bit in common with nextBidi)
+ *
+ * @param aLength an in/out parameter: on entry contains the maximum length to
+ * return, on exit returns length of the first-letter fragment (which may
+ * include leading and trailing punctuation, for example)
+ */
+static bool FindFirstLetterRange(const nsTextFragment* aFrag,
+ const nsAtom* aLang,
+ const gfxTextRun* aTextRun, int32_t aOffset,
+ const gfxSkipCharsIterator& aIter,
+ int32_t* aLength) {
+ int32_t i;
+ int32_t length = *aLength;
+ int32_t endOffset = aOffset + length;
+ gfxSkipCharsIterator iter(aIter);
+
+ // Currently the only language-specific special case we handle here is the
+ // Dutch "IJ" digraph.
+ auto LangTagIsDutch = [](const nsAtom* aLang) -> bool {
+ if (!aLang) {
+ return false;
+ }
+ if (aLang == nsGkAtoms::nl) {
+ return true;
+ }
+ // We don't need to fully parse as a Locale; just check the initial subtag.
+ nsDependentAtomString langStr(aLang);
+ int32_t index = langStr.FindChar('-');
+ if (index > 0) {
+ langStr.Truncate(index);
+ return langStr.EqualsLiteral("nl");
+ }
+ return false;
+ };
+
+ // skip leading whitespace, then consume clusters that start with punctuation
+ i = FindEndOfPunctuationRun(
+ aFrag, aTextRun, &iter, aOffset,
+ GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1), endOffset);
+ if (i == length) {
+ return false;
+ }
+
+ // If the next character is not a letter, number or symbol, there is no
+ // first-letter.
+ // Return true so that we don't go on looking, but set aLength to 0.
+ const char32_t usv =
+ aFrag->ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
+ if (!nsContentUtils::IsAlphanumericOrSymbol(usv)) {
+ *aLength = 0;
+ return true;
+ }
+
+ // consume another cluster (the actual first letter)
+
+ // For complex scripts such as Indic and SEAsian, where first-letter
+ // should extend to entire orthographic "syllable" clusters, we don't
+ // want to allow this to split a ligature.
+ bool allowSplitLigature;
+ bool usesIndicHalfForms = false;
+
+ typedef intl::Script Script;
+ Script script = intl::UnicodeProperties::GetScriptCode(usv);
+ switch (script) {
+ default:
+ allowSplitLigature = true;
+ break;
+
+ // Don't break regional-indicator ligatures.
+ case Script::COMMON:
+ allowSplitLigature = !gfxFontUtils::IsRegionalIndicator(usv);
+ break;
+
+ // For now, lacking any definitive specification of when to apply this
+ // behavior, we'll base the decision on the HarfBuzz shaping engine
+ // used for each script: those that are handled by the Indic, Tibetan,
+ // Myanmar and SEAsian shapers will apply the "don't split ligatures"
+ // rule.
+
+ // Indic
+ case Script::BENGALI:
+ case Script::DEVANAGARI:
+ case Script::GUJARATI:
+ usesIndicHalfForms = true;
+ [[fallthrough]];
+
+ case Script::GURMUKHI:
+ case Script::KANNADA:
+ case Script::MALAYALAM:
+ case Script::ORIYA:
+ case Script::TAMIL:
+ case Script::TELUGU:
+ case Script::SINHALA:
+ case Script::BALINESE:
+ case Script::LEPCHA:
+ case Script::REJANG:
+ case Script::SUNDANESE:
+ case Script::JAVANESE:
+ case Script::KAITHI:
+ case Script::MEETEI_MAYEK:
+ case Script::CHAKMA:
+ case Script::SHARADA:
+ case Script::TAKRI:
+ case Script::KHMER:
+
+ // Tibetan
+ case Script::TIBETAN:
+
+ // Myanmar
+ case Script::MYANMAR:
+
+ // Other SEAsian
+ case Script::BUGINESE:
+ case Script::NEW_TAI_LUE:
+ case Script::CHAM:
+ case Script::TAI_THAM:
+
+ // What about Thai/Lao - any special handling needed?
+ // Should we special-case Arabic lam-alef?
+
+ allowSplitLigature = false;
+ break;
+ }
+
+ iter.SetOriginalOffset(aOffset + i);
+ FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
+
+ i = iter.GetOriginalOffset() - aOffset;
+
+ // Heuristic for Indic scripts that like to form conjuncts:
+ // If we ended at a virama that is ligated with the preceding character
+ // (e.g. creating a half-form), then don't stop here; include the next
+ // cluster as well so that we don't break a conjunct.
+ //
+ // Unfortunately this cannot distinguish between a letter+virama that ligate
+ // to create a half-form (in which case we have a conjunct that should not
+ // be broken) and a letter+virama that ligate purely for presentational
+ // reasons to position the (visible) virama component (in which case breaking
+ // after the virama would be acceptable). So results may be imperfect,
+ // depending how the font has chosen to implement visible viramas.
+ if (usesIndicHalfForms) {
+ while (i + 1 < length &&
+ !aTextRun->IsLigatureGroupStart(iter.GetSkippedOffset())) {
+ char32_t c = aFrag->ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
+ if (intl::UnicodeProperties::GetCombiningClass(c) ==
+ HB_UNICODE_COMBINING_CLASS_VIRAMA) {
+ iter.AdvanceOriginal(1);
+ FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
+ i = iter.GetOriginalOffset() - aOffset;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (i + 1 == length) {
+ return true;
+ }
+
+ // Check for Dutch "ij" digraph special case, but only if both letters have
+ // the same case.
+ if (script == Script::LATIN && LangTagIsDutch(aLang)) {
+ char16_t ch1 = aFrag->CharAt(AssertedCast<uint32_t>(aOffset + i));
+ char16_t ch2 = aFrag->CharAt(AssertedCast<uint32_t>(aOffset + i + 1));
+ if ((ch1 == 'i' && ch2 == 'j') || (ch1 == 'I' && ch2 == 'J')) {
+ iter.SetOriginalOffset(aOffset + i + 1);
+ FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
+ i = iter.GetOriginalOffset() - aOffset;
+ if (i + 1 == length) {
+ return true;
+ }
+ }
+ }
+
+ // consume clusters that start with punctuation
+ i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1,
+ endOffset);
+ if (i < length) {
+ *aLength = i;
+ }
+ return true;
+}
+
+static uint32_t FindStartAfterSkippingWhitespace(
+ nsTextFrame::PropertyProvider* aProvider,
+ nsIFrame::InlineIntrinsicISizeData* aData, const nsStyleText* aTextStyle,
+ gfxSkipCharsIterator* aIterator, uint32_t aFlowEndInTextRun) {
+ if (aData->mSkipWhitespace) {
+ while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
+ IsTrimmableSpace(aProvider->GetFragment(),
+ aIterator->GetOriginalOffset(), aTextStyle)) {
+ aIterator->AdvanceOriginal(1);
+ }
+ }
+ return aIterator->GetSkippedOffset();
+}
+
+float nsTextFrame::GetFontSizeInflation() const {
+ if (!HasFontSizeInflation()) {
+ return 1.0f;
+ }
+ return GetProperty(FontSizeInflationProperty());
+}
+
+void nsTextFrame::SetFontSizeInflation(float aInflation) {
+ if (aInflation == 1.0f) {
+ if (HasFontSizeInflation()) {
+ RemoveStateBits(TEXT_HAS_FONT_INFLATION);
+ RemoveProperty(FontSizeInflationProperty());
+ }
+ return;
+ }
+
+ AddStateBits(TEXT_HAS_FONT_INFLATION);
+ SetProperty(FontSizeInflationProperty(), aInflation);
+}
+
+void nsTextFrame::SetHangableISize(nscoord aISize) {
+ MOZ_ASSERT(aISize >= 0, "unexpected negative hangable advance");
+ if (aISize <= 0) {
+ ClearHangableISize();
+ return;
+ }
+ SetProperty(HangableWhitespaceProperty(), aISize);
+ mPropertyFlags |= PropertyFlags::HangableWS;
+}
+
+nscoord nsTextFrame::GetHangableISize() const {
+ MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::HangableWS) ==
+ HasProperty(HangableWhitespaceProperty()),
+ "flag/property mismatch!");
+ return (mPropertyFlags & PropertyFlags::HangableWS)
+ ? GetProperty(HangableWhitespaceProperty())
+ : 0;
+}
+
+void nsTextFrame::ClearHangableISize() {
+ if (mPropertyFlags & PropertyFlags::HangableWS) {
+ RemoveProperty(HangableWhitespaceProperty());
+ mPropertyFlags &= ~PropertyFlags::HangableWS;
+ }
+}
+
+void nsTextFrame::SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS) {
+ MOZ_ASSERT(aTrimmableWS.mAdvance >= 0, "negative trimmable size");
+ if (aTrimmableWS.mAdvance <= 0) {
+ ClearTrimmableWS();
+ return;
+ }
+ SetProperty(TrimmableWhitespaceProperty(), aTrimmableWS);
+ mPropertyFlags |= PropertyFlags::TrimmableWS;
+}
+
+gfxTextRun::TrimmableWS nsTextFrame::GetTrimmableWS() const {
+ MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==
+ HasProperty(TrimmableWhitespaceProperty()),
+ "flag/property mismatch!");
+ return (mPropertyFlags & PropertyFlags::TrimmableWS)
+ ? GetProperty(TrimmableWhitespaceProperty())
+ : gfxTextRun::TrimmableWS{};
+}
+
+void nsTextFrame::ClearTrimmableWS() {
+ if (mPropertyFlags & PropertyFlags::TrimmableWS) {
+ RemoveProperty(TrimmableWhitespaceProperty());
+ mPropertyFlags &= ~PropertyFlags::TrimmableWS;
+ }
+}
+
+/* virtual */
+void nsTextFrame::MarkIntrinsicISizesDirty() {
+ ClearTextRuns();
+ nsIFrame::MarkIntrinsicISizesDirty();
+}
+
+// XXX this doesn't handle characters shaped by line endings. We need to
+// temporarily override the "current line ending" settings.
+void nsTextFrame::AddInlineMinISizeForFlow(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData,
+ TextRunType aTextRunType) {
+ uint32_t flowEndInTextRun;
+ gfxSkipCharsIterator iter =
+ EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
+ aData->LineContainer(), aData->mLine, &flowEndInTextRun);
+ gfxTextRun* textRun = GetTextRun(aTextRunType);
+ if (!textRun) {
+ return;
+ }
+
+ // Pass null for the line container. This will disable tab spacing, but that's
+ // OK since we can't really handle tabs for intrinsic sizing anyway.
+ const nsStyleText* textStyle = StyleText();
+ const nsTextFragment* frag = TextFragment();
+
+ // If we're hyphenating, the PropertyProvider needs the actual length;
+ // otherwise we can just pass INT32_MAX to mean "all the text"
+ int32_t len = INT32_MAX;
+ bool hyphenating = frag->GetLength() > 0 &&
+ (textStyle->mHyphens == StyleHyphens::Auto ||
+ (textStyle->mHyphens == StyleHyphens::Manual &&
+ !!(textRun->GetFlags() &
+ gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
+ if (hyphenating) {
+ gfxSkipCharsIterator tmp(iter);
+ len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
+ tmp.ConvertSkippedToOriginal(flowEndInTextRun)) -
+ iter.GetOriginalOffset();
+ }
+ PropertyProvider provider(textRun, textStyle, frag, this, iter, len, nullptr,
+ 0, aTextRunType);
+
+ bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
+ bool preformatNewlines = textStyle->NewlineIsSignificant(this);
+ bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
+ bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
+ gfxFloat tabWidth = -1;
+ uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
+ &iter, flowEndInTextRun);
+
+ // text-combine-upright frame is constantly 1em on inline-axis.
+ if (Style()->IsTextCombined()) {
+ if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) {
+ aData->OptionallyBreak();
+ }
+ aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
+ aData->mTrailingWhitespace = 0;
+ return;
+ }
+
+ if (textStyle->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere &&
+ textStyle->WordCanWrap(this)) {
+ aData->OptionallyBreak();
+ aData->mCurrentLine +=
+ textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun));
+ aData->mTrailingWhitespace = 0;
+ aData->mAtStartOfLine = false;
+ aData->OptionallyBreak();
+ return;
+ }
+
+ AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer;
+ if (hyphenating) {
+ if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {
+ provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
+ hyphBuffer.Elements());
+ } else {
+ hyphenating = false;
+ }
+ }
+
+ for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
+ bool preformattedNewline = false;
+ bool preformattedTab = false;
+ if (i < flowEndInTextRun) {
+ // XXXldb Shouldn't we be including the newline as part of the
+ // segment that it ends rather than part of the segment that it
+ // starts?
+ preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
+ preformattedTab = preformatTabs && textRun->CharIsTab(i);
+ if (!textRun->CanBreakLineBefore(i) && !preformattedNewline &&
+ !preformattedTab &&
+ (!hyphenating ||
+ !gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start]))) {
+ // we can't break here (and it's not the end of the flow)
+ continue;
+ }
+ }
+
+ if (i > wordStart) {
+ nscoord width = NSToCoordCeilClamped(
+ textRun->GetAdvanceWidth(Range(wordStart, i), &provider));
+ width = std::max(0, width);
+ aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
+ aData->mAtStartOfLine = false;
+
+ if (collapseWhitespace || whitespaceCanHang) {
+ uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i,
+ &iter, whitespaceCanHang);
+ if (trimStart == start) {
+ // This is *all* trimmable whitespace, so whatever trailingWhitespace
+ // we saw previously is still trailing...
+ aData->mTrailingWhitespace += width;
+ } else {
+ // Some non-whitespace so the old trailingWhitespace is no longer
+ // trailing
+ nscoord wsWidth = NSToCoordCeilClamped(
+ textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
+ aData->mTrailingWhitespace = std::max(0, wsWidth);
+ }
+ } else {
+ aData->mTrailingWhitespace = 0;
+ }
+ }
+
+ if (preformattedTab) {
+ PropertyProvider::Spacing spacing;
+ provider.GetSpacing(Range(i, i + 1), &spacing);
+ aData->mCurrentLine += nscoord(spacing.mBefore);
+ if (tabWidth < 0) {
+ tabWidth = ComputeTabWidthAppUnits(this);
+ }
+ gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
+ provider.MinTabAdvance());
+ aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
+ wordStart = i + 1;
+ } else if (i < flowEndInTextRun ||
+ (i == textRun->GetLength() &&
+ (textRun->GetFlags2() &
+ nsTextFrameUtils::Flags::HasTrailingBreak))) {
+ if (preformattedNewline) {
+ aData->ForceBreak();
+ } else if (i < flowEndInTextRun && hyphenating &&
+ gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start])) {
+ aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth()));
+ } else {
+ aData->OptionallyBreak();
+ }
+ if (aData->mSkipWhitespace) {
+ iter.SetSkippedOffset(i);
+ wordStart = FindStartAfterSkippingWhitespace(
+ &provider, aData, textStyle, &iter, flowEndInTextRun);
+ } else {
+ wordStart = i;
+ }
+ }
+ }
+
+ if (start < flowEndInTextRun) {
+ // Check if we have collapsible whitespace at the end
+ aData->mSkipWhitespace = IsTrimmableSpace(
+ provider.GetFragment(),
+ iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
+ }
+}
+
+bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
+ return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
+}
+
+// XXX Need to do something here to avoid incremental reflow bugs due to
+// first-line and first-letter changing min-width
+/* virtual */
+void nsTextFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
+
+ if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
+ // FIXME: Ideally, if we already have a text run, we'd move it to be
+ // the uninflated text run.
+ ClearTextRun(nullptr, nsTextFrame::eInflated);
+ mFontMetrics = nullptr;
+ }
+
+ nsTextFrame* f;
+ const gfxTextRun* lastTextRun = nullptr;
+ // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
+ // in the flow are handled right here.
+ for (f = this; f; f = f->GetNextContinuation()) {
+ // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
+ // haven't set up textruns yet for f. Except in OOM situations,
+ // lastTextRun will only be null for the first text frame.
+ if (f == this || f->GetTextRun(trtype) != lastTextRun) {
+ nsIFrame* lc;
+ if (aData->LineContainer() &&
+ aData->LineContainer() != (lc = FindLineContainer(f))) {
+ NS_ASSERTION(f != this,
+ "wrong InlineMinISizeData container"
+ " for first continuation");
+ aData->mLine = nullptr;
+ aData->SetLineContainer(lc);
+ }
+
+ // This will process all the text frames that share the same textrun as f.
+ f->AddInlineMinISizeForFlow(aRenderingContext, aData, trtype);
+ lastTextRun = f->GetTextRun(trtype);
+ }
+ }
+}
+
+// XXX this doesn't handle characters shaped by line endings. We need to
+// temporarily override the "current line ending" settings.
+void nsTextFrame::AddInlinePrefISizeForFlow(
+ gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData,
+ TextRunType aTextRunType) {
+ uint32_t flowEndInTextRun;
+ gfxSkipCharsIterator iter =
+ EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
+ aData->LineContainer(), aData->mLine, &flowEndInTextRun);
+ gfxTextRun* textRun = GetTextRun(aTextRunType);
+ if (!textRun) {
+ return;
+ }
+
+ // Pass null for the line container. This will disable tab spacing, but that's
+ // OK since we can't really handle tabs for intrinsic sizing anyway.
+
+ const nsStyleText* textStyle = StyleText();
+ const nsTextFragment* frag = TextFragment();
+ PropertyProvider provider(textRun, textStyle, frag, this, iter, INT32_MAX,
+ nullptr, 0, aTextRunType);
+
+ // text-combine-upright frame is constantly 1em on inline-axis.
+ if (Style()->IsTextCombined()) {
+ aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
+ aData->mTrailingWhitespace = 0;
+ aData->mLineIsEmpty = false;
+ return;
+ }
+
+ bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
+ bool preformatNewlines = textStyle->NewlineIsSignificant(this);
+ bool preformatTabs = textStyle->TabIsSignificant();
+ gfxFloat tabWidth = -1;
+ uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
+ &iter, flowEndInTextRun);
+
+ // XXX Should we consider hyphenation here?
+ // If newlines and tabs aren't preformatted, nothing to do inside
+ // the loop so make i skip to the end
+ uint32_t loopStart =
+ (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
+ for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
+ bool preformattedNewline = false;
+ bool preformattedTab = false;
+ if (i < flowEndInTextRun) {
+ // XXXldb Shouldn't we be including the newline as part of the
+ // segment that it ends rather than part of the segment that it
+ // starts?
+ NS_ASSERTION(preformatNewlines || preformatTabs,
+ "We can't be here unless newlines are "
+ "hard breaks or there are tabs");
+ preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
+ preformattedTab = preformatTabs && textRun->CharIsTab(i);
+ if (!preformattedNewline && !preformattedTab) {
+ // we needn't break here (and it's not the end of the flow)
+ continue;
+ }
+ }
+
+ if (i > lineStart) {
+ nscoord width = NSToCoordCeilClamped(
+ textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
+ width = std::max(0, width);
+ aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
+ aData->mLineIsEmpty = false;
+
+ if (collapseWhitespace) {
+ uint32_t trimStart =
+ GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
+ if (trimStart == start) {
+ // This is *all* trimmable whitespace, so whatever trailingWhitespace
+ // we saw previously is still trailing...
+ aData->mTrailingWhitespace += width;
+ } else {
+ // Some non-whitespace so the old trailingWhitespace is no longer
+ // trailing
+ nscoord wsWidth = NSToCoordCeilClamped(
+ textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
+ aData->mTrailingWhitespace = std::max(0, wsWidth);
+ }
+ } else {
+ aData->mTrailingWhitespace = 0;
+ }
+ }
+
+ if (preformattedTab) {
+ PropertyProvider::Spacing spacing;
+ provider.GetSpacing(Range(i, i + 1), &spacing);
+ aData->mCurrentLine += nscoord(spacing.mBefore);
+ if (tabWidth < 0) {
+ tabWidth = ComputeTabWidthAppUnits(this);
+ }
+ gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
+ provider.MinTabAdvance());
+ aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
+ aData->mLineIsEmpty = false;
+ lineStart = i + 1;
+ } else if (preformattedNewline) {
+ aData->ForceBreak();
+ lineStart = i;
+ }
+ }
+
+ // Check if we have collapsible whitespace at the end
+ if (start < flowEndInTextRun) {
+ aData->mSkipWhitespace = IsTrimmableSpace(
+ provider.GetFragment(),
+ iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
+ }
+}
+
+// XXX Need to do something here to avoid incremental reflow bugs due to
+// first-line and first-letter changing pref-width
+/* virtual */
+void nsTextFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
+ nsIFrame::InlinePrefISizeData* aData) {
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
+
+ if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
+ // FIXME: Ideally, if we already have a text run, we'd move it to be
+ // the uninflated text run.
+ ClearTextRun(nullptr, nsTextFrame::eInflated);
+ mFontMetrics = nullptr;
+ }
+
+ nsTextFrame* f;
+ const gfxTextRun* lastTextRun = nullptr;
+ // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
+ // in the flow are handled right here.
+ for (f = this; f; f = f->GetNextContinuation()) {
+ // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
+ // haven't set up textruns yet for f. Except in OOM situations,
+ // lastTextRun will only be null for the first text frame.
+ if (f == this || f->GetTextRun(trtype) != lastTextRun) {
+ nsIFrame* lc;
+ if (aData->LineContainer() &&
+ aData->LineContainer() != (lc = FindLineContainer(f))) {
+ NS_ASSERTION(f != this,
+ "wrong InlinePrefISizeData container"
+ " for first continuation");
+ aData->mLine = nullptr;
+ aData->SetLineContainer(lc);
+ }
+
+ // This will process all the text frames that share the same textrun as f.
+ f->AddInlinePrefISizeForFlow(aRenderingContext, aData, trtype);
+ lastTextRun = f->GetTextRun(trtype);
+ }
+ }
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult nsTextFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ // Inlines and text don't compute size before reflow.
+ return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
+ AspectRatioUsage::None};
+}
+
+static nsRect RoundOut(const gfxRect& aRect) {
+ nsRect r;
+ r.x = NSToCoordFloor(aRect.X());
+ r.y = NSToCoordFloor(aRect.Y());
+ r.width = NSToCoordCeil(aRect.XMost()) - r.x;
+ r.height = NSToCoordCeil(aRect.YMost()) - r.y;
+ return r;
+}
+
+nsRect nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
+ if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
+ // This is conservative, but OK.
+ return InkOverflowRect();
+ }
+
+ gfxSkipCharsIterator iter =
+ const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return nsRect();
+ }
+
+ PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
+ nsTextFrame::eInflated, mFontMetrics);
+ // Trim trailing whitespace
+ provider.InitializeForDisplay(true);
+
+ gfxTextRun::Metrics metrics = mTextRun->MeasureText(
+ ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
+ aDrawTarget, &provider);
+ if (GetWritingMode().IsLineInverted()) {
+ metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
+ }
+ // mAscent should be the same as metrics.mAscent, but it's what we use to
+ // paint so that's the one we'll use.
+ nsRect boundingBox = RoundOut(metrics.mBoundingBox);
+ boundingBox += nsPoint(0, mAscent);
+ if (mTextRun->IsVertical()) {
+ // Swap line-relative textMetrics dimensions to physical coordinates.
+ std::swap(boundingBox.x, boundingBox.y);
+ std::swap(boundingBox.width, boundingBox.height);
+ }
+ return boundingBox;
+}
+
+/* virtual */
+nsresult nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
+ nscoord* aXMost) {
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
+ provider.InitializeForMeasure();
+
+ gfxTextRun::Metrics metrics = mTextRun->MeasureText(
+ ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
+ aContext->GetDrawTarget(), &provider);
+ // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
+ *aX = NSToCoordFloor(metrics.mBoundingBox.x);
+ *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
+
+ return NS_OK;
+}
+
+static bool HasSoftHyphenBefore(const nsTextFragment* aFrag,
+ const gfxTextRun* aTextRun,
+ int32_t aStartOffset,
+ const gfxSkipCharsIterator& aIter) {
+ if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
+ aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
+ return true;
+ }
+ if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasShy)) {
+ return false;
+ }
+ gfxSkipCharsIterator iter = aIter;
+ while (iter.GetOriginalOffset() > aStartOffset) {
+ iter.AdvanceOriginal(-1);
+ if (!iter.IsOriginalCharSkipped()) {
+ break;
+ }
+ if (aFrag->CharAt(AssertedCast<uint32_t>(iter.GetOriginalOffset())) ==
+ CH_SHY) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
+ * because their text has all been taken and reflowed by earlier frames.
+ */
+static void RemoveEmptyInFlows(nsTextFrame* aFrame,
+ nsTextFrame* aFirstToNotRemove) {
+ MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly");
+ // We have to be careful here, because some RemoveFrame implementations
+ // remove and destroy not only the passed-in frame but also all its following
+ // in-flows (and sometimes all its following continuations in general). So
+ // we remove |f| and everything up to but not including firstToNotRemove from
+ // the flow first, to make sure that only the things we want destroyed are
+ // destroyed.
+
+ // This sadly duplicates some of the logic from
+ // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating
+ // all of it, because we know that the prev-continuation links of
+ // firstToNotRemove and f are fluid, and non-null.
+ NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
+ aFirstToNotRemove->GetPrevInFlow() &&
+ aFirstToNotRemove->GetPrevInFlow() != nullptr,
+ "aFirstToNotRemove should have a fluid prev continuation");
+ NS_ASSERTION(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() &&
+ aFrame->GetPrevInFlow() != nullptr,
+ "aFrame should have a fluid prev continuation");
+
+ nsTextFrame* prevContinuation = aFrame->GetPrevContinuation();
+ nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
+
+ for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
+ f = f->GetNextContinuation()) {
+ // f is going to be destroyed soon, after it is unlinked from the
+ // continuation chain. If its textrun is going to be destroyed we need to
+ // do it now, before we unlink the frames to remove from the flow,
+ // because Destroy calls ClearTextRuns() and that will start at the
+ // first frame with the text run and walk the continuations.
+ if (f->IsInTextRunUserData()) {
+ f->ClearTextRuns();
+ } else {
+ f->DisconnectTextRuns();
+ }
+ }
+
+ prevContinuation->SetNextInFlow(aFirstToNotRemove);
+ aFirstToNotRemove->SetPrevInFlow(prevContinuation);
+
+ // **Note: it is important here that we clear the Next link from lastRemoved
+ // BEFORE clearing the Prev link from aFrame, because SetPrevInFlow() will
+ // follow the Next pointers, wiping out the cached mFirstContinuation field
+ // from each following frame in the list. We need this to stop when it
+ // reaches lastRemoved!
+ lastRemoved->SetNextInFlow(nullptr);
+ aFrame->SetPrevInFlow(nullptr);
+
+ nsContainerFrame* parent = aFrame->GetParent();
+ nsIFrame::DestroyContext context(aFrame->PresShell());
+ nsBlockFrame* parentBlock = do_QueryFrame(parent);
+ if (parentBlock) {
+ // Manually call DoRemoveFrame so we can tell it that we're
+ // removing empty frames; this will keep it from blowing away
+ // text runs.
+ parentBlock->DoRemoveFrame(context, aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
+ } else {
+ // Just remove it normally; use FrameChildListID::NoReflowPrincipal to avoid
+ // posting new reflows.
+ parent->RemoveFrame(context, FrameChildListID::NoReflowPrincipal, aFrame);
+ }
+}
+
+void nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
+ uint32_t aSetLengthFlags) {
+ mContentLengthHint = aLength;
+ int32_t end = GetContentOffset() + aLength;
+ nsTextFrame* f = GetNextInFlow();
+ if (!f) {
+ return;
+ }
+
+ // If our end offset is moving, then even if frames are not being pushed or
+ // pulled, content is moving to or from the next line and the next line
+ // must be reflowed.
+ // If the next-continuation is dirty, then we should dirty the next line now
+ // because we may have skipped doing it if we dirtied it in
+ // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
+ // and ChildIsDirty to handle a range of frames would be worse.
+ if (aLineLayout &&
+ (end != f->mContentOffset || f->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
+ aLineLayout->SetDirtyNextLine();
+ }
+
+ if (end < f->mContentOffset) {
+ // Our frame is shrinking. Give the text to our next in flow.
+ if (aLineLayout && HasSignificantTerminalNewline() &&
+ !GetParent()->IsLetterFrame() &&
+ (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
+ // Whatever text we hand to our next-in-flow will end up in a frame all of
+ // its own, since it ends in a forced linebreak. Might as well just put
+ // it in a separate frame now. This is important to prevent text run
+ // churn; if we did not do that, then we'd likely end up rebuilding
+ // textruns for all our following continuations.
+ // We skip this optimization when the parent is a first-letter frame
+ // because it doesn't deal well with more than one child frame.
+ // We also skip this optimization if we were called during bidi
+ // resolution, so as not to create a new frame which doesn't appear in
+ // the bidi resolver's list of frames
+ nsIFrame* newFrame =
+ PresShell()->FrameConstructor()->CreateContinuingFrame(this,
+ GetParent());
+ nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
+ GetParent()->InsertFrames(FrameChildListID::NoReflowPrincipal, this,
+ aLineLayout->GetLine(),
+ nsFrameList(next, next));
+ f = next;
+ }
+
+ f->mContentOffset = end;
+ if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
+ ClearTextRuns();
+ f->ClearTextRuns();
+ }
+ return;
+ }
+ // Our frame is growing. Take text from our in-flow(s).
+ // We can take text from frames in lines beyond just the next line.
+ // We don't dirty those lines. That's OK, because when we reflow
+ // our empty next-in-flow, it will take text from its next-in-flow and
+ // dirty that line.
+
+ // Note that in the process we may end up removing some frames from
+ // the flow if they end up empty.
+ nsTextFrame* framesToRemove = nullptr;
+ while (f && f->mContentOffset < end) {
+ f->mContentOffset = end;
+ if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
+ ClearTextRuns();
+ f->ClearTextRuns();
+ }
+ nsTextFrame* next = f->GetNextInFlow();
+ // Note: the "f->GetNextSibling() == next" check below is to restrict
+ // this optimization to the case where they are on the same child list.
+ // Otherwise we might remove the only child of a nsFirstLetterFrame
+ // for example and it can't handle that. See bug 597627 for details.
+ if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
+ (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
+ // |f| is now empty. We may as well remove it, instead of copying all
+ // the text from |next| into it instead; the latter leads to use
+ // rebuilding textruns for all following continuations.
+ // We skip this optimization if we were called during bidi resolution,
+ // since the bidi resolver may try to handle the destroyed frame later
+ // and crash
+ if (!framesToRemove) {
+ // Remember that we have to remove this frame.
+ framesToRemove = f;
+ }
+ } else if (framesToRemove) {
+ RemoveEmptyInFlows(framesToRemove, f);
+ framesToRemove = nullptr;
+ }
+ f = next;
+ }
+
+ MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),
+ "How did we exit the loop if we null out framesToRemove if "
+ "!next || next->mContentOffset > end ?");
+
+ if (framesToRemove) {
+ // We are guaranteed that we exited the loop with f not null, per the
+ // postcondition above
+ RemoveEmptyInFlows(framesToRemove, f);
+ }
+
+#ifdef DEBUG
+ f = this;
+ int32_t iterations = 0;
+ while (f && iterations < 10) {
+ f->GetContentLength(); // Assert if negative length
+ f = f->GetNextContinuation();
+ ++iterations;
+ }
+ f = this;
+ iterations = 0;
+ while (f && iterations < 10) {
+ f->GetContentLength(); // Assert if negative length
+ f = f->GetPrevContinuation();
+ ++iterations;
+ }
+#endif
+}
+
+bool nsTextFrame::IsFloatingFirstLetterChild() const {
+ nsIFrame* frame = GetParent();
+ return frame && frame->IsFloating() && frame->IsLetterFrame();
+}
+
+bool nsTextFrame::IsInitialLetterChild() const {
+ nsIFrame* frame = GetParent();
+ return frame && frame->StyleTextReset()->mInitialLetterSize != 0.0f &&
+ frame->IsLetterFrame();
+}
+
+struct NewlineProperty {
+ int32_t mStartOffset;
+ // The offset of the first \n after mStartOffset, or -1 if there is none
+ int32_t mNewlineOffset;
+};
+
+void nsTextFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ InvalidateSelectionState();
+
+ // XXX If there's no line layout, we shouldn't even have created this
+ // frame. This may happen if, for example, this is text inside a table
+ // but not inside a cell. For now, just don't reflow.
+ if (!aReflowInput.mLineLayout) {
+ ClearMetrics(aMetrics);
+ return;
+ }
+
+ ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(),
+ aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics,
+ aStatus);
+}
+
+#ifdef ACCESSIBILITY
+/**
+ * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
+ */
+class MOZ_STACK_CLASS ReflowTextA11yNotifier {
+ public:
+ ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent)
+ : mContent(aContent), mPresContext(aPresContext) {}
+ ~ReflowTextA11yNotifier() {
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->UpdateText(mPresContext->PresShell(), mContent);
+ }
+ }
+
+ private:
+ ReflowTextA11yNotifier();
+ ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
+ ReflowTextA11yNotifier& operator=(const ReflowTextA11yNotifier&);
+
+ nsIContent* mContent;
+ nsPresContext* mPresContext;
+};
+#endif
+
+void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
+ DrawTarget* aDrawTarget, ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+#ifdef NOISY_REFLOW
+ ListTag(stdout);
+ printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
+#endif
+
+ nsPresContext* presContext = PresContext();
+
+#ifdef ACCESSIBILITY
+ // Schedule the update of accessible tree since rendered text might be
+ // changed.
+ if (StyleVisibility()->IsVisible()) {
+ ReflowTextA11yNotifier(presContext, mContent);
+ }
+#endif
+
+ /////////////////////////////////////////////////////////////////////
+ // Set up flags and clear out state
+ /////////////////////////////////////////////////////////////////////
+
+ // Clear out the reflow input flags in mState. We also clear the whitespace
+ // flags because this can change whether the frame maps whitespace-only text
+ // or not. We also clear the flag that tracks whether we had a pending
+ // reflow request from CharacterDataChanged (since we're reflowing now).
+ RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
+ mReflowRequestedForCharDataChange = false;
+ RemoveProperty(WebRenderTextBounds());
+
+ // Discard cached continuations array that will be invalidated by the reflow.
+ if (nsTextFrame* first = FirstContinuation()) {
+ first->ClearCachedContinuations();
+ }
+
+ // Temporarily map all possible content while we construct our new textrun.
+ // so that when doing reflow our styles prevail over any part of the
+ // textrun we look at. Note that next-in-flows may be mapping the same
+ // content; gfxTextRun construction logic will ensure that we take priority.
+ int32_t maxContentLength = GetInFlowContentLength();
+
+ InvalidateSelectionState();
+
+ // We don't need to reflow if there is no content.
+ if (!maxContentLength) {
+ ClearMetrics(aMetrics);
+ return;
+ }
+
+#ifdef NOISY_BIDI
+ printf("Reflowed textframe\n");
+#endif
+
+ const nsStyleText* textStyle = StyleText();
+
+ bool atStartOfLine = aLineLayout.LineAtStart();
+ if (atStartOfLine) {
+ AddStateBits(TEXT_START_OF_LINE);
+ }
+
+ uint32_t flowEndInTextRun;
+ nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
+ const nsTextFragment* frag = TextFragment();
+
+ // DOM offsets of the text range we need to measure, after trimming
+ // whitespace, restricting to first-letter, and restricting preformatted text
+ // to nearest newline
+ int32_t length = maxContentLength;
+ int32_t offset = GetContentOffset();
+
+ // Restrict preformatted text to the nearest newline
+ int32_t newLineOffset = -1; // this will be -1 or a content offset
+ int32_t contentNewLineOffset = -1;
+ // Pointer to the nsGkAtoms::newline set on this frame's element
+ NewlineProperty* cachedNewlineOffset = nullptr;
+ if (textStyle->NewlineIsSignificant(this)) {
+ cachedNewlineOffset = mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
+ ? static_cast<NewlineProperty*>(
+ mContent->GetProperty(nsGkAtoms::newline))
+ : nullptr;
+ if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
+ (cachedNewlineOffset->mNewlineOffset == -1 ||
+ cachedNewlineOffset->mNewlineOffset >= offset)) {
+ contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
+ } else {
+ contentNewLineOffset =
+ FindChar(frag, offset, GetContent()->TextLength() - offset, '\n');
+ }
+ if (contentNewLineOffset < offset + length) {
+ /*
+ The new line offset could be outside this frame if the frame has been
+ split by bidi resolution. In that case we won't use it in this reflow
+ (newLineOffset will remain -1), but we will still cache it in mContent
+ */
+ newLineOffset = contentNewLineOffset;
+ }
+ if (newLineOffset >= 0) {
+ length = newLineOffset + 1 - offset;
+ }
+ }
+ if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
+ HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
+ // Skip leading whitespace. Make sure we don't skip a 'pre-line'
+ // newline if there is one.
+ int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
+ int32_t whitespaceCount =
+ GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
+ if (whitespaceCount) {
+ offset += whitespaceCount;
+ length -= whitespaceCount;
+ // Make sure this frame maps the trimmable whitespace.
+ if (MOZ_UNLIKELY(offset > GetContentEnd())) {
+ SetLength(offset - GetContentOffset(), &aLineLayout,
+ ALLOW_FRAME_CREATION_AND_DESTRUCTION);
+ }
+ }
+ }
+
+ // If trimming whitespace left us with nothing to do, return early.
+ if (length == 0) {
+ ClearMetrics(aMetrics);
+ return;
+ }
+
+ bool completedFirstLetter = false;
+ // Layout dependent styles are a problem because we need to reconstruct
+ // the gfxTextRun based on our layout.
+ if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
+ SetLength(maxContentLength, &aLineLayout,
+ ALLOW_FRAME_CREATION_AND_DESTRUCTION);
+
+ if (aLineLayout.GetInFirstLetter()) {
+ // floating first-letter boundaries are significant in textrun
+ // construction, so clear the textrun out every time we hit a first-letter
+ // and have changed our length (which controls the first-letter boundary)
+ ClearTextRuns();
+ // Find the length of the first-letter. We need a textrun for this.
+ // REVIEW: maybe-bogus inflation should be ok (fixed below)
+ gfxSkipCharsIterator iter =
+ EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
+ aLineLayout.GetLine(), &flowEndInTextRun);
+
+ if (mTextRun) {
+ int32_t firstLetterLength = length;
+ if (aLineLayout.GetFirstLetterStyleOK()) {
+ // We only pass a language code to FindFirstLetterRange if it was
+ // explicit in the content.
+ const nsStyleFont* styleFont = StyleFont();
+ const nsAtom* lang = styleFont->mExplicitLanguage
+ ? styleFont->mLanguage.get()
+ : nullptr;
+ completedFirstLetter = FindFirstLetterRange(
+ frag, lang, mTextRun, offset, iter, &firstLetterLength);
+ if (newLineOffset >= 0) {
+ // Don't allow a preformatted newline to be part of a first-letter.
+ firstLetterLength = std::min(firstLetterLength, length - 1);
+ if (length == 1) {
+ // There is no text to be consumed by the first-letter before the
+ // preformatted newline. Note that the first letter is therefore
+ // complete (FindFirstLetterRange will have returned false).
+ completedFirstLetter = true;
+ }
+ }
+ } else {
+ // We're in a first-letter frame's first in flow, so if there
+ // was a first-letter, we'd be it. However, for one reason
+ // or another (e.g., preformatted line break before this text),
+ // we're not actually supposed to have first-letter style. So
+ // just make a zero-length first-letter.
+ firstLetterLength = 0;
+ completedFirstLetter = true;
+ }
+ length = firstLetterLength;
+ if (length) {
+ AddStateBits(TEXT_FIRST_LETTER);
+ }
+ // Change this frame's length to the first-letter length right now
+ // so that when we rebuild the textrun it will be built with the
+ // right first-letter boundary
+ SetLength(offset + length - GetContentOffset(), &aLineLayout,
+ ALLOW_FRAME_CREATION_AND_DESTRUCTION);
+ // Ensure that the textrun will be rebuilt
+ ClearTextRuns();
+ }
+ }
+ }
+
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+
+ if (!IsCurrentFontInflation(fontSizeInflation)) {
+ // FIXME: Ideally, if we already have a text run, we'd move it to be
+ // the uninflated text run.
+ ClearTextRun(nullptr, nsTextFrame::eInflated);
+ mFontMetrics = nullptr;
+ }
+
+ gfxSkipCharsIterator iter =
+ EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
+ aLineLayout.GetLine(), &flowEndInTextRun);
+
+ NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
+ "EnsureTextRun should have set font size inflation");
+
+ if (mTextRun && iter.GetOriginalEnd() < offset + length) {
+ // The textrun does not map enough text for this frame. This can happen
+ // when the textrun was ended in the middle of a text node because a
+ // preformatted newline was encountered, and prev-in-flow frames have
+ // consumed all the text of the textrun. We need a new textrun.
+ ClearTextRuns();
+ iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
+ aLineLayout.GetLine(), &flowEndInTextRun);
+ }
+
+ if (!mTextRun) {
+ ClearMetrics(aMetrics);
+ return;
+ }
+
+ NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(
+ offset + length) <= mTextRun->GetLength(),
+ "Text run does not map enough text for our reflow");
+
+ /////////////////////////////////////////////////////////////////////
+ // See how much text should belong to this text frame, and measure it
+ /////////////////////////////////////////////////////////////////////
+
+ iter.SetOriginalOffset(offset);
+ nscoord xOffsetForTabs =
+ (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab)
+ ? (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
+ lineContainer->GetUsedBorderAndPadding().left)
+ : -1;
+ PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
+ lineContainer, xOffsetForTabs,
+ nsTextFrame::eInflated);
+
+ uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
+
+ gfxFont::BoundingBoxType boundingBoxType = gfxFont::LOOSE_INK_EXTENTS;
+ if (IsFloatingFirstLetterChild() || IsInitialLetterChild()) {
+ if (nsFirstLetterFrame* firstLetter = do_QueryFrame(GetParent())) {
+ if (firstLetter->UseTightBounds()) {
+ boundingBoxType = gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS;
+ }
+ }
+ }
+
+ int32_t limitLength = length;
+ int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
+ bool forceBreakAfter = false;
+ if (forceBreak >= length) {
+ forceBreakAfter = forceBreak == length;
+ // The break is not within the text considered for this textframe.
+ forceBreak = -1;
+ }
+ if (forceBreak >= 0) {
+ limitLength = forceBreak;
+ }
+ // This is the heart of text reflow right here! We don't know where
+ // to break, so we need to see how much text fits in the available width.
+ uint32_t transformedLength;
+ if (offset + limitLength >= int32_t(frag->GetLength())) {
+ NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
+ "Content offset/length out of bounds");
+ NS_ASSERTION(flowEndInTextRun >= transformedOffset,
+ "Negative flow length?");
+ transformedLength = flowEndInTextRun - transformedOffset;
+ } else {
+ // we're not looking at all the content, so we need to compute the
+ // length of the transformed substring we're looking at
+ gfxSkipCharsIterator iter(provider.GetStart());
+ iter.SetOriginalOffset(offset + limitLength);
+ transformedLength = iter.GetSkippedOffset() - transformedOffset;
+ }
+ gfxTextRun::Metrics textMetrics;
+ uint32_t transformedLastBreak = 0;
+ bool usedHyphenation = false;
+ gfxTextRun::TrimmableWS trimmableWS;
+ gfxFloat availWidth = aAvailableWidth;
+ if (Style()->IsTextCombined()) {
+ // If text-combine-upright is 'all', we would compress whatever long
+ // text into ~1em width, so there is no limited on the avail width.
+ availWidth = std::numeric_limits<gfxFloat>::infinity();
+ }
+ bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
+ HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML);
+ bool isBreakSpaces =
+ textStyle->mWhiteSpaceCollapse == StyleWhiteSpaceCollapse::BreakSpaces;
+ // allow whitespace to overflow the container
+ bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
+ gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
+ gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
+ bool shouldSuppressLineBreak = ShouldSuppressLineBreak();
+ if (shouldSuppressLineBreak) {
+ suppressBreak = gfxTextRun::eSuppressAllBreaks;
+ } else if (!aLineLayout.LineIsBreakable()) {
+ suppressBreak = gfxTextRun::eSuppressInitialBreak;
+ }
+ uint32_t transformedCharsFit = mTextRun->BreakAndMeasureText(
+ transformedOffset, transformedLength, HasAnyStateBits(TEXT_START_OF_LINE),
+ availWidth, provider, suppressBreak, boundingBoxType, aDrawTarget,
+ textStyle->WordCanWrap(this), textStyle->WhiteSpaceCanWrap(this),
+ isBreakSpaces,
+ // The following are output parameters:
+ canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWS : nullptr,
+ textMetrics, usedHyphenation, transformedLastBreak,
+ // In/out
+ breakPriority);
+ if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
+ // If we're measuring a zero-length piece of text, update
+ // the height manually.
+ nsFontMetrics* fm = provider.GetFontMetrics();
+ if (fm) {
+ textMetrics.mAscent = gfxFloat(fm->MaxAscent());
+ textMetrics.mDescent = gfxFloat(fm->MaxDescent());
+ }
+ }
+ if (GetWritingMode().IsLineInverted()) {
+ std::swap(textMetrics.mAscent, textMetrics.mDescent);
+ textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
+ }
+ // The "end" iterator points to the first character after the string mapped
+ // by this frame. Basically, its original-string offset is offset+charsFit
+ // after we've computed charsFit.
+ gfxSkipCharsIterator end(provider.GetEndHint());
+ end.SetSkippedOffset(transformedOffset + transformedCharsFit);
+ int32_t charsFit = end.GetOriginalOffset() - offset;
+ if (offset + charsFit == newLineOffset) {
+ // We broke before a trailing preformatted '\n'. The newline should
+ // be assigned to this frame. Note that newLineOffset will be -1 if
+ // there was no preformatted newline, so we wouldn't get here in that
+ // case.
+ ++charsFit;
+ }
+ // That might have taken us beyond our assigned content range (because
+ // we might have advanced over some skipped chars that extend outside
+ // this frame), so get back in.
+ int32_t lastBreak = -1;
+ if (charsFit >= limitLength) {
+ charsFit = limitLength;
+ if (transformedLastBreak != UINT32_MAX) {
+ // lastBreak is needed.
+ // This may set lastBreak greater than 'length', but that's OK
+ lastBreak = end.ConvertSkippedToOriginal(transformedOffset +
+ transformedLastBreak);
+ }
+ end.SetOriginalOffset(offset + charsFit);
+ // If we were forced to fit, and the break position is after a soft hyphen,
+ // note that this is a hyphenation break.
+ if ((forceBreak >= 0 || forceBreakAfter) &&
+ HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
+ usedHyphenation = true;
+ }
+ }
+ if (usedHyphenation) {
+ // Fix up metrics to include hyphen
+ AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &textMetrics,
+ boundingBoxType, aDrawTarget);
+ AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
+ }
+ if (textMetrics.mBoundingBox.IsEmpty()) {
+ AddStateBits(TEXT_NO_RENDERED_GLYPHS);
+ }
+
+ bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
+ if (trimmableWS.mAdvance > 0.0) {
+ if (canTrimTrailingWhitespace) {
+ // Optimization: if we we can be sure this frame will be at end of line,
+ // then trim the whitespace now.
+ if (brokeText || HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
+ // We're definitely going to break so our trailing whitespace should
+ // definitely be trimmed. Record that we've already done it.
+ AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
+ textMetrics.mAdvanceWidth -= trimmableWS.mAdvance;
+ trimmableWS.mAdvance = 0.0;
+ }
+ ClearHangableISize();
+ ClearTrimmableWS();
+ } else if (whitespaceCanHang) {
+ // Figure out how much whitespace will hang if at end-of-line.
+ gfxFloat hang =
+ std::min(std::max(0.0, textMetrics.mAdvanceWidth - availWidth),
+ gfxFloat(trimmableWS.mAdvance));
+ SetHangableISize(NSToCoordRound(trimmableWS.mAdvance - hang));
+ // nsLineLayout only needs the TrimmableWS property if justifying, so
+ // check whether this is relevant.
+ if (textStyle->mTextAlign == StyleTextAlign::Justify ||
+ textStyle->mTextAlignLast == StyleTextAlignLast::Justify) {
+ SetTrimmableWS(trimmableWS);
+ }
+ textMetrics.mAdvanceWidth -= hang;
+ trimmableWS.mAdvance = 0.0;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("How did trimmableWS get set?!");
+ ClearHangableISize();
+ ClearTrimmableWS();
+ trimmableWS.mAdvance = 0.0;
+ }
+ } else {
+ // Remove any stale frame properties.
+ ClearHangableISize();
+ ClearTrimmableWS();
+ }
+
+ if (!brokeText && lastBreak >= 0) {
+ // Since everything fit and no break was forced,
+ // record the last break opportunity
+ NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth,
+ "If the text doesn't fit, and we have a break opportunity, "
+ "why didn't MeasureText use it?");
+ MOZ_ASSERT(lastBreak >= offset, "Strange break position");
+ aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset, true,
+ breakPriority);
+ }
+
+ int32_t contentLength = offset + charsFit - GetContentOffset();
+
+ /////////////////////////////////////////////////////////////////////
+ // Compute output metrics
+ /////////////////////////////////////////////////////////////////////
+
+ // first-letter frames should use the tight bounding box metrics for
+ // ascent/descent for good drop-cap effects
+ if (HasAnyStateBits(TEXT_FIRST_LETTER)) {
+ textMetrics.mAscent =
+ std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
+ textMetrics.mDescent =
+ std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
+ }
+
+ // Setup metrics for caller
+ // Disallow negative widths
+ WritingMode wm = GetWritingMode();
+ LogicalSize finalSize(wm);
+ finalSize.ISize(wm) =
+ NSToCoordCeilClamped(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth));
+
+ nscoord fontBaseline;
+ // Note(dshin): Baseline should tecnhically be halfway through the em box for
+ // a central baseline. It is simply half of the text run block size so that it
+ // can be easily calculated in `GetNaturalBaselineBOffset`.
+ if (transformedCharsFit == 0 && !usedHyphenation) {
+ aMetrics.SetBlockStartAscent(0);
+ finalSize.BSize(wm) = 0;
+ fontBaseline = 0;
+ } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
+ fontBaseline = NSToCoordCeil(textMetrics.mAscent);
+ const auto size = fontBaseline + NSToCoordCeil(textMetrics.mDescent);
+ // Use actual text metrics for floating first letter frame.
+ aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
+ : size / 2);
+ finalSize.BSize(wm) = size;
+ } else {
+ // Otherwise, ascent should contain the overline drawable area.
+ // And also descent should contain the underline drawable area.
+ // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
+ nsFontMetrics* fm = provider.GetFontMetrics();
+ nscoord fontAscent =
+ wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
+ nscoord fontDescent =
+ wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
+ fontBaseline = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
+ const auto size =
+ fontBaseline +
+ std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
+ aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
+ : size / 2);
+ finalSize.BSize(wm) = size;
+ }
+ if (Style()->IsTextCombined()) {
+ nsFontMetrics* fm = provider.GetFontMetrics();
+ nscoord width = finalSize.ISize(wm);
+ nscoord em = fm->EmHeight();
+ // Compress the characters in horizontal axis if necessary.
+ if (width <= em) {
+ RemoveProperty(TextCombineScaleFactorProperty());
+ } else {
+ SetProperty(TextCombineScaleFactorProperty(),
+ static_cast<float>(em) / static_cast<float>(width));
+ finalSize.ISize(wm) = em;
+ }
+ // Make the characters be in an 1em square.
+ if (finalSize.BSize(wm) != em) {
+ fontBaseline =
+ aMetrics.BlockStartAscent() + (em - finalSize.BSize(wm)) / 2;
+ aMetrics.SetBlockStartAscent(fontBaseline);
+ finalSize.BSize(wm) = em;
+ }
+ }
+ aMetrics.SetSize(wm, finalSize);
+
+ NS_ASSERTION(aMetrics.BlockStartAscent() >= 0, "Negative ascent???");
+ NS_ASSERTION(
+ (Style()->IsTextCombined() ? aMetrics.ISize(aMetrics.GetWritingMode())
+ : aMetrics.BSize(aMetrics.GetWritingMode())) -
+ aMetrics.BlockStartAscent() >=
+ 0,
+ "Negative descent???");
+
+ mAscent = fontBaseline;
+
+ // Handle text that runs outside its normal bounds.
+ nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
+ if (mTextRun->IsVertical()) {
+ // Swap line-relative textMetrics dimensions to physical coordinates.
+ std::swap(boundingBox.x, boundingBox.y);
+ std::swap(boundingBox.width, boundingBox.height);
+ if (GetWritingMode().IsVerticalRL()) {
+ boundingBox.x = -boundingBox.XMost();
+ boundingBox.x += aMetrics.Width() - mAscent;
+ } else {
+ boundingBox.x += mAscent;
+ }
+ } else {
+ boundingBox.y += mAscent;
+ }
+ aMetrics.SetOverflowAreasToDesiredBounds();
+ aMetrics.InkOverflow().UnionRect(aMetrics.InkOverflow(), boundingBox);
+
+ // When we have text decorations, we don't need to compute their overflow now
+ // because we're guaranteed to do it later
+ // (see nsLineLayout::RelativePositionFrames)
+ UnionAdditionalOverflow(presContext, aLineLayout.LineContainerFrame(),
+ provider, &aMetrics.InkOverflow(), false, true);
+
+ /////////////////////////////////////////////////////////////////////
+ // Clean up, update state
+ /////////////////////////////////////////////////////////////////////
+
+ // If all our characters are discarded or collapsed, then trimmable width
+ // from the last textframe should be preserved. Otherwise the trimmable width
+ // from this textframe overrides. (Currently in CSS trimmable width can be
+ // at most one space so there's no way for trimmable width from a previous
+ // frame to accumulate with trimmable width from this frame.)
+ if (transformedCharsFit > 0) {
+ aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWS.mAdvance));
+ AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
+ }
+ bool breakAfter = forceBreakAfter;
+ if (!shouldSuppressLineBreak) {
+ if (charsFit > 0 && charsFit == length &&
+ textStyle->mHyphens != StyleHyphens::None &&
+ HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
+ bool fits =
+ textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
+ // Record a potential break after final soft hyphen
+ aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
+ gfxBreakPriority::eNormalBreak);
+ }
+ // length == 0 means either the text is empty or it's all collapsed away
+ bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
+ if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
+ transformedOffset + transformedLength == mTextRun->GetLength() &&
+ (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak)) {
+ // We placed all the text in the textrun and we have a break opportunity
+ // at the end of the textrun. We need to record it because the following
+ // content may not care about nsLineBreaker.
+
+ // Note that because we didn't break, we can be sure that (thanks to the
+ // code up above) textMetrics.mAdvanceWidth includes the width of any
+ // trailing whitespace. So we need to subtract trimmableWidth here
+ // because if we did break at this point, that much width would be
+ // trimmed.
+ if (textMetrics.mAdvanceWidth - trimmableWS.mAdvance > availWidth) {
+ breakAfter = true;
+ } else {
+ aLineLayout.NotifyOptionalBreakPosition(this, length, true,
+ gfxBreakPriority::eNormalBreak);
+ }
+ }
+ }
+
+ // Compute reflow status
+ if (contentLength != maxContentLength) {
+ aStatus.SetIncomplete();
+ }
+
+ if (charsFit == 0 && length > 0 && !usedHyphenation) {
+ // Couldn't place any text
+ aStatus.SetInlineLineBreakBeforeAndReset();
+ } else if (contentLength > 0 &&
+ mContentOffset + contentLength - 1 == newLineOffset) {
+ // Ends in \n
+ aStatus.SetInlineLineBreakAfter();
+ aLineLayout.SetLineEndsInBR(true);
+ } else if (breakAfter) {
+ aStatus.SetInlineLineBreakAfter();
+ }
+ if (completedFirstLetter) {
+ aLineLayout.SetFirstLetterStyleOK(false);
+ aStatus.SetFirstLetterComplete();
+ }
+ if (brokeText && breakPriority == gfxBreakPriority::eWordWrapBreak) {
+ aLineLayout.SetUsedOverflowWrap();
+ }
+
+ // Updated the cached NewlineProperty, or delete it.
+ if (contentLength < maxContentLength &&
+ textStyle->NewlineIsSignificant(this) &&
+ (contentNewLineOffset < 0 ||
+ mContentOffset + contentLength <= contentNewLineOffset)) {
+ if (!cachedNewlineOffset) {
+ cachedNewlineOffset = new NewlineProperty;
+ if (NS_FAILED(mContent->SetProperty(
+ nsGkAtoms::newline, cachedNewlineOffset,
+ nsINode::DeleteProperty<NewlineProperty>))) {
+ delete cachedNewlineOffset;
+ cachedNewlineOffset = nullptr;
+ }
+ mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
+ }
+ if (cachedNewlineOffset) {
+ cachedNewlineOffset->mStartOffset = offset;
+ cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
+ }
+ } else if (cachedNewlineOffset) {
+ mContent->RemoveProperty(nsGkAtoms::newline);
+ mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
+ }
+
+ // Compute space and letter counts for justification, if required
+ if ((lineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
+ lineContainer->StyleText()->mTextAlignLast ==
+ StyleTextAlignLast::Justify ||
+ shouldSuppressLineBreak) &&
+ !lineContainer->IsInSVGTextSubtree()) {
+ AddStateBits(TEXT_JUSTIFICATION_ENABLED);
+ Range range(uint32_t(offset), uint32_t(offset + charsFit));
+ aLineLayout.SetJustificationInfo(provider.ComputeJustification(range));
+ }
+
+ SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
+
+ InvalidateFrame();
+
+#ifdef NOISY_REFLOW
+ ListTag(stdout);
+ printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.Width(),
+ aMetrics.Height(), aMetrics.BlockStartAscent(), aStatus);
+#endif
+}
+
+/* virtual */
+bool nsTextFrame::CanContinueTextRun() const {
+ // We can continue a text run through a text frame
+ return true;
+}
+
+nsTextFrame::TrimOutput nsTextFrame::TrimTrailingWhiteSpace(
+ DrawTarget* aDrawTarget) {
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),
+ "frame should have been reflowed");
+
+ TrimOutput result;
+ result.mChanged = false;
+ result.mDeltaWidth = 0;
+
+ AddStateBits(TEXT_END_OF_LINE);
+
+ if (!GetTextRun(nsTextFrame::eInflated)) {
+ // If reflow didn't create a textrun, there must have been no content once
+ // leading whitespace was trimmed, so nothing more to do here.
+ return result;
+ }
+
+ int32_t contentLength = GetContentLength();
+ if (!contentLength) {
+ return result;
+ }
+
+ gfxSkipCharsIterator start =
+ EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
+ NS_ENSURE_TRUE(mTextRun, result);
+
+ uint32_t trimmedStart = start.GetSkippedOffset();
+
+ const nsTextFragment* frag = TextFragment();
+ TrimmedOffsets trimmed = GetTrimmedOffsets(frag);
+ gfxSkipCharsIterator trimmedEndIter = start;
+ const nsStyleText* textStyle = StyleText();
+ gfxFloat delta = 0;
+ uint32_t trimmedEnd =
+ trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
+
+ if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE) &&
+ trimmed.GetEnd() < GetContentEnd()) {
+ gfxSkipCharsIterator end = trimmedEndIter;
+ uint32_t endOffset =
+ end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
+ if (trimmedEnd < endOffset) {
+ // We can't be dealing with tabs here ... they wouldn't be trimmed. So
+ // it's OK to pass null for the line container.
+ PropertyProvider provider(mTextRun, textStyle, frag, this, start,
+ contentLength, nullptr, 0,
+ nsTextFrame::eInflated);
+ delta =
+ mTextRun->GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
+ result.mChanged = true;
+ }
+ }
+
+ gfxFloat advanceDelta;
+ mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
+ HasAnyStateBits(TEXT_START_OF_LINE), true,
+ &advanceDelta);
+ if (advanceDelta != 0) {
+ result.mChanged = true;
+ }
+
+ // aDeltaWidth is *subtracted* from our width.
+ // If advanceDelta is positive then setting the line break made us longer,
+ // so aDeltaWidth could go negative.
+ result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
+ // If aDeltaWidth goes negative, that means this frame might not actually fit
+ // anymore!!! We need higher level line layout to recover somehow.
+ // If it's because the frame has a soft hyphen that is now being displayed,
+ // this should actually be OK, because our reflow recorded the break
+ // opportunity that allowed the soft hyphen to be used, and we wouldn't
+ // have recorded the opportunity unless the hyphen fit (or was the first
+ // opportunity on the line).
+ // Otherwise this can/ really only happen when we have glyphs with special
+ // shapes at the end of lines, I think. Breaking inside a kerning pair won't
+ // do it because that would mean we broke inside this textrun, and
+ // BreakAndMeasureText should make sure the resulting shaped substring fits.
+ // Maybe if we passed a maxTextLength? But that only happens at direction
+ // changes (so we wouldn't kern across the boundary) or for first-letter
+ // (which always fits because it starts the line!).
+ NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,
+ "Negative deltawidth, something odd is happening");
+
+#ifdef NOISY_TRIM
+ ListTag(stdout);
+ printf(": trim => %d\n", result.mDeltaWidth);
+#endif
+ return result;
+}
+
+OverflowAreas nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame,
+ bool aIncludeShadows) {
+ RemoveProperty(WebRenderTextBounds());
+
+ nsRect bounds(nsPoint(0, 0), GetSize());
+ OverflowAreas result(bounds, bounds);
+
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ if (!mTextRun) {
+ return result;
+ }
+
+ PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
+ // Don't trim trailing space, in case we need to paint it as selected.
+ provider.InitializeForDisplay(false);
+
+ gfxTextRun::Metrics textMetrics =
+ mTextRun->MeasureText(ComputeTransformedRange(provider),
+ gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
+ if (GetWritingMode().IsLineInverted()) {
+ textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
+ }
+ nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
+ boundingBox += nsPoint(0, mAscent);
+ if (mTextRun->IsVertical()) {
+ // Swap line-relative textMetrics dimensions to physical coordinates.
+ std::swap(boundingBox.x, boundingBox.y);
+ std::swap(boundingBox.width, boundingBox.height);
+ }
+ nsRect& vis = result.InkOverflow();
+ vis.UnionRect(vis, boundingBox);
+ UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true,
+ aIncludeShadows);
+ return result;
+}
+
+static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
+ const gfxTextRun* aTextRun, uint32_t aSkippedOffset,
+ const nsTextFragment* aFrag, int32_t aFragOffset,
+ int32_t aFragLen, nsAString& aOut) {
+ nsAutoString fragString;
+ char16_t* out;
+ bool needsToMaskPassword = NeedsToMaskPassword(aFrame);
+ if (aStyle->mTextTransform.IsNone() && !needsToMaskPassword &&
+ aStyle->mWebkitTextSecurity == StyleTextSecurity::None) {
+ // No text-transform, so we can copy directly to the output string.
+ aOut.SetLength(aOut.Length() + aFragLen);
+ out = aOut.EndWriting() - aFragLen;
+ } else {
+ // Use a temporary string as source for the transform.
+ fragString.SetLength(aFragLen);
+ out = fragString.BeginWriting();
+ }
+
+ // Copy the text, with \n and \t replaced by <space> if appropriate.
+ MOZ_ASSERT(aFragOffset >= 0);
+ for (uint32_t i = 0; i < static_cast<uint32_t>(aFragLen); ++i) {
+ char16_t ch = aFrag->CharAt(static_cast<uint32_t>(aFragOffset) + i);
+ if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) ||
+ (ch == '\t' && !aStyle->TabIsSignificant())) {
+ ch = ' ';
+ }
+ out[i] = ch;
+ }
+
+ if (!aStyle->mTextTransform.IsNone() || needsToMaskPassword ||
+ aStyle->mWebkitTextSecurity != StyleTextSecurity::None) {
+ MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed);
+ if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
+ // Apply text-transform according to style in the transformed run.
+ char16_t maskChar =
+ needsToMaskPassword ? 0 : aStyle->TextSecurityMaskChar();
+ auto transformedTextRun =
+ static_cast<const nsTransformedTextRun*>(aTextRun);
+ nsAutoString convertedString;
+ AutoTArray<bool, 50> charsToMergeArray;
+ AutoTArray<bool, 50> deletedCharsArray;
+ nsCaseTransformTextRunFactory::TransformString(
+ fragString, convertedString, /* aGlobalTransform = */ Nothing(),
+ maskChar, /* aCaseTransformsOnly = */ true, nullptr,
+ charsToMergeArray, deletedCharsArray, transformedTextRun,
+ aSkippedOffset);
+ aOut.Append(convertedString);
+ } else {
+ // Should not happen (see assertion above), but as a fallback...
+ aOut.Append(fragString);
+ }
+ }
+}
+
+static void LineStartsOrEndsAtHardLineBreak(nsTextFrame* aFrame,
+ nsBlockFrame* aLineContainer,
+ bool* aStartsAtHardBreak,
+ bool* aEndsAtHardBreak) {
+ bool foundValidLine;
+ nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine);
+ if (!foundValidLine) {
+ NS_ERROR("Invalid line!");
+ *aStartsAtHardBreak = *aEndsAtHardBreak = true;
+ return;
+ }
+
+ *aEndsAtHardBreak = !iter.GetLine()->IsLineWrapped();
+ if (iter.Prev()) {
+ *aStartsAtHardBreak = !iter.GetLine()->IsLineWrapped();
+ } else {
+ // Hit block boundary
+ *aStartsAtHardBreak = true;
+ }
+}
+
+nsIFrame::RenderedText nsTextFrame::GetRenderedText(
+ uint32_t aStartOffset, uint32_t aEndOffset, TextOffsetType aOffsetType,
+ TrailingWhitespace aTrimTrailingWhitespace) {
+ MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets");
+ MOZ_ASSERT(!GetPrevContinuation() ||
+ (aOffsetType == TextOffsetType::OffsetsInContentText &&
+ aStartOffset >= (uint32_t)GetContentOffset() &&
+ aEndOffset <= (uint32_t)GetContentEnd()),
+ "Must be called on first-in-flow, or content offsets must be "
+ "given and be within this frame.");
+
+ // The handling of offsets could be more efficient...
+ RenderedText result;
+ nsBlockFrame* lineContainer = nullptr;
+ nsTextFrame* textFrame;
+ const nsTextFragment* textFrag = TextFragment();
+ uint32_t offsetInRenderedString = 0;
+ bool haveOffsets = false;
+
+ for (textFrame = this; textFrame;
+ textFrame = textFrame->GetNextContinuation()) {
+ if (textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ // We don't trust dirty frames, especially when computing rendered text.
+ break;
+ }
+
+ // Ensure the text run and grab the gfxSkipCharsIterator for it
+ gfxSkipCharsIterator iter =
+ textFrame->EnsureTextRun(nsTextFrame::eInflated);
+ if (!textFrame->mTextRun) {
+ break;
+ }
+ gfxSkipCharsIterator tmpIter = iter;
+
+ // Check if the frame starts/ends at a hard line break, to determine
+ // whether whitespace should be trimmed.
+ bool startsAtHardBreak, endsAtHardBreak;
+ if (!HasAnyStateBits(TEXT_START_OF_LINE | TEXT_END_OF_LINE)) {
+ startsAtHardBreak = endsAtHardBreak = false;
+ } else if (nsBlockFrame* thisLc =
+ do_QueryFrame(FindLineContainer(textFrame))) {
+ if (thisLc != lineContainer) {
+ // Setup line cursor when needed.
+ lineContainer = thisLc;
+ lineContainer->SetupLineCursorForQuery();
+ }
+ LineStartsOrEndsAtHardLineBreak(textFrame, lineContainer,
+ &startsAtHardBreak, &endsAtHardBreak);
+ } else {
+ // Weird situation where we have a line layout without a block.
+ // No soft breaks occur in this situation.
+ startsAtHardBreak = endsAtHardBreak = true;
+ }
+
+ // Whether we need to trim whitespaces after the text frame.
+ // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags
+ // in the cases where this should not occur.
+ TrimmedOffsetFlags trimFlags = TrimmedOffsetFlags::Default;
+ if (!textFrame->IsAtEndOfLine() ||
+ aTrimTrailingWhitespace != TrailingWhitespace::Trim ||
+ !endsAtHardBreak) {
+ trimFlags |= TrimmedOffsetFlags::NoTrimAfter;
+ }
+
+ // Whether to trim whitespaces before the text frame.
+ if (!startsAtHardBreak) {
+ trimFlags |= TrimmedOffsetFlags::NoTrimBefore;
+ }
+
+ TrimmedOffsets trimmedOffsets =
+ textFrame->GetTrimmedOffsets(textFrag, trimFlags);
+ bool trimmedSignificantNewline =
+ trimmedOffsets.GetEnd() < GetContentEnd() &&
+ HasSignificantTerminalNewline();
+ uint32_t skippedToRenderedStringOffset =
+ offsetInRenderedString -
+ tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
+ uint32_t nextOffsetInRenderedString =
+ tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
+ (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
+
+ if (aOffsetType == TextOffsetType::OffsetsInRenderedText) {
+ if (nextOffsetInRenderedString <= aStartOffset) {
+ offsetInRenderedString = nextOffsetInRenderedString;
+ continue;
+ }
+ if (!haveOffsets) {
+ result.mOffsetWithinNodeText = tmpIter.ConvertSkippedToOriginal(
+ aStartOffset - skippedToRenderedStringOffset);
+ result.mOffsetWithinNodeRenderedText = aStartOffset;
+ haveOffsets = true;
+ }
+ if (offsetInRenderedString >= aEndOffset) {
+ break;
+ }
+ } else {
+ if (uint32_t(textFrame->GetContentEnd()) <= aStartOffset) {
+ offsetInRenderedString = nextOffsetInRenderedString;
+ continue;
+ }
+ if (!haveOffsets) {
+ result.mOffsetWithinNodeText = aStartOffset;
+ // Skip trimmed space when computed the rendered text offset.
+ int32_t clamped =
+ std::max<int32_t>(aStartOffset, trimmedOffsets.mStart);
+ result.mOffsetWithinNodeRenderedText =
+ tmpIter.ConvertOriginalToSkipped(clamped) +
+ skippedToRenderedStringOffset;
+ MOZ_ASSERT(
+ result.mOffsetWithinNodeRenderedText >= offsetInRenderedString &&
+ result.mOffsetWithinNodeRenderedText <= INT32_MAX,
+ "Bad offset within rendered text");
+ haveOffsets = true;
+ }
+ if (uint32_t(textFrame->mContentOffset) >= aEndOffset) {
+ break;
+ }
+ }
+
+ int32_t startOffset;
+ int32_t endOffset;
+ if (aOffsetType == TextOffsetType::OffsetsInRenderedText) {
+ startOffset = tmpIter.ConvertSkippedToOriginal(
+ aStartOffset - skippedToRenderedStringOffset);
+ endOffset = tmpIter.ConvertSkippedToOriginal(
+ aEndOffset - skippedToRenderedStringOffset);
+ } else {
+ startOffset = aStartOffset;
+ endOffset = std::min<uint32_t>(INT32_MAX, aEndOffset);
+ }
+
+ // If startOffset and/or endOffset are inside of trimmedOffsets' range,
+ // then clamp the edges of trimmedOffsets accordingly.
+ int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd();
+ trimmedOffsets.mStart =
+ std::max<uint32_t>(trimmedOffsets.mStart, startOffset);
+ trimmedOffsets.mLength =
+ std::min<uint32_t>(origTrimmedOffsetsEnd, endOffset) -
+ trimmedOffsets.mStart;
+ if (trimmedOffsets.mLength <= 0) {
+ offsetInRenderedString = nextOffsetInRenderedString;
+ continue;
+ }
+
+ const nsStyleText* textStyle = textFrame->StyleText();
+ iter.SetOriginalOffset(trimmedOffsets.mStart);
+ while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
+ int32_t runLength;
+ bool isSkipped = iter.IsOriginalCharSkipped(&runLength);
+ runLength = std::min(runLength,
+ trimmedOffsets.GetEnd() - iter.GetOriginalOffset());
+ if (isSkipped) {
+ MOZ_ASSERT(runLength >= 0);
+ for (uint32_t i = 0; i < static_cast<uint32_t>(runLength); ++i) {
+ const char16_t ch = textFrag->CharAt(
+ AssertedCast<uint32_t>(iter.GetOriginalOffset() + i));
+ if (ch == CH_SHY) {
+ // We should preserve soft hyphens. They can't be transformed.
+ result.mString.Append(ch);
+ }
+ }
+ } else {
+ TransformChars(textFrame, textStyle, textFrame->mTextRun,
+ iter.GetSkippedOffset(), textFrag,
+ iter.GetOriginalOffset(), runLength, result.mString);
+ }
+ iter.AdvanceOriginal(runLength);
+ }
+
+ if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
+ // A significant newline was trimmed off (we must be
+ // white-space:pre-line). Put it back.
+ result.mString.Append('\n');
+ }
+ offsetInRenderedString = nextOffsetInRenderedString;
+ }
+
+ if (!haveOffsets) {
+ result.mOffsetWithinNodeText = textFrag->GetLength();
+ result.mOffsetWithinNodeRenderedText = offsetInRenderedString;
+ }
+ return result;
+}
+
+/* virtual */
+bool nsTextFrame::IsEmpty() {
+ NS_ASSERTION(
+ !HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE),
+ "Invalid state");
+
+ // XXXldb Should this check compatibility mode as well???
+ const nsStyleText* textStyle = StyleText();
+ if (textStyle->WhiteSpaceIsSignificant()) {
+ // When WhiteSpaceIsSignificant styles are in effect, we only treat the
+ // frame as empty if its content really is entirely *empty* (not just
+ // whitespace), AND it is NOT editable or within an <input> element.
+ // In these cases we consider that the whitespace-preserving style makes
+ // the frame behave as non-empty so that its height doesn't become zero.
+ return GetContentLength() == 0 && !GetContent()->IsEditable() &&
+ !GetContent()->GetParent()->IsHTMLElement(nsGkAtoms::input);
+ }
+
+ if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE)) {
+ return false;
+ }
+
+ if (HasAnyStateBits(TEXT_IS_ONLY_WHITESPACE)) {
+ return true;
+ }
+
+ bool isEmpty = IsAllWhitespace(TextFragment(),
+ textStyle->mWhiteSpaceCollapse !=
+ StyleWhiteSpaceCollapse::PreserveBreaks);
+ AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
+ return isEmpty;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+// Translate the mapped content into a string that's printable
+void nsTextFrame::ToCString(nsCString& aBuf) const {
+ // Get the frames text content
+ const nsTextFragment* frag = TextFragment();
+ if (!frag) {
+ return;
+ }
+
+ const int32_t length = GetContentEnd() - mContentOffset;
+ if (length <= 0) {
+ // Negative lengths are possible during invalidation.
+ return;
+ }
+
+ const uint32_t fragLength = AssertedCast<uint32_t>(GetContentEnd());
+ uint32_t fragOffset = AssertedCast<uint32_t>(GetContentOffset());
+
+ while (fragOffset < fragLength) {
+ char16_t ch = frag->CharAt(fragOffset++);
+ if (ch == '\r') {
+ aBuf.AppendLiteral("\\r");
+ } else if (ch == '\n') {
+ aBuf.AppendLiteral("\\n");
+ } else if (ch == '\t') {
+ aBuf.AppendLiteral("\\t");
+ } else if ((ch < ' ') || (ch >= 127)) {
+ aBuf.Append(nsPrintfCString("\\u%04x", ch));
+ } else {
+ aBuf.Append(ch);
+ }
+ }
+}
+
+nsresult nsTextFrame::GetFrameName(nsAString& aResult) const {
+ MakeFrameName(u"Text"_ns, aResult);
+ nsAutoCString tmp;
+ ToCString(tmp);
+ tmp.SetLength(std::min<size_t>(tmp.Length(), 50u));
+ aResult += u"\""_ns + NS_ConvertASCIItoUTF16(tmp) + u"\""_ns;
+ return NS_OK;
+}
+
+void nsTextFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+
+ str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
+
+ // Output the first/last content offset and prev/next in flow info
+ bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
+ str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
+ isComplete ? 'T' : 'F');
+
+ if (IsSelected()) {
+ str += " SELECTED";
+ }
+ fprintf_stderr(out, "%s\n", str.get());
+}
+
+void nsTextFrame::ListTextRuns(FILE* out,
+ nsTHashSet<const void*>& aSeen) const {
+ if (!mTextRun || aSeen.Contains(mTextRun)) {
+ return;
+ }
+ aSeen.Insert(mTextRun);
+ mTextRun->Dump(out);
+}
+#endif
+
+void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) {
+ AddStateBits(NS_FRAME_IS_BIDI);
+ if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+ mContent->RemoveProperty(nsGkAtoms::flowlength);
+ mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+ }
+
+ /*
+ * After Bidi resolution we may need to reassign text runs.
+ * This is called during bidi resolution from the block container, so we
+ * shouldn't be holding a local reference to a textrun anywhere.
+ */
+ ClearTextRuns();
+
+ nsTextFrame* prev = GetPrevContinuation();
+ if (prev) {
+ // the bidi resolver can be very evil when columns/pages are involved. Don't
+ // let it violate our invariants.
+ int32_t prevOffset = prev->GetContentOffset();
+ aStart = std::max(aStart, prevOffset);
+ aEnd = std::max(aEnd, prevOffset);
+ prev->ClearTextRuns();
+ }
+
+ mContentOffset = aStart;
+ SetLength(aEnd - aStart, nullptr, 0);
+}
+
+/**
+ * @return true if this text frame ends with a newline character. It should
+ * return false if it is not a text frame.
+ */
+bool nsTextFrame::HasSignificantTerminalNewline() const {
+ return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
+}
+
+bool nsTextFrame::IsAtEndOfLine() const {
+ return HasAnyStateBits(TEXT_END_OF_LINE);
+}
+
+Maybe<nscoord> nsTextFrame::GetNaturalBaselineBOffset(
+ WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const {
+ if (aBaselineGroup == BaselineSharingGroup::Last) {
+ return Nothing{};
+ }
+
+ if (!aWM.IsOrthogonalTo(GetWritingMode())) {
+ if (aWM.IsCentralBaseline()) {
+ return Some(GetLogicalUsedBorderAndPadding(aWM).BStart(aWM) +
+ ContentBSize(aWM) / 2);
+ }
+ return Some(mAscent);
+ }
+
+ // When the text frame has a writing mode orthogonal to the desired
+ // writing mode, return a baseline coincides its parent frame.
+ nsIFrame* parent = GetParent();
+ nsPoint position = GetNormalPosition();
+ nscoord parentAscent = parent->GetLogicalBaseline(aWM);
+ if (aWM.IsVerticalRL()) {
+ nscoord parentDescent = parent->GetSize().width - parentAscent;
+ nscoord descent = parentDescent - position.x;
+ return Some(GetSize().width - descent);
+ }
+ return Some(parentAscent - (aWM.IsVertical() ? position.x : position.y));
+}
+
+bool nsTextFrame::HasAnyNoncollapsedCharacters() {
+ gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
+ int32_t offset = GetContentOffset(), offsetEnd = GetContentEnd();
+ int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
+ int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
+ return skippedOffset != skippedOffsetEnd;
+}
+
+bool nsTextFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
+ return ComputeCustomOverflowInternal(aOverflowAreas, true);
+}
+
+bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas& aOverflowAreas,
+ bool aIncludeShadows) {
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ return true;
+ }
+
+ nsIFrame* decorationsBlock;
+ if (IsFloatingFirstLetterChild()) {
+ decorationsBlock = GetParent();
+ } else {
+ nsIFrame* f = this;
+ for (;;) {
+ nsBlockFrame* fBlock = do_QueryFrame(f);
+ if (fBlock) {
+ decorationsBlock = fBlock;
+ break;
+ }
+
+ f = f->GetParent();
+ if (!f) {
+ NS_ERROR("Couldn't find any block ancestor (for text decorations)");
+ return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
+ }
+ }
+ }
+
+ aOverflowAreas = RecomputeOverflow(decorationsBlock, aIncludeShadows);
+ return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)
+
+void nsTextFrame::AssignJustificationGaps(
+ const mozilla::JustificationAssignment& aAssign) {
+ int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
+ static_assert(sizeof(aAssign) == 1,
+ "The encoding might be broken if JustificationAssignment "
+ "is larger than 1 byte");
+ SetProperty(JustificationAssignmentProperty(), encoded);
+}
+
+mozilla::JustificationAssignment nsTextFrame::GetJustificationAssignment()
+ const {
+ int32_t encoded = GetProperty(JustificationAssignmentProperty());
+ mozilla::JustificationAssignment result;
+ result.mGapsAtStart = encoded >> 8;
+ result.mGapsAtEnd = encoded & 0xFF;
+ return result;
+}
+
+uint32_t nsTextFrame::CountGraphemeClusters() const {
+ const nsTextFragment* frag = TextFragment();
+ MOZ_ASSERT(frag, "Text frame must have text fragment");
+ nsAutoString content;
+ frag->AppendTo(content, AssertedCast<uint32_t>(GetContentOffset()),
+ AssertedCast<uint32_t>(GetContentLength()));
+ return unicode::CountGraphemeClusters(content);
+}
+
+bool nsTextFrame::HasNonSuppressedText() const {
+ if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
+ // If we haven't reflowed yet, or are currently doing so,
+ // just return true because we can't be sure.
+ NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) {
+ return true;
+ }
+
+ if (!GetTextRun(nsTextFrame::eInflated)) {
+ return false;
+ }
+
+ TrimmedOffsets offsets =
+ GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
+ return offsets.mLength != 0;
+}
diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h
new file mode 100644
index 0000000000..568d3333c2
--- /dev/null
+++ b/layout/generic/nsTextFrame.h
@@ -0,0 +1,1075 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextFrame_h__
+#define nsTextFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/gfx/2D.h"
+
+#include "nsIFrame.h"
+#include "nsISelectionController.h"
+#include "nsSplittableFrame.h"
+#include "gfxSkipChars.h"
+#include "gfxTextRun.h"
+#include "JustificationUtils.h"
+
+// Undo the windows.h damage
+#if defined(XP_WIN) && defined(DrawText)
+# undef DrawText
+#endif
+
+class nsTextPaintStyle;
+class nsLineList_iterator;
+struct SelectionDetails;
+class nsTextFragment;
+
+namespace mozilla {
+class SVGContextPaint;
+class SVGTextFrame;
+class nsDisplayTextGeometry;
+class nsDisplayText;
+} // namespace mozilla
+
+class nsTextFrame : public nsIFrame {
+ typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
+ typedef mozilla::SelectionTypeMask SelectionTypeMask;
+ typedef mozilla::SelectionType SelectionType;
+ typedef mozilla::TextRangeStyle TextRangeStyle;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::Size Size;
+ typedef gfxTextRun::Range Range;
+
+ public:
+ enum TextRunType : uint8_t;
+ struct TabWidthStore;
+
+ /**
+ * An implementation of gfxTextRun::PropertyProvider that computes spacing and
+ * hyphenation based on CSS properties for a text frame.
+ */
+ class MOZ_STACK_CLASS PropertyProvider final
+ : public gfxTextRun::PropertyProvider {
+ typedef gfxTextRun::Range Range;
+ typedef gfxTextRun::HyphenType HyphenType;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ public:
+ /**
+ * Use this constructor for reflow, when we don't know what text is
+ * really mapped by the frame and we have a lot of other data around.
+ *
+ * @param aLength can be INT32_MAX to indicate we cover all the text
+ * associated with aFrame up to where its flow chain ends in the given
+ * textrun. If INT32_MAX is passed, justification and hyphen-related methods
+ * cannot be called, nor can GetOriginalLength().
+ */
+ PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
+ const nsTextFragment* aFrag, nsTextFrame* aFrame,
+ const gfxSkipCharsIterator& aStart, int32_t aLength,
+ nsIFrame* aLineContainer,
+ nscoord aOffsetFromBlockOriginForTabs,
+ nsTextFrame::TextRunType aWhichTextRun);
+
+ /**
+ * Use this constructor after the frame has been reflowed and we don't
+ * have other data around. Gets everything from the frame. EnsureTextRun
+ * *must* be called before this!!!
+ */
+ PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
+ nsTextFrame::TextRunType aWhichTextRun,
+ nsFontMetrics* aFontMetrics);
+
+ /**
+ * As above, but assuming we want the inflated text run and associated
+ * metrics.
+ */
+ PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart)
+ : PropertyProvider(aFrame, aStart, nsTextFrame::eInflated,
+ aFrame->InflatedFontMetrics()) {}
+
+ // Call this after construction if you're not going to reflow the text
+ void InitializeForDisplay(bool aTrimAfter);
+
+ void InitializeForMeasure();
+
+ void GetSpacing(Range aRange, Spacing* aSpacing) const final;
+ gfxFloat GetHyphenWidth() const final;
+ void GetHyphenationBreaks(Range aRange,
+ HyphenType* aBreakBefore) const final;
+ mozilla::StyleHyphens GetHyphensOption() const final {
+ return mTextStyle->mHyphens;
+ }
+ mozilla::gfx::ShapedTextFlags GetShapedTextFlags() const final;
+
+ already_AddRefed<DrawTarget> GetDrawTarget() const final;
+
+ uint32_t GetAppUnitsPerDevUnit() const final {
+ return mTextRun->GetAppUnitsPerDevUnit();
+ }
+
+ void GetSpacingInternal(Range aRange, Spacing* aSpacing,
+ bool aIgnoreTabs) const;
+
+ /**
+ * Compute the justification information in given DOM range, return
+ * justification info and assignments if requested.
+ */
+ mozilla::JustificationInfo ComputeJustification(
+ Range aRange,
+ nsTArray<mozilla::JustificationAssignment>* aAssignments = nullptr);
+
+ const nsTextFrame* GetFrame() const { return mFrame; }
+ // This may not be equal to the frame offset/length in because we may have
+ // adjusted for whitespace trimming according to the state bits set in the
+ // frame (for the static provider)
+ const gfxSkipCharsIterator& GetStart() const { return mStart; }
+ // May return INT32_MAX if that was given to the constructor
+ uint32_t GetOriginalLength() const {
+ NS_ASSERTION(mLength != INT32_MAX, "Length not known");
+ return mLength;
+ }
+ const nsTextFragment* GetFragment() const { return mFrag; }
+
+ gfxFontGroup* GetFontGroup() const {
+ if (!mFontGroup) {
+ mFontGroup = GetFontMetrics()->GetThebesFontGroup();
+ }
+ return mFontGroup;
+ }
+
+ nsFontMetrics* GetFontMetrics() const {
+ if (!mFontMetrics) {
+ InitFontGroupAndFontMetrics();
+ }
+ return mFontMetrics;
+ }
+
+ void CalcTabWidths(Range aTransformedRange, gfxFloat aTabWidth) const;
+
+ gfxFloat MinTabAdvance() const;
+
+ const gfxSkipCharsIterator& GetEndHint() const { return mTempIterator; }
+
+ protected:
+ void SetupJustificationSpacing(bool aPostReflow);
+
+ void InitFontGroupAndFontMetrics() const;
+
+ const RefPtr<gfxTextRun> mTextRun;
+ mutable gfxFontGroup* mFontGroup;
+ mutable RefPtr<nsFontMetrics> mFontMetrics;
+ const nsStyleText* mTextStyle;
+ const nsTextFragment* mFrag;
+ const nsIFrame* mLineContainer;
+ nsTextFrame* mFrame;
+ gfxSkipCharsIterator mStart; // Offset in original and transformed string
+ const gfxSkipCharsIterator mTempIterator;
+
+ // Either null, or pointing to the frame's TabWidthProperty.
+ mutable nsTextFrame::TabWidthStore* mTabWidths;
+ // How far we've done tab-width calculation; this is ONLY valid when
+ // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
+ // It's a DOM offset relative to the current frame's offset.
+ mutable uint32_t mTabWidthsAnalyzedLimit;
+
+ int32_t mLength; // DOM string length, may be INT32_MAX
+ const gfxFloat mWordSpacing; // space for each whitespace char
+ const gfxFloat mLetterSpacing; // space for each letter
+ mutable gfxFloat mMinTabAdvance; // min advance for <tab> char
+ mutable gfxFloat mHyphenWidth;
+ mutable gfxFloat mOffsetFromBlockOriginForTabs;
+
+ // The values in mJustificationSpacings corresponds to unskipped
+ // characters start from mJustificationArrayStart.
+ uint32_t mJustificationArrayStart;
+ nsTArray<Spacing> mJustificationSpacings;
+
+ const bool mReflowing;
+ const nsTextFrame::TextRunType mWhichTextRun;
+ };
+
+ explicit nsTextFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID = kClassID)
+ : nsIFrame(aStyle, aPresContext, aID) {}
+
+ NS_DECL_FRAMEARENA_HELPERS(nsTextFrame)
+
+ friend class nsContinuingTextFrame;
+
+ // nsQueryFrame
+ NS_DECL_QUERYFRAME
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContinuationsProperty,
+ nsTArray<nsTextFrame*>)
+
+ // nsIFrame
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) final;
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ void Destroy(DestroyContext&) override;
+
+ Cursor GetCursor(const nsPoint&) final;
+
+ nsresult CharacterDataChanged(const CharacterDataChangeInfo&) final;
+
+ nsTextFrame* FirstContinuation() const override {
+ return const_cast<nsTextFrame*>(this);
+ }
+ nsTextFrame* GetPrevContinuation() const override { return nullptr; }
+ nsTextFrame* GetNextContinuation() const final { return mNextContinuation; }
+ void SetNextContinuation(nsIFrame* aNextContinuation) final {
+ NS_ASSERTION(!aNextContinuation || Type() == aNextContinuation->Type(),
+ "setting a next continuation with incorrect type!");
+ NS_ASSERTION(
+ !nsSplittableFrame::IsInNextContinuationChain(aNextContinuation, this),
+ "creating a loop in continuation chain!");
+ mNextContinuation = static_cast<nsTextFrame*>(aNextContinuation);
+ if (aNextContinuation)
+ aNextContinuation->RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ // Setting a non-fluid continuation might affect our flow length (they're
+ // quite rare so we assume it always does) so we delete our cached value:
+ if (GetContent()->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+ GetContent()->RemoveProperty(nsGkAtoms::flowlength);
+ GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+ }
+ }
+ nsTextFrame* GetNextInFlow() const final {
+ return mNextContinuation && mNextContinuation->HasAnyStateBits(
+ NS_FRAME_IS_FLUID_CONTINUATION)
+ ? mNextContinuation
+ : nullptr;
+ }
+ void SetNextInFlow(nsIFrame* aNextInFlow) final {
+ NS_ASSERTION(!aNextInFlow || Type() == aNextInFlow->Type(),
+ "setting a next in flow with incorrect type!");
+ NS_ASSERTION(
+ !nsSplittableFrame::IsInNextContinuationChain(aNextInFlow, this),
+ "creating a loop in continuation chain!");
+ mNextContinuation = static_cast<nsTextFrame*>(aNextInFlow);
+ if (mNextContinuation &&
+ !mNextContinuation->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) {
+ // Changing from non-fluid to fluid continuation might affect our flow
+ // length, so we delete our cached value:
+ if (GetContent()->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+ GetContent()->RemoveProperty(nsGkAtoms::flowlength);
+ GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+ }
+ }
+ if (aNextInFlow) {
+ aNextInFlow->AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
+ }
+ }
+ nsTextFrame* LastInFlow() const final;
+ nsTextFrame* LastContinuation() const final;
+
+ bool ShouldSuppressLineBreak() const;
+
+ void InvalidateFrame(uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true) final;
+ void InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey = 0,
+ bool aRebuildDisplayItems = true) final;
+
+#ifdef DEBUG_FRAME_DUMP
+ void List(FILE* out = stderr, const char* aPrefix = "",
+ ListFlags aFlags = ListFlags()) const final;
+ nsresult GetFrameName(nsAString& aResult) const final;
+ void ToCString(nsCString& aBuf) const;
+ void ListTextRuns(FILE* out, nsTHashSet<const void*>& aSeen) const final;
+#endif
+
+ // Returns this text frame's content's text fragment.
+ //
+ // Assertions in Init() ensure we only ever get a Text node as content.
+ const nsTextFragment* TextFragment() const {
+ return &mContent->AsText()->TextFragment();
+ }
+
+ /**
+ * Check that the text in this frame is entirely whitespace. Importantly,
+ * this function considers non-breaking spaces (0xa0) to be whitespace,
+ * whereas nsTextFrame::IsEmpty does not. It also considers both one and
+ * two-byte chars.
+ */
+ bool IsEntirelyWhitespace() const;
+
+ ContentOffsets CalcContentOffsetsFromFramePoint(const nsPoint& aPoint) final;
+ ContentOffsets GetCharacterOffsetAtFramePoint(const nsPoint& aPoint);
+
+ /**
+ * This is called only on the primary text frame. It indicates that
+ * the selection state of the given character range has changed.
+ * Frames corresponding to the character range are unconditionally invalidated
+ * (Selection::Repaint depends on this).
+ * @param aStart start of character range.
+ * @param aEnd end (exclusive) of character range.
+ * @param aSelected true iff the character range is now selected.
+ * @param aType the type of the changed selection.
+ */
+ void SelectionStateChanged(uint32_t aStart, uint32_t aEnd, bool aSelected,
+ SelectionType aSelectionType);
+
+ FrameSearchResult PeekOffsetNoAmount(bool aForward, int32_t* aOffset) final;
+ FrameSearchResult PeekOffsetCharacter(
+ bool aForward, int32_t* aOffset,
+ PeekOffsetCharacterOptions aOptions = PeekOffsetCharacterOptions()) final;
+ FrameSearchResult PeekOffsetWord(bool aForward, bool aWordSelectEatSpace,
+ bool aIsKeyboardSelect, int32_t* aOffset,
+ PeekWordState* aState,
+ bool aTrimSpaces) final;
+
+ // Helper method that editor code uses to test for visibility.
+ [[nodiscard]] bool HasVisibleText();
+
+ // Flags for aSetLengthFlags
+ enum { ALLOW_FRAME_CREATION_AND_DESTRUCTION = 0x01 };
+
+ // Update offsets to account for new length. This may clear mTextRun.
+ void SetLength(int32_t aLength, nsLineLayout* aLineLayout,
+ uint32_t aSetLengthFlags = 0);
+
+ std::pair<int32_t, int32_t> GetOffsets() const final;
+
+ void AdjustOffsetsForBidi(int32_t start, int32_t end) final;
+
+ nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) final;
+ nsresult GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength,
+ nsTArray<nsRect>& aRects) final;
+
+ nsresult GetChildFrameContainingOffset(int32_t inContentOffset, bool inHint,
+ int32_t* outFrameContentOffset,
+ nsIFrame** outChildFrame) final;
+
+ bool IsEmpty() final;
+ bool IsSelfEmpty() final { return IsEmpty(); }
+ Maybe<nscoord> GetNaturalBaselineBOffset(
+ mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
+ BaselineExportContext) const override;
+
+ bool HasSignificantTerminalNewline() const final;
+
+ /**
+ * Returns true if this text frame is logically adjacent to the end of the
+ * line.
+ */
+ bool IsAtEndOfLine() const;
+
+ /**
+ * Call this only after reflow the frame. Returns true if non-collapsed
+ * characters are present.
+ */
+ bool HasNoncollapsedCharacters() const {
+ return HasAnyStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
+ }
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() final;
+#endif
+
+ float GetFontSizeInflation() const;
+ bool IsCurrentFontInflation(float aInflation) const;
+ bool HasFontSizeInflation() const {
+ return HasAnyStateBits(TEXT_HAS_FONT_INFLATION);
+ }
+ void SetFontSizeInflation(float aInflation);
+
+ void MarkIntrinsicISizesDirty() final;
+ nscoord GetMinISize(gfxContext* aRenderingContext) final;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) final;
+ void AddInlineMinISize(gfxContext* aRenderingContext,
+ InlineMinISizeData* aData) override;
+ void AddInlinePrefISize(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData) override;
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) final;
+ nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const final;
+ nsresult GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
+ nscoord* aXMost) final;
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final;
+ bool CanContinueTextRun() const final;
+ // Method that is called for a text frame that is logically
+ // adjacent to the end of the line (i.e. followed only by empty text frames,
+ // placeholders or inlines containing such).
+ struct TrimOutput {
+ // true if we trimmed some space or changed metrics in some other way.
+ // In this case, we should call RecomputeOverflow on this frame.
+ bool mChanged;
+ // an amount to *subtract* from the frame's width (zero if !mChanged)
+ nscoord mDeltaWidth;
+ };
+ TrimOutput TrimTrailingWhiteSpace(DrawTarget* aDrawTarget);
+ RenderedText GetRenderedText(
+ uint32_t aStartOffset = 0, uint32_t aEndOffset = UINT32_MAX,
+ TextOffsetType aOffsetType = TextOffsetType::OffsetsInContentText,
+ TrailingWhitespace aTrimTrailingWhitespace =
+ TrailingWhitespace::Trim) final;
+
+ mozilla::OverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame,
+ bool aIncludeShadows = true);
+
+ enum TextRunType : uint8_t {
+ // Anything in reflow (but not intrinsic width calculation) or
+ // painting should use the inflated text run (i.e., with font size
+ // inflation applied).
+ eInflated,
+ // Intrinsic width calculation should use the non-inflated text run.
+ // When there is font size inflation, it will be different.
+ eNotInflated
+ };
+
+ void AddInlineMinISizeForFlow(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData,
+ TextRunType aTextRunType);
+ void AddInlinePrefISizeForFlow(gfxContext* aRenderingContext,
+ InlinePrefISizeData* aData,
+ TextRunType aTextRunType);
+
+ /**
+ * Calculate the horizontal bounds of the grapheme clusters that fit entirely
+ * inside the given left[top]/right[bottom] edges (which are positive lengths
+ * from the respective frame edge). If an input value is zero it is ignored
+ * and the result for that edge is zero. All out parameter values are
+ * undefined when the method returns false.
+ * @return true if at least one whole grapheme cluster fit between the edges
+ */
+ bool MeasureCharClippedText(nscoord aVisIStartEdge, nscoord aVisIEndEdge,
+ nscoord* aSnappedStartEdge,
+ nscoord* aSnappedEndEdge);
+ /**
+ * Same as above; this method also the returns the corresponding text run
+ * offset and number of characters that fit. All out parameter values are
+ * undefined when the method returns false.
+ * @return true if at least one whole grapheme cluster fit between the edges
+ */
+ bool MeasureCharClippedText(PropertyProvider& aProvider,
+ nscoord aVisIStartEdge, nscoord aVisIEndEdge,
+ uint32_t* aStartOffset, uint32_t* aMaxLength,
+ nscoord* aSnappedStartEdge,
+ nscoord* aSnappedEndEdge);
+
+ /**
+ * Return true if this box has some text to display.
+ * It returns false if at least one of these conditions are met:
+ * a. the frame hasn't been reflowed yet
+ * b. GetContentLength() == 0
+ * c. it contains only non-significant white-space
+ */
+ bool HasNonSuppressedText() const;
+
+ /**
+ * Object with various callbacks for PaintText() to invoke for different parts
+ * of the frame's text rendering, when we're generating paths rather than
+ * painting.
+ *
+ * Callbacks are invoked in the following order:
+ *
+ * NotifySelectionBackgroundNeedsFill?
+ * PaintDecorationLine*
+ * NotifyBeforeText
+ * NotifyGlyphPathEmitted*
+ * NotifyAfterText
+ * PaintDecorationLine*
+ * PaintSelectionDecorationLine*
+ *
+ * The color of each part of the frame's text rendering is passed as an
+ * argument to the NotifyBefore* callback for that part. The nscolor can take
+ * on one of the three selection special colors defined in LookAndFeel.h --
+ * NS_TRANSPARENT, NS_SAME_AS_FOREGROUND_COLOR and
+ * NS_40PERCENT_FOREGROUND_COLOR.
+ */
+ struct DrawPathCallbacks : gfxTextRunDrawCallbacks {
+ /**
+ * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted.
+ */
+ explicit DrawPathCallbacks(bool aShouldPaintSVGGlyphs = false)
+ : gfxTextRunDrawCallbacks(aShouldPaintSVGGlyphs) {}
+
+ /**
+ * Called to have the selection highlight drawn before the text is drawn
+ * over the top.
+ */
+ virtual void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect,
+ nscolor aColor,
+ DrawTarget& aDrawTarget) {}
+
+ /**
+ * Called before (for under/over-line) or after (for line-through) the text
+ * is drawn to have a text decoration line drawn.
+ */
+ virtual void PaintDecorationLine(Rect aPath, nscolor aColor) {}
+
+ /**
+ * Called after selected text is drawn to have a decoration line drawn over
+ * the text. (All types of text decoration are drawn after the text when
+ * text is selected.)
+ */
+ virtual void PaintSelectionDecorationLine(Rect aPath, nscolor aColor) {}
+
+ /**
+ * Called just before any paths have been emitted to the gfxContext
+ * for the glyphs of the frame's text.
+ */
+ virtual void NotifyBeforeText(nscolor aColor) {}
+
+ /**
+ * Called just after all the paths have been emitted to the gfxContext
+ * for the glyphs of the frame's text.
+ */
+ virtual void NotifyAfterText() {}
+
+ /**
+ * Called just before a path corresponding to a selection decoration line
+ * has been emitted to the gfxContext.
+ */
+ virtual void NotifyBeforeSelectionDecorationLine(nscolor aColor) {}
+
+ /**
+ * Called just after a path corresponding to a selection decoration line
+ * has been emitted to the gfxContext.
+ */
+ virtual void NotifySelectionDecorationLinePathEmitted() {}
+ };
+
+ struct MOZ_STACK_CLASS PaintTextParams {
+ gfxContext* context;
+ Point framePt;
+ LayoutDeviceRect dirtyRect;
+ mozilla::SVGContextPaint* contextPaint = nullptr;
+ DrawPathCallbacks* callbacks = nullptr;
+ enum {
+ PaintText, // Normal text painting.
+ GenerateTextMask // To generate a mask from a text frame. Should
+ // only paint text itself with opaque color.
+ // Text shadow, text selection color and text
+ // decoration are all discarded in this state.
+ };
+ uint8_t state = PaintText;
+ explicit PaintTextParams(gfxContext* aContext) : context(aContext) {}
+
+ bool IsPaintText() const { return state == PaintText; }
+ bool IsGenerateTextMask() const { return state == GenerateTextMask; }
+ };
+
+ struct PaintTextSelectionParams;
+ struct DrawTextRunParams;
+ struct DrawTextParams;
+ struct ClipEdges;
+ struct PaintShadowParams;
+ struct PaintDecorationLineParams;
+
+ struct PriorityOrderedSelectionsForRange {
+ /// List of Selection Details active for the given range.
+ /// Ordered by priority, i.e. the last element has the highest priority.
+ nsTArray<const SelectionDetails*> mSelectionRanges;
+ Range mRange;
+ };
+
+ // Primary frame paint method called from nsDisplayText. Can also be used
+ // to generate paths rather than paint the frame's text by passing a callback
+ // object. The private DrawText() is what applies the text to a graphics
+ // context.
+ void PaintText(const PaintTextParams& aParams, const nscoord aVisIStartEdge,
+ const nscoord aVisIEndEdge, const nsPoint& aToReferenceFrame,
+ const bool aIsSelected, float aOpacity = 1.0f);
+ // helper: paint text frame when we're impacted by at least one selection.
+ // Return false if the text was not painted and we should continue with
+ // the fast path.
+ bool PaintTextWithSelection(const PaintTextSelectionParams& aParams,
+ const ClipEdges& aClipEdges);
+ // helper: paint text with foreground and background colors determined
+ // by selection(s). Also computes a mask of all selection types applying to
+ // our text, returned in aAllSelectionTypeMask.
+ // Return false if the text was not painted and we should continue with
+ // the fast path.
+ bool PaintTextWithSelectionColors(
+ const PaintTextSelectionParams& aParams,
+ const mozilla::UniquePtr<SelectionDetails>& aDetails,
+ SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges);
+ // helper: paint text decorations for text selected by aSelectionType
+ void PaintTextSelectionDecorations(
+ const PaintTextSelectionParams& aParams,
+ const mozilla::UniquePtr<SelectionDetails>& aDetails,
+ SelectionType aSelectionType);
+
+ SelectionTypeMask ResolveSelections(
+ const PaintTextSelectionParams& aParams, const SelectionDetails* aDetails,
+ nsTArray<PriorityOrderedSelectionsForRange>& aResult,
+ SelectionType aSelectionType, bool* aAnyBackgrounds = nullptr) const;
+
+ void DrawEmphasisMarks(gfxContext* aContext, mozilla::WritingMode aWM,
+ const mozilla::gfx::Point& aTextBaselinePt,
+ const mozilla::gfx::Point& aFramePt, Range aRange,
+ const nscolor* aDecorationOverrideColor,
+ PropertyProvider* aProvider);
+
+ nscolor GetCaretColorAt(int32_t aOffset) final;
+
+ // @param aSelectionFlags may be multiple of nsISelectionDisplay::DISPLAY_*.
+ // @return nsISelectionController.idl's `getDisplaySelection`.
+ int16_t GetSelectionStatus(int16_t* aSelectionFlags);
+
+ int32_t GetContentOffset() const { return mContentOffset; }
+ int32_t GetContentLength() const {
+ NS_ASSERTION(GetContentEnd() - mContentOffset >= 0, "negative length");
+ return GetContentEnd() - mContentOffset;
+ }
+ int32_t GetContentEnd() const;
+ // This returns the length the frame thinks it *should* have after it was
+ // last reflowed (0 if it hasn't been reflowed yet). This should be used only
+ // when setting up the text offsets for a new continuation frame.
+ int32_t GetContentLengthHint() const { return mContentLengthHint; }
+
+ // Compute the length of the content mapped by this frame
+ // and all its in-flow siblings. Basically this means starting at
+ // mContentOffset and going to the end of the text node or the next bidi
+ // continuation boundary.
+ int32_t GetInFlowContentLength();
+
+ /**
+ * Acquires the text run for this content, if necessary.
+ * @param aWhichTextRun indicates whether to get an inflated or non-inflated
+ * text run
+ * @param aRefDrawTarget the DrawTarget to use as a reference for creating the
+ * textrun, if available (if not, we'll create one which will just be slower)
+ * @param aLineContainer the block ancestor for this frame, or nullptr if
+ * unknown
+ * @param aFlowEndInTextRun if non-null, this returns the textrun offset of
+ * end of the text associated with this frame and its in-flow siblings
+ * @return a gfxSkipCharsIterator set up to map DOM offsets for this frame
+ * to offsets into the textrun; its initial offset is set to this frame's
+ * content offset
+ */
+ gfxSkipCharsIterator EnsureTextRun(TextRunType aWhichTextRun,
+ DrawTarget* aRefDrawTarget = nullptr,
+ nsIFrame* aLineContainer = nullptr,
+ const nsLineList_iterator* aLine = nullptr,
+ uint32_t* aFlowEndInTextRun = nullptr);
+
+ gfxTextRun* GetTextRun(TextRunType aWhichTextRun) const {
+ if (aWhichTextRun == eInflated || !HasFontSizeInflation()) return mTextRun;
+ return GetUninflatedTextRun();
+ }
+ gfxTextRun* GetUninflatedTextRun() const;
+ void SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
+ float aInflation);
+ bool IsInTextRunUserData() const {
+ return HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA |
+ TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA);
+ }
+ /**
+ * Notify the frame that it should drop its pointer to a text run.
+ * Returns whether the text run was removed (i.e., whether it was
+ * associated with this frame, either as its inflated or non-inflated
+ * text run.
+ */
+ bool RemoveTextRun(gfxTextRun* aTextRun);
+ /**
+ * Clears out |mTextRun| (or the uninflated text run, when aInflated
+ * is nsTextFrame::eNotInflated and there is inflation) from all frames that
+ * hold a reference to it, starting at |aStartContinuation|, or if it's
+ * nullptr, starting at |this|. Deletes the text run if all references
+ * were cleared and it's not cached.
+ */
+ void ClearTextRun(nsTextFrame* aStartContinuation, TextRunType aWhichTextRun);
+
+ void ClearTextRuns() {
+ ClearTextRun(nullptr, nsTextFrame::eInflated);
+ if (HasFontSizeInflation()) {
+ ClearTextRun(nullptr, nsTextFrame::eNotInflated);
+ }
+ }
+
+ /**
+ * Wipe out references to textrun(s) without deleting the textruns.
+ */
+ void DisconnectTextRuns();
+
+ // Get the DOM content range mapped by this frame after excluding
+ // whitespace subject to start-of-line and end-of-line trimming.
+ // The textrun must have been created before calling this.
+ struct TrimmedOffsets {
+ int32_t mStart;
+ int32_t mLength;
+ int32_t GetEnd() const { return mStart + mLength; }
+ };
+ enum class TrimmedOffsetFlags : uint8_t {
+ Default = 0,
+ NotPostReflow = 1 << 0,
+ NoTrimAfter = 1 << 1,
+ NoTrimBefore = 1 << 2
+ };
+ TrimmedOffsets GetTrimmedOffsets(
+ const nsTextFragment* aFrag,
+ TrimmedOffsetFlags aFlags = TrimmedOffsetFlags::Default) const;
+
+ // Similar to Reflow(), but for use from nsLineLayout
+ void ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
+ DrawTarget* aDrawTarget, ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus);
+
+ nscoord ComputeLineHeight() const;
+
+ bool IsFloatingFirstLetterChild() const;
+
+ bool IsInitialLetterChild() const;
+
+ bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) final;
+ bool ComputeCustomOverflowInternal(mozilla::OverflowAreas& aOverflowAreas,
+ bool aIncludeShadows);
+
+ void AssignJustificationGaps(const mozilla::JustificationAssignment& aAssign);
+ mozilla::JustificationAssignment GetJustificationAssignment() const;
+
+ uint32_t CountGraphemeClusters() const;
+
+ bool HasAnyNoncollapsedCharacters() final;
+
+ /**
+ * Call this after you have manually changed the text node contents without
+ * notifying that change. This behaves as if all the text contents changed.
+ * (You should only use this for native anonymous content.)
+ */
+ void NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength);
+
+ nsFontMetrics* InflatedFontMetrics() const;
+
+ nsRect WebRenderBounds();
+
+ // Find the continuation (which may be this frame itself) containing the
+ // given offset. Note that this may return null, if the offset is beyond the
+ // text covered by the continuation chain.
+ // (To be used only on the first textframe in the chain.)
+ nsTextFrame* FindContinuationForOffset(int32_t aOffset);
+
+ void SetHangableISize(nscoord aISize);
+ nscoord GetHangableISize() const;
+ void ClearHangableISize();
+
+ void SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS);
+ gfxTextRun::TrimmableWS GetTrimmableWS() const;
+ void ClearTrimmableWS();
+
+ protected:
+ virtual ~nsTextFrame();
+
+ friend class mozilla::nsDisplayTextGeometry;
+ friend class mozilla::nsDisplayText;
+
+ mutable RefPtr<nsFontMetrics> mFontMetrics;
+ RefPtr<gfxTextRun> mTextRun;
+ nsTextFrame* mNextContinuation = nullptr;
+ // The key invariant here is that mContentOffset never decreases along
+ // a next-continuation chain. And of course mContentOffset is always <= the
+ // the text node's content length, and the mContentOffset for the first frame
+ // is always 0. Furthermore the text mapped by a frame is determined by
+ // GetContentOffset() and GetContentLength()/GetContentEnd(), which get
+ // the length from the difference between this frame's offset and the next
+ // frame's offset, or the text length if there is no next frame. This means
+ // the frames always map the text node without overlapping or leaving any
+ // gaps.
+ int32_t mContentOffset = 0;
+ // This does *not* indicate the length of text currently mapped by the frame;
+ // instead it's a hint saying that this frame *wants* to map this much text
+ // so if we create a new continuation, this is where that continuation should
+ // start.
+ int32_t mContentLengthHint = 0;
+ nscoord mAscent = 0;
+
+ // Cached selection state.
+ enum class SelectionState : uint8_t {
+ Unknown,
+ Selected,
+ NotSelected,
+ };
+ mutable SelectionState mIsSelected = SelectionState::Unknown;
+
+ // Flags used to track whether certain properties are present.
+ // (Public to keep MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS happy.)
+ public:
+ enum class PropertyFlags : uint8_t {
+ // Whether a cached continuations array is present.
+ Continuations = 1 << 0,
+ // Whether a HangableWhitespace property is present.
+ HangableWS = 1 << 1,
+ // Whether a TrimmableWhitespace property is present.
+ TrimmableWS = 2 << 1,
+ };
+
+ protected:
+ PropertyFlags mPropertyFlags = PropertyFlags(0);
+
+ /**
+ * Return true if the frame is part of a Selection.
+ * Helper method to implement the public IsSelected() API.
+ */
+ bool IsFrameSelected() const final;
+
+ void InvalidateSelectionState() { mIsSelected = SelectionState::Unknown; }
+
+ mozilla::UniquePtr<SelectionDetails> GetSelectionDetails();
+
+ void UnionAdditionalOverflow(nsPresContext* aPresContext, nsIFrame* aBlock,
+ PropertyProvider& aProvider,
+ nsRect* aInkOverflowRect,
+ bool aIncludeTextDecorations,
+ bool aIncludeShadows);
+
+ // Update information of emphasis marks, and return the visial
+ // overflow rect of the emphasis marks.
+ nsRect UpdateTextEmphasis(mozilla::WritingMode aWM,
+ PropertyProvider& aProvider);
+
+ void PaintOneShadow(const PaintShadowParams& aParams,
+ const mozilla::StyleSimpleShadow& aShadowDetails,
+ gfxRect& aBoundingBox, uint32_t aBlurFlags);
+
+ void PaintShadows(mozilla::Span<const mozilla::StyleSimpleShadow>,
+ const PaintShadowParams& aParams);
+
+ struct LineDecoration {
+ nsIFrame* mFrame;
+
+ // This is represents the offset from our baseline to mFrame's baseline;
+ // positive offsets are *above* the baseline and negative offsets below
+ nscoord mBaselineOffset;
+
+ // This represents the offset from the initial position of the underline
+ const mozilla::LengthPercentageOrAuto mTextUnderlineOffset;
+
+ // for CSS property text-decoration-thickness, the width refers to the
+ // thickness of the decoration line
+ const mozilla::StyleTextDecorationLength mTextDecorationThickness;
+ nscolor mColor;
+ mozilla::StyleTextDecorationStyle mStyle;
+
+ // The text-underline-position property; affects the underline offset only
+ // if mTextUnderlineOffset is auto.
+ const mozilla::StyleTextUnderlinePosition mTextUnderlinePosition;
+
+ LineDecoration(nsIFrame* const aFrame, const nscoord aOff,
+ mozilla::StyleTextUnderlinePosition aUnderlinePosition,
+ const mozilla::LengthPercentageOrAuto& aUnderlineOffset,
+ const mozilla::StyleTextDecorationLength& aDecThickness,
+ const nscolor aColor,
+ const mozilla::StyleTextDecorationStyle aStyle)
+ : mFrame(aFrame),
+ mBaselineOffset(aOff),
+ mTextUnderlineOffset(aUnderlineOffset),
+ mTextDecorationThickness(aDecThickness),
+ mColor(aColor),
+ mStyle(aStyle),
+ mTextUnderlinePosition(aUnderlinePosition) {}
+
+ LineDecoration(const LineDecoration& aOther) = default;
+
+ bool operator==(const LineDecoration& aOther) const {
+ return mFrame == aOther.mFrame && mStyle == aOther.mStyle &&
+ mColor == aOther.mColor &&
+ mBaselineOffset == aOther.mBaselineOffset &&
+ mTextUnderlinePosition == aOther.mTextUnderlinePosition &&
+ mTextUnderlineOffset == aOther.mTextUnderlineOffset &&
+ mTextDecorationThickness == aOther.mTextDecorationThickness;
+ }
+
+ bool operator!=(const LineDecoration& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+ struct TextDecorations {
+ AutoTArray<LineDecoration, 1> mOverlines, mUnderlines, mStrikes;
+
+ TextDecorations() = default;
+
+ bool HasDecorationLines() const {
+ return HasUnderline() || HasOverline() || HasStrikeout();
+ }
+ bool HasUnderline() const { return !mUnderlines.IsEmpty(); }
+ bool HasOverline() const { return !mOverlines.IsEmpty(); }
+ bool HasStrikeout() const { return !mStrikes.IsEmpty(); }
+ bool operator==(const TextDecorations& aOther) const {
+ return mOverlines == aOther.mOverlines &&
+ mUnderlines == aOther.mUnderlines && mStrikes == aOther.mStrikes;
+ }
+ bool operator!=(const TextDecorations& aOther) const {
+ return !(*this == aOther);
+ }
+ };
+ enum TextDecorationColorResolution { eResolvedColors, eUnresolvedColors };
+ void GetTextDecorations(nsPresContext* aPresContext,
+ TextDecorationColorResolution aColorResolution,
+ TextDecorations& aDecorations);
+
+ void DrawTextRun(Range aRange, const mozilla::gfx::Point& aTextBaselinePt,
+ const DrawTextRunParams& aParams);
+
+ void DrawTextRunAndDecorations(Range aRange,
+ const mozilla::gfx::Point& aTextBaselinePt,
+ const DrawTextParams& aParams,
+ const TextDecorations& aDecorations);
+
+ void DrawText(Range aRange, const mozilla::gfx::Point& aTextBaselinePt,
+ const DrawTextParams& aParams);
+
+ // Set non empty rect to aRect, it should be overflow rect or frame rect.
+ // If the result rect is larger than the given rect, this returns true.
+ bool CombineSelectionUnderlineRect(nsPresContext* aPresContext,
+ nsRect& aRect);
+
+ // This sets *aShadows to the appropriate shadows, if any, for the given
+ // type of selection.
+ // If text-shadow was not specified, *aShadows is left untouched.
+ // Note that the returned shadow(s) will only be valid as long as the
+ // textPaintStyle remains in scope.
+ void GetSelectionTextShadow(
+ SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
+ mozilla::Span<const mozilla::StyleSimpleShadow>* aShadows);
+
+ /**
+ * Utility methods to paint selection.
+ */
+ void DrawSelectionDecorations(
+ gfxContext* aContext, const LayoutDeviceRect& aDirtyRect,
+ mozilla::SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
+ const TextRangeStyle& aRangeStyle, const Point& aPt,
+ gfxFloat aICoordInFrame, gfxFloat aWidth, gfxFloat aAscent,
+ const gfxFont::Metrics& aFontMetrics, DrawPathCallbacks* aCallbacks,
+ bool aVertical, mozilla::StyleTextDecorationLine aDecoration);
+
+ void PaintDecorationLine(const PaintDecorationLineParams& aParams);
+ /**
+ * ComputeDescentLimitForSelectionUnderline() computes the most far position
+ * where we can put selection underline.
+ *
+ * @return The maximum underline offset from the baseline (positive value
+ * means that the underline can put below the baseline).
+ */
+ gfxFloat ComputeDescentLimitForSelectionUnderline(
+ nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics);
+ /**
+ * This function encapsulates all knowledge of how selections affect
+ * foreground and background colors.
+ * @param aForeground the foreground color to use
+ * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
+ * background should be painted
+ * @return true if the selection affects colors, false otherwise
+ */
+ static bool GetSelectionTextColors(SelectionType aSelectionType,
+ nsAtom* aHighlightName,
+ nsTextPaintStyle& aTextPaintStyle,
+ const TextRangeStyle& aRangeStyle,
+ nscolor* aForeground,
+ nscolor* aBackground);
+ /**
+ * ComputeSelectionUnderlineHeight() computes selection underline height of
+ * the specified selection type from the font metrics.
+ */
+ static gfxFloat ComputeSelectionUnderlineHeight(
+ nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics,
+ SelectionType aSelectionType);
+
+ /**
+ * @brief Helper struct which contains selection data such as its details,
+ * range and priority.
+ */
+ struct SelectionRange {
+ const SelectionDetails* mDetails{nullptr};
+ gfxTextRun::Range mRange;
+ /// used to determine the order of overlapping selections of the same type.
+ uint32_t mPriority{0};
+ };
+ /**
+ * @brief Helper: Extracts a list of `SelectionRange` structs from given
+ * `SelectionDetails` and computes a priority for overlapping selection
+ * ranges.
+ */
+ static SelectionTypeMask CreateSelectionRangeList(
+ const SelectionDetails* aDetails, SelectionType aSelectionType,
+ const PaintTextSelectionParams& aParams,
+ nsTArray<SelectionRange>& aSelectionRanges, bool* aAnyBackgrounds);
+
+ /**
+ * @brief Creates an array of `CombinedSelectionRange`s from given list
+ * of `SelectionRange`s.
+ * Each instance of `CombinedSelectionRange` represents a piece of text with
+ * constant Selections.
+ *
+ * Example:
+ *
+ * Consider this text fragment, [] and () marking selection ranges:
+ * ab[cd(e]f)g
+ * This results in the following array of combined ranges:
+ * - [0]: range: (2, 4), selections: "[]"
+ * - [1]: range: (4, 5), selections: "[]", "()"
+ * - [2]: range: (5, 6), selections: "()"
+ * Depending on the priorities of the ranges, [1] may have a different order
+ * of its ranges. The given example indicates that "()" has a higher priority
+ * than "[]".
+ *
+ * @param aSelectionRanges Array of `SelectionRange` objects. Must be
+ * sorted by the start offset.
+ * @param aCombinedSelectionRanges Out parameter. Returns the constructed
+ * array of combined selection ranges.
+ */
+ static void CombineSelectionRanges(
+ const nsTArray<SelectionRange>& aSelectionRanges,
+ nsTArray<PriorityOrderedSelectionsForRange>& aCombinedSelectionRanges);
+
+ ContentOffsets GetCharacterOffsetAtFramePointInternal(
+ const nsPoint& aPoint, bool aForInsertionPoint);
+
+ static float GetTextCombineScaleFactor(nsTextFrame* aFrame);
+
+ void ClearFrameOffsetCache();
+
+ void ClearMetrics(ReflowOutput& aMetrics);
+
+ // Return pointer to an array of all frames in the continuation chain, or
+ // null if we're too short of memory.
+ nsTArray<nsTextFrame*>* GetContinuations();
+
+ // Clear any cached continuations array; this should be called whenever the
+ // chain is modified.
+ inline void ClearCachedContinuations();
+
+ /**
+ * UpdateIteratorFromOffset() updates the iterator from a given offset.
+ * Also, aInOffset may be updated to cluster start if aInOffset isn't
+ * the offset of cluster start.
+ */
+ void UpdateIteratorFromOffset(const PropertyProvider& aProperties,
+ int32_t& aInOffset,
+ gfxSkipCharsIterator& aIter);
+
+ nsPoint GetPointFromIterator(const gfxSkipCharsIterator& aIter,
+ PropertyProvider& aProperties);
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::TrimmedOffsetFlags)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::PropertyFlags)
+
+inline void nsTextFrame::ClearCachedContinuations() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mPropertyFlags & PropertyFlags::Continuations) {
+ RemoveProperty(ContinuationsProperty());
+ mPropertyFlags &= ~PropertyFlags::Continuations;
+ }
+}
+
+#endif
diff --git a/layout/generic/nsTextFrameUtils.cpp b/layout/generic/nsTextFrameUtils.cpp
new file mode 100644
index 0000000000..85b9177d4e
--- /dev/null
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextFrameUtils.h"
+
+#include "mozilla/dom/Text.h"
+#include "nsBidiUtils.h"
+#include "nsCharTraits.h"
+#include "nsIContent.h"
+#include "nsStyleStruct.h"
+#include "nsTextFragment.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// static
+bool nsTextFrameUtils::IsSpaceCombiningSequenceTail(const char16_t* aChars,
+ int32_t aLength) {
+ return aLength > 0 &&
+ (mozilla::unicode::IsClusterExtenderExcludingJoiners(aChars[0]) ||
+ (IsBidiControl(aChars[0]) &&
+ IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1)));
+}
+
+static bool IsDiscardable(char16_t ch, nsTextFrameUtils::Flags* aFlags) {
+ // Unlike IS_DISCARDABLE, we don't discard \r. \r will be ignored by
+ // gfxTextRun and discarding it would force us to copy text in many cases of
+ // preformatted text containing \r\n.
+ if (ch == CH_SHY) {
+ *aFlags |= nsTextFrameUtils::Flags::HasShy;
+ return true;
+ }
+ return IsBidiControl(ch);
+}
+
+static bool IsDiscardable(uint8_t ch, nsTextFrameUtils::Flags* aFlags) {
+ if (ch == CH_SHY) {
+ *aFlags |= nsTextFrameUtils::Flags::HasShy;
+ return true;
+ }
+ return false;
+}
+
+static bool IsSegmentBreak(char16_t aCh) { return aCh == '\n'; }
+
+static bool IsSpaceOrTab(char16_t aCh) { return aCh == ' ' || aCh == '\t'; }
+
+static bool IsSpaceOrTabOrSegmentBreak(char16_t aCh) {
+ return IsSpaceOrTab(aCh) || IsSegmentBreak(aCh);
+}
+
+template <typename CharT>
+/* static */
+bool nsTextFrameUtils::IsSkippableCharacterForTransformText(CharT aChar) {
+ return aChar == ' ' || aChar == '\t' || aChar == '\n' || aChar == CH_SHY ||
+ (aChar > 0xFF && IsBidiControl(aChar));
+}
+
+#ifdef DEBUG
+template <typename CharT>
+static void AssertSkippedExpectedChars(const CharT* aText,
+ const gfxSkipChars& aSkipChars,
+ int32_t aSkipCharsOffset) {
+ gfxSkipCharsIterator it(aSkipChars);
+ it.AdvanceOriginal(aSkipCharsOffset);
+ while (it.GetOriginalOffset() < it.GetOriginalEnd()) {
+ CharT ch = aText[it.GetOriginalOffset() - aSkipCharsOffset];
+ MOZ_ASSERT(!it.IsOriginalCharSkipped() ||
+ nsTextFrameUtils::IsSkippableCharacterForTransformText(ch),
+ "skipped unexpected character; need to update "
+ "IsSkippableCharacterForTransformText?");
+ it.AdvanceOriginal(1);
+ }
+}
+#endif
+
+template <class CharT>
+static CharT* TransformWhiteSpaces(
+ const CharT* aText, uint32_t aLength, uint32_t aBegin, uint32_t aEnd,
+ bool aHasSegmentBreak, bool& aInWhitespace, CharT* aOutput,
+ nsTextFrameUtils::Flags& aFlags,
+ nsTextFrameUtils::CompressionMode aCompression, gfxSkipChars* aSkipChars) {
+ MOZ_ASSERT(aCompression == nsTextFrameUtils::COMPRESS_WHITESPACE ||
+ aCompression == nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE,
+ "whitespaces should be skippable!!");
+ // Get the context preceding/following this white space range.
+ // For 8-bit text (sizeof CharT == 1), the checks here should get optimized
+ // out, and isSegmentBreakSkippable should be initialized to be 'false'.
+ bool isSegmentBreakSkippable =
+ sizeof(CharT) > 1 &&
+ ((aBegin > 0 && IS_ZERO_WIDTH_SPACE(aText[aBegin - 1])) ||
+ (aEnd < aLength && IS_ZERO_WIDTH_SPACE(aText[aEnd])));
+ if (sizeof(CharT) > 1 && !isSegmentBreakSkippable && aBegin > 0 &&
+ aEnd < aLength) {
+ uint32_t ucs4before;
+ uint32_t ucs4after;
+ if (aBegin > 1 &&
+ NS_IS_SURROGATE_PAIR(aText[aBegin - 2], aText[aBegin - 1])) {
+ ucs4before = SURROGATE_TO_UCS4(aText[aBegin - 2], aText[aBegin - 1]);
+ } else {
+ ucs4before = aText[aBegin - 1];
+ }
+ if (aEnd + 1 < aLength &&
+ NS_IS_SURROGATE_PAIR(aText[aEnd], aText[aEnd + 1])) {
+ ucs4after = SURROGATE_TO_UCS4(aText[aEnd], aText[aEnd + 1]);
+ } else {
+ ucs4after = aText[aEnd];
+ }
+ // Discard newlines between characters that have F, W, or H
+ // EastAsianWidth property and neither side is Hangul.
+ isSegmentBreakSkippable =
+ IsSegmentBreakSkipChar(ucs4before) && IsSegmentBreakSkipChar(ucs4after);
+ }
+
+ for (uint32_t i = aBegin; i < aEnd; ++i) {
+ CharT ch = aText[i];
+ bool keepChar = false;
+ bool keepTransformedWhiteSpace = false;
+ if (IsDiscardable(ch, &aFlags)) {
+ aSkipChars->SkipChar();
+ continue;
+ }
+ if (IsSpaceOrTab(ch)) {
+ if (aHasSegmentBreak) {
+ // If white-space is set to normal, nowrap, or pre-line, white space
+ // characters are considered collapsible and all spaces and tabs
+ // immediately preceding or following a segment break are removed.
+ aSkipChars->SkipChar();
+ continue;
+ }
+
+ if (aInWhitespace) {
+ aSkipChars->SkipChar();
+ continue;
+ } else {
+ keepTransformedWhiteSpace = true;
+ }
+ } else {
+ // Apply Segment Break Transformation Rules (CSS Text 3 - 4.1.2) for
+ // segment break characters.
+ if (aCompression == nsTextFrameUtils::COMPRESS_WHITESPACE ||
+ // XXX: According to CSS Text 3, a lone CR should not always be
+ // kept, but still go through the Segment Break Transformation
+ // Rules. However, this is what current modern browser engines
+ // (webkit/blink/edge) do. So, once we can get some clarity
+ // from the specification issue, we should either remove the
+ // lone CR condition here, or leave it here with this comment
+ // being rephrased.
+ // Please see https://github.com/w3c/csswg-drafts/issues/855.
+ ch == '\r') {
+ keepChar = true;
+ } else {
+ // aCompression == COMPRESS_WHITESPACE_NEWLINE
+
+ // Any collapsible segment break immediately following another
+ // collapsible segment break is removed. Then the remaining segment
+ // break is either transformed into a space (U+0020) or removed
+ // depending on the context before and after the break.
+ if (isSegmentBreakSkippable || aInWhitespace) {
+ aSkipChars->SkipChar();
+ continue;
+ }
+ isSegmentBreakSkippable = true;
+ keepTransformedWhiteSpace = true;
+ }
+ }
+
+ if (keepChar) {
+ *aOutput++ = ch;
+ aSkipChars->KeepChar();
+ aInWhitespace = IsSpaceOrTab(ch);
+ } else if (keepTransformedWhiteSpace) {
+ *aOutput++ = ' ';
+ aSkipChars->KeepChar();
+ aInWhitespace = true;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Should've skipped the character!!");
+ }
+ }
+ return aOutput;
+}
+
+template <class CharT>
+CharT* nsTextFrameUtils::TransformText(const CharT* aText, uint32_t aLength,
+ CharT* aOutput,
+ CompressionMode aCompression,
+ uint8_t* aIncomingFlags,
+ gfxSkipChars* aSkipChars,
+ Flags* aAnalysisFlags) {
+ Flags flags = Flags();
+#ifdef DEBUG
+ int32_t skipCharsOffset = aSkipChars->GetOriginalCharCount();
+#endif
+
+ bool lastCharArabic = false;
+ if (aCompression == COMPRESS_NONE ||
+ aCompression == COMPRESS_NONE_TRANSFORM_TO_SPACE) {
+ // Skip discardables.
+ uint32_t i;
+ for (i = 0; i < aLength; ++i) {
+ CharT ch = aText[i];
+ if (IsDiscardable(ch, &flags)) {
+ aSkipChars->SkipChar();
+ } else {
+ aSkipChars->KeepChar();
+ if (ch > ' ') {
+ lastCharArabic = IS_ARABIC_CHAR(ch);
+ } else if (aCompression == COMPRESS_NONE_TRANSFORM_TO_SPACE) {
+ if (ch == '\t' || ch == '\n') {
+ ch = ' ';
+ }
+ } else {
+ // aCompression == COMPRESS_NONE
+ if (ch == '\t') {
+ flags |= Flags::HasTab;
+ } else if (ch == '\n') {
+ flags |= Flags::HasNewline;
+ }
+ }
+ *aOutput++ = ch;
+ }
+ }
+ if (lastCharArabic) {
+ *aIncomingFlags |= INCOMING_ARABICCHAR;
+ } else {
+ *aIncomingFlags &= ~INCOMING_ARABICCHAR;
+ }
+ *aIncomingFlags &= ~INCOMING_WHITESPACE;
+ } else {
+ bool inWhitespace = (*aIncomingFlags & INCOMING_WHITESPACE) != 0;
+ uint32_t i;
+ for (i = 0; i < aLength; ++i) {
+ CharT ch = aText[i];
+ // CSS Text 3 - 4.1. The White Space Processing Rules
+ // White space processing in CSS affects only the document white space
+ // characters: spaces (U+0020), tabs (U+0009), and segment breaks.
+ // Since we need the context of segment breaks and their surrounding
+ // white spaces to proceed the white space processing, a consecutive run
+ // of spaces/tabs/segment breaks is collected in a first pass loop, then
+ // we apply the collapsing and transformation rules to this run in a
+ // second pass loop.
+ if (IsSpaceOrTabOrSegmentBreak(ch)) {
+ bool keepLastSpace = false;
+ bool hasSegmentBreak = IsSegmentBreak(ch);
+ uint32_t countTrailingDiscardables = 0;
+ uint32_t j;
+ for (j = i + 1; j < aLength && (IsSpaceOrTabOrSegmentBreak(aText[j]) ||
+ IsDiscardable(aText[j], &flags));
+ j++) {
+ if (IsSegmentBreak(aText[j])) {
+ hasSegmentBreak = true;
+ }
+ }
+ // Exclude trailing discardables before checking space combining
+ // sequence tail.
+ for (; IsDiscardable(aText[j - 1], &flags); j--) {
+ countTrailingDiscardables++;
+ }
+ // If the last white space is followed by a combining sequence tail,
+ // exclude it from the range of TransformWhiteSpaces.
+ if (sizeof(CharT) > 1 && aText[j - 1] == ' ' && j < aLength &&
+ IsSpaceCombiningSequenceTail(&aText[j], aLength - j)) {
+ keepLastSpace = true;
+ j--;
+ }
+ if (j > i) {
+ aOutput = TransformWhiteSpaces(aText, aLength, i, j, hasSegmentBreak,
+ inWhitespace, aOutput, flags,
+ aCompression, aSkipChars);
+ }
+ // We need to keep KeepChar()/SkipChar() in order, so process the
+ // last white space first, then process the trailing discardables.
+ if (keepLastSpace) {
+ keepLastSpace = false;
+ *aOutput++ = ' ';
+ aSkipChars->KeepChar();
+ lastCharArabic = false;
+ j++;
+ }
+ for (; countTrailingDiscardables > 0; countTrailingDiscardables--) {
+ aSkipChars->SkipChar();
+ j++;
+ }
+ i = j - 1;
+ continue;
+ }
+ // Process characters other than the document white space characters.
+ if (IsDiscardable(ch, &flags)) {
+ aSkipChars->SkipChar();
+ } else {
+ *aOutput++ = ch;
+ aSkipChars->KeepChar();
+ }
+ lastCharArabic = IS_ARABIC_CHAR(ch);
+ inWhitespace = false;
+ }
+
+ if (lastCharArabic) {
+ *aIncomingFlags |= INCOMING_ARABICCHAR;
+ } else {
+ *aIncomingFlags &= ~INCOMING_ARABICCHAR;
+ }
+ if (inWhitespace) {
+ *aIncomingFlags |= INCOMING_WHITESPACE;
+ } else {
+ *aIncomingFlags &= ~INCOMING_WHITESPACE;
+ }
+ }
+
+ *aAnalysisFlags = flags;
+
+#ifdef DEBUG
+ AssertSkippedExpectedChars(aText, *aSkipChars, skipCharsOffset);
+#endif
+ return aOutput;
+}
+
+/*
+ * NOTE: The TransformText and IsSkippableCharacterForTransformText template
+ * functions are part of the public API of nsTextFrameUtils, while
+ * their function bodies are not available in the header. They may stop working
+ * (fail to resolve symbol in link time) once their callsites are moved to a
+ * different translation unit (e.g. a different unified source file).
+ * Explicit instantiating this function template with `uint8_t` and `char16_t`
+ * could prevent us from the potential risk.
+ */
+template uint8_t* nsTextFrameUtils::TransformText(
+ const uint8_t* aText, uint32_t aLength, uint8_t* aOutput,
+ CompressionMode aCompression, uint8_t* aIncomingFlags,
+ gfxSkipChars* aSkipChars, Flags* aAnalysisFlags);
+template char16_t* nsTextFrameUtils::TransformText(
+ const char16_t* aText, uint32_t aLength, char16_t* aOutput,
+ CompressionMode aCompression, uint8_t* aIncomingFlags,
+ gfxSkipChars* aSkipChars, Flags* aAnalysisFlags);
+template bool nsTextFrameUtils::IsSkippableCharacterForTransformText(
+ uint8_t aChar);
+template bool nsTextFrameUtils::IsSkippableCharacterForTransformText(
+ char16_t aChar);
+
+template <typename CharT>
+static uint32_t DoComputeApproximateLengthWithWhitespaceCompression(
+ const CharT* aChars, uint32_t aLength, const nsStyleText* aStyleText) {
+ // This is an approximation so we don't really need anything
+ // too fancy here.
+ uint32_t len;
+ if (aStyleText->WhiteSpaceIsSignificant()) {
+ return aLength;
+ }
+ bool prevWS = true; // more important to ignore blocks with
+ // only whitespace than get inline boundaries
+ // exactly right
+ len = 0;
+ for (uint32_t i = 0; i < aLength; ++i) {
+ CharT c = aChars[i];
+ if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
+ if (!prevWS) {
+ ++len;
+ }
+ prevWS = true;
+ } else {
+ ++len;
+ prevWS = false;
+ }
+ }
+ return len;
+}
+
+uint32_t nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
+ Text* aText, const nsStyleText* aStyleText) {
+ const nsTextFragment* frag = &aText->TextFragment();
+ if (frag->Is2b()) {
+ return DoComputeApproximateLengthWithWhitespaceCompression(
+ frag->Get2b(), frag->GetLength(), aStyleText);
+ }
+ return DoComputeApproximateLengthWithWhitespaceCompression(
+ frag->Get1b(), frag->GetLength(), aStyleText);
+}
+
+uint32_t nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
+ const nsAString& aString, const nsStyleText* aStyleText) {
+ return DoComputeApproximateLengthWithWhitespaceCompression(
+ aString.BeginReading(), aString.Length(), aStyleText);
+}
+
+bool nsSkipCharsRunIterator::NextRun() {
+ do {
+ if (mRunLength) {
+ mIterator.AdvanceOriginal(mRunLength);
+ NS_ASSERTION(mRunLength > 0,
+ "No characters in run (initial length too large?)");
+ if (!mSkipped || mLengthIncludesSkipped) {
+ mRemainingLength -= mRunLength;
+ }
+ }
+ if (!mRemainingLength) {
+ return false;
+ }
+ int32_t length;
+ mSkipped = mIterator.IsOriginalCharSkipped(&length);
+ mRunLength = std::min(length, mRemainingLength);
+ } while (!mVisitSkipped && mSkipped);
+
+ return true;
+}
diff --git a/layout/generic/nsTextFrameUtils.h b/layout/generic/nsTextFrameUtils.h
new file mode 100644
index 0000000000..5b9edf271e
--- /dev/null
+++ b/layout/generic/nsTextFrameUtils.h
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSTEXTFRAMEUTILS_H_
+#define NSTEXTFRAMEUTILS_H_
+
+#include "gfxSkipChars.h"
+#include "nsBidiUtils.h"
+
+class nsIContent;
+struct nsStyleText;
+
+namespace mozilla {
+namespace dom {
+class Text;
+}
+} // namespace mozilla
+
+#define BIG_TEXT_NODE_SIZE 4096
+
+#define CH_NBSP 160
+#define CH_SHY 173
+#define CH_CJKSP 12288 // U+3000 IDEOGRAPHIC SPACE (CJK Full-Width Space)
+
+class nsTextFrameUtils {
+ public:
+ // These constants are used as textrun flags for textframe textruns.
+ //
+ // If you add a flag, please add support for it in gfxTextRun::Dump.
+ enum class Flags : uint16_t {
+ // The following flags are set by TransformText
+
+ // the text has at least one untransformed tab character
+ HasTab = 0x01,
+ // the original text has at least one soft hyphen character
+ HasShy = 0x02,
+ // the text has at least one untransformed newline character
+ HasNewline = 0x04,
+
+ // Flag used in textrun construction to *prevent* hiding of fallback text
+ // for pending user-fonts (used for Canvas2d text).
+ DontSkipDrawingForPendingUserFonts = 0x08,
+
+ // The following flags are set by nsTextFrame
+
+ IsSimpleFlow = 0x10,
+ IncomingWhitespace = 0x20,
+ TrailingWhitespace = 0x40,
+ CompressedLeadingWhitespace = 0x80,
+ NoBreaks = 0x100,
+ IsTransformed = 0x200,
+ // This gets set if there's a break opportunity at the end of the textrun.
+ // We normally don't use this break opportunity because the following text
+ // will have a break opportunity at the start, but it's useful for line
+ // layout to know about it in case the following content is not text
+ HasTrailingBreak = 0x400,
+
+ // This is set if the textrun was created for a textframe whose
+ // NS_FRAME_IS_IN_SINGLE_CHAR_MI flag is set. This occurs if the textframe
+ // belongs to a MathML <mi> element whose embedded text consists of a
+ // single character.
+ IsSingleCharMi = 0x800,
+
+ // This is set if the text run might be observing for glyph changes.
+ MightHaveGlyphChanges = 0x1000,
+
+ // For internal use by the memory reporter when accounting for
+ // storage used by textruns.
+ // Because the reporter may visit each textrun multiple times while
+ // walking the frame trees and textrun cache, it needs to mark
+ // textruns that have been seen so as to avoid multiple-accounting.
+ RunSizeAccounted = 0x2000,
+
+ // The following are defined by gfxTextRunFactory rather than here,
+ // so that it also has access to the _INCOMING and MATH_SCRIPT flags
+ // for shaping purposes.
+ // They live in the gfxShapedText::mFlags field rather than the
+ // gfxTextRun::mFlags2 field.
+ // TEXT_TRAILING_ARABICCHAR
+ // TEXT_INCOMING_ARABICCHAR
+ // TEXT_USE_MATH_SCRIPT
+ };
+
+ // These constants are used in TransformText to represent context information
+ // from previous textruns.
+ enum { INCOMING_NONE = 0, INCOMING_WHITESPACE = 1, INCOMING_ARABICCHAR = 2 };
+
+ /**
+ * Returns true if aChars/aLength are something that make a space
+ * character not be whitespace when they follow the space character
+ * (combining mark or join control, ignoring intervening direction
+ * controls).
+ */
+ static bool IsSpaceCombiningSequenceTail(const char16_t* aChars,
+ int32_t aLength);
+ static bool IsSpaceCombiningSequenceTail(const uint8_t* aChars,
+ int32_t aLength) {
+ return false;
+ }
+
+ enum CompressionMode {
+ COMPRESS_NONE,
+ COMPRESS_WHITESPACE,
+ COMPRESS_WHITESPACE_NEWLINE,
+ COMPRESS_NONE_TRANSFORM_TO_SPACE
+ };
+
+ /**
+ * Create a text run from a run of Unicode text. The text may have whitespace
+ * compressed. A preformatted tab is sent to the text run as a single space.
+ * (Tab spacing must be performed by textframe later.) Certain other
+ * characters are discarded.
+ *
+ * @param aCompression control what is compressed to a
+ * single space character: no compression, compress spaces (not followed
+ * by combining mark) and tabs, compress those plus newlines, or
+ * no compression except newlines are discarded.
+ * @param aIncomingFlags a flag indicating whether there was whitespace
+ * or an Arabic character preceding this text. We set it to indicate if
+ * there's an Arabic character or whitespace preceding the end of this text.
+ */
+ template <class CharT>
+ static CharT* TransformText(const CharT* aText, uint32_t aLength,
+ CharT* aOutput, CompressionMode aCompression,
+ uint8_t* aIncomingFlags, gfxSkipChars* aSkipChars,
+ nsTextFrameUtils::Flags* aAnalysisFlags);
+
+ /**
+ * Returns whether aChar is a character that nsTextFrameUtils::TransformText
+ * might mark as skipped. This is used by
+ * SVGTextContentElement::GetNumberOfChars to know whether reflowing frames,
+ * so that we have the results of TransformText, is required, or whether we
+ * can use a fast path instead.
+ */
+ template <class CharT>
+ static bool IsSkippableCharacterForTransformText(CharT aChar);
+
+ static void AppendLineBreakOffset(nsTArray<uint32_t>* aArray,
+ uint32_t aOffset) {
+ if (aArray->Length() > 0 && (*aArray)[aArray->Length() - 1] == aOffset) {
+ return;
+ }
+ aArray->AppendElement(aOffset);
+ }
+
+ static uint32_t ComputeApproximateLengthWithWhitespaceCompression(
+ mozilla::dom::Text*, const nsStyleText*);
+ static uint32_t ComputeApproximateLengthWithWhitespaceCompression(
+ const nsAString&, const nsStyleText*);
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrameUtils::Flags)
+
+class nsSkipCharsRunIterator {
+ public:
+ enum LengthMode {
+ LENGTH_UNSKIPPED_ONLY = false,
+ LENGTH_INCLUDES_SKIPPED = true
+ };
+ nsSkipCharsRunIterator(const gfxSkipCharsIterator& aStart,
+ LengthMode aLengthIncludesSkipped, uint32_t aLength)
+ : mIterator(aStart),
+ mRemainingLength(aLength),
+ mRunLength(0),
+ mSkipped(false),
+ mVisitSkipped(false),
+ mLengthIncludesSkipped(aLengthIncludesSkipped) {}
+ void SetVisitSkipped() { mVisitSkipped = true; }
+ void SetOriginalOffset(int32_t aOffset) {
+ mIterator.SetOriginalOffset(aOffset);
+ }
+ void SetSkippedOffset(uint32_t aOffset) {
+ mIterator.SetSkippedOffset(aOffset);
+ }
+
+ // guaranteed to return only positive-length runs
+ bool NextRun();
+ bool IsSkipped() const { return mSkipped; }
+ // Always returns something > 0
+ int32_t GetRunLength() const { return mRunLength; }
+ const gfxSkipCharsIterator& GetPos() const { return mIterator; }
+ int32_t GetOriginalOffset() const { return mIterator.GetOriginalOffset(); }
+ uint32_t GetSkippedOffset() const { return mIterator.GetSkippedOffset(); }
+
+ private:
+ gfxSkipCharsIterator mIterator;
+ int32_t mRemainingLength;
+ int32_t mRunLength;
+ bool mSkipped;
+ bool mVisitSkipped;
+ bool mLengthIncludesSkipped;
+};
+
+#endif /*NSTEXTFRAMEUTILS_H_*/
diff --git a/layout/generic/nsTextPaintStyle.cpp b/layout/generic/nsTextPaintStyle.cpp
new file mode 100644
index 0000000000..0eff737602
--- /dev/null
+++ b/layout/generic/nsTextPaintStyle.cpp
@@ -0,0 +1,580 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextPaintStyle.h"
+
+#include "nsCSSColorUtils.h"
+#include "nsCSSRendering.h"
+#include "nsFrameSelection.h"
+#include "nsLayoutUtils.h"
+#include "nsTextFrame.h"
+#include "nsStyleConsts.h"
+
+#include "mozilla/LookAndFeel.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB) {
+ if (colorA == colorB) {
+ nscolor res;
+ res = NS_RGB(NS_GET_R(colorA) ^ 0xff, NS_GET_G(colorA) ^ 0xff,
+ NS_GET_B(colorA) ^ 0xff);
+ return res;
+ }
+ return colorA;
+}
+
+nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
+ : mFrame(aFrame),
+ mPresContext(aFrame->PresContext()),
+ mInitCommonColors(false),
+ mInitSelectionColorsAndShadow(false),
+ mResolveColors(true),
+ mSelectionTextColor(NS_RGBA(0, 0, 0, 0)),
+ mSelectionBGColor(NS_RGBA(0, 0, 0, 0)),
+ mSufficientContrast(0),
+ mFrameBackgroundColor(NS_RGBA(0, 0, 0, 0)),
+ mSystemFieldForegroundColor(NS_RGBA(0, 0, 0, 0)),
+ mSystemFieldBackgroundColor(NS_RGBA(0, 0, 0, 0)) {}
+
+bool nsTextPaintStyle::EnsureSufficientContrast(nscolor* aForeColor,
+ nscolor* aBackColor) {
+ InitCommonColors();
+
+ const bool sameAsForeground = *aForeColor == NS_SAME_AS_FOREGROUND_COLOR;
+ if (sameAsForeground) {
+ *aForeColor = GetTextColor();
+ }
+
+ // If the combination of selection background color and frame background color
+ // has sufficient contrast, don't exchange the selection colors.
+ //
+ // Note we use a different threshold here: mSufficientContrast is for contrast
+ // between text and background colors, but since we're diffing two
+ // backgrounds, we don't need that much contrast. We match the heuristic from
+ // NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG and use 20% of mSufficientContrast.
+ const int32_t minLuminosityDifferenceForBackground = mSufficientContrast / 5;
+ const int32_t backLuminosityDifference =
+ NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
+ if (backLuminosityDifference >= minLuminosityDifferenceForBackground) {
+ return false;
+ }
+
+ // Otherwise, we should use the higher-contrast color for the selection
+ // background color.
+ //
+ // For NS_SAME_AS_FOREGROUND_COLOR we only do this if the background is
+ // totally indistinguishable, that is, if the luminosity difference is 0.
+ if (sameAsForeground && backLuminosityDifference) {
+ return false;
+ }
+
+ int32_t foreLuminosityDifference =
+ NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
+ if (backLuminosityDifference < foreLuminosityDifference) {
+ std::swap(*aForeColor, *aBackColor);
+ // Ensure foreground color is opaque to guarantee contrast.
+ *aForeColor = NS_RGB(NS_GET_R(*aForeColor), NS_GET_G(*aForeColor),
+ NS_GET_B(*aForeColor));
+ return true;
+ }
+ return false;
+}
+
+nscolor nsTextPaintStyle::GetTextColor() {
+ if (mFrame->IsInSVGTextSubtree()) {
+ if (!mResolveColors) {
+ return NS_SAME_AS_FOREGROUND_COLOR;
+ }
+
+ const nsStyleSVG* style = mFrame->StyleSVG();
+ switch (style->mFill.kind.tag) {
+ case StyleSVGPaintKind::Tag::None:
+ return NS_RGBA(0, 0, 0, 0);
+ case StyleSVGPaintKind::Tag::Color:
+ return nsLayoutUtils::GetColor(mFrame, &nsStyleSVG::mFill);
+ default:
+ NS_ERROR("cannot resolve SVG paint to nscolor");
+ return NS_RGBA(0, 0, 0, 255);
+ }
+ }
+
+ return nsLayoutUtils::GetColor(mFrame, &nsStyleText::mWebkitTextFillColor);
+}
+
+bool nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
+ nscolor* aBackColor) {
+ NS_ASSERTION(aForeColor, "aForeColor is null");
+ NS_ASSERTION(aBackColor, "aBackColor is null");
+
+ if (!InitSelectionColorsAndShadow()) {
+ return false;
+ }
+
+ *aForeColor = mSelectionTextColor;
+ *aBackColor = mSelectionBGColor;
+ return true;
+}
+
+void nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
+ nscolor* aBackColor) {
+ NS_ASSERTION(aForeColor, "aForeColor is null");
+ NS_ASSERTION(aBackColor, "aBackColor is null");
+
+ const nsFrameSelection* frameSelection = mFrame->GetConstFrameSelection();
+ const Selection* selection =
+ frameSelection->GetSelection(SelectionType::eFind);
+ const SelectionCustomColors* customColors = nullptr;
+ if (selection) {
+ customColors = selection->GetCustomColors();
+ }
+
+ if (!customColors) {
+ nscolor backColor = LookAndFeel::Color(
+ LookAndFeel::ColorID::TextHighlightBackground, mFrame);
+ nscolor foreColor = LookAndFeel::Color(
+ LookAndFeel::ColorID::TextHighlightForeground, mFrame);
+ EnsureSufficientContrast(&foreColor, &backColor);
+ *aForeColor = foreColor;
+ *aBackColor = backColor;
+ return;
+ }
+
+ if (customColors->mForegroundColor && customColors->mBackgroundColor) {
+ nscolor foreColor = *customColors->mForegroundColor;
+ nscolor backColor = *customColors->mBackgroundColor;
+
+ if (EnsureSufficientContrast(&foreColor, &backColor) &&
+ customColors->mAltForegroundColor &&
+ customColors->mAltBackgroundColor) {
+ foreColor = *customColors->mAltForegroundColor;
+ backColor = *customColors->mAltBackgroundColor;
+ }
+
+ *aForeColor = foreColor;
+ *aBackColor = backColor;
+ return;
+ }
+
+ InitCommonColors();
+
+ if (customColors->mBackgroundColor) {
+ // !mForegroundColor means "currentColor"; the current color of the text.
+ nscolor foreColor = GetTextColor();
+ nscolor backColor = *customColors->mBackgroundColor;
+
+ int32_t luminosityDifference =
+ NS_LUMINOSITY_DIFFERENCE(foreColor, backColor);
+
+ if (mSufficientContrast > luminosityDifference &&
+ customColors->mAltBackgroundColor) {
+ int32_t altLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(
+ foreColor, *customColors->mAltBackgroundColor);
+
+ if (luminosityDifference < altLuminosityDifference) {
+ backColor = *customColors->mAltBackgroundColor;
+ }
+ }
+
+ *aForeColor = foreColor;
+ *aBackColor = backColor;
+ return;
+ }
+
+ if (customColors->mForegroundColor) {
+ nscolor foreColor = *customColors->mForegroundColor;
+ // !mBackgroundColor means "transparent"; the current color of the
+ // background.
+
+ int32_t luminosityDifference =
+ NS_LUMINOSITY_DIFFERENCE(foreColor, mFrameBackgroundColor);
+
+ if (mSufficientContrast > luminosityDifference &&
+ customColors->mAltForegroundColor) {
+ int32_t altLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(
+ *customColors->mForegroundColor, mFrameBackgroundColor);
+
+ if (luminosityDifference < altLuminosityDifference) {
+ foreColor = *customColors->mAltForegroundColor;
+ }
+ }
+
+ *aForeColor = foreColor;
+ *aBackColor = NS_TRANSPARENT;
+ return;
+ }
+
+ // There are neither mForegroundColor nor mBackgroundColor.
+ *aForeColor = GetTextColor();
+ *aBackColor = NS_TRANSPARENT;
+}
+
+bool nsTextPaintStyle::GetCustomHighlightTextColor(nsAtom* aHighlightName,
+ nscolor* aForeColor) {
+ NS_ASSERTION(aForeColor, "aForeColor is null");
+
+ // non-existing highlights will be stored as `aHighlightName->nullptr`,
+ // so subsequent calls only need a hashtable lookup and don't have
+ // to enter the style engine.
+ RefPtr<ComputedStyle> highlightStyle =
+ mCustomHighlightPseudoStyles.LookupOrInsertWith(
+ aHighlightName, [this, &aHighlightName] {
+ return mFrame->ComputeHighlightSelectionStyle(aHighlightName);
+ });
+ if (!highlightStyle) {
+ // highlight `aHighlightName` doesn't exist or has no style rules.
+ return false;
+ }
+
+ *aForeColor = highlightStyle->GetVisitedDependentColor(
+ &nsStyleText::mWebkitTextFillColor);
+
+ return highlightStyle->HasAuthorSpecifiedTextColor();
+}
+
+bool nsTextPaintStyle::GetCustomHighlightBackgroundColor(nsAtom* aHighlightName,
+ nscolor* aBackColor) {
+ NS_ASSERTION(aBackColor, "aBackColor is null");
+ // non-existing highlights will be stored as `aHighlightName->nullptr`,
+ // so subsequent calls only need a hashtable lookup and don't have
+ // to enter the style engine.
+ RefPtr<ComputedStyle> highlightStyle =
+ mCustomHighlightPseudoStyles.LookupOrInsertWith(
+ aHighlightName, [this, &aHighlightName] {
+ return mFrame->ComputeHighlightSelectionStyle(aHighlightName);
+ });
+ if (!highlightStyle) {
+ // highlight `aHighlightName` doesn't exist or has no style rules.
+ return false;
+ }
+
+ *aBackColor = highlightStyle->GetVisitedDependentColor(
+ &nsStyleBackground::mBackgroundColor);
+ return NS_GET_A(*aBackColor) != 0;
+}
+
+void nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor) {
+ NS_ASSERTION(aForeColor, "aForeColor is null");
+
+ const nscolor textColor = GetTextColor();
+ *aForeColor = NS_RGBA(NS_GET_R(textColor), NS_GET_G(textColor),
+ NS_GET_B(textColor), 127);
+}
+
+void nsTextPaintStyle::GetIMESelectionColors(SelectionStyleIndex aIndex,
+ nscolor* aForeColor,
+ nscolor* aBackColor) {
+ NS_ASSERTION(aForeColor, "aForeColor is null");
+ NS_ASSERTION(aBackColor, "aBackColor is null");
+
+ nsSelectionStyle* selectionStyle = SelectionStyle(aIndex);
+ *aForeColor = selectionStyle->mTextColor;
+ *aBackColor = selectionStyle->mBGColor;
+}
+
+bool nsTextPaintStyle::GetSelectionUnderlineForPaint(
+ SelectionStyleIndex aIndex, nscolor* aLineColor, float* aRelativeSize,
+ StyleTextDecorationStyle* aStyle) {
+ NS_ASSERTION(aLineColor, "aLineColor is null");
+ NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
+
+ nsSelectionStyle* selectionStyle = SelectionStyle(aIndex);
+ if (selectionStyle->mUnderlineStyle == StyleTextDecorationStyle::None ||
+ selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
+ selectionStyle->mUnderlineRelativeSize <= 0.0f)
+ return false;
+
+ *aLineColor = selectionStyle->mUnderlineColor;
+ *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
+ *aStyle = selectionStyle->mUnderlineStyle;
+ return true;
+}
+
+void nsTextPaintStyle::InitCommonColors() {
+ if (mInitCommonColors) {
+ return;
+ }
+
+ auto bgColor = nsCSSRendering::FindEffectiveBackgroundColor(mFrame);
+ mFrameBackgroundColor = bgColor.mColor;
+
+ mSystemFieldForegroundColor =
+ LookAndFeel::Color(LookAndFeel::ColorID::Fieldtext, mFrame);
+ mSystemFieldBackgroundColor =
+ LookAndFeel::Color(LookAndFeel::ColorID::Field, mFrame);
+
+ if (bgColor.mIsThemed) {
+ // Assume a native widget has sufficient contrast always
+ mSufficientContrast = 0;
+ mInitCommonColors = true;
+ return;
+ }
+
+ nscolor defaultWindowBackgroundColor =
+ LookAndFeel::Color(LookAndFeel::ColorID::Window, mFrame);
+ nscolor selectionTextColor =
+ LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext, mFrame);
+ nscolor selectionBGColor =
+ LookAndFeel::Color(LookAndFeel::ColorID::Highlight, mFrame);
+
+ mSufficientContrast = std::min(
+ std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
+ NS_LUMINOSITY_DIFFERENCE(selectionTextColor, selectionBGColor)),
+ NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, selectionBGColor));
+
+ mInitCommonColors = true;
+}
+
+nscolor nsTextPaintStyle::GetSystemFieldForegroundColor() {
+ InitCommonColors();
+ return mSystemFieldForegroundColor;
+}
+
+nscolor nsTextPaintStyle::GetSystemFieldBackgroundColor() {
+ InitCommonColors();
+ return mSystemFieldBackgroundColor;
+}
+
+bool nsTextPaintStyle::InitSelectionColorsAndShadow() {
+ if (mInitSelectionColorsAndShadow) {
+ return true;
+ }
+
+ int16_t selectionFlags;
+ const int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
+ if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
+ selectionStatus < nsISelectionController::SELECTION_ON) {
+ // Not displaying the normal selection.
+ // We're not caching this fact, so every call to GetSelectionColors
+ // will come through here. We could avoid this, but it's not really worth
+ // it.
+ return false;
+ }
+
+ mInitSelectionColorsAndShadow = true;
+
+ // Use ::selection pseudo class if applicable.
+ if (RefPtr<ComputedStyle> style =
+ mFrame->ComputeSelectionStyle(selectionStatus)) {
+ mSelectionBGColor =
+ style->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
+ mSelectionTextColor =
+ style->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
+ mSelectionPseudoStyle = std::move(style);
+ return true;
+ }
+
+ mSelectionTextColor =
+ LookAndFeel::Color(LookAndFeel::ColorID::Highlighttext, mFrame);
+
+ nscolor selectionBGColor =
+ LookAndFeel::Color(LookAndFeel::ColorID::Highlight, mFrame);
+
+ switch (selectionStatus) {
+ case nsISelectionController::SELECTION_ATTENTION: {
+ mSelectionTextColor = LookAndFeel::Color(
+ LookAndFeel::ColorID::TextSelectAttentionForeground, mFrame);
+ mSelectionBGColor = LookAndFeel::Color(
+ LookAndFeel::ColorID::TextSelectAttentionBackground, mFrame);
+ mSelectionBGColor =
+ EnsureDifferentColors(mSelectionBGColor, selectionBGColor);
+ break;
+ }
+ case nsISelectionController::SELECTION_ON: {
+ mSelectionBGColor = selectionBGColor;
+ break;
+ }
+ default: {
+ mSelectionBGColor = LookAndFeel::Color(
+ LookAndFeel::ColorID::TextSelectDisabledBackground, mFrame);
+ mSelectionBGColor =
+ EnsureDifferentColors(mSelectionBGColor, selectionBGColor);
+ break;
+ }
+ }
+
+ if (mResolveColors) {
+ EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
+ }
+ return true;
+}
+
+nsTextPaintStyle::nsSelectionStyle* nsTextPaintStyle::SelectionStyle(
+ SelectionStyleIndex aIndex) {
+ Maybe<nsSelectionStyle>& selectionStyle = mSelectionStyle[aIndex];
+ if (!selectionStyle) {
+ selectionStyle.emplace(InitSelectionStyle(aIndex));
+ }
+ return selectionStyle.ptr();
+}
+
+struct StyleIDs {
+ LookAndFeel::ColorID mForeground, mBackground, mLine;
+ LookAndFeel::IntID mLineStyle;
+ LookAndFeel::FloatID mLineRelativeSize;
+};
+EnumeratedArray<nsTextPaintStyle::SelectionStyleIndex,
+ nsTextPaintStyle::SelectionStyleIndex::Count, StyleIDs>
+ SelectionStyleIDs = {
+ StyleIDs{LookAndFeel::ColorID::IMERawInputForeground,
+ LookAndFeel::ColorID::IMERawInputBackground,
+ LookAndFeel::ColorID::IMERawInputUnderline,
+ LookAndFeel::IntID::IMERawInputUnderlineStyle,
+ LookAndFeel::FloatID::IMEUnderlineRelativeSize},
+ StyleIDs{LookAndFeel::ColorID::IMESelectedRawTextForeground,
+ LookAndFeel::ColorID::IMESelectedRawTextBackground,
+ LookAndFeel::ColorID::IMESelectedRawTextUnderline,
+ LookAndFeel::IntID::IMESelectedRawTextUnderlineStyle,
+ LookAndFeel::FloatID::IMEUnderlineRelativeSize},
+ StyleIDs{LookAndFeel::ColorID::IMEConvertedTextForeground,
+ LookAndFeel::ColorID::IMEConvertedTextBackground,
+ LookAndFeel::ColorID::IMEConvertedTextUnderline,
+ LookAndFeel::IntID::IMEConvertedTextUnderlineStyle,
+ LookAndFeel::FloatID::IMEUnderlineRelativeSize},
+ StyleIDs{LookAndFeel::ColorID::IMESelectedConvertedTextForeground,
+ LookAndFeel::ColorID::IMESelectedConvertedTextBackground,
+ LookAndFeel::ColorID::IMESelectedConvertedTextUnderline,
+ LookAndFeel::IntID::IMESelectedConvertedTextUnderline,
+ LookAndFeel::FloatID::IMEUnderlineRelativeSize},
+ StyleIDs{LookAndFeel::ColorID::End, LookAndFeel::ColorID::End,
+ LookAndFeel::ColorID::SpellCheckerUnderline,
+ LookAndFeel::IntID::SpellCheckerUnderlineStyle,
+ LookAndFeel::FloatID::SpellCheckerUnderlineRelativeSize}};
+
+nsTextPaintStyle::nsSelectionStyle nsTextPaintStyle::InitSelectionStyle(
+ SelectionStyleIndex aIndex) {
+ const StyleIDs& styleIDs = SelectionStyleIDs[aIndex];
+
+ nscolor foreColor, backColor;
+ if (styleIDs.mForeground == LookAndFeel::ColorID::End) {
+ foreColor = NS_SAME_AS_FOREGROUND_COLOR;
+ } else {
+ foreColor = LookAndFeel::Color(styleIDs.mForeground, mFrame);
+ }
+ if (styleIDs.mBackground == LookAndFeel::ColorID::End) {
+ backColor = NS_TRANSPARENT;
+ } else {
+ backColor = LookAndFeel::Color(styleIDs.mBackground, mFrame);
+ }
+
+ // Convert special color to actual color
+ NS_ASSERTION(foreColor != NS_TRANSPARENT,
+ "foreColor cannot be NS_TRANSPARENT");
+ NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
+ "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
+ NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
+ "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
+
+ if (mResolveColors) {
+ foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
+
+ if (NS_GET_A(backColor) > 0) {
+ EnsureSufficientContrast(&foreColor, &backColor);
+ }
+ }
+
+ nscolor lineColor;
+ float relativeSize;
+ StyleTextDecorationStyle lineStyle;
+ GetSelectionUnderline(mFrame, aIndex, &lineColor, &relativeSize, &lineStyle);
+
+ if (mResolveColors) {
+ lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
+ }
+
+ return nsSelectionStyle{foreColor, backColor, lineColor, lineStyle,
+ relativeSize};
+}
+
+/* static */
+bool nsTextPaintStyle::GetSelectionUnderline(nsIFrame* aFrame,
+ SelectionStyleIndex aIndex,
+ nscolor* aLineColor,
+ float* aRelativeSize,
+ StyleTextDecorationStyle* aStyle) {
+ NS_ASSERTION(aFrame, "aFrame is null");
+ NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
+ NS_ASSERTION(aStyle, "aStyle is null");
+
+ const StyleIDs& styleIDs = SelectionStyleIDs[aIndex];
+
+ nscolor color = LookAndFeel::Color(styleIDs.mLine, aFrame);
+ const int32_t lineStyle = LookAndFeel::GetInt(styleIDs.mLineStyle);
+ auto style = static_cast<StyleTextDecorationStyle>(lineStyle);
+ if (lineStyle > static_cast<int32_t>(StyleTextDecorationStyle::Sentinel)) {
+ NS_ERROR("Invalid underline style value is specified");
+ style = StyleTextDecorationStyle::Solid;
+ }
+ float size = LookAndFeel::GetFloat(styleIDs.mLineRelativeSize);
+
+ NS_ASSERTION(size, "selection underline relative size must be larger than 0");
+
+ if (aLineColor) {
+ *aLineColor = color;
+ }
+ *aRelativeSize = size;
+ *aStyle = style;
+
+ return style != StyleTextDecorationStyle::None && color != NS_TRANSPARENT &&
+ size > 0.0f;
+}
+
+bool nsTextPaintStyle::GetSelectionShadow(
+ Span<const StyleSimpleShadow>* aShadows) {
+ if (!InitSelectionColorsAndShadow()) {
+ return false;
+ }
+
+ if (mSelectionPseudoStyle) {
+ *aShadows = mSelectionPseudoStyle->StyleText()->mTextShadow.AsSpan();
+ return true;
+ }
+
+ return false;
+}
+
+inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) {
+ nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), NS_GET_G(aForeColor),
+ NS_GET_B(aForeColor), (uint8_t)(255 * 0.4f));
+ // Don't use true alpha color for readability.
+ return NS_ComposeColors(aBackColor, foreColor);
+}
+
+nscolor nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
+ nscolor aDefaultForeColor,
+ nscolor aBackColor) {
+ if (aColor == NS_SAME_AS_FOREGROUND_COLOR) {
+ return aDefaultForeColor;
+ }
+
+ if (aColor != NS_40PERCENT_FOREGROUND_COLOR) {
+ return aColor;
+ }
+
+ // Get actual background color
+ nscolor actualBGColor = aBackColor;
+ if (actualBGColor == NS_TRANSPARENT) {
+ InitCommonColors();
+ actualBGColor = mFrameBackgroundColor;
+ }
+ return Get40PercentColor(aDefaultForeColor, actualBGColor);
+}
+
+nscolor nsTextPaintStyle::GetWebkitTextStrokeColor() {
+ if (mFrame->IsInSVGTextSubtree()) {
+ return 0;
+ }
+ return mFrame->StyleText()->mWebkitTextStrokeColor.CalcColor(mFrame);
+}
+
+float nsTextPaintStyle::GetWebkitTextStrokeWidth() {
+ if (mFrame->IsInSVGTextSubtree()) {
+ return 0.0f;
+ }
+ nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth;
+ return mFrame->PresContext()->AppUnitsToFloatDevPixels(coord);
+}
diff --git a/layout/generic/nsTextPaintStyle.h b/layout/generic/nsTextPaintStyle.h
new file mode 100644
index 0000000000..a99ee9fd46
--- /dev/null
+++ b/layout/generic/nsTextPaintStyle.h
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextPaintStyle_h__
+#define nsTextPaintStyle_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/Span.h"
+
+#include "nsAtomHashKeys.h"
+#include "nsISelectionController.h"
+#include "nsTHashMap.h"
+
+class nsTextFrame;
+class nsPresContext;
+
+namespace mozilla {
+enum class StyleTextDecorationStyle : uint8_t;
+}
+
+/**
+ * This helper object computes colors used for painting, and also IME
+ * underline information. The data is computed lazily and cached as necessary.
+ * These live for just the duration of one paint operation.
+ */
+class MOZ_STACK_CLASS nsTextPaintStyle {
+ using ComputedStyle = mozilla::ComputedStyle;
+ using SelectionType = mozilla::SelectionType;
+ using StyleTextDecorationStyle = mozilla::StyleTextDecorationStyle;
+ using StyleSimpleShadow = mozilla::StyleSimpleShadow;
+
+ public:
+ explicit nsTextPaintStyle(nsTextFrame* aFrame);
+
+ void SetResolveColors(bool aResolveColors) {
+ mResolveColors = aResolveColors;
+ }
+
+ nscolor GetTextColor();
+
+ // SVG text has its own painting process, so we should never get its stroke
+ // property from here.
+ nscolor GetWebkitTextStrokeColor();
+ float GetWebkitTextStrokeWidth();
+
+ // Index used to look up styles for different types of selection.
+ enum class SelectionStyleIndex : uint8_t {
+ RawInput = 0,
+ SelRawText,
+ ConvText,
+ SelConvText,
+ SpellChecker,
+ // Not an actual enum value; used to size the array of styles.
+ Count,
+ };
+
+ /**
+ * Compute the colors for normally-selected text. Returns false if
+ * the normal selection is not being displayed.
+ */
+ bool GetSelectionColors(nscolor* aForeColor, nscolor* aBackColor);
+ void GetHighlightColors(nscolor* aForeColor, nscolor* aBackColor);
+ // Computes colors for custom highlights.
+ // Returns false if there are no rules associated with `aHighlightName`.
+ bool GetCustomHighlightTextColor(nsAtom* aHighlightName, nscolor* aForeColor);
+ bool GetCustomHighlightBackgroundColor(nsAtom* aHighlightName,
+ nscolor* aBackColor);
+ void GetURLSecondaryColor(nscolor* aForeColor);
+ void GetIMESelectionColors(SelectionStyleIndex aIndex, nscolor* aForeColor,
+ nscolor* aBackColor);
+ // if this returns false, we don't need to draw underline.
+ bool GetSelectionUnderlineForPaint(SelectionStyleIndex aIndex,
+ nscolor* aLineColor, float* aRelativeSize,
+ StyleTextDecorationStyle* aStyle);
+
+ // if this returns false, we don't need to draw underline.
+ static bool GetSelectionUnderline(nsIFrame*, SelectionStyleIndex aIndex,
+ nscolor* aLineColor, float* aRelativeSize,
+ StyleTextDecorationStyle* aStyle);
+
+ // if this returns false, no text-shadow was specified for the selection
+ // and the *aShadow parameter was not modified.
+ bool GetSelectionShadow(mozilla::Span<const StyleSimpleShadow>* aShadows);
+
+ nsPresContext* PresContext() const { return mPresContext; }
+
+ static SelectionStyleIndex GetUnderlineStyleIndexForSelectionType(
+ SelectionType aSelectionType) {
+ switch (aSelectionType) {
+ case SelectionType::eIMERawClause:
+ return SelectionStyleIndex::RawInput;
+ case SelectionType::eIMESelectedRawClause:
+ return SelectionStyleIndex::SelRawText;
+ case SelectionType::eIMEConvertedClause:
+ return SelectionStyleIndex::ConvText;
+ case SelectionType::eIMESelectedClause:
+ return SelectionStyleIndex::SelConvText;
+ case SelectionType::eSpellCheck:
+ return SelectionStyleIndex::SpellChecker;
+ default:
+ NS_WARNING("non-IME selection type");
+ return SelectionStyleIndex::RawInput;
+ }
+ }
+
+ nscolor GetSystemFieldForegroundColor();
+ nscolor GetSystemFieldBackgroundColor();
+
+ protected:
+ nsTextFrame* mFrame;
+ nsPresContext* mPresContext;
+ bool mInitCommonColors;
+ bool mInitSelectionColorsAndShadow;
+ bool mResolveColors;
+
+ // Selection data
+
+ nscolor mSelectionTextColor;
+ nscolor mSelectionBGColor;
+ RefPtr<ComputedStyle> mSelectionPseudoStyle;
+ nsTHashMap<RefPtr<nsAtom>, RefPtr<ComputedStyle>>
+ mCustomHighlightPseudoStyles;
+
+ // Common data
+
+ int32_t mSufficientContrast;
+ nscolor mFrameBackgroundColor;
+ nscolor mSystemFieldForegroundColor;
+ nscolor mSystemFieldBackgroundColor;
+
+ // selection colors and underline info, the colors are resolved colors if
+ // mResolveColors is true (which is the default), i.e., the foreground color
+ // and background color are swapped if it's needed. And also line color will
+ // be resolved from them.
+ struct nsSelectionStyle {
+ nscolor mTextColor;
+ nscolor mBGColor;
+ nscolor mUnderlineColor;
+ StyleTextDecorationStyle mUnderlineStyle;
+ float mUnderlineRelativeSize;
+ };
+ mozilla::EnumeratedArray<SelectionStyleIndex, SelectionStyleIndex::Count,
+ mozilla::Maybe<nsSelectionStyle>>
+ mSelectionStyle;
+
+ // Color initializations
+ void InitCommonColors();
+ bool InitSelectionColorsAndShadow();
+
+ nsSelectionStyle* SelectionStyle(SelectionStyleIndex aIndex);
+ nsSelectionStyle InitSelectionStyle(SelectionStyleIndex aIndex);
+
+ // Ensures sufficient contrast between the frame background color and the
+ // selection background color, and swaps the selection text and background
+ // colors accordingly.
+ bool EnsureSufficientContrast(nscolor* aForeColor, nscolor* aBackColor);
+
+ nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
+ nscolor aBackColor);
+};
+
+#endif // nsTextPaintStyle_h__
diff --git a/layout/generic/nsTextRunTransformations.cpp b/layout/generic/nsTextRunTransformations.cpp
new file mode 100644
index 0000000000..d18a7ec293
--- /dev/null
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -0,0 +1,940 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextRunTransformations.h"
+
+#include <utility>
+
+#include "GreekCasing.h"
+#include "IrishCasing.h"
+#include "MathMLTextRunFactory.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/gfx/2D.h"
+#include "nsGkAtoms.h"
+#include "nsSpecialCasingData.h"
+#include "nsStyleConsts.h"
+#include "nsTextFrameUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// Unicode characters needing special casing treatment in tr/az languages
+#define LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE 0x0130
+#define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
+
+// Greek sigma needs custom handling for the lowercase transform; for details
+// see bug 740120.
+#define GREEK_CAPITAL_LETTER_SIGMA 0x03A3
+#define GREEK_SMALL_LETTER_FINAL_SIGMA 0x03C2
+#define GREEK_SMALL_LETTER_SIGMA 0x03C3
+
+already_AddRefed<nsTransformedTextRun> nsTransformedTextRun::Create(
+ const gfxTextRunFactory::Parameters* aParams,
+ nsTransformingTextRunFactory* aFactory, gfxFontGroup* aFontGroup,
+ const char16_t* aString, uint32_t aLength,
+ const gfx::ShapedTextFlags aFlags, const nsTextFrameUtils::Flags aFlags2,
+ nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles, bool aOwnsFactory) {
+ NS_ASSERTION(!(aFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT),
+ "didn't expect text to be marked as 8-bit here");
+
+ void* storage =
+ AllocateStorageForTextRun(sizeof(nsTransformedTextRun), aLength);
+ if (!storage) {
+ return nullptr;
+ }
+
+ RefPtr<nsTransformedTextRun> result = new (storage)
+ nsTransformedTextRun(aParams, aFactory, aFontGroup, aString, aLength,
+ aFlags, aFlags2, std::move(aStyles), aOwnsFactory);
+ return result.forget();
+}
+
+void nsTransformedTextRun::SetCapitalization(uint32_t aStart, uint32_t aLength,
+ bool* aCapitalization) {
+ if (mCapitalize.IsEmpty()) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mCapitalize.AppendElements(GetLength());
+ memset(mCapitalize.Elements(), 0, GetLength() * sizeof(bool));
+ }
+ memcpy(mCapitalize.Elements() + aStart, aCapitalization,
+ aLength * sizeof(bool));
+ mNeedsRebuild = true;
+}
+
+bool nsTransformedTextRun::SetPotentialLineBreaks(Range aRange,
+ const uint8_t* aBreakBefore) {
+ bool changed = gfxTextRun::SetPotentialLineBreaks(aRange, aBreakBefore);
+ if (changed) {
+ mNeedsRebuild = true;
+ }
+ return changed;
+}
+
+size_t nsTransformedTextRun::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) {
+ size_t total = gfxTextRun::SizeOfExcludingThis(aMallocSizeOf);
+ total += mStyles.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ total += mCapitalize.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ if (mOwnsFactory) {
+ total += aMallocSizeOf(mFactory);
+ }
+ return total;
+}
+
+size_t nsTransformedTextRun::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+already_AddRefed<nsTransformedTextRun>
+nsTransformingTextRunFactory::MakeTextRun(
+ const char16_t* aString, uint32_t aLength,
+ const gfxTextRunFactory::Parameters* aParams, gfxFontGroup* aFontGroup,
+ gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
+ nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles, bool aOwnsFactory) {
+ return nsTransformedTextRun::Create(aParams, this, aFontGroup, aString,
+ aLength, aFlags, aFlags2,
+ std::move(aStyles), aOwnsFactory);
+}
+
+already_AddRefed<nsTransformedTextRun>
+nsTransformingTextRunFactory::MakeTextRun(
+ const uint8_t* aString, uint32_t aLength,
+ const gfxTextRunFactory::Parameters* aParams, gfxFontGroup* aFontGroup,
+ gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
+ nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles, bool aOwnsFactory) {
+ // We'll only have a Unicode code path to minimize the amount of code needed
+ // for these rarely used features
+ NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
+ aLength);
+ return MakeTextRun(unicodeString.get(), aLength, aParams, aFontGroup,
+ aFlags & ~gfx::ShapedTextFlags::TEXT_IS_8BIT, aFlags2,
+ std::move(aStyles), aOwnsFactory);
+}
+
+void MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
+ const bool* aCharsToMerge,
+ const bool* aDeletedChars) {
+ MOZ_ASSERT(!aDest->TrailingGlyphRun(), "unexpected glyphRuns in aDest!");
+ uint32_t offset = 0;
+ AutoTArray<gfxTextRun::DetailedGlyph, 2> glyphs;
+ const gfxTextRun::CompressedGlyph continuationGlyph =
+ gfxTextRun::CompressedGlyph::MakeComplex(false, false);
+ const gfxTextRun::CompressedGlyph* srcGlyphs = aSrc->GetCharacterGlyphs();
+ gfxTextRun::CompressedGlyph* destGlyphs = aDest->GetCharacterGlyphs();
+ for (gfxTextRun::GlyphRunIterator iter(aSrc, gfxTextRun::Range(aSrc));
+ !iter.AtEnd(); iter.NextRun()) {
+ const gfxTextRun::GlyphRun* run = iter.GlyphRun();
+ aDest->AddGlyphRun(run->mFont, run->mMatchType, offset, false,
+ run->mOrientation, run->mIsCJK);
+
+ bool anyMissing = false;
+ uint32_t mergeRunStart = iter.StringStart();
+ // Initialize to a copy of the first source glyph in the merge run.
+ gfxTextRun::CompressedGlyph mergedGlyph = srcGlyphs[mergeRunStart];
+ uint32_t stringEnd = iter.StringEnd();
+ for (uint32_t k = iter.StringStart(); k < stringEnd; ++k) {
+ const gfxTextRun::CompressedGlyph g = srcGlyphs[k];
+ if (g.IsSimpleGlyph()) {
+ if (!anyMissing) {
+ gfxTextRun::DetailedGlyph details;
+ details.mGlyphID = g.GetSimpleGlyph();
+ details.mAdvance = g.GetSimpleAdvance();
+ glyphs.AppendElement(details);
+ }
+ } else {
+ if (g.IsMissing()) {
+ anyMissing = true;
+ glyphs.Clear();
+ }
+ if (g.GetGlyphCount() > 0) {
+ glyphs.AppendElements(aSrc->GetDetailedGlyphs(k), g.GetGlyphCount());
+ }
+ }
+
+ if (k + 1 < iter.StringEnd() && aCharsToMerge[k + 1]) {
+ // next char is supposed to merge with current, so loop without
+ // writing current merged glyph to the destination
+ continue;
+ }
+
+ // If the start of the merge run is actually a character that should
+ // have been merged with the previous character (this can happen
+ // if there's a font change in the middle of a case-mapped character,
+ // that decomposed into a sequence of base+diacritics, for example),
+ // just discard the entire merge run. See comment at start of this
+ // function.
+ NS_WARNING_ASSERTION(
+ !aCharsToMerge[mergeRunStart],
+ "unable to merge across a glyph run boundary, glyph(s) discarded");
+ if (!aCharsToMerge[mergeRunStart]) {
+ // Determine if we can just copy the existing simple glyph record.
+ if (mergedGlyph.IsSimpleGlyph() && glyphs.Length() == 1) {
+ destGlyphs[offset] = mergedGlyph;
+ } else {
+ // Otherwise set up complex glyph record and store detailed glyphs.
+ mergedGlyph.SetComplex(mergedGlyph.IsClusterStart(),
+ mergedGlyph.IsLigatureGroupStart());
+ destGlyphs[offset] = mergedGlyph;
+ aDest->SetDetailedGlyphs(offset, glyphs.Length(), glyphs.Elements());
+ if (anyMissing) {
+ destGlyphs[offset].SetMissing();
+ }
+ }
+ offset++;
+
+ while (offset < aDest->GetLength() && aDeletedChars[offset]) {
+ destGlyphs[offset++] = continuationGlyph;
+ }
+ }
+
+ glyphs.Clear();
+ anyMissing = false;
+ mergeRunStart = k + 1;
+ if (mergeRunStart < stringEnd) {
+ mergedGlyph = srcGlyphs[mergeRunStart];
+ }
+ }
+ NS_ASSERTION(glyphs.Length() == 0,
+ "Leftover glyphs, don't request merging of the last character "
+ "with its next!");
+ }
+ NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
+}
+
+gfxTextRunFactory::Parameters GetParametersForInner(
+ nsTransformedTextRun* aTextRun, gfx::ShapedTextFlags* aFlags,
+ DrawTarget* aRefDrawTarget) {
+ gfxTextRunFactory::Parameters params = {
+ aRefDrawTarget, nullptr, nullptr,
+ nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()};
+ *aFlags = aTextRun->GetFlags();
+ return params;
+}
+
+// Some languages have special casing conventions that differ from the
+// default Unicode mappings.
+// The enum values here are named for well-known exemplar languages that
+// exhibit the behavior in question; multiple lang tags may map to the
+// same setting here, if the behavior is shared by other languages.
+enum LanguageSpecificCasingBehavior {
+ eLSCB_None, // default non-lang-specific behavior
+ eLSCB_Dutch, // treat "ij" digraph as a unit for capitalization
+ eLSCB_Greek, // strip accent when uppercasing Greek vowels
+ eLSCB_Irish, // keep prefix letters as lowercase when uppercasing Irish
+ eLSCB_Turkish, // preserve dotted/dotless-i distinction in uppercase
+ eLSCB_Lithuanian // retain dot on lowercase i/j when an accent is present
+};
+
+static LanguageSpecificCasingBehavior GetCasingFor(const nsAtom* aLang) {
+ if (!aLang) {
+ return eLSCB_None;
+ }
+ if (aLang == nsGkAtoms::tr || aLang == nsGkAtoms::az ||
+ aLang == nsGkAtoms::ba || aLang == nsGkAtoms::crh ||
+ aLang == nsGkAtoms::tt) {
+ return eLSCB_Turkish;
+ }
+ if (aLang == nsGkAtoms::nl) {
+ return eLSCB_Dutch;
+ }
+ if (aLang == nsGkAtoms::el) {
+ return eLSCB_Greek;
+ }
+ if (aLang == nsGkAtoms::ga) {
+ return eLSCB_Irish;
+ }
+ if (aLang == nsGkAtoms::lt_) {
+ return eLSCB_Lithuanian;
+ }
+
+ // Is there a region subtag we should ignore?
+ nsAtomString langStr(const_cast<nsAtom*>(aLang));
+ int index = langStr.FindChar('-');
+ if (index > 0) {
+ langStr.Truncate(index);
+ RefPtr<nsAtom> truncatedLang = NS_Atomize(langStr);
+ return GetCasingFor(truncatedLang);
+ }
+
+ return eLSCB_None;
+}
+
+bool nsCaseTransformTextRunFactory::TransformString(
+ const nsAString& aString, nsString& aConvertedString,
+ const Maybe<StyleTextTransform>& aGlobalTransform, char16_t aMaskChar,
+ bool aCaseTransformsOnly, const nsAtom* aLanguage,
+ nsTArray<bool>& aCharsToMergeArray, nsTArray<bool>& aDeletedCharsArray,
+ const nsTransformedTextRun* aTextRun, uint32_t aOffsetInTextRun,
+ nsTArray<uint8_t>* aCanBreakBeforeArray,
+ nsTArray<RefPtr<nsTransformedCharStyle>>* aStyleArray) {
+ bool auxiliaryOutputArrays = aCanBreakBeforeArray && aStyleArray;
+ MOZ_ASSERT(!auxiliaryOutputArrays || aTextRun,
+ "text run must be provided to use aux output arrays");
+
+ uint32_t length = aString.Length();
+ const char16_t* str = aString.BeginReading();
+ // If an unconditional mask character was passed, we'll use it; if not, any
+ // masking called for by the textrun styles will use TextEditor's mask char.
+ const char16_t mask = aMaskChar ? aMaskChar : TextEditor::PasswordMask();
+
+ bool mergeNeeded = false;
+
+ bool capitalizeDutchIJ = false;
+ bool prevIsLetter = false;
+ bool ntPrefix = false; // true immediately after a word-initial 'n' or 't'
+ // when doing Irish lowercasing
+ bool seenSoftDotted = false; // true immediately after an I or J that is
+ // converted to lowercase in Lithuanian mode
+ uint32_t sigmaIndex = uint32_t(-1);
+ nsUGenCategory cat;
+
+ StyleTextTransform style =
+ aGlobalTransform.valueOr(StyleTextTransform::None());
+ bool forceNonFullWidth = false;
+ const nsAtom* lang = aLanguage;
+
+ LanguageSpecificCasingBehavior languageSpecificCasing = GetCasingFor(lang);
+ mozilla::GreekCasing::State greekState;
+ mozilla::IrishCasing::State irishState;
+ uint32_t irishMark = uint32_t(-1); // location of possible prefix letter(s)
+ // in the output string
+ uint32_t irishMarkSrc = uint32_t(-1); // corresponding location in source
+ // string (may differ from output due
+ // to expansions like eszet -> 'SS')
+ uint32_t greekMark = uint32_t(-1); // location of uppercase ETA that may need
+ // tonos added (if it is disjunctive eta)
+ const char16_t kGreekUpperEta = 0x0397;
+
+ for (uint32_t i = 0; i < length; ++i, ++aOffsetInTextRun) {
+ uint32_t ch = str[i];
+
+ RefPtr<nsTransformedCharStyle> charStyle;
+ if (aTextRun) {
+ charStyle = aTextRun->mStyles[aOffsetInTextRun];
+ style = aGlobalTransform.valueOr(charStyle->mTextTransform);
+ forceNonFullWidth = charStyle->mForceNonFullWidth;
+
+ nsAtom* newLang =
+ charStyle->mExplicitLanguage ? charStyle->mLanguage.get() : nullptr;
+ if (lang != newLang) {
+ lang = newLang;
+ languageSpecificCasing = GetCasingFor(lang);
+ greekState.Reset();
+ irishState.Reset();
+ irishMark = uint32_t(-1);
+ irishMarkSrc = uint32_t(-1);
+ greekMark = uint32_t(-1);
+ }
+ }
+
+ // These should be mutually exclusive: mMaskPassword is set if we are
+ // handling <input type=password>, where the TextEditor code controls
+ // masking and we use its PasswordMask() character, in which case
+ // aMaskChar (from -webkit-text-security) is not used.
+ MOZ_ASSERT_IF(aMaskChar, !(charStyle && charStyle->mMaskPassword));
+
+ bool maskPassword = (charStyle && charStyle->mMaskPassword) || aMaskChar;
+ int extraChars = 0;
+ const mozilla::unicode::MultiCharMapping* mcm;
+ bool inhibitBreakBefore = false; // have we just deleted preceding hyphen?
+
+ if (i < length - 1 && NS_IS_SURROGATE_PAIR(ch, str[i + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
+ }
+ const uint32_t originalCh = ch;
+
+ // Skip case transform if we're masking current character.
+ if (!maskPassword) {
+ switch (style.case_) {
+ case StyleTextTransformCase::None:
+ break;
+
+ case StyleTextTransformCase::Lowercase:
+ if (languageSpecificCasing == eLSCB_Turkish) {
+ if (ch == 'I') {
+ ch = LATIN_SMALL_LETTER_DOTLESS_I;
+ prevIsLetter = true;
+ sigmaIndex = uint32_t(-1);
+ break;
+ }
+ if (ch == LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) {
+ ch = 'i';
+ prevIsLetter = true;
+ sigmaIndex = uint32_t(-1);
+ break;
+ }
+ }
+
+ if (languageSpecificCasing == eLSCB_Lithuanian) {
+ // clang-format off
+ /* From SpecialCasing.txt:
+ * # Introduce an explicit dot above when lowercasing capital I's and J's
+ * # whenever there are more accents above.
+ * # (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek)
+ *
+ * 0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I
+ * 004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J
+ * 012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK
+ * 00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE
+ * 00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE
+ * 0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE
+ */
+ // clang-format on
+ if (ch == 'I' || ch == 'J' || ch == 0x012E) {
+ ch = ToLowerCase(ch);
+ prevIsLetter = true;
+ seenSoftDotted = true;
+ sigmaIndex = uint32_t(-1);
+ break;
+ }
+ if (ch == 0x00CC) {
+ aConvertedString.Append('i');
+ aConvertedString.Append(0x0307);
+ extraChars += 2;
+ ch = 0x0300;
+ prevIsLetter = true;
+ seenSoftDotted = false;
+ sigmaIndex = uint32_t(-1);
+ break;
+ }
+ if (ch == 0x00CD) {
+ aConvertedString.Append('i');
+ aConvertedString.Append(0x0307);
+ extraChars += 2;
+ ch = 0x0301;
+ prevIsLetter = true;
+ seenSoftDotted = false;
+ sigmaIndex = uint32_t(-1);
+ break;
+ }
+ if (ch == 0x0128) {
+ aConvertedString.Append('i');
+ aConvertedString.Append(0x0307);
+ extraChars += 2;
+ ch = 0x0303;
+ prevIsLetter = true;
+ seenSoftDotted = false;
+ sigmaIndex = uint32_t(-1);
+ break;
+ }
+ }
+
+ cat = mozilla::unicode::GetGenCategory(ch);
+
+ if (languageSpecificCasing == eLSCB_Irish &&
+ cat == nsUGenCategory::kLetter) {
+ // See bug 1018805 for Irish lowercasing requirements
+ if (!prevIsLetter && (ch == 'n' || ch == 't')) {
+ ntPrefix = true;
+ } else {
+ if (ntPrefix && mozilla::IrishCasing::IsUpperVowel(ch)) {
+ aConvertedString.Append('-');
+ ++extraChars;
+ }
+ ntPrefix = false;
+ }
+ } else {
+ ntPrefix = false;
+ }
+
+ if (seenSoftDotted && cat == nsUGenCategory::kMark) {
+ // The seenSoftDotted flag will only be set in Lithuanian mode.
+ if (ch == 0x0300 || ch == 0x0301 || ch == 0x0303) {
+ aConvertedString.Append(0x0307);
+ ++extraChars;
+ }
+ }
+ seenSoftDotted = false;
+
+ // Special lowercasing behavior for Greek Sigma: note that this is
+ // listed as context-sensitive in Unicode's SpecialCasing.txt, but is
+ // *not* a language-specific mapping; it applies regardless of the
+ // language of the element.
+ //
+ // The lowercase mapping for CAPITAL SIGMA should be to SMALL SIGMA
+ // (i.e. the non-final form) whenever there is a following letter, or
+ // when the CAPITAL SIGMA occurs in isolation (neither preceded nor
+ // followed by a LETTER); and to FINAL SIGMA when it is preceded by
+ // another letter but not followed by one.
+ //
+ // To implement the context-sensitive nature of this mapping, we keep
+ // track of whether the previous character was a letter. If not,
+ // CAPITAL SIGMA will map directly to SMALL SIGMA. If the previous
+ // character was a letter, CAPITAL SIGMA maps to FINAL SIGMA and we
+ // record the position in the converted string; if we then encounter
+ // another letter, that FINAL SIGMA is replaced with a standard
+ // SMALL SIGMA.
+
+ // If sigmaIndex is not -1, it marks where we have provisionally
+ // mapped a CAPITAL SIGMA to FINAL SIGMA; if we now find another
+ // letter, we need to change it to SMALL SIGMA.
+ if (sigmaIndex != uint32_t(-1)) {
+ if (cat == nsUGenCategory::kLetter) {
+ aConvertedString.SetCharAt(GREEK_SMALL_LETTER_SIGMA, sigmaIndex);
+ }
+ }
+
+ if (ch == GREEK_CAPITAL_LETTER_SIGMA) {
+ // If preceding char was a letter, map to FINAL instead of SMALL,
+ // and note where it occurred by setting sigmaIndex; we'll change
+ // it to standard SMALL SIGMA later if another letter follows
+ if (prevIsLetter) {
+ ch = GREEK_SMALL_LETTER_FINAL_SIGMA;
+ sigmaIndex = aConvertedString.Length();
+ } else {
+ // CAPITAL SIGMA not preceded by a letter is unconditionally
+ // mapped to SMALL SIGMA
+ ch = GREEK_SMALL_LETTER_SIGMA;
+ sigmaIndex = uint32_t(-1);
+ }
+ prevIsLetter = true;
+ break;
+ }
+
+ // ignore diacritics for the purpose of contextual sigma mapping;
+ // otherwise, reset prevIsLetter appropriately and clear the
+ // sigmaIndex marker
+ if (cat != nsUGenCategory::kMark) {
+ prevIsLetter = (cat == nsUGenCategory::kLetter);
+ sigmaIndex = uint32_t(-1);
+ }
+
+ mcm = mozilla::unicode::SpecialLower(ch);
+ if (mcm) {
+ int j = 0;
+ while (j < 2 && mcm->mMappedChars[j + 1]) {
+ aConvertedString.Append(mcm->mMappedChars[j]);
+ ++extraChars;
+ ++j;
+ }
+ ch = mcm->mMappedChars[j];
+ break;
+ }
+
+ ch = ToLowerCase(ch);
+ break;
+
+ case StyleTextTransformCase::Uppercase:
+ if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
+ ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
+ break;
+ }
+
+ if (languageSpecificCasing == eLSCB_Greek) {
+ bool markEta;
+ bool updateEta;
+ ch = mozilla::GreekCasing::UpperCase(ch, greekState, markEta,
+ updateEta);
+ if (markEta) {
+ greekMark = aConvertedString.Length();
+ } else if (updateEta) {
+ // Remove the TONOS from an uppercase ETA-TONOS that turned out
+ // not to be disjunctive-eta.
+ MOZ_ASSERT(aConvertedString.Length() > 0 &&
+ greekMark < aConvertedString.Length(),
+ "bad greekMark!");
+ aConvertedString.SetCharAt(kGreekUpperEta, greekMark);
+ greekMark = uint32_t(-1);
+ }
+ break;
+ }
+
+ if (languageSpecificCasing == eLSCB_Lithuanian) {
+ /*
+ * # Remove DOT ABOVE after "i" with upper or titlecase
+ *
+ * 0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE
+ */
+ if (ch == 'i' || ch == 'j' || ch == 0x012F) {
+ seenSoftDotted = true;
+ ch = ToTitleCase(ch);
+ break;
+ }
+ if (seenSoftDotted) {
+ seenSoftDotted = false;
+ if (ch == 0x0307) {
+ ch = uint32_t(-1);
+ break;
+ }
+ }
+ }
+
+ if (languageSpecificCasing == eLSCB_Irish) {
+ bool mark;
+ uint8_t action;
+ ch = mozilla::IrishCasing::UpperCase(ch, irishState, mark, action);
+ if (mark) {
+ irishMark = aConvertedString.Length();
+ irishMarkSrc = i;
+ break;
+ } else if (action) {
+ nsString& str = aConvertedString; // shorthand
+ switch (action) {
+ case 1:
+ // lowercase a single prefix letter
+ MOZ_ASSERT(str.Length() > 0 && irishMark < str.Length(),
+ "bad irishMark!");
+ str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
+ irishMark = uint32_t(-1);
+ irishMarkSrc = uint32_t(-1);
+ break;
+ case 2:
+ // lowercase two prefix letters (immediately before current
+ // pos)
+ MOZ_ASSERT(str.Length() >= 2 && irishMark == str.Length() - 2,
+ "bad irishMark!");
+ str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
+ str.SetCharAt(ToLowerCase(str[irishMark + 1]), irishMark + 1);
+ irishMark = uint32_t(-1);
+ irishMarkSrc = uint32_t(-1);
+ break;
+ case 3:
+ // lowercase one prefix letter, and delete following hyphen
+ // (which must be the immediately-preceding char)
+ MOZ_ASSERT(str.Length() >= 2 && irishMark == str.Length() - 2,
+ "bad irishMark!");
+ MOZ_ASSERT(
+ irishMark != uint32_t(-1) && irishMarkSrc != uint32_t(-1),
+ "failed to set irishMarks");
+ str.Replace(irishMark, 2, ToLowerCase(str[irishMark]));
+ aDeletedCharsArray[irishMarkSrc + 1] = true;
+ // Remove the trailing entries (corresponding to the deleted
+ // hyphen) from the auxiliary arrays.
+ uint32_t len = aCharsToMergeArray.Length();
+ MOZ_ASSERT(len >= 2);
+ aCharsToMergeArray.TruncateLength(len - 1);
+ if (auxiliaryOutputArrays) {
+ MOZ_ASSERT(aStyleArray->Length() == len);
+ MOZ_ASSERT(aCanBreakBeforeArray->Length() == len);
+ aStyleArray->TruncateLength(len - 1);
+ aCanBreakBeforeArray->TruncateLength(len - 1);
+ inhibitBreakBefore = true;
+ }
+ mergeNeeded = true;
+ irishMark = uint32_t(-1);
+ irishMarkSrc = uint32_t(-1);
+ break;
+ }
+ // ch has been set to the uppercase for current char;
+ // No need to check for SpecialUpper here as none of the
+ // characters that could trigger an Irish casing action have
+ // special mappings.
+ break;
+ }
+ // If we didn't have any special action to perform, fall through
+ // to check for special uppercase (ß)
+ }
+
+ // Updated mapping for German eszett, not currently reflected in the
+ // Unicode data files. This is behind a pref, as it may not work well
+ // with many (esp. older) fonts.
+ if (ch == 0x00DF &&
+ StaticPrefs::
+ layout_css_text_transform_uppercase_eszett_enabled()) {
+ ch = 0x1E9E;
+ break;
+ }
+
+ mcm = mozilla::unicode::SpecialUpper(ch);
+ if (mcm) {
+ int j = 0;
+ while (j < 2 && mcm->mMappedChars[j + 1]) {
+ aConvertedString.Append(mcm->mMappedChars[j]);
+ ++extraChars;
+ ++j;
+ }
+ ch = mcm->mMappedChars[j];
+ break;
+ }
+
+ // Bug 1476304: we exclude Georgian letters U+10D0..10FF because of
+ // lack of widespread font support for the corresponding Mtavruli
+ // characters at this time (July 2018).
+ // This condition is to be removed once the major platforms ship with
+ // fonts that support U+1C90..1CBF.
+ if (ch < 0x10D0 || ch > 0x10FF) {
+ ch = ToUpperCase(ch);
+ }
+ break;
+
+ case StyleTextTransformCase::Capitalize:
+ if (aTextRun) {
+ if (capitalizeDutchIJ && ch == 'j') {
+ ch = 'J';
+ capitalizeDutchIJ = false;
+ break;
+ }
+ capitalizeDutchIJ = false;
+ if (aOffsetInTextRun < aTextRun->mCapitalize.Length() &&
+ aTextRun->mCapitalize[aOffsetInTextRun]) {
+ if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
+ ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
+ break;
+ }
+ if (languageSpecificCasing == eLSCB_Dutch && ch == 'i') {
+ ch = 'I';
+ capitalizeDutchIJ = true;
+ break;
+ }
+ if (languageSpecificCasing == eLSCB_Lithuanian) {
+ /*
+ * # Remove DOT ABOVE after "i" with upper or titlecase
+ *
+ * 0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE
+ */
+ if (ch == 'i' || ch == 'j' || ch == 0x012F) {
+ seenSoftDotted = true;
+ ch = ToTitleCase(ch);
+ break;
+ }
+ if (seenSoftDotted) {
+ seenSoftDotted = false;
+ if (ch == 0x0307) {
+ ch = uint32_t(-1);
+ break;
+ }
+ }
+ }
+
+ mcm = mozilla::unicode::SpecialTitle(ch);
+ if (mcm) {
+ int j = 0;
+ while (j < 2 && mcm->mMappedChars[j + 1]) {
+ aConvertedString.Append(mcm->mMappedChars[j]);
+ ++extraChars;
+ ++j;
+ }
+ ch = mcm->mMappedChars[j];
+ break;
+ }
+
+ ch = ToTitleCase(ch);
+ }
+ }
+ break;
+
+ case StyleTextTransformCase::MathAuto:
+ // text-transform: math-auto is used for automatic italicization of
+ // single-char <mi> elements. However, some legacy cases (italic style
+ // fallback and <mi> with leading/trailing whitespace) are still
+ // handled in MathMLTextRunFactory.
+ if (length == 1) {
+ uint32_t ch2 =
+ MathMLTextRunFactory::MathVariant(ch, StyleMathVariant::Italic);
+ if (StaticPrefs::mathml_mathvariant_styling_fallback_disabled()) {
+ ch = ch2;
+ } else if (ch2 != ch) {
+ // Bug 930504. Some platforms do not have fonts for Mathematical
+ // Alphanumeric Symbols. Hence we only perform the transform if a
+ // character is actually available.
+ FontMatchType matchType;
+ RefPtr<gfxFont> mathFont =
+ aTextRun->GetFontGroup()->FindFontForChar(
+ ch2, 0, 0, intl::Script::COMMON, nullptr, &matchType);
+ if (mathFont) {
+ ch = ch2;
+ }
+ }
+ }
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("all cases should be handled");
+ break;
+ }
+
+ if (!aCaseTransformsOnly) {
+ if (!forceNonFullWidth &&
+ (style.other_ & StyleTextTransformOther::FULL_WIDTH)) {
+ ch = mozilla::unicode::GetFullWidth(ch);
+ }
+
+ if (style.other_ & StyleTextTransformOther::FULL_SIZE_KANA) {
+ // clang-format off
+ static const uint32_t kSmallKanas[] = {
+ // ã ãƒ ã… ã‡ ã‰ ã£ ã‚ƒ ã‚… ょ
+ 0x3041, 0x3043, 0x3045, 0x3047, 0x3049, 0x3063, 0x3083, 0x3085, 0x3087,
+ // ã‚Ž ã‚• ã‚–
+ 0x308E, 0x3095, 0x3096,
+ // ァ ィ ゥ ェ ォ ッ ャ ュ ョ
+ 0x30A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30C3, 0x30E3, 0x30E5, 0x30E7,
+ // ヮ ヵ ヶ ㇰ ㇱ ㇲ ㇳ ㇴ ㇵ
+ 0x30EE, 0x30F5, 0x30F6, 0x31F0, 0x31F1, 0x31F2, 0x31F3, 0x31F4, 0x31F5,
+ // ㇶ ㇷ ㇸ ㇹ ㇺ ㇻ ㇼ ㇽ ㇾ
+ 0x31F6, 0x31F7, 0x31F8, 0x31F9, 0x31FA, 0x31FB, 0x31FC, 0x31FD, 0x31FE,
+ // ㇿ
+ 0x31FF,
+ // ァ ィ ゥ ェ ォ ャ ュ ョ ッ
+ 0xFF67, 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F,
+ // 𛄲 𛅠𛅑 𛅒 𛅕 𛅤 𛅥 𛅦
+ 0x1B132, 0x1B150, 0x1B151, 0x1B152, 0x1B155, 0x1B164, 0x1B165, 0x1B166,
+ // ð›…§
+ 0x1B167};
+ static const uint16_t kFullSizeKanas[] = {
+ // ゠ㄠㆠ㈠㊠㤠や ゆ よ
+ 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x3064, 0x3084, 0x3086, 0x3088,
+ // ã‚ ã‹ ã‘
+ 0x308F, 0x304B, 0x3051,
+ // ア イ ウ エ オ ツ ヤ ユ ヨ
+ 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30C4, 0x30E4, 0x30E6, 0x30E8,
+ // ワ ã‚« ケ ク ã‚· ス ト ヌ ãƒ
+ 0x30EF, 0x30AB, 0x30B1, 0x30AF, 0x30B7, 0x30B9, 0x30C8, 0x30CC, 0x30CF,
+ // ヒ フ ヘ ホ ム ラ リ ル レ
+ 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30E0, 0x30E9, 0x30EA, 0x30EB, 0x30EC,
+ // ロ
+ 0x30ED,
+ // ア イ ウ エ オ ヤ ユ ヨ ツ
+ 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF94, 0xFF95, 0xFF96, 0xFF82,
+ // 㓠゠ゑ を コ ヰ ヱ ヲ ン
+ 0x3053, 0x3090, 0x3091, 0x3092, 0x30B3, 0x30F0, 0x30F1, 0x30F2, 0x30F3};
+ // clang-format on
+
+ size_t index;
+ const uint16_t len = MOZ_ARRAY_LENGTH(kSmallKanas);
+ if (mozilla::BinarySearch(kSmallKanas, 0, len, ch, &index)) {
+ ch = kFullSizeKanas[index];
+ }
+ }
+ }
+
+ if (forceNonFullWidth) {
+ ch = mozilla::unicode::GetFullWidthInverse(ch);
+ }
+ }
+
+ if (ch == uint32_t(-1)) {
+ aDeletedCharsArray.AppendElement(true);
+ mergeNeeded = true;
+ } else {
+ aDeletedCharsArray.AppendElement(false);
+ aCharsToMergeArray.AppendElement(false);
+ if (auxiliaryOutputArrays) {
+ aStyleArray->AppendElement(charStyle);
+ aCanBreakBeforeArray->AppendElement(
+ inhibitBreakBefore
+ ? gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_NONE
+ : aTextRun->CanBreakBefore(aOffsetInTextRun));
+ }
+
+ if (IS_IN_BMP(ch)) {
+ aConvertedString.Append(maskPassword ? mask : ch);
+ } else {
+ if (maskPassword) {
+ aConvertedString.Append(mask);
+ // TODO: We should show a password mask for a surrogate pair later.
+ aConvertedString.Append(mask);
+ } else {
+ aConvertedString.Append(H_SURROGATE(ch));
+ aConvertedString.Append(L_SURROGATE(ch));
+ }
+ ++extraChars;
+ }
+ if (!IS_IN_BMP(originalCh)) {
+ // Skip the trailing surrogate.
+ ++aOffsetInTextRun;
+ ++i;
+ aDeletedCharsArray.AppendElement(true);
+ }
+
+ while (extraChars-- > 0) {
+ mergeNeeded = true;
+ aCharsToMergeArray.AppendElement(true);
+ if (auxiliaryOutputArrays) {
+ aStyleArray->AppendElement(charStyle);
+ aCanBreakBeforeArray->AppendElement(
+ gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_NONE);
+ }
+ }
+ }
+ }
+
+ // These output arrays, if present, must always have matching lengths:
+ if (auxiliaryOutputArrays) {
+ DebugOnly<uint32_t> len = aCharsToMergeArray.Length();
+ MOZ_ASSERT(aStyleArray->Length() == len);
+ MOZ_ASSERT(aCanBreakBeforeArray->Length() == len);
+ }
+
+ return mergeNeeded;
+}
+
+void nsCaseTransformTextRunFactory::RebuildTextRun(
+ nsTransformedTextRun* aTextRun, DrawTarget* aRefDrawTarget,
+ gfxMissingFontRecorder* aMFR) {
+ nsAutoString convertedString;
+ AutoTArray<bool, 50> charsToMergeArray;
+ AutoTArray<bool, 50> deletedCharsArray;
+ AutoTArray<uint8_t, 50> canBreakBeforeArray;
+ AutoTArray<RefPtr<nsTransformedCharStyle>, 50> styleArray;
+
+ auto globalTransform =
+ mAllUppercase
+ ? Some(StyleTextTransform{StyleTextTransformCase::Uppercase, {}})
+ : Nothing();
+ bool mergeNeeded = TransformString(
+ aTextRun->mString, convertedString, globalTransform, mMaskChar,
+ /* aCaseTransformsOnly = */ false, nullptr, charsToMergeArray,
+ deletedCharsArray, aTextRun, 0, &canBreakBeforeArray, &styleArray);
+
+ gfx::ShapedTextFlags flags;
+ gfxTextRunFactory::Parameters innerParams =
+ GetParametersForInner(aTextRun, &flags, aRefDrawTarget);
+ gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
+
+ RefPtr<nsTransformedTextRun> transformedChild;
+ RefPtr<gfxTextRun> cachedChild;
+ gfxTextRun* child;
+
+ if (mInnerTransformingTextRunFactory) {
+ transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
+ convertedString.BeginReading(), convertedString.Length(), &innerParams,
+ fontGroup, flags, nsTextFrameUtils::Flags(), std::move(styleArray),
+ false);
+ child = transformedChild.get();
+ } else {
+ cachedChild = fontGroup->MakeTextRun(
+ convertedString.BeginReading(), convertedString.Length(), &innerParams,
+ flags, nsTextFrameUtils::Flags(), aMFR);
+ child = cachedChild.get();
+ }
+ if (!child) {
+ return;
+ }
+ // Copy potential linebreaks into child so they're preserved
+ // (and also child will be shaped appropriately)
+ NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
+ "Dropped characters or break-before values somewhere!");
+ gfxTextRun::Range range(0, uint32_t(canBreakBeforeArray.Length()));
+ child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
+ if (transformedChild) {
+ transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
+ }
+
+ aTextRun->ResetGlyphRuns();
+ if (mergeNeeded) {
+ // Now merge multiple characters into one multi-glyph character as required
+ // and deal with skipping deleted accent chars
+ NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
+ "source length mismatch");
+ NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
+ "destination length mismatch");
+ MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
+ deletedCharsArray.Elements());
+ } else {
+ // No merging to do, so just copy; this produces a more optimized textrun.
+ // We can't steal the data because the child may be cached and stealing
+ // the data would break the cache.
+ aTextRun->CopyGlyphDataFrom(child, gfxTextRun::Range(child), 0);
+ }
+}
diff --git a/layout/generic/nsTextRunTransformations.h b/layout/generic/nsTextRunTransformations.h
new file mode 100644
index 0000000000..915211c6af
--- /dev/null
+++ b/layout/generic/nsTextRunTransformations.h
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSTEXTRUNTRANSFORMATIONS_H_
+#define NSTEXTRUNTRANSFORMATIONS_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+#include "gfxTextRun.h"
+#include "mozilla/ComputedStyle.h"
+#include "nsPresContext.h"
+#include "nsStyleStruct.h"
+
+class nsTransformedTextRun;
+
+struct nsTransformedCharStyle final {
+ NS_INLINE_DECL_REFCOUNTING(nsTransformedCharStyle)
+
+ explicit nsTransformedCharStyle(mozilla::ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : mFont(aStyle->StyleFont()->mFont),
+ mLanguage(aStyle->StyleFont()->mLanguage),
+ mPresContext(aPresContext),
+ mTextTransform(aStyle->StyleText()->mTextTransform),
+ mMathVariant(aStyle->StyleFont()->mMathVariant),
+ mExplicitLanguage(aStyle->StyleFont()->mExplicitLanguage) {}
+
+ nsFont mFont;
+ RefPtr<nsAtom> mLanguage;
+ RefPtr<nsPresContext> mPresContext;
+ mozilla::StyleTextTransform mTextTransform;
+ mozilla::StyleMathVariant mMathVariant;
+ bool mExplicitLanguage;
+ bool mForceNonFullWidth = false;
+ bool mMaskPassword = false;
+
+ private:
+ ~nsTransformedCharStyle() = default;
+ nsTransformedCharStyle(const nsTransformedCharStyle& aOther) = delete;
+ nsTransformedCharStyle& operator=(const nsTransformedCharStyle& aOther) =
+ delete;
+};
+
+class nsTransformingTextRunFactory {
+ public:
+ virtual ~nsTransformingTextRunFactory() = default;
+
+ // Default 8-bit path just transforms to Unicode and takes that path
+ already_AddRefed<nsTransformedTextRun> MakeTextRun(
+ const uint8_t* aString, uint32_t aLength,
+ const gfxFontGroup::Parameters* aParams, gfxFontGroup* aFontGroup,
+ mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
+ nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles, bool aOwnsFactory);
+
+ already_AddRefed<nsTransformedTextRun> MakeTextRun(
+ const char16_t* aString, uint32_t aLength,
+ const gfxFontGroup::Parameters* aParams, gfxFontGroup* aFontGroup,
+ mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
+ nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles, bool aOwnsFactory);
+
+ virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
+ mozilla::gfx::DrawTarget* aRefDrawTarget,
+ gfxMissingFontRecorder* aMFR) = 0;
+};
+
+/**
+ * Builds textruns that transform the text in some way (e.g., capitalize)
+ * and then render the text using some other textrun implementation.
+ * This factory also supports "text-security" transforms that convert all
+ * characters to a single symbol.
+ */
+class nsCaseTransformTextRunFactory : public nsTransformingTextRunFactory {
+ public:
+ // We could add an optimization here so that when there is no inner
+ // factory, no title-case conversion, and no upper-casing of SZLIG, we
+ // override MakeTextRun (after making it virtual in the superclass) and have
+ // it just convert the string to uppercase or lowercase and create the textrun
+ // via the fontgroup.
+
+ // Takes ownership of aInnerTransformTextRunFactory
+ nsCaseTransformTextRunFactory(mozilla::UniquePtr<nsTransformingTextRunFactory>
+ aInnerTransformingTextRunFactory,
+ bool aAllUppercase, char16_t aMaskChar)
+ : mInnerTransformingTextRunFactory(
+ std::move(aInnerTransformingTextRunFactory)),
+ mAllUppercase(aAllUppercase),
+ mMaskChar(aMaskChar) {}
+
+ virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
+ mozilla::gfx::DrawTarget* aRefDrawTarget,
+ gfxMissingFontRecorder* aMFR) override;
+
+ // Perform a transformation on the given string, writing the result into
+ // aConvertedString. If aGlobalTransform is passed, the transform is global
+ // and aLanguage is used to determine any language-specific behavior;
+ // otherwise, an nsTransformedTextRun should be passed in as aTextRun and its
+ // styles will be used to determine the transform(s) to be applied.
+ // If such an input textrun is provided, then its line-breaks and styles
+ // will be copied to the output arrays, which must also be provided by
+ // the caller. For the global transform usage (no input textrun), these are
+ // ignored.
+ // If aMaskChar is non-zero, it is used as a "masking" character to replace
+ // all characters in the text (for -webkit-text-security).
+ // If aCaseTransformsOnly is true, then only the upper/lower/capitalize
+ // transformations are performed; full-width and full-size-kana are ignored.
+ // If `aTextRun` is not nullptr and characters which are styled with setting
+ // `nsTransformedCharStyle::mMaskPassword` to true, they are replaced with
+ // password mask characters and are not transformed (i.e., won't be added
+ // or merged for the specified transform). However, unmasked characters
+ // whose `nsTransformedCharStyle::mMaskPassword` is set to false are
+ // transformed normally.
+ // Note that "masking" behavior may be triggered either by
+ // -webkit-text-security, resulting in a non-zero aMaskChar being passed,
+ // or by <input type=password>, which results in the editor code setting
+ // nsTransformedCharStyle::mMaskPassword. (The latter mechanism enables the
+ // editor to temporarily reveal the just-entered character during typing,
+ // whereas simply setting aMaskChar would unconditionally mask all the text.)
+ static bool TransformString(
+ const nsAString& aString, nsString& aConvertedString,
+ const mozilla::Maybe<mozilla::StyleTextTransform>& aGlobalTransform,
+ char16_t aMaskChar, bool aCaseTransformsOnly, const nsAtom* aLanguage,
+ nsTArray<bool>& aCharsToMergeArray, nsTArray<bool>& aDeletedCharsArray,
+ const nsTransformedTextRun* aTextRun = nullptr,
+ uint32_t aOffsetInTextRun = 0,
+ nsTArray<uint8_t>* aCanBreakBeforeArray = nullptr,
+ nsTArray<RefPtr<nsTransformedCharStyle>>* aStyleArray = nullptr);
+
+ protected:
+ mozilla::UniquePtr<nsTransformingTextRunFactory>
+ mInnerTransformingTextRunFactory;
+ bool mAllUppercase;
+ char16_t mMaskChar;
+};
+
+/**
+ * So that we can reshape as necessary, we store enough information
+ * to fully rebuild the textrun contents.
+ */
+class nsTransformedTextRun final : public gfxTextRun {
+ public:
+ static already_AddRefed<nsTransformedTextRun> Create(
+ const gfxTextRunFactory::Parameters* aParams,
+ nsTransformingTextRunFactory* aFactory, gfxFontGroup* aFontGroup,
+ const char16_t* aString, uint32_t aLength,
+ const mozilla::gfx::ShapedTextFlags aFlags,
+ const nsTextFrameUtils::Flags aFlags2,
+ nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles, bool aOwnsFactory);
+
+ ~nsTransformedTextRun() {
+ if (mOwnsFactory) {
+ delete mFactory;
+ }
+ }
+
+ void SetCapitalization(uint32_t aStart, uint32_t aLength,
+ bool* aCapitalization);
+ virtual bool SetPotentialLineBreaks(Range aRange,
+ const uint8_t* aBreakBefore) override;
+ /**
+ * Called after SetCapitalization and SetPotentialLineBreaks
+ * are done and before we request any data from the textrun. Also always
+ * called after a Create.
+ */
+ void FinishSettingProperties(mozilla::gfx::DrawTarget* aRefDrawTarget,
+ gfxMissingFontRecorder* aMFR) {
+ if (mNeedsRebuild) {
+ mNeedsRebuild = false;
+ mFactory->RebuildTextRun(this, aRefDrawTarget, aMFR);
+ }
+ }
+
+ // override the gfxTextRun impls to account for additional members here
+ virtual size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) override;
+ virtual size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) override;
+
+ nsTransformingTextRunFactory* mFactory;
+ nsTArray<RefPtr<nsTransformedCharStyle>> mStyles;
+ nsTArray<bool> mCapitalize;
+ nsString mString;
+ bool mOwnsFactory;
+ bool mNeedsRebuild;
+
+ private:
+ nsTransformedTextRun(const gfxTextRunFactory::Parameters* aParams,
+ nsTransformingTextRunFactory* aFactory,
+ gfxFontGroup* aFontGroup, const char16_t* aString,
+ uint32_t aLength,
+ const mozilla::gfx::ShapedTextFlags aFlags,
+ const nsTextFrameUtils::Flags aFlags2,
+ nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles,
+ bool aOwnsFactory)
+ : gfxTextRun(aParams, aLength, aFontGroup, aFlags, aFlags2),
+ mFactory(aFactory),
+ mStyles(std::move(aStyles)),
+ mString(aString, aLength),
+ mOwnsFactory(aOwnsFactory),
+ mNeedsRebuild(true) {
+ mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1);
+ }
+};
+
+/**
+ * Copy a given textrun, but merge certain characters into a single logical
+ * character. Glyphs for a character are added to the glyph list for the
+ * previous character and then the merged character is eliminated. Visually the
+ * results are identical.
+ *
+ * This is used for text-transform:uppercase when we encounter a SZLIG,
+ * whose uppercase form is "SS", or other ligature or precomposed form
+ * that expands to multiple codepoints during case transformation,
+ * and for Greek text when combining diacritics have been deleted.
+ *
+ * This function is unable to merge characters when they occur in different
+ * glyph runs. This only happens in tricky edge cases where a character was
+ * decomposed by case-mapping (e.g. there's no precomposed uppercase version
+ * of an accented lowercase letter), and then font-matching caused the
+ * diacritics to be assigned to a different font than the base character.
+ * In this situation, the diacritic(s) get discarded, which is less than
+ * ideal, but they probably weren't going to render very well anyway.
+ * Bug 543200 will improve this by making font-matching operate on entire
+ * clusters instead of individual codepoints.
+ *
+ * For simplicity, this produces a textrun containing all DetailedGlyphs,
+ * no simple glyphs. So don't call it unless you really have merging to do.
+ *
+ * @param aCharsToMerge when aCharsToMerge[i] is true, this character in aSrc
+ * is merged into the previous character
+ *
+ * @param aDeletedChars when aDeletedChars[i] is true, the character at this
+ * position in aDest was deleted (has no corresponding char in aSrc)
+ */
+void MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
+ const bool* aCharsToMerge,
+ const bool* aDeletedChars);
+
+gfxTextRunFactory::Parameters GetParametersForInner(
+ nsTransformedTextRun* aTextRun, mozilla::gfx::ShapedTextFlags* aFlags,
+ mozilla::gfx::DrawTarget* aRefDrawTarget);
+
+#endif /*NSTEXTRUNTRANSFORMATIONS_H_*/
diff --git a/layout/generic/nsVideoFrame.cpp b/layout/generic/nsVideoFrame.cpp
new file mode 100644
index 0000000000..d5bcc45969
--- /dev/null
+++ b/layout/generic/nsVideoFrame.cpp
@@ -0,0 +1,741 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for the HTML <video> element */
+
+#include "nsVideoFrame.h"
+
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "nsDisplayList.h"
+#include "nsGenericHTMLElement.h"
+#include "nsPresContext.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIContentInlines.h"
+#include "nsImageFrame.h"
+#include "nsIImageLoadingContent.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "ImageContainer.h"
+#include "nsStyleUtil.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsVideoFrame(aStyle, aPresShell->GetPresContext());
+}
+
+nsIFrame* NS_NewHTMLAudioFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsAudioFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)
+NS_QUERYFRAME_HEAD(nsVideoFrame)
+ NS_QUERYFRAME_ENTRY(nsVideoFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(nsAudioFrame)
+NS_QUERYFRAME_HEAD(nsAudioFrame)
+ NS_QUERYFRAME_ENTRY(nsAudioFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsVideoFrame)
+
+// A matrix to obtain a correct-rotated video frame.
+static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth,
+ gfxFloat aRotatedHeight,
+ VideoRotation aDegrees) {
+ Matrix shiftVideoCenterToOrigin;
+ if (aDegrees == VideoRotation::kDegree_90 ||
+ aDegrees == VideoRotation::kDegree_270) {
+ shiftVideoCenterToOrigin =
+ Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0);
+ } else {
+ shiftVideoCenterToOrigin =
+ Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0);
+ }
+
+ auto angle = static_cast<double>(aDegrees) / 180.0 * M_PI;
+ Matrix rotation = Matrix::Rotation(static_cast<gfx::Float>(angle));
+ Matrix shiftLeftTopToOrigin =
+ Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0);
+ return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin;
+}
+
+static void SwapScaleWidthHeightForRotation(IntSize& aSize,
+ VideoRotation aDegrees) {
+ if (aDegrees == VideoRotation::kDegree_90 ||
+ aDegrees == VideoRotation::kDegree_270) {
+ int32_t tmpWidth = aSize.width;
+ aSize.width = aSize.height;
+ aSize.height = tmpWidth;
+ }
+}
+
+nsVideoFrame::nsVideoFrame(ComputedStyle* aStyle, nsPresContext* aPc,
+ ClassID aClassID)
+ : nsContainerFrame(aStyle, aPc, aClassID),
+ mIsAudio(aClassID == nsAudioFrame::kClassID) {
+ EnableVisibilityTracking();
+}
+
+nsVideoFrame::~nsVideoFrame() = default;
+
+nsAudioFrame::nsAudioFrame(ComputedStyle* aStyle, nsPresContext* aPc)
+ : nsVideoFrame(aStyle, aPc, kClassID) {}
+
+nsAudioFrame::~nsAudioFrame() = default;
+
+nsresult nsVideoFrame::CreateAnonymousContent(
+ nsTArray<ContentInfo>& aElements) {
+ nsNodeInfoManager* nodeInfoManager =
+ GetContent()->GetComposedDoc()->NodeInfoManager();
+ RefPtr<NodeInfo> nodeInfo;
+
+ if (HasVideoElement()) {
+ // Create an anonymous image element as a child to hold the poster
+ // image. We may not have a poster image now, but one could be added
+ // before we load, or on a subsequent load.
+ nodeInfo = nodeInfoManager->GetNodeInfo(
+ nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+ mPosterImage = NS_NewHTMLImageElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY);
+ UpdatePosterSource(false);
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aElements.AppendElement(mPosterImage);
+
+ // Set up the caption overlay div for showing any TextTrack data
+ nodeInfo = nodeInfoManager->GetNodeInfo(
+ nsGkAtoms::div, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+ mCaptionDiv = NS_NewHTMLDivElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(mCaptionDiv, NS_ERROR_OUT_OF_MEMORY);
+ nsGenericHTMLElement* div =
+ static_cast<nsGenericHTMLElement*>(mCaptionDiv.get());
+ div->SetClassName(u"caption-box"_ns);
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aElements.AppendElement(mCaptionDiv);
+ UpdateTextTrack();
+ }
+
+ return NS_OK;
+}
+
+void nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFliter) {
+ if (mPosterImage) {
+ aElements.AppendElement(mPosterImage);
+ }
+
+ if (mCaptionDiv) {
+ aElements.AppendElement(mCaptionDiv);
+ }
+}
+
+nsIContent* nsVideoFrame::GetVideoControls() const {
+ if (!mContent->GetShadowRoot()) {
+ return nullptr;
+ }
+
+ // The video controls <div> is the only child of the UA Widget Shadow Root
+ // if it is present. It is only lazily inserted into the DOM when
+ // the controls attribute is set.
+ MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
+ MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount());
+ return mContent->GetShadowRoot()->GetFirstChild();
+}
+
+void nsVideoFrame::Destroy(DestroyContext& aContext) {
+ if (mReflowCallbackPosted) {
+ PresShell()->CancelReflowCallback(this);
+ }
+ aContext.AddAnonymousContent(mCaptionDiv.forget());
+ aContext.AddAnonymousContent(mPosterImage.forget());
+ nsContainerFrame::Destroy(aContext);
+}
+
+class DispatchResizeEvent : public Runnable {
+ public:
+ explicit DispatchResizeEvent(nsIContent* aContent,
+ const nsLiteralString& aName)
+ : Runnable("DispatchResizeEvent"), mContent(aContent), mName(aName) {}
+ NS_IMETHOD Run() override {
+ nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, mName,
+ CanBubble::eNo, Cancelable::eNo);
+ return NS_OK;
+ }
+ nsCOMPtr<nsIContent> mContent;
+ const nsLiteralString mName;
+};
+
+bool nsVideoFrame::ReflowFinished() {
+ mReflowCallbackPosted = false;
+ auto GetSize = [&](nsIContent* aContent) -> Maybe<nsSize> {
+ if (!aContent) {
+ return Nothing();
+ }
+ nsIFrame* f = aContent->GetPrimaryFrame();
+ if (!f) {
+ return Nothing();
+ }
+ return Some(f->GetSize());
+ };
+
+ AutoTArray<nsCOMPtr<nsIRunnable>, 2> events;
+
+ if (auto size = GetSize(mCaptionDiv)) {
+ if (*size != mCaptionTrackedSize) {
+ mCaptionTrackedSize = *size;
+ events.AppendElement(
+ new DispatchResizeEvent(mCaptionDiv, u"resizecaption"_ns));
+ }
+ }
+ nsIContent* controls = GetVideoControls();
+ if (auto size = GetSize(controls)) {
+ if (*size != mControlsTrackedSize) {
+ mControlsTrackedSize = *size;
+ events.AppendElement(
+ new DispatchResizeEvent(controls, u"resizevideocontrols"_ns));
+ }
+ }
+ for (auto& event : events) {
+ nsContentUtils::AddScriptRunner(event.forget());
+ }
+ return false;
+}
+
+void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsVideoFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE(
+ NS_FRAME_TRACE_CALLS,
+ ("enter nsVideoFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableISize(), aReflowInput.AvailableBSize()));
+
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow");
+
+ const WritingMode myWM = aReflowInput.GetWritingMode();
+ nscoord contentBoxBSize = aReflowInput.ComputedBSize();
+ const auto logicalBP = aReflowInput.ComputedLogicalBorderPadding(myWM);
+ const nscoord borderBoxISize =
+ aReflowInput.ComputedISize() + logicalBP.IStartEnd(myWM);
+ const bool isBSizeShrinkWrapping = (contentBoxBSize == NS_UNCONSTRAINEDSIZE);
+
+ nscoord borderBoxBSize;
+ if (!isBSizeShrinkWrapping) {
+ borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
+ }
+
+ nsIContent* videoControlsDiv = GetVideoControls();
+
+ // Reflow the child frames. We may have up to three: an image
+ // frame (for the poster image), a container frame for the controls,
+ // and a container frame for the caption.
+ for (nsIFrame* child : mFrames) {
+ nsSize oldChildSize = child->GetSize();
+ nsReflowStatus childStatus;
+ const WritingMode childWM = child->GetWritingMode();
+ LogicalSize availableSize = aReflowInput.ComputedSize(childWM);
+ availableSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, child,
+ availableSize);
+ ReflowOutput kidDesiredSize(myWM);
+ const nsSize containerSize =
+ aReflowInput.ComputedSizeAsContainerIfConstrained();
+
+ if (child->GetContent() == mPosterImage) {
+ // Reflow the poster frame.
+ const LogicalPoint childOrigin = logicalBP.StartOffset(myWM);
+ const LogicalSize posterRenderSize = aReflowInput.ComputedSize(childWM);
+ kidReflowInput.SetComputedISize(posterRenderSize.ISize(childWM));
+ kidReflowInput.SetComputedBSize(posterRenderSize.BSize(childWM));
+
+ ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM,
+ childOrigin, containerSize, ReflowChildFlags::Default,
+ childStatus);
+ MOZ_ASSERT(childStatus.IsFullyComplete(),
+ "We gave our child unconstrained available block-size, "
+ "so it should be complete!");
+
+ FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
+ myWM, childOrigin, containerSize,
+ ReflowChildFlags::Default);
+
+ } else if (child->GetContent() == mCaptionDiv ||
+ child->GetContent() == videoControlsDiv) {
+ // Reflow the caption and control bar frames.
+ const LogicalPoint childOrigin = logicalBP.StartOffset(myWM);
+ ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM,
+ childOrigin, containerSize, ReflowChildFlags::Default,
+ childStatus);
+ MOZ_ASSERT(childStatus.IsFullyComplete(),
+ "We gave our child unconstrained available block-size, "
+ "so it should be complete!");
+
+ if (child->GetContent() == videoControlsDiv && isBSizeShrinkWrapping) {
+ // Resolve our own BSize based on the controls' size in the
+ // same axis. Unless we're size-contained, in which case we
+ // have to behave as if we have an intrinsic size of 0.
+ if (GetContainSizeAxes().mBContained) {
+ contentBoxBSize = 0;
+ } else {
+ contentBoxBSize = kidDesiredSize.BSize(myWM);
+ }
+ }
+
+ FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
+ myWM, childOrigin, containerSize,
+ ReflowChildFlags::Default);
+
+ if (child->GetSize() != oldChildSize) {
+ // We might find non-primary frames in printing due to
+ // ReplicateFixedFrames, but we don't care about that.
+ MOZ_ASSERT(child->IsPrimaryFrame() ||
+ PresContext()->IsPrintingOrPrintPreview(),
+ "We only look at the primary frame in ReflowFinished");
+ if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresShell()->PostReflowCallback(this);
+ }
+ }
+ } else {
+ NS_ERROR("Unexpected extra child frame in nsVideoFrame; skipping");
+ }
+ }
+
+ if (isBSizeShrinkWrapping) {
+ if (contentBoxBSize == NS_UNCONSTRAINEDSIZE) {
+ // We didn't get a BSize from our intrinsic size/ratio, nor did we
+ // get one from our controls. Just use BSize of 0.
+ contentBoxBSize = 0;
+ }
+ contentBoxBSize = aReflowInput.ApplyMinMaxBSize(contentBoxBSize);
+ borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
+ }
+
+ LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
+ aMetrics.SetSize(myWM, logicalDesiredSize);
+
+ aMetrics.SetOverflowAreasToDesiredBounds();
+
+ FinishAndStoreOverflow(&aMetrics);
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsVideoFrame::Reflow: size=%d,%d",
+ aMetrics.Width(), aMetrics.Height()));
+
+ MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType; }
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsVideoFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"HTMLVideo"_ns, aResult);
+}
+#endif
+
+nsIFrame::SizeComputationResult nsVideoFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ if (!HasVideoElement()) {
+ return nsContainerFrame::ComputeSize(
+ aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
+ aBorderPadding, aSizeOverrides, aFlags);
+ }
+
+ return {ComputeSizeWithIntrinsicDimensions(
+ aRenderingContext, aWM, GetIntrinsicSize(), GetAspectRatio(),
+ aCBSize, aMargin, aBorderPadding, aSizeOverrides, aFlags),
+ AspectRatioUsage::None};
+}
+
+nscoord nsVideoFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ // Bind the result variable to a RAII-based debug object - the variable
+ // therefore must match the function's return value.
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ // This call handles size-containment
+ nsSize size = GetIntrinsicSize().ToSize().valueOr(nsSize());
+ result = GetWritingMode().IsVertical() ? size.height : size.width;
+ return result;
+}
+
+nscoord nsVideoFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ // <audio> / <video> has the same min / pref ISize.
+ return GetMinISize(aRenderingContext);
+}
+
+Maybe<nsSize> nsVideoFrame::PosterImageSize() const {
+ // Use the poster image frame's size.
+ nsIFrame* child = GetPosterImage()->GetPrimaryFrame();
+ return child->GetIntrinsicSize().ToSize();
+}
+
+AspectRatio nsVideoFrame::GetIntrinsicRatio() const {
+ if (!HasVideoElement()) {
+ // Audio elements have no intrinsic ratio.
+ return AspectRatio();
+ }
+
+ // 'contain:[inline-]size' replaced elements have no intrinsic ratio.
+ if (GetContainSizeAxes().IsAny()) {
+ return AspectRatio();
+ }
+
+ auto* element = static_cast<HTMLVideoElement*>(GetContent());
+ if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
+ return AspectRatio::FromSize(*size);
+ }
+
+ if (ShouldDisplayPoster()) {
+ if (Maybe<nsSize> imgSize = PosterImageSize()) {
+ return AspectRatio::FromSize(*imgSize);
+ }
+ }
+
+ if (StylePosition()->mAspectRatio.HasRatio()) {
+ return AspectRatio();
+ }
+
+ return AspectRatio::FromSize(kFallbackIntrinsicSizeInPixels);
+}
+
+bool nsVideoFrame::ShouldDisplayPoster() const {
+ if (!HasVideoElement()) {
+ return false;
+ }
+
+ auto* element = static_cast<HTMLVideoElement*>(GetContent());
+ if (element->GetPlayedOrSeeked() && HasVideoData()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage);
+ NS_ENSURE_TRUE(imgContent, false);
+
+ nsCOMPtr<imgIRequest> request;
+ nsresult res = imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ if (NS_FAILED(res) || !request) {
+ return false;
+ }
+
+ uint32_t status = 0;
+ res = request->GetImageStatus(&status);
+ if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) {
+ return false;
+ }
+
+ return true;
+}
+
+IntrinsicSize nsVideoFrame::GetIntrinsicSize() {
+ const auto containAxes = GetContainSizeAxes();
+ const auto isVideo = HasVideoElement();
+ // Intrinsic size will be given by contain-intrinsic-size if the element is
+ // size-contained. If both axes have containment, FinishIntrinsicSize() will
+ // ignore the fallback size argument, so we can just pass no intrinsic size,
+ // or whatever.
+ if (containAxes.IsBoth()) {
+ return FinishIntrinsicSize(containAxes, {});
+ }
+
+ if (!isVideo) {
+ // An audio element with no "controls" attribute, distinguished by the last
+ // and only child being the control, falls back to no intrinsic size.
+ if (!mFrames.LastChild()) {
+ return FinishIntrinsicSize(containAxes, {});
+ }
+
+ return FinishIntrinsicSize(containAxes,
+ IntrinsicSize(kFallbackIntrinsicSize));
+ }
+
+ auto* element = static_cast<HTMLVideoElement*>(GetContent());
+ if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
+ return FinishIntrinsicSize(containAxes,
+ IntrinsicSize(CSSPixel::ToAppUnits(*size)));
+ }
+
+ if (ShouldDisplayPoster()) {
+ if (Maybe<nsSize> imgSize = PosterImageSize()) {
+ return FinishIntrinsicSize(containAxes, IntrinsicSize(*imgSize));
+ }
+ }
+
+ if (StylePosition()->mAspectRatio.HasRatio()) {
+ return {};
+ }
+
+ return FinishIntrinsicSize(containAxes,
+ IntrinsicSize(kFallbackIntrinsicSize));
+}
+
+void nsVideoFrame::UpdatePosterSource(bool aNotify) {
+ NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
+ HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
+
+ if (element->HasAttr(nsGkAtoms::poster) &&
+ !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::poster,
+ nsGkAtoms::_empty, eIgnoreCase)) {
+ nsAutoString posterStr;
+ element->GetPoster(posterStr);
+ mPosterImage->SetAttr(kNameSpaceID_None, nsGkAtoms::src, posterStr,
+ aNotify);
+ } else {
+ mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify);
+ }
+}
+
+nsresult nsVideoFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ if (aAttribute == nsGkAtoms::poster && HasVideoElement()) {
+ UpdatePosterSource(true);
+ }
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+void nsVideoFrame::OnVisibilityChange(
+ Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ if (HasVideoElement()) {
+ static_cast<HTMLMediaElement*>(GetContent())
+ ->OnVisibilityChange(aNewVisibility);
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(mPosterImage);
+ if (imageLoader) {
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ }
+
+ nsContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
+
+bool nsVideoFrame::HasVideoData() const {
+ if (!HasVideoElement()) {
+ return false;
+ }
+ auto* element = static_cast<HTMLVideoElement*>(GetContent());
+ return element->GetVideoSize().isSome();
+}
+
+void nsVideoFrame::UpdateTextTrack() {
+ static_cast<HTMLMediaElement*>(GetContent())->NotifyCueDisplayStatesChanged();
+}
+
+namespace mozilla {
+
+class nsDisplayVideo : public nsPaintedDisplayItem {
+ public:
+ nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayVideo);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayVideo)
+
+ NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO)
+
+ already_AddRefed<ImageContainer> GetImageContainer(gfxRect& aDestGFXRect) {
+ nsRect area = Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ HTMLVideoElement* element =
+ static_cast<HTMLVideoElement*>(Frame()->GetContent());
+
+ Maybe<CSSIntSize> videoSizeInPx = element->GetVideoSize();
+ if (videoSizeInPx.isNothing() || area.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<ImageContainer> container = element->GetImageContainer();
+ if (!container) {
+ return nullptr;
+ }
+
+ // Retrieve the size of the decoded video frame, before being scaled
+ // by pixel aspect ratio.
+ mozilla::gfx::IntSize frameSize = container->GetCurrentSize();
+ if (frameSize.width == 0 || frameSize.height == 0) {
+ // No image, or zero-sized image. Don't render.
+ return nullptr;
+ }
+
+ const auto aspectRatio = AspectRatio::FromSize(*videoSizeInPx);
+ const IntrinsicSize intrinsicSize(CSSPixel::ToAppUnits(*videoSizeInPx));
+ nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
+ area, intrinsicSize, aspectRatio, Frame()->StylePosition());
+
+ aDestGFXRect = Frame()->PresContext()->AppUnitsToGfxUnits(dest);
+ aDestGFXRect.Round();
+ if (aDestGFXRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ return container.forget();
+ }
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override {
+ HTMLVideoElement* element =
+ static_cast<HTMLVideoElement*>(Frame()->GetContent());
+ gfxRect destGFXRect;
+ RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
+ if (!container) {
+ return true;
+ }
+
+ container->SetRotation(element->RotationDegrees());
+
+ // If the image container is empty, we don't want to fallback. Any other
+ // failure will be due to resource constraints and fallback is unlikely to
+ // help us. Hence we can ignore the return value from PushImage.
+ LayoutDeviceRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width,
+ destGFXRect.height);
+ aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources,
+ aSc, rect, rect);
+ return true;
+ }
+
+ // For opaque videos, we will want to override GetOpaqueRegion here.
+ // This is tracked by bug 1545498.
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = true;
+ nsIFrame* f = Frame();
+ return f->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ // Only report FirstContentfulPaint when the video is set
+ bool IsContentful() const override {
+ nsVideoFrame* f = static_cast<nsVideoFrame*>(Frame());
+ HTMLVideoElement* video = HTMLVideoElement::FromNode(f->GetContent());
+ return video->VideoWidth() > 0;
+ }
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) override {
+ HTMLVideoElement* element =
+ static_cast<HTMLVideoElement*>(Frame()->GetContent());
+ gfxRect destGFXRect;
+ RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
+ if (!container) {
+ return;
+ }
+
+ VideoRotation rotationDeg = element->RotationDegrees();
+ Matrix preTransform = ComputeRotationMatrix(
+ destGFXRect.Width(), destGFXRect.Height(), rotationDeg);
+ Matrix transform =
+ preTransform * Matrix::Translation(destGFXRect.x, destGFXRect.y);
+
+ AutoLockImage autoLock(container);
+ Image* image = autoLock.GetImage(TimeStamp::Now());
+ if (!image) {
+ return;
+ }
+ RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
+ if (!surface || !surface->IsValid()) {
+ return;
+ }
+ gfx::IntSize size = surface->GetSize();
+
+ IntSize scaleToSize(static_cast<int32_t>(destGFXRect.Width()),
+ static_cast<int32_t>(destGFXRect.Height()));
+ // scaleHint is set regardless of rotation, so swap w/h if needed.
+ SwapScaleWidthHeightForRotation(scaleToSize, rotationDeg);
+ transform.PreScale(scaleToSize.width / double(size.Width()),
+ scaleToSize.height / double(size.Height()));
+
+ gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
+ aCtx->SetMatrix(
+ gfxUtils::SnapTransformTranslation(aCtx->CurrentMatrix(), nullptr));
+
+ transform = gfxUtils::SnapTransform(
+ transform, gfxRect(0, 0, size.width, size.height), nullptr);
+ aCtx->Multiply(ThebesMatrix(transform));
+
+ aCtx->GetDrawTarget()->FillRect(
+ Rect(0, 0, size.width, size.height),
+ SurfacePattern(surface, ExtendMode::CLAMP, Matrix(),
+ nsLayoutUtils::GetSamplingFilterForFrame(Frame())),
+ DrawOptions());
+ }
+};
+
+} // namespace mozilla
+
+void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) return;
+
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ if (HidesContent()) {
+ return;
+ }
+
+ const bool shouldDisplayPoster = ShouldDisplayPoster();
+
+ // NOTE: If we're displaying a poster image (instead of video data), we can
+ // trust the nsImageFrame to constrain its drawing to its content rect
+ // (which happens to be the same as our content rect).
+ uint32_t clipFlags;
+ if (shouldDisplayPoster ||
+ !nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) {
+ clipFlags = DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
+ } else {
+ clipFlags = 0;
+ }
+
+ DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
+ aBuilder, this, clipFlags);
+
+ if (HasVideoElement() && !shouldDisplayPoster) {
+ aLists.Content()->AppendNewToTop<nsDisplayVideo>(aBuilder, this);
+ }
+
+ // Add child frames to display list. We expect various children,
+ // but only want to draw mPosterImage conditionally. Others we
+ // always add to the display list.
+ for (nsIFrame* child : mFrames) {
+ if (child->GetContent() != mPosterImage || shouldDisplayPoster) {
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
+ aBuilder, child,
+ aBuilder->GetVisibleRect() - child->GetOffsetTo(this),
+ aBuilder->GetDirtyRect() - child->GetOffsetTo(this));
+
+ child->BuildDisplayListForStackingContext(aBuilder, aLists.Content());
+ }
+ }
+}
diff --git a/layout/generic/nsVideoFrame.h b/layout/generic/nsVideoFrame.h
new file mode 100644
index 0000000000..b8685c8f9e
--- /dev/null
+++ b/layout/generic/nsVideoFrame.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for the HTML <video> element */
+
+#ifndef nsVideoFrame_h___
+#define nsVideoFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIReflowCallback.h"
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsPresContext;
+class nsDisplayItem;
+
+class nsVideoFrame : public nsContainerFrame,
+ public nsIReflowCallback,
+ public nsIAnonymousContentCreator {
+ public:
+ template <typename T>
+ using Maybe = mozilla::Maybe<T>;
+ using Nothing = mozilla::Nothing;
+ using Visibility = mozilla::Visibility;
+
+ nsVideoFrame(ComputedStyle* aStyle, nsPresContext* aPc)
+ : nsVideoFrame(aStyle, aPc, kClassID) {}
+ nsVideoFrame(ComputedStyle*, nsPresContext*, ClassID);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsVideoFrame)
+
+ void ReflowCallbackCanceled() final { mReflowCallbackPosted = false; }
+ bool ReflowFinished() final;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) final;
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) final;
+
+ void OnVisibilityChange(
+ Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) final;
+
+ /* get the size of the video's display */
+ mozilla::IntrinsicSize GetIntrinsicSize() final;
+ mozilla::AspectRatio GetIntrinsicRatio() const final;
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ mozilla::ComputeSizeFlags aFlags) final;
+ nscoord GetMinISize(gfxContext* aRenderingContext) final;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) final;
+ void Destroy(DestroyContext&) final;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final;
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::AccType AccessibleType() final;
+#endif
+
+ nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) final;
+ void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilters) final;
+
+ mozilla::dom::Element* GetPosterImage() const { return mPosterImage; }
+
+ // Returns true if we should display the poster. Note that once we show
+ // a video frame, the poster will never be displayed again.
+ bool ShouldDisplayPoster() const;
+
+ nsIContent* GetCaptionOverlay() const { return mCaptionDiv; }
+ nsIContent* GetVideoControls() const;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ protected:
+ // Returns true if we're rendering for a video element. We create an
+ // nsAudioFrame (which is still an nsVideoFrame) to render controls for an
+ // audio element.
+ bool HasVideoElement() const { return !mIsAudio; }
+
+ // Returns true if there is video data to render. Can return false
+ // when we're the frame for an audio element, or we've created a video
+ // element for a media which is audio-only.
+ bool HasVideoData() const;
+
+ // Get the poster image's size if there is any.
+ mozilla::Maybe<nsSize> PosterImageSize() const;
+
+ // Sets the mPosterImage's src attribute to be the video's poster attribute,
+ // if we're the frame for a video element. Only call on frames for video
+ // elements, not for frames for audio elements.
+ void UpdatePosterSource(bool aNotify);
+
+ // Notify the mediaElement that the mCaptionDiv was created.
+ void UpdateTextTrack();
+
+ virtual ~nsVideoFrame();
+
+ // Anonymous child which is the image element of the poster frame.
+ RefPtr<mozilla::dom::Element> mPosterImage;
+
+ // Anonymous child which is the text track caption display div.
+ nsCOMPtr<nsIContent> mCaptionDiv;
+
+ // Some sizes tracked for notification purposes.
+ // TODO: Maybe the calling code could be rewritten to use ResizeObserver for
+ // this nowadays.
+ nsSize mControlsTrackedSize{-1, -1};
+ nsSize mCaptionTrackedSize{-1, -1};
+ bool mReflowCallbackPosted = false;
+ const bool mIsAudio;
+};
+
+// NOTE(emilio): This class here only for the purpose of having different
+// ClassFlags for <audio> elements. This frame shouldn't contain extra logic, as
+// things are set up now, because <audio> can also use video controls etc.
+// In the future we might want to rejigger this to be less weird (e.g, an audio
+// frame might not need a caption, or text tracks, or what not).
+class nsAudioFrame final : public nsVideoFrame {
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(nsAudioFrame)
+
+ nsAudioFrame(ComputedStyle*, nsPresContext*);
+ virtual ~nsAudioFrame();
+};
+
+#endif /* nsVideoFrame_h___ */
diff --git a/layout/generic/test/bug1174521.html b/layout/generic/test/bug1174521.html
new file mode 100644
index 0000000000..a62558c366
--- /dev/null
+++ b/layout/generic/test/bug1174521.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <div>
+ <div style="float: left; padding-top: 20px">
+ <a href="#" onclick="parent.postMessage({msg: 'DONE'}, '*'); return false;">test</a>
+ </div>
+ </div>
+ <div style="position: absolute">
+ A
+ </div>
+ B
+ </body>
+</html>
diff --git a/layout/generic/test/bug344830_testembed.svg b/layout/generic/test/bug344830_testembed.svg
new file mode 100644
index 0000000000..5dd98abe65
--- /dev/null
+++ b/layout/generic/test/bug344830_testembed.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 200 200">
+<g id="g1" transform="translate(100, 100)">
+<circle cx="0" cy="0" r="50" fill="green" />
+<text x="0" y="10" font-size="24" text-anchor="middle" fill="yellow">Kibology</text>
+</g>
+</svg>
diff --git a/layout/generic/test/bug421839-2-page.html b/layout/generic/test/bug421839-2-page.html
new file mode 100644
index 0000000000..75402ca535
--- /dev/null
+++ b/layout/generic/test/bug421839-2-page.html
@@ -0,0 +1,55 @@
+<html>
+<head>
+</head>
+<body style="position: absolute;">
+<iframe id="a"></iframe>
+<iframe></iframe>
+<script>
+function tripleclick(){
+var wu = SpecialPowers.getDOMWindowUtils(window);
+wu.sendMouseEvent('mousedown', 100, 100, 0, 1, 0);
+setTimeout(tripleclick,20);
+}
+setTimeout(tripleclick,200,0, 0);
+
+function doe2() {
+document.body.setAttribute('style', 'position: absolute;');
+document.body.offsetHeight;
+document.getElementById('a').setAttribute('style', 'position: absolute; direction: rtl; ');
+setTimeout(doe3,200);
+}
+
+function doe3() {
+document.getElementsByTagName('*')[2].setAttribute('style', 'unicode-bidi: inherit; ime-mode: disabled; font-family: Al Bayan; ');
+}
+setTimeout(doe2,500,0);
+
+setTimeout(function(){window.location.reload()}, 1000);
+
+
+function designmodes(i){
+if (i>=0)
+ {
+try {
+window.frames[i].document.designMode='on';
+window.frames[i].document.execCommand('inserthtml', false, 'tesxt ');
+window.frames[i].document.designMode='off';
+}
+catch(e) {}
+}
+else {
+i = window.frames.length-1;
+ }
+ i--;
+setTimeout(designmodes,50,i);
+}
+setTimeout(designmodes,500,window.frames.length-1);
+
+function doe2(i) {
+document.body.style.position == 'absolute' ? document.body.style.position = '' : document.body.style.position = 'absolute';
+setTimeout(doe2,200,i);
+}
+setTimeout(doe2,500,0);
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/bug633762_iframe.html b/layout/generic/test/bug633762_iframe.html
new file mode 100644
index 0000000000..185d9af65a
--- /dev/null
+++ b/layout/generic/test/bug633762_iframe.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+<div style="background: red; height: 4000px;">hi</div>
+<div id="b" style="background: blue; height: 10000px;"></div>
+<div id="a" style="background: yellow; height: 100px;"></div>
+<div style="background: green; height: 4000px;"></div>
+</body>
+</html>
diff --git a/layout/generic/test/chrome.toml b/layout/generic/test/chrome.toml
new file mode 100644
index 0000000000..67d8fa6ef0
--- /dev/null
+++ b/layout/generic/test/chrome.toml
@@ -0,0 +1,28 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+support-files = [
+ "file_bug514732_window.xhtml",
+ "frame_selection_underline-ref.xhtml",
+ "frame_selection_underline.css",
+ "frame_selection_underline.xhtml",
+]
+
+["test_backspace_delete.xhtml"]
+skip-if = ["true"] # Bug 1163311
+
+["test_bug469613.xhtml"]
+
+["test_bug469774.xhtml"]
+
+["test_bug508115.xhtml"]
+
+["test_bug514732-2.xhtml"]
+
+["test_bug632379.xhtml"]
+skip-if = [
+ "verify && os == 'win'", # Bug 1207914
+ "os == 'linux'", # Bug 1207914
+ "os == 'win'", # Bug 1765788
+]
+
+["test_selection_underline.html"]
diff --git a/layout/generic/test/file_BrokenImageReference.png b/layout/generic/test/file_BrokenImageReference.png
new file mode 100644
index 0000000000..2a1e0dc9e4
--- /dev/null
+++ b/layout/generic/test/file_BrokenImageReference.png
Binary files differ
diff --git a/layout/generic/test/file_Dolske.png b/layout/generic/test/file_Dolske.png
new file mode 100644
index 0000000000..cf5c35e996
--- /dev/null
+++ b/layout/generic/test/file_Dolske.png
Binary files differ
diff --git a/layout/generic/test/file_IconTestServer.sjs b/layout/generic/test/file_IconTestServer.sjs
new file mode 100644
index 0000000000..b045ba9697
--- /dev/null
+++ b/layout/generic/test/file_IconTestServer.sjs
@@ -0,0 +1,93 @@
+const TIMEOUT_INTERVAL_MS = 100;
+
+function handleRequest(request, response) {
+ // Allow us to asynchronously construct the response with timeouts
+ // rather than forcing us to make the whole thing in one call. See
+ // bug 396226.
+ response.processAsync();
+
+ // Figure out whether the client wants to load the image, or just
+ // to tell us to finish the previous load
+ var query = {};
+ request.queryString.split("&").forEach(function (val) {
+ var [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+ if (query.continue == "true") {
+ // Debugging information so we can figure out the hang
+ dump("file_IconTestServer.js DEBUG - Got continue command\n");
+
+ // Get the context structure and finish the old request
+ getObjectState("context", function (obj) {
+ // magic or goop, depending on how you look at it
+ savedCtx = obj.wrappedJSObject;
+
+ // Write the rest of the data
+ savedCtx.ostream.writeFrom(
+ savedCtx.istream,
+ savedCtx.istream.available()
+ );
+
+ // Close the streams
+ savedCtx.ostream.close();
+ savedCtx.istream.close();
+
+ // Finish off 'the old response'
+ savedCtx.response.finish();
+ });
+
+ // Finish off 'the current response'
+ response.finish();
+ return;
+ }
+
+ // Debugging information so we can figure out the hang
+ dump("file_IconTestServer.js DEBUG - Got initial request\n");
+
+ // Context structure - we need to set this up properly to pass to setObjectState
+ var ctx = {
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+ };
+ ctx.wrappedJSObject = ctx;
+
+ // Save the response
+ ctx.response = response;
+
+ // We're serving up a png
+ response.setHeader("Content-Type", "image/png", false);
+
+ // Get the output stream
+ ctx.ostream = response.bodyOutputStream;
+
+ // Ugly hack, but effective - copied from dom/media/test/contentDuration1.sjs
+ var pngFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("CurWorkD", Ci.nsIFile);
+ var paths = "tests/layout/generic/test/file_Dolske.png";
+ var split = paths.split("/");
+ for (var i = 0; i < split.length; ++i) {
+ pngFile.append(split[i]);
+ }
+
+ // Get an input stream for the png data
+ ctx.istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ ctx.istream.init(pngFile, -1, 0, 0);
+
+ // Write the first 10 bytes, which is just boilerplate/magic bytes
+ ctx.ostream.writeFrom(ctx.istream, 10);
+
+ // Save the context structure for retrieval when we get pinged
+ setObjectState("context", ctx);
+
+ // Now we play the waiting game...
+
+ // Debugging information so we can figure out the hang
+ dump("file_IconTestServer.js DEBUG - Playing the waiting game\n");
+}
diff --git a/layout/generic/test/file_LoadingImageReference.png b/layout/generic/test/file_LoadingImageReference.png
new file mode 100644
index 0000000000..5641cf4f55
--- /dev/null
+++ b/layout/generic/test/file_LoadingImageReference.png
Binary files differ
diff --git a/layout/generic/test/file_SlowImage.sjs b/layout/generic/test/file_SlowImage.sjs
new file mode 100644
index 0000000000..a9eaeef838
--- /dev/null
+++ b/layout/generic/test/file_SlowImage.sjs
@@ -0,0 +1,45 @@
+"use strict";
+
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAA" +
+ "DUlEQVQImWNgY2P7DwABOgESJhRQtgAAAABJRU5ErkJggg=="
+);
+
+function handleRequest(request, response) {
+ response.processAsync();
+ getObjectState("context", function (obj) {
+ let ctx;
+ if (obj == null) {
+ ctx = {
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+ };
+ ctx.wrappedJSObject = ctx;
+
+ ctx.promise = new Promise(resolve => {
+ ctx.resolve = resolve;
+ });
+
+ setObjectState("context", ctx);
+ } else {
+ ctx = obj.wrappedJSObject;
+ }
+ Promise.resolve(ctx).then(next);
+ });
+
+ function next(ctx) {
+ if (request.queryString.indexOf("continue") >= 0) {
+ ctx.resolve();
+ }
+
+ ctx.promise.then(() => {
+ response.setHeader("Content-Type", "image/png");
+ response.write(IMG_BYTES);
+ response.finish();
+ });
+ }
+}
diff --git a/layout/generic/test/file_SlowPage.sjs b/layout/generic/test/file_SlowPage.sjs
new file mode 100644
index 0000000000..1860e8cbc9
--- /dev/null
+++ b/layout/generic/test/file_SlowPage.sjs
@@ -0,0 +1,43 @@
+"use strict";
+
+let timer;
+
+const DELAY_MS = 5000;
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ response.setHeader("Content-Type", "text/html", false);
+
+ // Include paint_listener.js so that we can call waitForAllPaintsFlushed
+ // on the window in which this is opened.
+ response.write(
+ '<script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>'
+ );
+
+ // Allow the opening window to react to loading being complete.
+ response.write('<body onload="window.opener.fullyLoaded()">');
+
+ // Send half of the content.
+ for (let i = 0; i < 100; ++i) {
+ response.write("<p>Some text.</p>");
+ }
+
+ // Allow the opening window to react to being partially loaded.
+ response.write("<script>window.opener.partiallyLoaded();</script>");
+
+ // Wait for 5 seconds, then send the rest of the content.
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(
+ () => {
+ for (let i = 0; i < 100; ++i) {
+ response.write("<p>Some text.</p>");
+ }
+ response.write("</body>");
+
+ response.finish();
+ },
+ DELAY_MS,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/generic/test/file_SlowTallImage.sjs b/layout/generic/test/file_SlowTallImage.sjs
new file mode 100644
index 0000000000..382d36e49a
--- /dev/null
+++ b/layout/generic/test/file_SlowTallImage.sjs
@@ -0,0 +1,21 @@
+"use strict";
+
+let { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+// A tall 1x1000 black png.
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAPoAQMAAAAleAYdAAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAF0lEQVQ4jWNgGAWjYBSMglEwCkbBUAcAB9AAASBs/t4AAAAASUVORK5CYII="
+);
+
+// Cargo-culted from file_SlowImage.sjs
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "image/png");
+ let delay = request.queryString.indexOf("slow") >= 0 ? 600 : 200;
+ setTimeout(function () {
+ response.write(IMG_BYTES);
+ response.finish();
+ }, delay);
+}
diff --git a/layout/generic/test/file_bug1307853.html b/layout/generic/test/file_bug1307853.html
new file mode 100644
index 0000000000..132dc65db0
--- /dev/null
+++ b/layout/generic/test/file_bug1307853.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<title>Iframe for test for Mozilla bug 1307853</title>
+<meta charset="UTF-8">
+<style>
+
+html, body { overflow: hidden; margin: 0; padding: 0; border: none; }
+
+.wrapper {
+ box-sizing: border-box;
+ width: 100px;
+ padding-right: 20%;
+}
+
+#inner {
+ height: 1em;
+ background: aqua;
+}
+
+</style>
+
+<div class="wrapper">
+ <div id="inner"></div>
+</div>
diff --git a/layout/generic/test/file_bug1566783.html b/layout/generic/test/file_bug1566783.html
new file mode 100644
index 0000000000..77c5658cde
--- /dev/null
+++ b/layout/generic/test/file_bug1566783.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<style>
+.spacer { height: 200vh; }
+</style>
+<script>
+ function loadFailed() {
+ parent.ok(false, "Image load should not fail");
+ }
+</script>
+<div class="spacer"></div>
+<img id="fast" src="file_SlowTallImage.sjs?fast" onerror="loadFailed()">
+<div class="spacer"></div>
+<img id="slow" src="file_SlowTallImage.sjs?slow" onerror="loadFailed()">
+<div class="spacer"></div>
+<script>
+onload = function() {
+ setTimeout(function() {
+ let rect = document.getElementById("slow").getBoundingClientRect();
+ parent.is(rect.height, 1000, "#slow should take space");
+ parent.is(rect.top, 0, "#slow should be at the top of the viewport");
+ parent.SimpleTest.finish();
+ }, 0);
+}
+</script>
diff --git a/layout/generic/test/file_bug448987.html b/layout/generic/test/file_bug448987.html
new file mode 100644
index 0000000000..a22bf29a7f
--- /dev/null
+++ b/layout/generic/test/file_bug448987.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body onload="focus_area()">
+<pre tabindex="1" id="pre">
+STEPS TO REPRODUCE:
+1. TAB to the image map area below
+
+EXPECTED RESULT:
+a focus border is painted just inside the image edge
+<pre>
+
+<div style="background:lime;padding:1em;float:left"><img src='data:image/gif;base64,R0lGODlhFAFuAPcAAPf39//7/+fn59bT1u/r787Lzq0UAN7b3hhFrRhJtRA0hBA8lMYYALWytffz94wQAMa+vb26vRhNxufj5+/v78bDxvfz772+vcbHxghRCM7PzggkYyFZ1tYkCNbX1hhRzpyenO+6AN7f3gBlANauAGOW7zFl1kp95wg8pbW2tZyanHOi797f5zlx3msMAAB9CP/PAL22va2mraWipedJMSlRtf91Y72WAFqK76WmpRBFta2qrcaeAK2urfdpUuc8If39/e9ZQmPTY94wGFrLWrUkEISq9/n5+ZR5ABCWGLWOAIRpAGNRAClJlK2GAL0sGGssITG2OaWCAK3H9848KaW+762qpcbX/73jvWN5pGvTc/eGc//vCFppfIwgEJRBMYSGlPeWhJSSjHN5jMaSjKWmrb3P787V1ilBa4R9c9nXy+rq6ntNQu/y95Su3svO1kphlISa1t7b1vf7/3uGraWGexdAmoySlDm6QrWyraWqrRiiIWtpa0LDStrl/1JtrcbHzpxlWntlUklVcztZk3vbhISStfffWufr786upVmB1ta2taW21tLS0ve6rd7j5zlhtfemnJSetefn78LCw7W2vdnZ2bVFMf/3//f3/8Z1a9bb59bb3eHh4a2WjM7HxoR1OSGqKbW6xvT09JyGQrXD3v/r5+fk3s7rzufb3q2ytffXKbWiY6inp1p1WrWmjP/LxnOOzsC/v+/n56qSMffXzr2eEP/3hPHx8f//987T57m4ubVZShk5f97LjO/HKQ03ks7DpdZlWv/vMd7LxqWuvefr/86uKd66QgwyhcnJyf/ztbKxsrWqpe/37//z7///75zTpf/397Szsvfn1ozLjO/jlN7v3qWko62OEO/r5//73tbX3vf37wotd1qqYxlNvYyujN7X1qKgoHuWe5KUmkKOQhZKvildMRh5IZqZmcXEwZ2amaajoNfU0QMaTAsiV5STlSo+bZibpSti2puZl5u28pGs5pqbn9Hd8/fr94WFi7i1sBguYfP09v///ywAAAAAFAFuAAAI/wD/CRxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iDYinEtJAWLUSIfJv6DVFShnOMXTEzpYrXKlPMXDGW6apZhli0CHmqtk+oJC9ejDDH6azBTGaMKDLBQUKCvzoC/5XAwcSJElV0UbB71lk0PFH6CGmqxS3cuZgyAwhw1tiUEiY+CEZBunTpwR9SfyisiNGmzYyNYqlWLZrtak6JRLmcoQKBCQQswEZ6ZcUJDn0RoFiwoAkhOH/+wCHURDkKHRIII0e+uoWbSbGRYv/SFy0qnj1yM6ArUEDDAQEOOBu9UqIF3xrMCWWRBGiAnP8eqKEBBpVIkgUhyg22nWocxKJLeEMBYIEAqVSDBx5vjTCCeinEgIEH8Mk3lB/G2YffAlmIcoAIcgxYAQQwXiAjjC+WkUV1qHH3QXaKbAIhUAAQwMIBWkCG3obq9dBABQcER1QmVeBwQgv4KZAFICIMgEGMEXTp5ZcNhJmCjQlmp5pfKCBgyHA/6hQkC6lUdl56Gbgiw5IiUCAiUCRKaQICChBSSYsydhiDly9ioOiiMKYQZgMqEJLmYH4pp4ACTRzQ5k4AOEABAYUQ4RaddqYwAHxDmVGClJAssMEYA8b/IGaHELQ3QIAriiDCAQcM4OIFYVqRRZkJILCAAhtsAMcEbG5q02YOFCLEqEiWCuKePn1WXw3IgqFBBEoqeQEG7uka3KfCWYCuABP0qgEEjt5RQ2DGIrvBIAOw4KSzOWEiLbUZZJAGnnoGVUUJ2yJ7BwYN3NlABBX4J0BwAAzH2cWcefqbBwXAq0INy9k7SB7jNmkBvzj5O20odA4MwQHCAaXtCdy+eoEMOVjRQK3vFYzxQp0KyfHNTYgMwgw7XHAqbAFgi7JLAPzLMpIu5/nzTjPX3EUEOSC98wDANftQ0FlGUPS9KqSd9KkOPG2TygBnAEYDGLAQn09+IIyDCa6i/9HDDEenUECTd1sUdBbIdgFC2jPQbbLbNUW7Mp13OF74TpmssOqfyd7RdQ6C9+w0RaXUQAgdeQDeOJOoQk6TBW0dGXDlBTDrkxEI04zsIDIEXve+Gm2yySkDRLCDzqxf7npMsE8rewZihC72TXmvsLerG4hxdA/JX50RAQPACzHhoy/fEgCx0xl97dPbZITmVO7euxVKt95RkAMUQC755tPkTPpIUoHgbLeT4liPb8kCg+/sVr6LAIAC7VoR8Ponk/85j04CZF8DYzIHI+AufhtAw+K4x7+PBIkAE6RgBdUypwDGQAP2wwmJjHC9xB2tATBs30YqtsGIPKMWjgjDFv+GuIUwRAIWpiAJNLaxjFsc4olPvMUytvETC7YwYBmMoUOo4YhICLGIkXAENbxXED6Y0QMs4MNmRKBGhFThfTTDXhqQxiSf8cQUkfABDYYwhA50YAg/CCQNaBAEYYQhFR6BhhN/EYJGOjIEMIgkDH7hC218pA1+uMIUNsnJrnzFgx6MgyhjEQtGFOQaULliBrKoQ4PUIgxBCCQgfzDIWhpyFghRFx9iYIED8AEC/8AAHyZwEGN4sD6ACqEKGqcBAu6EGlvYYwcYYIAiPOGaRfgjLWnwAz4KA5EZ2cYhYBACEvDgBjewhTp5QAISNDKSjUTGKTKSiSvQ8AT4PAEOEMb/TynZpy9oQoECFiAJO/4DlURQJSsbUgsb/MCPDCiCRLVZy4cOgQzt48MF/jGBHKQhABAY5kHyQsM/IUuEMqifxW5iijBIs5qaSMQniKGBRSRCE0/QpiwZ0AFNGDQi0BhnCHighGywIhhqENAngsEKW7TzkeX0hfIgYszjZEc7htlnCTTnp+SEbAF0IOZKEapKELxQiwYxRTSHINFLkCERNiUDL3I6yz4y4BLgLCMw28WHBhCADxo4yButx4Fj3StndezhS2Dhgx9QoQheIMMB2oCIR/DqDJi9QCCy+Ue7MoAK4qDIMoYBAxLcIBuvyNMkhjSAW50BAqxg5yPbeYx5/0ZkDlWwKgL+IAlGMCIOfyhMC/JpHwlAgg5ZSO6VJjQB+AyHrLxZZQSWphDGOrYIX0gEIig7JE6coRmX4Gkfp8mAJxDDIHzIQTAJALh/DMwgDvCgcaq0gS7ciX06iUQQaEAFA3zhE23olf629KIZeaIIPIUoAyK6CIncgguldQIt1EAAAbOnwDN6hVPdWU5z2kINECGRfXb7BnURYBbEKwYkCGMCvlRKEi1qbf40ADb7oSKV0TUrdRHiiP32NxCPQATHLkwjUQQiweQ1wBPyOpCV/iMTBDDIDOuTgEu9am0EcPJM8hiE/n5hRQOmEYExfOAFK9gABmgwGQ/y4AiTgv9XYU4Uo7qUgw2305znBHFD6HOCP9FhQu4acAEu8AfsXLVedADWocZVADT67MYJzfF00SoQR/igywYA8gHijGFZ8QLJ1DTAJWImETOs4ICAQtYYSLgYnHCZCl6AQqO5FDEaB8hXHYOAJxbM61Ab4BMOaTMJpAAKGtPaVrf+1SvYeWcenDMbelYIfXDQgt1qw8IVmHMKCg2YeilgbrRqT54KB2k8SHrHBWGsHrEboGPTWAP6AxYV/DhNAzzgARjV8kKmcOq9pXoDc0vsTRxhg3U/YAZvMFStaswuFBIgS+zRRK8XjOYlN+0fFzfIg7kQAicsoRkJDzfDHT6BIXWMFXf/buc5lUALZ0pZc3F8WYHJ9e5496AGVy3WsRZQBgzYKmwiKve50WqKggfhCQf/lsjNxS4Bk4GP9b63Fw4wVYZU4dTH+XfAGWiThtrg6A9gA8MakAKeOVc+QdsYIMLbazQ/IBA8RMgyuADhGywBFIAQEwTcM7HhVMwCQoPAMVK+cimwour/6ODmFgAHpTN6Rc01eQEkcdVKHavxBwD6nlAhhEhjcNJODkPBqRD2vD8sYvoSTsU0doAKvPQBLoh9HdqA+IRcvQTHOVa3lsR1mmBiC6P3ggvqEIMdPGxp7evUBDjRjIm7/QGJqBhChgHhYTMhesaHAPJHp3wPJCLl5lSC/xOkEIyqZ+3b4kM9xRygMY5l4Uw6V0AxZkGB6XFeN0NfKSxsYAMaPMEFaSAK2XcqqlcQb0IDHVAE9xZ7LgAFJdQQuENthWVlYHBWrcQSBPd1SAcFObM2vZcQDiAAnPBpZ2ZvX/AIpEYQGxcCdscEHThpKZhLIogMjaRyNzB+Lac8bXBqoOEqlcM920cQQXIKosABqhF/y1J/B3F/u/F56CYQwOcDsOYCYiAuT2gQ4jAE1AR7DCgItRODC2EcfSYB9WIz+EUTagV8NFAELsAGXWM5ivUPnpII9FZv9vYAzZBlewIN1BdhSyAIZQA6XxiHExIMjmRONygFSBABeohxAf/Ab9ZTM8sEhwfxJopwhPWCBmdYEEyYfwSxfxr4AFAAAshjNQjxDJFAA1sYe1AgCFWoUg9RH8VVhluziTJBcFvQZcLnhikFNhdIEL00bxD1fEDWiBhnDXQHAzyABEwAiPQzAFn2EBB0DDVoWuOHBG8Wjf8AAPKlO9mDZToUJG6wGjvibXqQJwBgEJ2IQTHwhJHAf2soioEIegfRUgjIALAHBWyQBmkziTn0EIpQGMlhWHAAgzUxRKHoAs74O3EoEBLydOO1hQ/gBb5xMgNBfRx3A8w4RwTTkNtIAL5wiESliEuAAQTEAivwQclEOzDTQBVzBnwxkAqTL2KDBZ3XhEj/AgL0KBDPwH9SyIYcyGrYAgtb8FAdYIJ1ECb9qAK9SGkIEZDc4W1oMEC1txKpYANqqIAKqQem4pQJAQDEIEtRB3ueoC/p+A/UkIwkoARLwAQc+Y8QMQdqIJLihwRLkFrRqAv9ZlIARzcuV4kEsBd8UYaw0pIFYZP454Ta+A+14ANf11/y6Jd+Z2l2ZQBeoAmLMCTFdzQ54yEPuBCxsBpGmADMoQB+M4gzgYs/CXuC0AOwGJcCEEvdJJEuEAhnQEDIWH0e55bgGBEAMAm/QE7ldIN2WQe3mY6Q6G+qZoEMIQ1x0AItwAFVpmrat5gCgZg4iUU7+Q+L4JhHZ28ceFYC/2GPduhWTcICHdMwxuchfFeVBhEHoika3rYwpigToveY9qaQqvAyP8UQADAHmjBIfXSHbTg4SngIEOaHvMmfHtlkviCc1miXeNckuXAwuMcByVSLXkkQjIBPfGmGWoSdnigQ76iB4FkGF8ACbaAGRVlvRYCZiCBk8YYo4tZc7mkQ48gd8acAY2Cga6YSFgB8Jsqa+zlZDToQiTBID0WgUECPq5CgPLCbVWOREvGgNUicd6dSFhqdqTYytpgQb+AnydSXqDkQIqqYw3Gf/mdvXsCVcsVroiZZlOUrMAIBFwAxjSYCfXek/2AGC0KGpUkI1cmnIAEA/DekbVikVBoRxP8QBPvFVgsIBQ2AX09KTlEqoYpKEWoATxG6BEswqcxSCvjEpbqnB1+KELqAA8rZOXlQptd5k9GlAtO1mKKnR0/ApoLwBUVwCbygCTDaBiyQPwXwIrXSaD1TgBMxCaFhhJZ3KWUAjb9oEhZwqGsKe2yQqRNxCpe2RwqYjzKAAZPVDasAT5d6d9gqEdoQnI1Uri4IrtJgBtAZGqR5Kd5SnwqxCWKaLCLUkQRxpi4ErQMRBj5gq3c4MNQFADJKZLVCYzYarQxxicyqc8dSmDcaEZm0FVeQsRqrsVyxSa4hEATgmATrreBasQdBAUb3A93agDnAnxYwrpbqBJhaNyYLX8H/GbNt2a5UJwAxKZ/HQgg+yhCpGkfJYl/c85f+qp3oJrAj+3bqt3z5oygQUAHtkXkUQ6gHkaM7AqjH0gQClxEkMiXQqU9bxYMSSIaB8gggK7IIeIccSLM/qhBBemkqG6m9SAEvS65KYJdL0LL2ChEAALNDJQU5e7fScImpwbUKoAdNolilQG01kz3MxCx7krSr1I7GyLRte29sIAqDoyubpii2kno1+xC6AH8SqwCG0LgYIWIthhwmMFwlAErGYQJoCwdRBi0pe5QLKAZwSxFbMLB1y4o5AIM3O5wkqQK/KxGZME4424zgWAqVh4QU9otuEJ3Yozgz0JVnaaaw6oQC/9C9+rVfvDuR3+ofvXIrHiACmmdCLYC69QK0G8oQxrACRsgBNZC/NQAJfxALp/Y+EggomTIhemKojrqkCxgIWKKEE8G0w5uoFogMnIqldfC1vum8LGiX0AuDFsBt3cYchkB/G+QAe1Mzg6A2DHqY3/uvi7kIB3yUd1jBEsMue6pvGzGOiWt5xxLC/ekQmVAKjFAKb4BZZwBvtuIG+yQl0ukqXdAiEjQHLsVNMHxvXzCoFOHAWqmQPSCeVoq8EmrFE4HBGskECwqtACC9gmEdKOC1YFgQVQBCg7A4ZuWqr5qYATSr3VsLSjqWVQw2eBsfDosRB8CsOWcszVEA1lkRz/8ggjMKAc85JbZrWFtjdnOwCGIpkV4AqoFcEALbZVnsMtdiDXRJkg3ANhMxB867ljn7ih5Qf0EiCWqsxjXwBrmQEFeQe/fSO8sExv26wkq7mAQgm3Z4b2U5vyYUB9NbLPWSBSJsEZ7SLh2jaBfwnP/0b11weu8BAKnQTZUZw8dJqMGLaaxIiimqJ6cwynbpCcc5EQCQyoRLxuRMdZwhJHRgGsyxAChQA6YEX7llUmMQA6pDld5jubIKrfIRAJoAdRRngt+cEgAwAABVyIZcUJtsgCEIcYxSAG7goRhqZV1gfPg1B8JAb6FGxW9gpMxbcP7HhQtppJlAgx12A3VJCif/bQF8GriWyoxMkF47IHAhqAaGsHP3jM860BqbMAmT4Aef0WeAIgkF8DeCaJgGQdB4fNCL8EdJdoeeMAmLahISEgeG5hcf3AQlhrVyCEGg2ysrwgirwqUeLQM7SYcleG/NkM0SQQAqncXao8mcYYgddk4yiwR5QKESIa7kxJZkvNeo+ZsH8AllAAeXcinMkQAf0AI4cGowZwLcogfiMCbI4wGJ7L12HDA6Zp0CIIxwytAMjBIAcACQENbdphyQIAI27J8ag0IUkNsWMDMdnTgPw7r/gAhU0HZ3aJvG7EqXNoVtGDim/A+TMHg2WJeswA3HXRBzF2FkLAjMHb5oBz4Q/5AHdwAHaBDZKCABLTC7AKzZCkAHcgAuSgKuXknVANtkZMBTqW1vgbBdZk0RQSIKlBLbyvEHoe0QsDFVpnah1syviZcIzsemEfCXDaFfKx172mO4DhkMzVZ4iwjhDIGMLLjK8bzacigAagAvVgACdzAGKk4HkhAHKbktC4AGhYLNAy7a2ZkBpb1SckBe920A+aYSEGQIggEY3YYCzOwRB551Hr0kB1AwH/lp9/0AvKAvDYqyuhh7fLA9ZRokyIBnhWd4R92gAEBa1ueWk0jHATAh4ZMHUN2ZDaBVSt4EjnJ8dq0QlquTT+gAAFDfDY5mP87a4JMFQ07kf4ECAt4RSf/e2/XVqvXZKZ+AYAvtdp5Ae3FbED0Wj8u9y+jm6LbgbM4m0+OnXaXr4Yit3ZqOVmnOyCaOMw1gCMPVZ1WCBp0ZtAtx59spEA7AAmzX5xilEs7wCAXwB5NC6IABCdzAEYlOi4y+qBPyCmj27M9eBJ8gfQ1BDbYqfKN45vaa6omATt4O6tmgATW7DaQ1xmzAOAoOXxAUuuJjCLHLF/P6bSRzLQ3hryOADjlugASwCJA+cbwmDLg0NrDwDBZxBLgwAcoACcNO7NjhBrWcEcm+5BWw7b9RB9AO7XiVcQohDQ71BNjej03ZvQ5JAJvwCt/+7bRwAHNQ6QJBWsvIBOc+Qq//mRDqMgG7MgAHcL3/xLVNYKofaOei8hZyoSH5XhAQRAYX7+9P0OtA4wjCoGYFTwFysA7CniYIcPX/XdTHjhGqsjcSYFiLbsEB8NMWf/FodgnntRBF9wMe34Zdo+1dLRAQdAavoAR2f/d2n/IMEVSDywSgoMuTG/cI4Sm6fQb+lBxXn8+VIBxxiAVGMvQaQg7w7TRBwgllD+3+TgVkQAxT9QywEAaPBfUXYfAHQAlZcM92gPX/LQFxoLYWYQZTUgNjGvbb3imbVgde8ABmXwRMfxA9NgRIV5ubuTqEre6b9grZMH5OcPfpJFUJsQyrMFR2GYC9Q/w1jhD/2SlxMCUA/9VtCeDwDqEFewAXkD8C6RAOL4MJJzskl5/0vdYBPyAMmoCQQaCF1XReFVPRBwEEFAAQB9aBaaJgwQI7CBQmYChBB4dYugD8o1jR4kWLAIyYqIFAwQaQG7rkqSDCQoCLAAR4AJTjywOYBmTKvESmFgUAAUzVihRkSIciD77keNNjRo4GEDwIyImRIgACLK2QklLViRMlN3gc80Xt5D9o226tCkFCCRJQzQAZnZF06USncS/qamGCg0OGeXXEgiv3H5Y9SV6MGJHBMOERL9KZM/fNQ0YCB9548gLzwcyZDDR30NxZplBx/yyMburX9D8gQHAd+NQKzgKDBxUiyCvhw/+HFnHMEEBpeo6fKjg4Iui1od9xkSRN9rboQMCACpXqvLScmQEVKjR+DNEs84snD28ayJDhVoAD5k6dQ68khhQS+FWz8tiK7NevsjxuSKH1akCB8cqrYIDz0jsNIzc+wEsvhlDIggC/rtnjhcEMs/AwxNKJpq9/AlBJqkAqsywmzDCDyYs6mJpgAgEI+OpAv1IbpRNl+LmDEAVyjG2h2m7j4IRY3KjCjCuKvMKMKYwo4QS7JPBoA282kGeQLsAAYYeSXkxpAvb0qIMNEUks0YAvAklkAhYwSKGHHlIYsMCKDKzIAi6j08M9UOBDQr4b+lTiT1pYCQZNNdl08y05T2v/ww8zSnEjjo5moy2vBFCAZBO5UKkmnHDIScMVUEN1hRxywhEAE/TSWy86FcB8wIURY4UJijo0mOSAAQbwQASmOIQxLiBGmcCdCGQAA0cdd+SxIQlsu+3ZZ5utdIEmCOnijnLG0VbbHjBgQUvI6lxTDxDqCISNL9JNlxdNyFiEBUS4hKABeiPAYCn0DgSATpbGVUGMOkgRmBRaAmUlkU9YZEGD6Oi9oAB8A0gUo0yuqGIFHOq6SwcUDvJ4WR0qrYEbv6DSIAYZQFBZ5R4ePuDlFkujiF87QUiDDShy1jlnNgLxZMACKhC6gAF4lflXuYA4goIDlGGmFTHGGASNZHP0/xgFSWdDYeuOq/3DEFE0YHiXHbTFZxwZYihggnydAsCBqAq4II8dyNNDlUoggJiTR/r+DwMIIhB8wANcnNipTOA+QG66cyjj7hQePoMbFvxmOPDBNSj86LgAuMIIJhVsEOsmSi9dWQZRqIGFziPDYLyjYrciqaEPgLOit5+rAGXHVRbjdzFAkKFNeuuV3Pa2kT4t2AkakWWaVqAeo4upqa66FzuorRaOLAyRJGwRTjnFgwJk2UWGHdLfIYUGIDa8ZAK4lHvcuhuI4IKhARc6cAw0Z9tXGLUhfv+ZVwPqZz/8YUCBGNif3vyHk9MAYAol0FilEECI7hUDEGcYgJpyQP8Q2UyKIQiwFG8wApXFza14DsvcAGwHLorAbQIM410ZZrAyHDquXoS7nfIOBAQAUEAA4igf2VoxAxX8Kx71KAe2QPC4XciiAsrQgDs0oAxATLEC65CFLCKwCzDKQhkFsAQneugXC8QvPBDAXANiMLgGFoBocmAR55TnHBasEXNvhOP+5Fi0/x2uIlfAGJNqQC06AOIU2pjF+NbIOxW8BmupUwAduuGr3OGqAGLjZCc9UDicJAoqE5BMw8aTg7qlsl4YgNjmBOlDjKTmCKOInyUaUYEvMkOXzKhbK6ywA2ZMowHTYBMz8pAHZvAjBRGAACU+oYFGuLATE1jDGnBhR7n/7ItOEwiPHL35RxfaLmYSg2VKCBA/EXTzm5sU2yfhFcoIVqEEODgBJA5Ch6XIgWEKrIACLxCDU+4gCwuYZF4QcJA3cE6bLCJAiwTg0Ic2FJ6dc44ANMnACrCRf388QB0BWM4fKm0UFFhDJw7gAQ0ErQJdFNo6KKHFT1CiANE8QCc6IYBqEoAC13QAAIBATou88h9zAABU0IkmESSVBQy1QKpAms3RsAipS6Wqi3oKIwooaUk1UEATLiACuWEOAvirQAEWeIF/5oEgBVUIbB7k1DgV1UMW2FdR6VrUXy1UAFN92QGUalVsPnV5qZElAGi5BgF0QgQH4AQLOmGJpT60/0UuGsURLGtZwsoSCBQRauc8tC8HUIA0oO2sYOta1NDStamlHeoKSrBVBaChrHxEKwT6186+4oqGIOiIQWHTBFsFVrAWIeppKXDc0X50uMtlbnOd+1wfahUH9txAGea1g3rt7a86Pe4A5VaGGujAIQfNUTG+xVrople962Vve596hRK4tgVNEEmxymMvQBLgbUcrKgHACgFJSIu8G7gDotx7YAQnWMELNsIKVnACrhI4ZS0rACiF+48gPucTdBAdbDYAhgpPdMEjJnGJTXwaXaxASS3wCBqS2IMIaE7EfiGqRS9gAgV5eAxvUu6JffxjIKv3YhgzwQI24OK2hBi9ov8hgBwUcRcPg+Fhpwpyla185eG6wcEQNrKLsYQvHxKVAHGA8kfukIK1XRjLa2bzlTUi30PGFgTtYxtQOasvabjBBBLo8pzTvOQ2B1rQCI7DPPcMGwWoIMa9AqmeIzyI8qR50JOmtI/jcIK6OAk2OyZQjw8UBxM8CQxIkXSlTX3q9qJEz00ir1cNDMs2nIADRh6EytRGZVTnWtfMRYkZOHCb8R4kC3JgCkgZwZGPqOAoF3j1rp39bOV1QxHSGiEJDWFCWPqhBYf8cCq9BUNoh1vcTikFgyQVC2wjzQ8n4PMGxmDfGBAoeeOmN70dYAFDYE1SeYFEKXKBtCm0gM+VLID/Udp3gBnXW+HQpoAIssC1fYu3BW54RDf84oAp4ADKTdCDGgDaMnkDeuEjX/N66OCxEDbkAxxoQZAYQaQjVcEIGVNQx7LgwgLG+Lwk57mzUfgJECALdc6CFrSk1TE4iEIbHhCrknv+dF2rBDoNAMNrht4srBu0a4nca1hjUFaEixzqY/cxzWJghTuMAQ6lW5YFEVCDGnxNFPm8XOaMJnay553EZreC4/51BzDQQfCG8F4xRNE/D/ynAGLF31ISrnfIs3k9jNtBDmYgO+LR9p+CQ+vXiWY7vEZe9IJWifwCN55U2q089VpmBIY2ADo+fvSzt7JR0US+xac1BbunbVlzUwUz/eKd9sM/cH/jx83E50r5ufqkX2PmaeJHP8j7Om53J3DOyeo0uXGSfvfbnJOiQt/74yd/+c3vF5T0Jv3nZ//s199++Mdf/rR///vnf3/8HyggADs=' usemap="#bug" border="0"></div>
+
+<map name="bug" id="bug"><area id="area" shape="default" href="#area_1" onclick="alert( 'click' ); return false;"></map>
+
+<script>
+var timer;
+var shiftKeyOn = false;
+var counter = 0;
+function focus_area() {
+ document.getElementById("pre").onfocus = function() {
+ document.getElementById("pre").onfocus = null;
+ document.getElementById("area").onfocus = function() {
+ clearInterval(timer);
+ parent.SimpleTest.executeSoon(parent.firstIframeLoaded, 0);
+ }
+
+ //XXX This code tries to shift focus to the image map for some reason. This is not
+ // working directly after page load, hence it is retried 10 times, see bug bug 922524
+ timer = setInterval(function() {
+ if (counter > 10) {
+ clearInterval(timer);
+ parent.ok(false, "Too often tried to focus image map, giving up");
+ parent.finish();
+ return;
+ }
+ synthesizeKey("VK_TAB", { shiftKey: shiftKeyOn }, window);
+ shiftKeyOn = !shiftKeyOn;
+ counter++;
+ }, 100);
+ };
+ document.getElementById("pre").focus();
+}
+</script>
+
+</body>
+</html>
diff --git a/layout/generic/test/file_bug448987_notref.html b/layout/generic/test/file_bug448987_notref.html
new file mode 100644
index 0000000000..476f47c984
--- /dev/null
+++ b/layout/generic/test/file_bug448987_notref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body onload="parent.thirdIframeLoaded();">
+<pre tabindex="1" id="pre">
+STEPS TO REPRODUCE:
+1. TAB to the image map area below
+
+EXPECTED RESULT:
+a focus border is painted just inside the image edge
+<pre>
+
+<div style="background:lime;padding:1em;float:left"><img src='data:image/gif;base64,R0lGODlhFAFuAPcAAPf39//7/+fn59bT1u/r787Lzq0UAN7b3hhFrRhJtRA0hBA8lMYYALWytffz94wQAMa+vb26vRhNxufj5+/v78bDxvfz772+vcbHxghRCM7PzggkYyFZ1tYkCNbX1hhRzpyenO+6AN7f3gBlANauAGOW7zFl1kp95wg8pbW2tZyanHOi797f5zlx3msMAAB9CP/PAL22va2mraWipedJMSlRtf91Y72WAFqK76WmpRBFta2qrcaeAK2urfdpUuc8If39/e9ZQmPTY94wGFrLWrUkEISq9/n5+ZR5ABCWGLWOAIRpAGNRAClJlK2GAL0sGGssITG2OaWCAK3H9848KaW+762qpcbX/73jvWN5pGvTc/eGc//vCFppfIwgEJRBMYSGlPeWhJSSjHN5jMaSjKWmrb3P787V1ilBa4R9c9nXy+rq6ntNQu/y95Su3svO1kphlISa1t7b1vf7/3uGraWGexdAmoySlDm6QrWyraWqrRiiIWtpa0LDStrl/1JtrcbHzpxlWntlUklVcztZk3vbhISStfffWufr786upVmB1ta2taW21tLS0ve6rd7j5zlhtfemnJSetefn78LCw7W2vdnZ2bVFMf/3//f3/8Z1a9bb59bb3eHh4a2WjM7HxoR1OSGqKbW6xvT09JyGQrXD3v/r5+fk3s7rzufb3q2ytffXKbWiY6inp1p1WrWmjP/LxnOOzsC/v+/n56qSMffXzr2eEP/3hPHx8f//987T57m4ubVZShk5f97LjO/HKQ03ks7DpdZlWv/vMd7LxqWuvefr/86uKd66QgwyhcnJyf/ztbKxsrWqpe/37//z7///75zTpf/397Szsvfn1ozLjO/jlN7v3qWko62OEO/r5//73tbX3vf37wotd1qqYxlNvYyujN7X1qKgoHuWe5KUmkKOQhZKvildMRh5IZqZmcXEwZ2amaajoNfU0QMaTAsiV5STlSo+bZibpSti2puZl5u28pGs5pqbn9Hd8/fr94WFi7i1sBguYfP09v///ywAAAAAFAFuAAAI/wD/CRxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iDYinEtJAWLUSIfJv6DVFShnOMXTEzpYrXKlPMXDGW6apZhli0CHmqtk+oJC9ejDDH6azBTGaMKDLBQUKCvzoC/5XAwcSJElV0UbB71lk0PFH6CGmqxS3cuZgyAwhw1tiUEiY+CEZBunTpwR9SfyisiNGmzYyNYqlWLZrtak6JRLmcoQKBCQQswEZ6ZcUJDn0RoFiwoAkhOH/+wCHURDkKHRIII0e+uoWbSbGRYv/SFy0qnj1yM6ArUEDDAQEOOBu9UqIF3xrMCWWRBGiAnP8eqKEBBpVIkgUhyg22nWocxKJLeEMBYIEAqVSDBx5vjTCCeinEgIEH8Mk3lB/G2YffAlmIcoAIcgxYAQQwXiAjjC+WkUV1qHH3QXaKbAIhUAAQwMIBWkCG3obq9dBABQcER1QmVeBwQgv4KZAFICIMgEGMEXTp5ZcNhJmCjQlmp5pfKCBgyHA/6hQkC6lUdl56Gbgiw5IiUCAiUCRKaQICChBSSYsydhiDly9ioOiiMKYQZgMqEJLmYH4pp4ACTRzQ5k4AOEABAYUQ4RaddqYwAHxDmVGClJAssMEYA8b/IGaHELQ3QIAriiDCAQcM4OIFYVqRRZkJILCAAhtsAMcEbG5q02YOFCLEqEiWCuKePn1WXw3IgqFBBEoqeQEG7uka3KfCWYCuABP0qgEEjt5RQ2DGIrvBIAOw4KSzOWEiLbUZZJAGnnoGVUUJ2yJ7BwYN3NlABBX4J0BwAAzH2cWcefqbBwXAq0INy9k7SB7jNmkBvzj5O20odA4MwQHCAaXtCdy+eoEMOVjRQK3vFYzxQp0KyfHNTYgMwgw7XHAqbAFgi7JLAPzLMpIu5/nzTjPX3EUEOSC98wDANftQ0FlGUPS9KqSd9KkOPG2TygBnAEYDGLAQn09+IIyDCa6i/9HDDEenUECTd1sUdBbIdgFC2jPQbbLbNUW7Mp13OF74TpmssOqfyd7RdQ6C9+w0RaXUQAgdeQDeOJOoQk6TBW0dGXDlBTDrkxEI04zsIDIEXve+Gm2yySkDRLCDzqxf7npMsE8rewZihC72TXmvsLerG4hxdA/JX50RAQPACzHhoy/fEgCx0xl97dPbZITmVO7euxVKt95RkAMUQC755tPkTPpIUoHgbLeT4liPb8kCg+/sVr6LAIAC7VoR8Ponk/85j04CZF8DYzIHI+AufhtAw+K4x7+PBIkAE6RgBdUypwDGQAP2wwmJjHC9xB2tATBs30YqtsGIPKMWjgjDFv+GuIUwRAIWpiAJNLaxjFsc4olPvMUytvETC7YwYBmMoUOo4YhICLGIkXAENbxXED6Y0QMs4MNmRKBGhFThfTTDXhqQxiSf8cQUkfABDYYwhA50YAg/CCQNaBAEYYQhFR6BhhN/EYJGOjIEMIgkDH7hC218pA1+uMIUNsnJrnzFgx6MgyhjEQtGFOQaULliBrKoQ4PUIgxBCCQgfzDIWhpyFghRFx9iYIED8AEC/8AAHyZwEGN4sD6ACqEKGqcBAu6EGlvYYwcYYIAiPOGaRfgjLWnwAz4KA5EZ2cYhYBACEvDgBjewhTp5QAISNDKSjUTGKTKSiSvQ8AT4PAEOEMb/TynZpy9oQoECFiAJO/4DlURQJSsbUgsb/MCPDCiCRLVZy4cOgQzt48MF/jGBHKQhABAY5kHyQsM/IUuEMqifxW5iijBIs5qaSMQniKGBRSRCE0/QpiwZ0AFNGDQi0BhnCHighGywIhhqENAngsEKW7TzkeX0hfIgYszjZEc7htlnCTTnp+SEbAF0IOZKEapKELxQiwYxRTSHINFLkCERNiUDL3I6yz4y4BLgLCMw28WHBhCADxo4yButx4Fj3StndezhS2Dhgx9QoQheIMMB2oCIR/DqDJi9QCCy+Ue7MoAK4qDIMoYBAxLcIBuvyNMkhjSAW50BAqxg5yPbeYx5/0ZkDlWwKgL+IAlGMCIOfyhMC/JpHwlAgg5ZSO6VJjQB+AyHrLxZZQSWphDGOrYIX0gEIig7JE6coRmX4Gkfp8mAJxDDIHzIQTAJALh/DMwgDvCgcaq0gS7ciX06iUQQaEAFA3zhE23olf629KIZeaIIPIUoAyK6CIncgguldQIt1EAAAbOnwDN6hVPdWU5z2kINECGRfXb7BnURYBbEKwYkCGMCvlRKEi1qbf40ADb7oSKV0TUrdRHiiP32NxCPQATHLkwjUQQiweQ1wBPyOpCV/iMTBDDIDOuTgEu9am0EcPJM8hiE/n5hRQOmEYExfOAFK9gABmgwGQ/y4AiTgv9XYU4Uo7qUgw2305znBHFD6HOCP9FhQu4acAEu8AfsXLVedADWocZVADT67MYJzfF00SoQR/igywYA8gHijGFZ8QLJ1DTAJWImETOs4ICAQtYYSLgYnHCZCl6AQqO5FDEaB8hXHYOAJxbM61Ab4BMOaTMJpAAKGtPaVrf+1SvYeWcenDMbelYIfXDQgt1qw8IVmHMKCg2YeilgbrRqT54KB2k8SHrHBWGsHrEboGPTWAP6AxYV/DhNAzzgARjV8kKmcOq9pXoDc0vsTRxhg3U/YAZvMFStaswuFBIgS+zRRK8XjOYlN+0fFzfIg7kQAicsoRkJDzfDHT6BIXWMFXf/buc5lUALZ0pZc3F8WYHJ9e5496AGVy3WsRZQBgzYKmwiKve50WqKggfhCQf/lsjNxS4Bk4GP9b63Fw4wVYZU4dTH+XfAGWiThtrg6A9gA8MakAKeOVc+QdsYIMLbazQ/IBA8RMgyuADhGywBFIAQEwTcM7HhVMwCQoPAMVK+cimwour/6ODmFgAHpTN6Rc01eQEkcdVKHavxBwD6nlAhhEhjcNJODkPBqRD2vD8sYvoSTsU0doAKvPQBLoh9HdqA+IRcvQTHOVa3lsR1mmBiC6P3ggvqEIMdPGxp7evUBDjRjIm7/QGJqBhChgHhYTMhesaHAPJHp3wPJCLl5lSC/xOkEIyqZ+3b4kM9xRygMY5l4Uw6V0AxZkGB6XFeN0NfKSxsYAMaPMEFaSAK2XcqqlcQb0IDHVAE9xZ7LgAFJdQQuENthWVlYHBWrcQSBPd1SAcFObM2vZcQDiAAnPBpZ2ZvX/AIpEYQGxcCdscEHThpKZhLIogMjaRyNzB+Lac8bXBqoOEqlcM920cQQXIKosABqhF/y1J/B3F/u/F56CYQwOcDsOYCYiAuT2gQ4jAE1AR7DCgItRODC2EcfSYB9WIz+EUTagV8NFAELsAGXWM5ivUPnpII9FZv9vYAzZBlewIN1BdhSyAIZQA6XxiHExIMjmRONygFSBABeohxAf/Ab9ZTM8sEhwfxJopwhPWCBmdYEEyYfwSxfxr4AFAAAshjNQjxDJFAA1sYe1AgCFWoUg9RH8VVhluziTJBcFvQZcLnhikFNhdIEL00bxD1fEDWiBhnDXQHAzyABEwAiPQzAFn2EBB0DDVoWuOHBG8Wjf8AAPKlO9mDZToUJG6wGjvibXqQJwBgEJ2IQTHwhJHAf2soioEIegfRUgjIALAHBWyQBmkziTn0EIpQGMlhWHAAgzUxRKHoAs74O3EoEBLydOO1hQ/gBb5xMgNBfRx3A8w4RwTTkNtIAL5wiESliEuAAQTEAivwQclEOzDTQBVzBnwxkAqTL2KDBZ3XhEj/AgL0KBDPwH9SyIYcyGrYAgtb8FAdYIJ1ECb9qAK9SGkIEZDc4W1oMEC1txKpYANqqIAKqQem4pQJAQDEIEtRB3ueoC/p+A/UkIwkoARLwAQc+Y8QMQdqIJLihwRLkFrRqAv9ZlIARzcuV4kEsBd8UYaw0pIFYZP454Ta+A+14ANf11/y6Jd+Z2l2ZQBeoAmLMCTFdzQ54yEPuBCxsBpGmADMoQB+M4gzgYs/CXuC0AOwGJcCEEvdJJEuEAhnQEDIWH0e55bgGBEAMAm/QE7ldIN2WQe3mY6Q6G+qZoEMIQ1x0AItwAFVpmrat5gCgZg4iUU7+Q+L4JhHZ28ceFYC/2GPduhWTcICHdMwxuchfFeVBhEHoika3rYwpigToveY9qaQqvAyP8UQADAHmjBIfXSHbTg4SngIEOaHvMmfHtlkviCc1miXeNckuXAwuMcByVSLXkkQjIBPfGmGWoSdnigQ76iB4FkGF8ACbaAGRVlvRYCZiCBk8YYo4tZc7mkQ48gd8acAY2Cga6YSFgB8Jsqa+zlZDToQiTBID0WgUECPq5CgPLCbVWOREvGgNUicd6dSFhqdqTYytpgQb+AnydSXqDkQIqqYw3Gf/mdvXsCVcsVroiZZlOUrMAIBFwAxjSYCfXek/2AGC0KGpUkI1cmnIAEA/DekbVikVBoRxP8QBPvFVgsIBQ2AX09KTlEqoYpKEWoATxG6BEswqcxSCvjEpbqnB1+KELqAA8rZOXlQptd5k9GlAtO1mKKnR0/ApoLwBUVwCbygCTDaBiyQPwXwIrXSaD1TgBMxCaFhhJZ3KWUAjb9oEhZwqGsKe2yQqRNxCpe2RwqYjzKAAZPVDasAT5d6d9gqEdoQnI1Uri4IrtJgBtAZGqR5Kd5SnwqxCWKaLCLUkQRxpi4ErQMRBj5gq3c4MNQFADJKZLVCYzYarQxxicyqc8dSmDcaEZm0FVeQsRqrsVyxSa4hEATgmATrreBasQdBAUb3A93agDnAnxYwrpbqBJhaNyYLX8H/GbNt2a5UJwAxKZ/HQgg+yhCpGkfJYl/c85f+qp3oJrAj+3bqt3z5oygQUAHtkXkUQ6gHkaM7AqjH0gQClxEkMiXQqU9bxYMSSIaB8gggK7IIeIccSLM/qhBBemkqG6m9SAEvS65KYJdL0LL2ChEAALNDJQU5e7fScImpwbUKoAdNolilQG01kz3MxCx7krSr1I7GyLRte29sIAqDoyubpii2kno1+xC6AH8SqwCG0LgYIWIthhwmMFwlAErGYQJoCwdRBi0pe5QLKAZwSxFbMLB1y4o5AIM3O5wkqQK/KxGZME4424zgWAqVh4QU9otuEJ3Yozgz0JVnaaaw6oQC/9C9+rVfvDuR3+ofvXIrHiACmmdCLYC69QK0G8oQxrACRsgBNZC/NQAJfxALp/Y+EggomTIhemKojrqkCxgIWKKEE8G0w5uoFogMnIqldfC1vum8LGiX0AuDFsBt3cYchkB/G+QAe1Mzg6A2DHqY3/uvi7kIB3yUd1jBEsMue6pvGzGOiWt5xxLC/ekQmVAKjFAKb4BZZwBvtuIG+yQl0ukqXdAiEjQHLsVNMHxvXzCoFOHAWqmQPSCeVoq8EmrFE4HBGskECwqtACC9gmEdKOC1YFgQVQBCg7A4ZuWqr5qYATSr3VsLSjqWVQw2eBsfDosRB8CsOWcszVEA1lkRz/8ggjMKAc85JbZrWFtjdnOwCGIpkV4AqoFcEALbZVnsMtdiDXRJkg3ANhMxB867ljn7ih5Qf0EiCWqsxjXwBrmQEFeQe/fSO8sExv26wkq7mAQgm3Z4b2U5vyYUB9NbLPWSBSJsEZ7SLh2jaBfwnP/0b11weu8BAKnQTZUZw8dJqMGLaaxIiimqJ6cwynbpCcc5EQCQyoRLxuRMdZwhJHRgGsyxAChQA6YEX7llUmMQA6pDld5jubIKrfIRAJoAdRRngt+cEgAwAABVyIZcUJtsgCEIcYxSAG7goRhqZV1gfPg1B8JAb6FGxW9gpMxbcP7HhQtppJlAgx12A3VJCif/bQF8GriWyoxMkF47IHAhqAaGsHP3jM860BqbMAmT4Aef0WeAIgkF8DeCaJgGQdB4fNCL8EdJdoeeMAmLahISEgeG5hcf3AQlhrVyCEGg2ysrwgirwqUeLQM7SYcleG/NkM0SQQAqncXao8mcYYgddk4yiwR5QKESIa7kxJZkvNeo+ZsH8AllAAeXcinMkQAf0AI4cGowZwLcogfiMCbI4wGJ7L12HDA6Zp0CIIxwytAMjBIAcACQENbdphyQIAI27J8ag0IUkNsWMDMdnTgPw7r/gAhU0HZ3aJvG7EqXNoVtGDim/A+TMHg2WJeswA3HXRBzF2FkLAjMHb5oBz4Q/5AHdwAHaBDZKCABLTC7AKzZCkAHcgAuSgKuXknVANtkZMBTqW1vgbBdZk0RQSIKlBLbyvEHoe0QsDFVpnah1syviZcIzsemEfCXDaFfKx172mO4DhkMzVZ4iwjhDIGMLLjK8bzacigAagAvVgACdzAGKk4HkhAHKbktC4AGhYLNAy7a2ZkBpb1SckBe920A+aYSEGQIggEY3YYCzOwRB551Hr0kB1AwH/lp9/0AvKAvDYqyuhh7fLA9ZRokyIBnhWd4R92gAEBa1ueWk0jHATAh4ZMHUN2ZDaBVSt4EjnJ8dq0QlquTT+gAAFDfDY5mP87a4JMFQ07kf4ECAt4RSf/e2/XVqvXZKZ+AYAvtdp5Ae3FbED0Wj8u9y+jm6LbgbM4m0+OnXaXr4Yit3ZqOVmnOyCaOMw1gCMPVZ1WCBp0ZtAtx59spEA7AAmzX5xilEs7wCAXwB5NC6IABCdzAEYlOi4y+qBPyCmj27M9eBJ8gfQ1BDbYqfKN45vaa6omATt4O6tmgATW7DaQ1xmzAOAoOXxAUuuJjCLHLF/P6bSRzLQ3hryOADjlugASwCJA+cbwmDLg0NrDwDBZxBLgwAcoACcNO7NjhBrWcEcm+5BWw7b9RB9AO7XiVcQohDQ71BNjej03ZvQ5JAJvwCt/+7bRwAHNQ6QJBWsvIBOc+Qq//mRDqMgG7MgAHcL3/xLVNYKofaOei8hZyoSH5XhAQRAYX7+9P0OtA4wjCoGYFTwFysA7CniYIcPX/XdTHjhGqsjcSYFiLbsEB8NMWf/FodgnntRBF9wMe34Zdo+1dLRAQdAavoAR2f/d2n/IMEVSDywSgoMuTG/cI4Sm6fQb+lBxXn8+VIBxxiAVGMvQaQg7w7TRBwgllD+3+TgVkQAxT9QywEAaPBfUXYfAHQAlZcM92gPX/LQFxoLYWYQZTUgNjGvbb3imbVgde8ABmXwRMfxA9NgRIV5ubuTqEre6b9grZMH5OcPfpJFUJsQyrMFR2GYC9Q/w1jhD/2SlxMCUA/9VtCeDwDqEFewAXkD8C6RAOL4MJJzskl5/0vdYBPyAMmoCQQaCF1XReFVPRBwEEFAAQB9aBaaJgwQI7CBQmYChBB4dYugD8o1jR4kWLAIyYqIFAwQaQG7rkqSDCQoCLAAR4AJTjywOYBmTKvESmFgUAAUzVihRkSIciD77keNNjRo4GEDwIyImRIgACLK2QklLViRMlN3gc80Xt5D9o226tCkFCCRJQzQAZnZF06USncS/qamGCg0OGeXXEgiv3H5Y9SV6MGJHBMOERL9KZM/fNQ0YCB9548gLzwcyZDDR30NxZplBx/yyMburX9D8gQHAd+NQKzgKDBxUiyCvhw/+HFnHMEEBpeo6fKjg4Iui1od9xkSRN9rboQMCACpXqvLScmQEVKjR+DNEs84snD28ayJDhVoAD5k6dQ68khhQS+FWz8tiK7NevsjxuSKH1akCB8cqrYIDz0jsNIzc+wEsvhlDIggC/rtnjhcEMs/AwxNKJpq9/AlBJqkAqsywmzDCDyYs6mJpgAgEI+OpAv1IbpRNl+LmDEAVyjG2h2m7j4IRY3KjCjCuKvMKMKYwo4QS7JPBoA282kGeQLsAAYYeSXkxpAvb0qIMNEUks0YAvAklkAhYwSKGHHlIYsMCKDKzIAi6j08M9UOBDQr4b+lTiT1pYCQZNNdl08y05T2v/ww8zSnEjjo5moy2vBFCAZBO5UKkmnHDIScMVUEN1hRxywhEAE/TSWy86FcB8wIURY4UJijo0mOSAAQbwQASmOIQxLiBGmcCdCGQAA0cdd+SxIQlsu+3ZZ5utdIEmCOnijnLG0VbbHjBgQUvI6lxTDxDqCISNL9JNlxdNyFiEBUS4hKABeiPAYCn0DgSATpbGVUGMOkgRmBRaAmUlkU9YZEGD6Oi9oAB8A0gUo0yuqGIFHOq6SwcUDvJ4WR0qrYEbv6DSIAYZQFBZ5R4ePuDlFkujiF87QUiDDShy1jlnNgLxZMACKhC6gAF4lflXuYA4goIDlGGmFTHGGASNZHP0/xgFSWdDYeuOq/3DEFE0YHiXHbTFZxwZYihggnydAsCBqAq4II8dyNNDlUoggJiTR/r+DwMIIhB8wANcnNipTOA+QG66cyjj7hQePoMbFvxmOPDBNSj86LgAuMIIJhVsEOsmSi9dWQZRqIGFziPDYLyjYrciqaEPgLOit5+rAGXHVRbjdzFAkKFNeuuV3Pa2kT4t2AkakWWaVqAeo4upqa66FzuorRaOLAyRJGwRTjnFgwJk2UWGHdLfIYUGIDa8ZAK4lHvcuhuI4IKhARc6cAw0Z9tXGLUhfv+ZVwPqZz/8YUCBGNif3vyHk9MAYAol0FilEECI7hUDEGcYgJpyQP8Q2UyKIQiwFG8wApXFza14DsvcAGwHLorAbQIM410ZZrAyHDquXoS7nfIOBAQAUEAA4igf2VoxAxX8Kx71KAe2QPC4XciiAsrQgDs0oAxATLEC65CFLCKwCzDKQhkFsAQneugXC8QvPBDAXANiMLgGFoBocmAR55TnHBasEXNvhOP+5Fi0/x2uIlfAGJNqQC06AOIU2pjF+NbIOxW8BmupUwAduuGr3OGqAGLjZCc9UDicJAoqE5BMw8aTg7qlsl4YgNjmBOlDjKTmCKOInyUaUYEvMkOXzKhbK6ywA2ZMowHTYBMz8pAHZvAjBRGAACU+oYFGuLATE1jDGnBhR7n/7ItOEwiPHL35RxfaLmYSg2VKCBA/EXTzm5sU2yfhFcoIVqEEODgBJA5Ch6XIgWEKrIACLxCDU+4gCwuYZF4QcJA3cE6bLCJAiwTg0Ic2FJ6dc44ANMnACrCRf388QB0BWM4fKm0UFFhDJw7gAQ0ErQJdFNo6KKHFT1CiANE8QCc6IYBqEoAC13QAAIBATou88h9zAABU0IkmESSVBQy1QKpAms3RsAipS6Wqi3oKIwooaUk1UEATLiACuWEOAvirQAEWeIF/5oEgBVUIbB7k1DgV1UMW2FdR6VrUXy1UAFN92QGUalVsPnV5qZElAGi5BgF0QgQH4AQLOmGJpT60/0UuGsURLGtZwsoSCBQRauc8tC8HUIA0oO2sYOta1NDStamlHeoKSrBVBaChrHxEKwT6186+4oqGIOiIQWHTBFsFVrAWIeppKXDc0X50uMtlbnOd+1wfahUH9txAGea1g3rt7a86Pe4A5VaGGujAIQfNUTG+xVrople962Vve596hRK4tgVNEEmxymMvQBLgbUcrKgHACgFJSIu8G7gDotx7YAQnWMELNsIKVnACrhI4ZS0rACiF+48gPucTdBAdbDYAhgpPdMEjJnGJTXwaXaxASS3wCBqS2IMIaE7EfiGqRS9gAgV5eAxvUu6JffxjIKv3YhgzwQI24OK2hBi9ov8hgBwUcRcPg+Fhpwpyla185eG6wcEQNrKLsYQvHxKVAHGA8kfukIK1XRjLa2bzlTUi30PGFgTtYxtQOasvabjBBBLo8pzTvOQ2B1rQCI7DPPcMGwWoIMa9AqmeIzyI8qR50JOmtI/jcIK6OAk2OyZQjw8UBxM8CQxIkXSlTX3q9qJEz00ir1cNDMs2nIADRh6EytRGZVTnWtfMRYkZOHCb8R4kC3JgCkgZwZGPqOAoF3j1rp39bOV1QxHSGiEJDWFCWPqhBYf8cCq9BUNoh1vcTikFgyQVC2wjzQ8n4PMGxmDfGBAoeeOmN70dYAFDYE1SeYFEKXKBtCm0gM+VLID/Udp3gBnXW+HQpoAIssC1fYu3BW54RDf84oAp4ADKTdCDGgDaMnkDeuEjX/N66OCxEDbkAxxoQZAYQaQjVcEIGVNQx7LgwgLG+Lwk57mzUfgJECALdc6CFrSk1TE4iEIbHhCrknv+dF2rBDoNAMNrht4srBu0a4nca1hjUFaEixzqY/cxzWJghTuMAQ6lW5YFEVCDGnxNFPm8XOaMJnay553EZreC4/51BzDQQfCG8F4xRNE/D/ynAGLF31ISrnfIs3k9jNtBDmYgO+LR9p+CQ+vXiWY7vEZe9IJWifwCN55U2q089VpmBIY2ADo+fvSzt7JR0US+xac1BbunbVlzUwUz/eKd9sM/cH/jx83E50r5ufqkX2PmaeJHP8j7Om53J3DOyeo0uXGSfvfbnJOiQt/74yd/+c3vF5T0Jv3nZ//s199++Mdf/rR///vnf3/8HyggADs=' usemap="#bug" border="0"></div>
+
+<map name="bug" id="bug"><area id="area" shape="rect" coords="0,0,275,109" href="#area_1" onclick="alert( 'click' ); return false;"></map>
+
+</body>
+</html>
diff --git a/layout/generic/test/file_bug448987_ref.html b/layout/generic/test/file_bug448987_ref.html
new file mode 100644
index 0000000000..862cc4695c
--- /dev/null
+++ b/layout/generic/test/file_bug448987_ref.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body onload="focus_area()">
+<pre tabindex="1" id="pre">
+STEPS TO REPRODUCE:
+1. TAB to the image map area below
+
+EXPECTED RESULT:
+a focus border is painted just inside the image edge
+<pre>
+
+<div style="background:lime;padding:1em;float:left"><img src='data:image/gif;base64,R0lGODlhFAFuAPcAAPf39//7/+fn59bT1u/r787Lzq0UAN7b3hhFrRhJtRA0hBA8lMYYALWytffz94wQAMa+vb26vRhNxufj5+/v78bDxvfz772+vcbHxghRCM7PzggkYyFZ1tYkCNbX1hhRzpyenO+6AN7f3gBlANauAGOW7zFl1kp95wg8pbW2tZyanHOi797f5zlx3msMAAB9CP/PAL22va2mraWipedJMSlRtf91Y72WAFqK76WmpRBFta2qrcaeAK2urfdpUuc8If39/e9ZQmPTY94wGFrLWrUkEISq9/n5+ZR5ABCWGLWOAIRpAGNRAClJlK2GAL0sGGssITG2OaWCAK3H9848KaW+762qpcbX/73jvWN5pGvTc/eGc//vCFppfIwgEJRBMYSGlPeWhJSSjHN5jMaSjKWmrb3P787V1ilBa4R9c9nXy+rq6ntNQu/y95Su3svO1kphlISa1t7b1vf7/3uGraWGexdAmoySlDm6QrWyraWqrRiiIWtpa0LDStrl/1JtrcbHzpxlWntlUklVcztZk3vbhISStfffWufr786upVmB1ta2taW21tLS0ve6rd7j5zlhtfemnJSetefn78LCw7W2vdnZ2bVFMf/3//f3/8Z1a9bb59bb3eHh4a2WjM7HxoR1OSGqKbW6xvT09JyGQrXD3v/r5+fk3s7rzufb3q2ytffXKbWiY6inp1p1WrWmjP/LxnOOzsC/v+/n56qSMffXzr2eEP/3hPHx8f//987T57m4ubVZShk5f97LjO/HKQ03ks7DpdZlWv/vMd7LxqWuvefr/86uKd66QgwyhcnJyf/ztbKxsrWqpe/37//z7///75zTpf/397Szsvfn1ozLjO/jlN7v3qWko62OEO/r5//73tbX3vf37wotd1qqYxlNvYyujN7X1qKgoHuWe5KUmkKOQhZKvildMRh5IZqZmcXEwZ2amaajoNfU0QMaTAsiV5STlSo+bZibpSti2puZl5u28pGs5pqbn9Hd8/fr94WFi7i1sBguYfP09v///ywAAAAAFAFuAAAI/wD/CRxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iDYinEtJAWLUSIfJv6DVFShnOMXTEzpYrXKlPMXDGW6apZhli0CHmqtk+oJC9ejDDH6azBTGaMKDLBQUKCvzoC/5XAwcSJElV0UbB71lk0PFH6CGmqxS3cuZgyAwhw1tiUEiY+CEZBunTpwR9SfyisiNGmzYyNYqlWLZrtak6JRLmcoQKBCQQswEZ6ZcUJDn0RoFiwoAkhOH/+wCHURDkKHRIII0e+uoWbSbGRYv/SFy0qnj1yM6ArUEDDAQEOOBu9UqIF3xrMCWWRBGiAnP8eqKEBBpVIkgUhyg22nWocxKJLeEMBYIEAqVSDBx5vjTCCeinEgIEH8Mk3lB/G2YffAlmIcoAIcgxYAQQwXiAjjC+WkUV1qHH3QXaKbAIhUAAQwMIBWkCG3obq9dBABQcER1QmVeBwQgv4KZAFICIMgEGMEXTp5ZcNhJmCjQlmp5pfKCBgyHA/6hQkC6lUdl56Gbgiw5IiUCAiUCRKaQICChBSSYsydhiDly9ioOiiMKYQZgMqEJLmYH4pp4ACTRzQ5k4AOEABAYUQ4RaddqYwAHxDmVGClJAssMEYA8b/IGaHELQ3QIAriiDCAQcM4OIFYVqRRZkJILCAAhtsAMcEbG5q02YOFCLEqEiWCuKePn1WXw3IgqFBBEoqeQEG7uka3KfCWYCuABP0qgEEjt5RQ2DGIrvBIAOw4KSzOWEiLbUZZJAGnnoGVUUJ2yJ7BwYN3NlABBX4J0BwAAzH2cWcefqbBwXAq0INy9k7SB7jNmkBvzj5O20odA4MwQHCAaXtCdy+eoEMOVjRQK3vFYzxQp0KyfHNTYgMwgw7XHAqbAFgi7JLAPzLMpIu5/nzTjPX3EUEOSC98wDANftQ0FlGUPS9KqSd9KkOPG2TygBnAEYDGLAQn09+IIyDCa6i/9HDDEenUECTd1sUdBbIdgFC2jPQbbLbNUW7Mp13OF74TpmssOqfyd7RdQ6C9+w0RaXUQAgdeQDeOJOoQk6TBW0dGXDlBTDrkxEI04zsIDIEXve+Gm2yySkDRLCDzqxf7npMsE8rewZihC72TXmvsLerG4hxdA/JX50RAQPACzHhoy/fEgCx0xl97dPbZITmVO7euxVKt95RkAMUQC755tPkTPpIUoHgbLeT4liPb8kCg+/sVr6LAIAC7VoR8Ponk/85j04CZF8DYzIHI+AufhtAw+K4x7+PBIkAE6RgBdUypwDGQAP2wwmJjHC9xB2tATBs30YqtsGIPKMWjgjDFv+GuIUwRAIWpiAJNLaxjFsc4olPvMUytvETC7YwYBmMoUOo4YhICLGIkXAENbxXED6Y0QMs4MNmRKBGhFThfTTDXhqQxiSf8cQUkfABDYYwhA50YAg/CCQNaBAEYYQhFR6BhhN/EYJGOjIEMIgkDH7hC218pA1+uMIUNsnJrnzFgx6MgyhjEQtGFOQaULliBrKoQ4PUIgxBCCQgfzDIWhpyFghRFx9iYIED8AEC/8AAHyZwEGN4sD6ACqEKGqcBAu6EGlvYYwcYYIAiPOGaRfgjLWnwAz4KA5EZ2cYhYBACEvDgBjewhTp5QAISNDKSjUTGKTKSiSvQ8AT4PAEOEMb/TynZpy9oQoECFiAJO/4DlURQJSsbUgsb/MCPDCiCRLVZy4cOgQzt48MF/jGBHKQhABAY5kHyQsM/IUuEMqifxW5iijBIs5qaSMQniKGBRSRCE0/QpiwZ0AFNGDQi0BhnCHighGywIhhqENAngsEKW7TzkeX0hfIgYszjZEc7htlnCTTnp+SEbAF0IOZKEapKELxQiwYxRTSHINFLkCERNiUDL3I6yz4y4BLgLCMw28WHBhCADxo4yButx4Fj3StndezhS2Dhgx9QoQheIMMB2oCIR/DqDJi9QCCy+Ue7MoAK4qDIMoYBAxLcIBuvyNMkhjSAW50BAqxg5yPbeYx5/0ZkDlWwKgL+IAlGMCIOfyhMC/JpHwlAgg5ZSO6VJjQB+AyHrLxZZQSWphDGOrYIX0gEIig7JE6coRmX4Gkfp8mAJxDDIHzIQTAJALh/DMwgDvCgcaq0gS7ciX06iUQQaEAFA3zhE23olf629KIZeaIIPIUoAyK6CIncgguldQIt1EAAAbOnwDN6hVPdWU5z2kINECGRfXb7BnURYBbEKwYkCGMCvlRKEi1qbf40ADb7oSKV0TUrdRHiiP32NxCPQATHLkwjUQQiweQ1wBPyOpCV/iMTBDDIDOuTgEu9am0EcPJM8hiE/n5hRQOmEYExfOAFK9gABmgwGQ/y4AiTgv9XYU4Uo7qUgw2305znBHFD6HOCP9FhQu4acAEu8AfsXLVedADWocZVADT67MYJzfF00SoQR/igywYA8gHijGFZ8QLJ1DTAJWImETOs4ICAQtYYSLgYnHCZCl6AQqO5FDEaB8hXHYOAJxbM61Ab4BMOaTMJpAAKGtPaVrf+1SvYeWcenDMbelYIfXDQgt1qw8IVmHMKCg2YeilgbrRqT54KB2k8SHrHBWGsHrEboGPTWAP6AxYV/DhNAzzgARjV8kKmcOq9pXoDc0vsTRxhg3U/YAZvMFStaswuFBIgS+zRRK8XjOYlN+0fFzfIg7kQAicsoRkJDzfDHT6BIXWMFXf/buc5lUALZ0pZc3F8WYHJ9e5496AGVy3WsRZQBgzYKmwiKve50WqKggfhCQf/lsjNxS4Bk4GP9b63Fw4wVYZU4dTH+XfAGWiThtrg6A9gA8MakAKeOVc+QdsYIMLbazQ/IBA8RMgyuADhGywBFIAQEwTcM7HhVMwCQoPAMVK+cimwour/6ODmFgAHpTN6Rc01eQEkcdVKHavxBwD6nlAhhEhjcNJODkPBqRD2vD8sYvoSTsU0doAKvPQBLoh9HdqA+IRcvQTHOVa3lsR1mmBiC6P3ggvqEIMdPGxp7evUBDjRjIm7/QGJqBhChgHhYTMhesaHAPJHp3wPJCLl5lSC/xOkEIyqZ+3b4kM9xRygMY5l4Uw6V0AxZkGB6XFeN0NfKSxsYAMaPMEFaSAK2XcqqlcQb0IDHVAE9xZ7LgAFJdQQuENthWVlYHBWrcQSBPd1SAcFObM2vZcQDiAAnPBpZ2ZvX/AIpEYQGxcCdscEHThpKZhLIogMjaRyNzB+Lac8bXBqoOEqlcM920cQQXIKosABqhF/y1J/B3F/u/F56CYQwOcDsOYCYiAuT2gQ4jAE1AR7DCgItRODC2EcfSYB9WIz+EUTagV8NFAELsAGXWM5ivUPnpII9FZv9vYAzZBlewIN1BdhSyAIZQA6XxiHExIMjmRONygFSBABeohxAf/Ab9ZTM8sEhwfxJopwhPWCBmdYEEyYfwSxfxr4AFAAAshjNQjxDJFAA1sYe1AgCFWoUg9RH8VVhluziTJBcFvQZcLnhikFNhdIEL00bxD1fEDWiBhnDXQHAzyABEwAiPQzAFn2EBB0DDVoWuOHBG8Wjf8AAPKlO9mDZToUJG6wGjvibXqQJwBgEJ2IQTHwhJHAf2soioEIegfRUgjIALAHBWyQBmkziTn0EIpQGMlhWHAAgzUxRKHoAs74O3EoEBLydOO1hQ/gBb5xMgNBfRx3A8w4RwTTkNtIAL5wiESliEuAAQTEAivwQclEOzDTQBVzBnwxkAqTL2KDBZ3XhEj/AgL0KBDPwH9SyIYcyGrYAgtb8FAdYIJ1ECb9qAK9SGkIEZDc4W1oMEC1txKpYANqqIAKqQem4pQJAQDEIEtRB3ueoC/p+A/UkIwkoARLwAQc+Y8QMQdqIJLihwRLkFrRqAv9ZlIARzcuV4kEsBd8UYaw0pIFYZP454Ta+A+14ANf11/y6Jd+Z2l2ZQBeoAmLMCTFdzQ54yEPuBCxsBpGmADMoQB+M4gzgYs/CXuC0AOwGJcCEEvdJJEuEAhnQEDIWH0e55bgGBEAMAm/QE7ldIN2WQe3mY6Q6G+qZoEMIQ1x0AItwAFVpmrat5gCgZg4iUU7+Q+L4JhHZ28ceFYC/2GPduhWTcICHdMwxuchfFeVBhEHoika3rYwpigToveY9qaQqvAyP8UQADAHmjBIfXSHbTg4SngIEOaHvMmfHtlkviCc1miXeNckuXAwuMcByVSLXkkQjIBPfGmGWoSdnigQ76iB4FkGF8ACbaAGRVlvRYCZiCBk8YYo4tZc7mkQ48gd8acAY2Cga6YSFgB8Jsqa+zlZDToQiTBID0WgUECPq5CgPLCbVWOREvGgNUicd6dSFhqdqTYytpgQb+AnydSXqDkQIqqYw3Gf/mdvXsCVcsVroiZZlOUrMAIBFwAxjSYCfXek/2AGC0KGpUkI1cmnIAEA/DekbVikVBoRxP8QBPvFVgsIBQ2AX09KTlEqoYpKEWoATxG6BEswqcxSCvjEpbqnB1+KELqAA8rZOXlQptd5k9GlAtO1mKKnR0/ApoLwBUVwCbygCTDaBiyQPwXwIrXSaD1TgBMxCaFhhJZ3KWUAjb9oEhZwqGsKe2yQqRNxCpe2RwqYjzKAAZPVDasAT5d6d9gqEdoQnI1Uri4IrtJgBtAZGqR5Kd5SnwqxCWKaLCLUkQRxpi4ErQMRBj5gq3c4MNQFADJKZLVCYzYarQxxicyqc8dSmDcaEZm0FVeQsRqrsVyxSa4hEATgmATrreBasQdBAUb3A93agDnAnxYwrpbqBJhaNyYLX8H/GbNt2a5UJwAxKZ/HQgg+yhCpGkfJYl/c85f+qp3oJrAj+3bqt3z5oygQUAHtkXkUQ6gHkaM7AqjH0gQClxEkMiXQqU9bxYMSSIaB8gggK7IIeIccSLM/qhBBemkqG6m9SAEvS65KYJdL0LL2ChEAALNDJQU5e7fScImpwbUKoAdNolilQG01kz3MxCx7krSr1I7GyLRte29sIAqDoyubpii2kno1+xC6AH8SqwCG0LgYIWIthhwmMFwlAErGYQJoCwdRBi0pe5QLKAZwSxFbMLB1y4o5AIM3O5wkqQK/KxGZME4424zgWAqVh4QU9otuEJ3Yozgz0JVnaaaw6oQC/9C9+rVfvDuR3+ofvXIrHiACmmdCLYC69QK0G8oQxrACRsgBNZC/NQAJfxALp/Y+EggomTIhemKojrqkCxgIWKKEE8G0w5uoFogMnIqldfC1vum8LGiX0AuDFsBt3cYchkB/G+QAe1Mzg6A2DHqY3/uvi7kIB3yUd1jBEsMue6pvGzGOiWt5xxLC/ekQmVAKjFAKb4BZZwBvtuIG+yQl0ukqXdAiEjQHLsVNMHxvXzCoFOHAWqmQPSCeVoq8EmrFE4HBGskECwqtACC9gmEdKOC1YFgQVQBCg7A4ZuWqr5qYATSr3VsLSjqWVQw2eBsfDosRB8CsOWcszVEA1lkRz/8ggjMKAc85JbZrWFtjdnOwCGIpkV4AqoFcEALbZVnsMtdiDXRJkg3ANhMxB867ljn7ih5Qf0EiCWqsxjXwBrmQEFeQe/fSO8sExv26wkq7mAQgm3Z4b2U5vyYUB9NbLPWSBSJsEZ7SLh2jaBfwnP/0b11weu8BAKnQTZUZw8dJqMGLaaxIiimqJ6cwynbpCcc5EQCQyoRLxuRMdZwhJHRgGsyxAChQA6YEX7llUmMQA6pDld5jubIKrfIRAJoAdRRngt+cEgAwAABVyIZcUJtsgCEIcYxSAG7goRhqZV1gfPg1B8JAb6FGxW9gpMxbcP7HhQtppJlAgx12A3VJCif/bQF8GriWyoxMkF47IHAhqAaGsHP3jM860BqbMAmT4Aef0WeAIgkF8DeCaJgGQdB4fNCL8EdJdoeeMAmLahISEgeG5hcf3AQlhrVyCEGg2ysrwgirwqUeLQM7SYcleG/NkM0SQQAqncXao8mcYYgddk4yiwR5QKESIa7kxJZkvNeo+ZsH8AllAAeXcinMkQAf0AI4cGowZwLcogfiMCbI4wGJ7L12HDA6Zp0CIIxwytAMjBIAcACQENbdphyQIAI27J8ag0IUkNsWMDMdnTgPw7r/gAhU0HZ3aJvG7EqXNoVtGDim/A+TMHg2WJeswA3HXRBzF2FkLAjMHb5oBz4Q/5AHdwAHaBDZKCABLTC7AKzZCkAHcgAuSgKuXknVANtkZMBTqW1vgbBdZk0RQSIKlBLbyvEHoe0QsDFVpnah1syviZcIzsemEfCXDaFfKx172mO4DhkMzVZ4iwjhDIGMLLjK8bzacigAagAvVgACdzAGKk4HkhAHKbktC4AGhYLNAy7a2ZkBpb1SckBe920A+aYSEGQIggEY3YYCzOwRB551Hr0kB1AwH/lp9/0AvKAvDYqyuhh7fLA9ZRokyIBnhWd4R92gAEBa1ueWk0jHATAh4ZMHUN2ZDaBVSt4EjnJ8dq0QlquTT+gAAFDfDY5mP87a4JMFQ07kf4ECAt4RSf/e2/XVqvXZKZ+AYAvtdp5Ae3FbED0Wj8u9y+jm6LbgbM4m0+OnXaXr4Yit3ZqOVmnOyCaOMw1gCMPVZ1WCBp0ZtAtx59spEA7AAmzX5xilEs7wCAXwB5NC6IABCdzAEYlOi4y+qBPyCmj27M9eBJ8gfQ1BDbYqfKN45vaa6omATt4O6tmgATW7DaQ1xmzAOAoOXxAUuuJjCLHLF/P6bSRzLQ3hryOADjlugASwCJA+cbwmDLg0NrDwDBZxBLgwAcoACcNO7NjhBrWcEcm+5BWw7b9RB9AO7XiVcQohDQ71BNjej03ZvQ5JAJvwCt/+7bRwAHNQ6QJBWsvIBOc+Qq//mRDqMgG7MgAHcL3/xLVNYKofaOei8hZyoSH5XhAQRAYX7+9P0OtA4wjCoGYFTwFysA7CniYIcPX/XdTHjhGqsjcSYFiLbsEB8NMWf/FodgnntRBF9wMe34Zdo+1dLRAQdAavoAR2f/d2n/IMEVSDywSgoMuTG/cI4Sm6fQb+lBxXn8+VIBxxiAVGMvQaQg7w7TRBwgllD+3+TgVkQAxT9QywEAaPBfUXYfAHQAlZcM92gPX/LQFxoLYWYQZTUgNjGvbb3imbVgde8ABmXwRMfxA9NgRIV5ubuTqEre6b9grZMH5OcPfpJFUJsQyrMFR2GYC9Q/w1jhD/2SlxMCUA/9VtCeDwDqEFewAXkD8C6RAOL4MJJzskl5/0vdYBPyAMmoCQQaCF1XReFVPRBwEEFAAQB9aBaaJgwQI7CBQmYChBB4dYugD8o1jR4kWLAIyYqIFAwQaQG7rkqSDCQoCLAAR4AJTjywOYBmTKvESmFgUAAUzVihRkSIciD77keNNjRo4GEDwIyImRIgACLK2QklLViRMlN3gc80Xt5D9o226tCkFCCRJQzQAZnZF06USncS/qamGCg0OGeXXEgiv3H5Y9SV6MGJHBMOERL9KZM/fNQ0YCB9548gLzwcyZDDR30NxZplBx/yyMburX9D8gQHAd+NQKzgKDBxUiyCvhw/+HFnHMEEBpeo6fKjg4Iui1od9xkSRN9rboQMCACpXqvLScmQEVKjR+DNEs84snD28ayJDhVoAD5k6dQ68khhQS+FWz8tiK7NevsjxuSKH1akCB8cqrYIDz0jsNIzc+wEsvhlDIggC/rtnjhcEMs/AwxNKJpq9/AlBJqkAqsywmzDCDyYs6mJpgAgEI+OpAv1IbpRNl+LmDEAVyjG2h2m7j4IRY3KjCjCuKvMKMKYwo4QS7JPBoA282kGeQLsAAYYeSXkxpAvb0qIMNEUks0YAvAklkAhYwSKGHHlIYsMCKDKzIAi6j08M9UOBDQr4b+lTiT1pYCQZNNdl08y05T2v/ww8zSnEjjo5moy2vBFCAZBO5UKkmnHDIScMVUEN1hRxywhEAE/TSWy86FcB8wIURY4UJijo0mOSAAQbwQASmOIQxLiBGmcCdCGQAA0cdd+SxIQlsu+3ZZ5utdIEmCOnijnLG0VbbHjBgQUvI6lxTDxDqCISNL9JNlxdNyFiEBUS4hKABeiPAYCn0DgSATpbGVUGMOkgRmBRaAmUlkU9YZEGD6Oi9oAB8A0gUo0yuqGIFHOq6SwcUDvJ4WR0qrYEbv6DSIAYZQFBZ5R4ePuDlFkujiF87QUiDDShy1jlnNgLxZMACKhC6gAF4lflXuYA4goIDlGGmFTHGGASNZHP0/xgFSWdDYeuOq/3DEFE0YHiXHbTFZxwZYihggnydAsCBqAq4II8dyNNDlUoggJiTR/r+DwMIIhB8wANcnNipTOA+QG66cyjj7hQePoMbFvxmOPDBNSj86LgAuMIIJhVsEOsmSi9dWQZRqIGFziPDYLyjYrciqaEPgLOit5+rAGXHVRbjdzFAkKFNeuuV3Pa2kT4t2AkakWWaVqAeo4upqa66FzuorRaOLAyRJGwRTjnFgwJk2UWGHdLfIYUGIDa8ZAK4lHvcuhuI4IKhARc6cAw0Z9tXGLUhfv+ZVwPqZz/8YUCBGNif3vyHk9MAYAol0FilEECI7hUDEGcYgJpyQP8Q2UyKIQiwFG8wApXFza14DsvcAGwHLorAbQIM410ZZrAyHDquXoS7nfIOBAQAUEAA4igf2VoxAxX8Kx71KAe2QPC4XciiAsrQgDs0oAxATLEC65CFLCKwCzDKQhkFsAQneugXC8QvPBDAXANiMLgGFoBocmAR55TnHBasEXNvhOP+5Fi0/x2uIlfAGJNqQC06AOIU2pjF+NbIOxW8BmupUwAduuGr3OGqAGLjZCc9UDicJAoqE5BMw8aTg7qlsl4YgNjmBOlDjKTmCKOInyUaUYEvMkOXzKhbK6ywA2ZMowHTYBMz8pAHZvAjBRGAACU+oYFGuLATE1jDGnBhR7n/7ItOEwiPHL35RxfaLmYSg2VKCBA/EXTzm5sU2yfhFcoIVqEEODgBJA5Ch6XIgWEKrIACLxCDU+4gCwuYZF4QcJA3cE6bLCJAiwTg0Ic2FJ6dc44ANMnACrCRf388QB0BWM4fKm0UFFhDJw7gAQ0ErQJdFNo6KKHFT1CiANE8QCc6IYBqEoAC13QAAIBATou88h9zAABU0IkmESSVBQy1QKpAms3RsAipS6Wqi3oKIwooaUk1UEATLiACuWEOAvirQAEWeIF/5oEgBVUIbB7k1DgV1UMW2FdR6VrUXy1UAFN92QGUalVsPnV5qZElAGi5BgF0QgQH4AQLOmGJpT60/0UuGsURLGtZwsoSCBQRauc8tC8HUIA0oO2sYOta1NDStamlHeoKSrBVBaChrHxEKwT6186+4oqGIOiIQWHTBFsFVrAWIeppKXDc0X50uMtlbnOd+1wfahUH9txAGea1g3rt7a86Pe4A5VaGGujAIQfNUTG+xVrople962Vve596hRK4tgVNEEmxymMvQBLgbUcrKgHACgFJSIu8G7gDotx7YAQnWMELNsIKVnACrhI4ZS0rACiF+48gPucTdBAdbDYAhgpPdMEjJnGJTXwaXaxASS3wCBqS2IMIaE7EfiGqRS9gAgV5eAxvUu6JffxjIKv3YhgzwQI24OK2hBi9ov8hgBwUcRcPg+Fhpwpyla185eG6wcEQNrKLsYQvHxKVAHGA8kfukIK1XRjLa2bzlTUi30PGFgTtYxtQOasvabjBBBLo8pzTvOQ2B1rQCI7DPPcMGwWoIMa9AqmeIzyI8qR50JOmtI/jcIK6OAk2OyZQjw8UBxM8CQxIkXSlTX3q9qJEz00ir1cNDMs2nIADRh6EytRGZVTnWtfMRYkZOHCb8R4kC3JgCkgZwZGPqOAoF3j1rp39bOV1QxHSGiEJDWFCWPqhBYf8cCq9BUNoh1vcTikFgyQVC2wjzQ8n4PMGxmDfGBAoeeOmN70dYAFDYE1SeYFEKXKBtCm0gM+VLID/Udp3gBnXW+HQpoAIssC1fYu3BW54RDf84oAp4ADKTdCDGgDaMnkDeuEjX/N66OCxEDbkAxxoQZAYQaQjVcEIGVNQx7LgwgLG+Lwk57mzUfgJECALdc6CFrSk1TE4iEIbHhCrknv+dF2rBDoNAMNrht4srBu0a4nca1hjUFaEixzqY/cxzWJghTuMAQ6lW5YFEVCDGnxNFPm8XOaMJnay553EZreC4/51BzDQQfCG8F4xRNE/D/ynAGLF31ISrnfIs3k9jNtBDmYgO+LR9p+CQ+vXiWY7vEZe9IJWifwCN55U2q089VpmBIY2ADo+fvSzt7JR0US+xac1BbunbVlzUwUz/eKd9sM/cH/jx83E50r5ufqkX2PmaeJHP8j7Om53J3DOyeo0uXGSfvfbnJOiQt/74yd/+c3vF5T0Jv3nZ//s199++Mdf/rR///vnf3/8HyggADs=' usemap="#bug" border="0"></div>
+
+<map name="bug" id="bug"><area id="area" shape="rect" coords="0,0,275,109" href="#area_1" onclick="alert( 'click' ); return false;"></map>
+
+<script>
+var timer;
+var shiftKeyOn = false;
+var counter = 0;
+function focus_area() {
+ document.getElementById("pre").onfocus = function() {
+ document.getElementById("pre").onfocus = null;
+ document.getElementById("area").onfocus = function() {
+ clearInterval(timer);
+ parent.SimpleTest.executeSoon(parent.secondIframeLoaded, 0);
+ }
+
+ //XXX This code tries to shift focus to the image map for some reason. This is not
+ // working directly after page load, hence it is retried 10 times, see bug bug 922524
+ timer = setInterval(function() {
+ if (counter > 10) {
+ clearInterval(timer);
+ parent.ok(false, "Too often tried to focus image map, giving up");
+ parent.finish();
+ return;
+ }
+ synthesizeKey("VK_TAB", { shiftKey: shiftKeyOn }, window);
+ shiftKeyOn = !shiftKeyOn;
+ counter++;
+ }, 100);
+ };
+ document.getElementById("pre").focus();
+}
+</script>
+
+</body>
+</html>
diff --git a/layout/generic/test/file_bug449653_1.html b/layout/generic/test/file_bug449653_1.html
new file mode 100644
index 0000000000..8a70cb05f0
--- /dev/null
+++ b/layout/generic/test/file_bug449653_1.html
@@ -0,0 +1,18 @@
+<html><head>
+<title>Bug 449653 - drawWindow on canvas fails on load, draws white instead of specified region</title>
+<style>
+html, body {
+margin: 0px;
+padding: 0px;
+}
+</style>
+</head><body>
+<div style="width: 100px; height: 100px; background-color: lime;"></div>
+<br>
+<canvas id="canvas" width="100" height="100" style="background-color: red;"></canvas>
+<script>
+ var canvas = document.getElementById("canvas");
+ var ctx = canvas.getContext("2d");
+ SpecialPowers.wrap(ctx).drawWindow(window, 0, 0, 100, 100, "white");
+</script>
+</body></html>
diff --git a/layout/generic/test/file_bug449653_1_ref.html b/layout/generic/test/file_bug449653_1_ref.html
new file mode 100644
index 0000000000..b1d78e0aa9
--- /dev/null
+++ b/layout/generic/test/file_bug449653_1_ref.html
@@ -0,0 +1,13 @@
+<html><head>
+<title>Bug 449653 - drawWindow on canvas fails on load, draws white instead of specified region</title>
+<style>
+html, body {
+margin: 0px;
+padding: 0px;
+}
+</style>
+</head><body>
+<div style="width: 100px; height: 100px; background-color: lime;"></div>
+<br>
+<div style="width: 100px; height: 100px; background-color: lime;"></div>
+</body></html>
diff --git a/layout/generic/test/file_bug514732_window.xhtml b/layout/generic/test/file_bug514732_window.xhtml
new file mode 100644
index 0000000000..5d7342776f
--- /dev/null
+++ b/layout/generic/test/file_bug514732_window.xhtml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="514732Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(startTests,0);"
+ title="bug 514732 test">
+
+ <script type="application/javascript"><![CDATA[
+ window.opener = window.arguments[0];
+ ]]></script>
+
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/docshell_helpers.js">
+ </script>
+
+ <script type="application/javascript"><![CDATA[
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 514732. The MozScrolledAreaChanged
+ // should be fired when a page is restored from the bfcache as though it had
+ // reloaded.
+ //
+ function* testIterator() {
+ // Make sure bfcache is on.
+ enableBFCache(true);
+
+
+ // Load a wide and tall page, and then another.
+ for (var i = 0; i < 2; ++i) {
+ doPageNavigation( {
+ uri: "data:text/html,<!DOCTYPE html><html>" +
+ "<head><title>bug 514732 bfcache test page " + i + "</title></head>" +
+ "<body>" +
+ '<div style="position: absolute; left: 10000px; top: 10000px; width: 500px; height: 500px;">' +
+ "</body></html>",
+ eventsToListenFor: ["MozScrolledAreaChanged"],
+ expectedEvents: [ { type: "MozScrolledAreaChanged" } ],
+ onNavComplete: nextTest
+ } );
+ yield;
+ }
+
+ // Navigate back to the first page. Don't test for width and height
+ // yet, just make sure we get an event.
+ doPageNavigation( {
+ back: true,
+ eventsToListenFor: ["MozScrolledAreaChanged"],
+ expectedEvents: [ { type: "MozScrolledAreaChanged" } ],
+ onNavComplete: nextTest
+ } );
+ yield;
+
+ // Navigate forth to our wide and tall page, this time testing for
+ // width and height on the event.
+ doPageNavigation( {
+ forward: true,
+ eventsToListenFor: ["MozScrolledAreaChanged"],
+ expectedEvents: [ { type: "MozScrolledAreaChanged" } ],
+ onNavComplete: nextTest
+ } );
+ yield;
+
+ finish();
+ }
+
+ function startTests() {
+ // Flush layout in outer and inner documents (in that order), to be sure
+ // our TestWindow's initial about:blank document doesn't have any
+ // pending reflows. (If we leave these pending reflows un-flushed, then
+ // they might happen after we've set up our MozScrolledAreaChanged
+ // event-listener; and that would confuse our test logic and lead to
+ // timeouts and test-failures.)
+ document.documentElement.offsetHeight;
+ TestWindow.getDocument().documentElement.offsetHeight;
+
+ // Kick off the first test via the generator-iterator.
+ nextTest();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/layout/generic/test/file_bug579767_1.html b/layout/generic/test/file_bug579767_1.html
new file mode 100644
index 0000000000..7d4c930770
--- /dev/null
+++ b/layout/generic/test/file_bug579767_1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
+<HTML style="background-color: yellow">
+ <FRAMESET cols="10%,90%" border="6">
+ <FRAMESET rows="90,210" border="6">
+ <FRAME src="data:text/html,<body bgcolor=blue>">
+ <FRAME src="data:text/html,<body bgcolor=green>">
+ </FRAMESET>
+ <FRAME src="data:text/html,<body bgcolor=red>">
+ </FRAMESET>
+</HTML>
diff --git a/layout/generic/test/file_bug579767_2.html b/layout/generic/test/file_bug579767_2.html
new file mode 100644
index 0000000000..a6a0927b9d
--- /dev/null
+++ b/layout/generic/test/file_bug579767_2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
+<HTML style="background-color: yellow">
+ <FRAMESET cols="11%,89%" border="6">
+ <FRAMESET rows="100,200" border="6">
+ <FRAME src="data:text/html,<body bgcolor=blue>">
+ <FRAME src="data:text/html,<body bgcolor=green>">
+ </FRAMESET>
+ <FRAME src="data:text/html,<body bgcolor=red>">
+ </FRAMESET>
+</HTML>
diff --git a/layout/generic/test/file_reframe_for_lazy_load_image.html b/layout/generic/test/file_reframe_for_lazy_load_image.html
new file mode 100644
index 0000000000..900f212341
--- /dev/null
+++ b/layout/generic/test/file_reframe_for_lazy_load_image.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1612649: We don't reframe for lazy load image state changes.
+</title>
+<div id="spacer" style="height: calc(100vh + 100px);"></div>
+<img id="image" loading="lazy" width="20" height="20" alt="this is an image"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC">
+<script>
+const is = opener.is.bind(opener);
+const add_task = opener.add_task;
+const original_finish = opener.SimpleTest.finish;
+const SimpleTest = opener.SimpleTest;
+SimpleTest.finish = function finish() {
+ self.close();
+ original_finish();
+}
+
+const utils = SpecialPowers.DOMWindowUtils;
+
+add_task(async () => {
+ await SimpleTest.promiseFocus();
+
+ const previousConstructCount = utils.framesConstructed;
+
+ const loadPromise = image.complete
+ ? null
+ : new Promise(resolve => image.addEventListener("load", resolve));
+
+ image.scrollIntoView();
+
+ await loadPromise;
+
+ image.getBoundingClientRect();
+
+ is(previousConstructCount, utils.framesConstructed,
+ "We should not have reframed");
+});
+</script>
diff --git a/layout/generic/test/file_scroll_position_restore.html b/layout/generic/test/file_scroll_position_restore.html
new file mode 100644
index 0000000000..44eda72df2
--- /dev/null
+++ b/layout/generic/test/file_scroll_position_restore.html
@@ -0,0 +1,111186 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="UTF-8">
+ <title>Directory Listing: /pub/firefox/tinderbox-builds/mozilla-central-win32/</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ </head>
+ <body onload="window.opener.handleLoad()">
+ <h1>Index of /pub/firefox/tinderbox-builds/mozilla-inbound-win32/</h1>
+ <table>
+ <tr>
+ <th>Type</th>
+ <th>Name</th>
+ <th>Size</th>
+ <th>Last Modified</th>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/">..</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442444362/">1442444362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442445380/">1442445380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442446344/">1442446344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442447004/">1442447004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442448336/">1442448336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442448442/">1442448442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442558151/">1442558151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442558812/">1442558812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442871815/">1442871815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442873979/">1442873979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442874157/">1442874157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442875117/">1442875117/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442875296/">1442875296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442876558/">1442876558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442876880/">1442876880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442877182/">1442877182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442877362/">1442877362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442878623/">1442878623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442878741/">1442878741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442880422/">1442880422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442880901/">1442880901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442881382/">1442881382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442882281/">1442882281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442882641/">1442882641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442883602/">1442883602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442883724/">1442883724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442888042/">1442888042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442890741/">1442890741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442891143/">1442891143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442891586/">1442891586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442896142/">1442896142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442899442/">1442899442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442901542/">1442901542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442901602/">1442901602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442905322/">1442905322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442905686/">1442905686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442906282/">1442906282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442906704/">1442906704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442907061/">1442907061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442910655/">1442910655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442916223/">1442916223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442918223/">1442918223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442919086/">1442919086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442919309/">1442919309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442923021/">1442923021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442925721/">1442925721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442926261/">1442926261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442926921/">1442926921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442927044/">1442927044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442929501/">1442929501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442932681/">1442932681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442933703/">1442933703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442934002/">1442934002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442934157/">1442934157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442934485/">1442934485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442934722/">1442934722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442934962/">1442934962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442935272/">1442935272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442935681/">1442935681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442935922/">1442935922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442936407/">1442936407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442938321/">1442938321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442938381/">1442938381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442939222/">1442939222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442940603/">1442940603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442941623/">1442941623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442941747/">1442941747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442942042/">1442942042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442945617/">1442945617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442946155/">1442946155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442946455/">1442946455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442946874/">1442946874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442946935/">1442946935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442946997/">1442946997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442948194/">1442948194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442949034/">1442949034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442949396/">1442949396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442949754/">1442949754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442950476/">1442950476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442951198/">1442951198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442957615/">1442957615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442958334/">1442958334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442958394/">1442958394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442958638/">1442958638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442961035/">1442961035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442961515/">1442961515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442962114/">1442962114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442962294/">1442962294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442962353/">1442962353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442963014/">1442963014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442963435/">1442963435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442964395/">1442964395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442965660/">1442965660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442967035/">1442967035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442967875/">1442967875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442968354/">1442968354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442968474/">1442968474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442968595/">1442968595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442968775/">1442968775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442969375/">1442969375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442970876/">1442970876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442971361/">1442971361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442971598/">1442971598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442975194/">1442975194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442976154/">1442976154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442977294/">1442977294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442977544/">1442977544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442978800/">1442978800/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442979216/">1442979216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442983475/">1442983475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442985816/">1442985816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442986241/">1442986241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442987075/">1442987075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442987195/">1442987195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442987735/">1442987735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442989474/">1442989474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442989654/">1442989654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442990014/">1442990014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442990193/">1442990193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442990974/">1442990974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442991394/">1442991394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442992457/">1442992457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442992475/">1442992475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442992655/">1442992655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442993014/">1442993014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442994216/">1442994216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1442996316/">1442996316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443005195/">1443005195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443005255/">1443005255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443006556/">1443006556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443008116/">1443008116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443009679/">1443009679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443013516/">1443013516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443013517/">1443013517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443014296/">1443014296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443015498/">1443015498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443016035/">1443016035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443016816/">1443016816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443018915/">1443018915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443020176/">1443020176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443021376/">1443021376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443021686/">1443021686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443023364/">1443023364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443025756/">1443025756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443026475/">1443026475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443026536/">1443026536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443028156/">1443028156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443028816/">1443028816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443030077/">1443030077/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443031758/">1443031758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443031816/">1443031816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443031875/">1443031875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443033380/">1443033380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443033677/">1443033677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443034096/">1443034096/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443036736/">1443036736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443039858/">1443039858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443040037/">1443040037/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443041300/">1443041300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443041716/">1443041716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443044240/">1443044240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443046816/">1443046816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443047422/">1443047422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443051976/">1443051976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443053956/">1443053956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443054857/">1443054857/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443056296/">1443056296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443057856/">1443057856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443058889/">1443058889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443060398/">1443060398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443065296/">1443065296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443066016/">1443066016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443066916/">1443066916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443067456/">1443067456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443067640/">1443067640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443067816/">1443067816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443067987/">1443067987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443071357/">1443071357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443072676/">1443072676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443075795/">1443075795/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443076395/">1443076395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443076875/">1443076875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443077055/">1443077055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443077238/">1443077238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443077355/">1443077355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443078136/">1443078136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443078495/">1443078495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443086775/">1443086775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443087916/">1443087916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443089240/">1443089240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443090195/">1443090195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443092355/">1443092355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443093016/">1443093016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443094339/">1443094339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443094815/">1443094815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443095955/">1443095955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443100276/">1443100276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443101116/">1443101116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443103820/">1443103820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443103896/">1443103896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443105038/">1443105038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443105193/">1443105193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443105856/">1443105856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443107668/">1443107668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443108735/">1443108735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443108976/">1443108976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443110956/">1443110956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443114736/">1443114736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443117436/">1443117436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443118815/">1443118815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443121641/">1443121641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443128475/">1443128475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443128542/">1443128542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443128630/">1443128630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443128631/">1443128631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443128840/">1443128840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443128895/">1443128895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443129135/">1443129135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443129195/">1443129195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443129917/">1443129917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443132016/">1443132016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443133335/">1443133335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443135196/">1443135196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443135316/">1443135316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443135856/">1443135856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443136638/">1443136638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443137055/">1443137055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443140116/">1443140116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443140655/">1443140655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443141075/">1443141075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443145757/">1443145757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443145996/">1443145996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443146296/">1443146296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443147189/">1443147189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443147676/">1443147676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443149116/">1443149116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443159915/">1443159915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443161172/">1443161172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443161416/">1443161416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443161898/">1443161898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443162858/">1443162858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443163155/">1443163155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443166639/">1443166639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443168916/">1443168916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443174375/">1443174375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443174916/">1443174916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443175215/">1443175215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443177075/">1443177075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443179715/">1443179715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443180015/">1443180015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443182235/">1443182235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443183015/">1443183015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443184936/">1443184936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443185178/">1443185178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443188055/">1443188055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443188357/">1443188357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443190840/">1443190840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443191616/">1443191616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443192998/">1443192998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443194975/">1443194975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443195216/">1443195216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443195454/">1443195454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443195640/">1443195640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443196415/">1443196415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443196655/">1443196655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443197736/">1443197736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443203078/">1443203078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443203079/">1443203079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443203674/">1443203674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443203735/">1443203735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443205476/">1443205476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443205716/">1443205716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443205895/">1443205895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443206732/">1443206732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443207157/">1443207157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443207877/">1443207877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443207993/">1443207993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443208296/">1443208296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443209796/">1443209796/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443210335/">1443210335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443210395/">1443210395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443210575/">1443210575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443210995/">1443210995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443211354/">1443211354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443211772/">1443211772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443211835/">1443211835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443212136/">1443212136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443212551/">1443212551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443214055/">1443214055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443214474/">1443214474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443215612/">1443215612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443216337/">1443216337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443217595/">1443217595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443225275/">1443225275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443229444/">1443229444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443230194/">1443230194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443238891/">1443238891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443252755/">1443252755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443253415/">1443253415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443256655/">1443256655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443272634/">1443272634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443285335/">1443285335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443291755/">1443291755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443306035/">1443306035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443331295/">1443331295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443332675/">1443332675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443335375/">1443335375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443340295/">1443340295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443345755/">1443345755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443353375/">1443353375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443365135/">1443365135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443367895/">1443367895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443382295/">1443382295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443387807/">1443387807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443390395/">1443390395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443390575/">1443390575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443396875/">1443396875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443397055/">1443397055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443397175/">1443397175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443400235/">1443400235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443401735/">1443401735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443402313/">1443402313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443403415/">1443403415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443403655/">1443403655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443406655/">1443406655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443407913/">1443407913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443411575/">1443411575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443413555/">1443413555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443413737/">1443413737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443415777/">1443415777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443416195/">1443416195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443416255/">1443416255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443416436/">1443416436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443419975/">1443419975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443423275/">1443423275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443430779/">1443430779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443430895/">1443430895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443431141/">1443431141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443434675/">1443434675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443435275/">1443435275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443439895/">1443439895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443441035/">1443441035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443441824/">1443441824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443442743/">1443442743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443444239/">1443444239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443445557/">1443445557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443445739/">1443445739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443446280/">1443446280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443447098/">1443447098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443448625/">1443448625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443448696/">1443448696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443448799/">1443448799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443449082/">1443449082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443449376/">1443449376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443451053/">1443451053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443452136/">1443452136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443453335/">1443453335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443456815/">1443456815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443467701/">1443467701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443467761/">1443467761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443467881/">1443467881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443467940/">1443467940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443468001/">1443468001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443468061/">1443468061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443468481/">1443468481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443468781/">1443468781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443469381/">1443469381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443469740/">1443469740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443469984/">1443469984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443470161/">1443470161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443470461/">1443470461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443470701/">1443470701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443471781/">1443471781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443476101/">1443476101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443476225/">1443476225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443476521/">1443476521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443476820/">1443476820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443477481/">1443477481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443477864/">1443477864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443479724/">1443479724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443480681/">1443480681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443481044/">1443481044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443481221/">1443481221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443481466/">1443481466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443481582/">1443481582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443481764/">1443481764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443481941/">1443481941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443482002/">1443482002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443482121/">1443482121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443482964/">1443482964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443483145/">1443483145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443484881/">1443484881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443485305/">1443485305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443485421/">1443485421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443486742/">1443486742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443489625/">1443489625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443490284/">1443490284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443490884/">1443490884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443491545/">1443491545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443492444/">1443492444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443492930/">1443492930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443493224/">1443493224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443493944/">1443493944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443494482/">1443494482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443499284/">1443499284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443499644/">1443499644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443500606/">1443500606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443501144/">1443501144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443502344/">1443502344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443505585/">1443505585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443505944/">1443505944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443507324/">1443507324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443508884/">1443508884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443509424/">1443509424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443510324/">1443510324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443511464/">1443511464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443512361/">1443512361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443514166/">1443514166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443519264/">1443519264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443519442/">1443519442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443520224/">1443520224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443522084/">1443522084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443523285/">1443523285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443524484/">1443524484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443527784/">1443527784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443527964/">1443527964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443531974/">1443531974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443532464/">1443532464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443533605/">1443533605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443535044/">1443535044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443537292/">1443537292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443537625/">1443537625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443537744/">1443537744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443538224/">1443538224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443539072/">1443539072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443539909/">1443539909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443540452/">1443540452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443540872/">1443540872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443541051/">1443541051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443541289/">1443541289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443542308/">1443542308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443542673/">1443542673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443543514/">1443543514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443544716/">1443544716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443545554/">1443545554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443546572/">1443546572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443548911/">1443548911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443549453/">1443549453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443550894/">1443550894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443551853/">1443551853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443552639/">1443552639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443554314/">1443554314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443556833/">1443556833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558639/">1443558639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558706/">1443558706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558707/">1443558707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558708/">1443558708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558709/">1443558709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558711/">1443558711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558712/">1443558712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558713/">1443558713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558718/">1443558718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558719/">1443558719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558720/">1443558720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558725/">1443558725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558726/">1443558726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558727/">1443558727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558729/">1443558729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558740/">1443558740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558741/">1443558741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558744/">1443558744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558746/">1443558746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558747/">1443558747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558748/">1443558748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558749/">1443558749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558988/">1443558988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558989/">1443558989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558991/">1443558991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558992/">1443558992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558993/">1443558993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558995/">1443558995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558997/">1443558997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558998/">1443558998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443558999/">1443558999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559004/">1443559004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559005/">1443559005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559007/">1443559007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559008/">1443559008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559019/">1443559019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559020/">1443559020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559023/">1443559023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559025/">1443559025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559026/">1443559026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559027/">1443559027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559028/">1443559028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443559292/">1443559292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443562267/">1443562267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443563346/">1443563346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443566586/">1443566586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443567969/">1443567969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443568327/">1443568327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443568508/">1443568508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443570128/">1443570128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443570247/">1443570247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443570607/">1443570607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443571507/">1443571507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443572352/">1443572352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443573847/">1443573847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443574208/">1443574208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443577269/">1443577269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443577465/">1443577465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443579619/">1443579619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443580507/">1443580507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443580629/">1443580629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443580747/">1443580747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443580867/">1443580867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443581107/">1443581107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443581108/">1443581108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443581227/">1443581227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443581588/">1443581588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443582428/">1443582428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443582787/">1443582787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443584169/">1443584169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443587647/">1443587647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443591247/">1443591247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443593347/">1443593347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443595926/">1443595926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443596107/">1443596107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443596286/">1443596286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443596897/">1443596897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443597007/">1443597007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443597967/">1443597967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443599048/">1443599048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443599167/">1443599167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443602468/">1443602468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607770/">1443607770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607771/">1443607771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607772/">1443607772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607774/">1443607774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607775/">1443607775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607776/">1443607776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607777/">1443607777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607780/">1443607780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607781/">1443607781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607782/">1443607782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607783/">1443607783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607787/">1443607787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607788/">1443607788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607789/">1443607789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607790/">1443607790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607802/">1443607802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607803/">1443607803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607805/">1443607805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607808/">1443607808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443607810/">1443607810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608129/">1443608129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608130/">1443608130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608131/">1443608131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608134/">1443608134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608135/">1443608135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608137/">1443608137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608140/">1443608140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608142/">1443608142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608147/">1443608147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608148/">1443608148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608149/">1443608149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608162/">1443608162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608163/">1443608163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608164/">1443608164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608169/">1443608169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608170/">1443608170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608172/">1443608172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608444/">1443608444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608445/">1443608445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608446/">1443608446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608449/">1443608449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608450/">1443608450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608452/">1443608452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608455/">1443608455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608456/">1443608456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608457/">1443608457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608458/">1443608458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608462/">1443608462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608463/">1443608463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608464/">1443608464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608476/">1443608476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608477/">1443608477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608478/">1443608478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608479/">1443608479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608484/">1443608484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608485/">1443608485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608487/">1443608487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608763/">1443608763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608764/">1443608764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608765/">1443608765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608766/">1443608766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608768/">1443608768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608770/">1443608770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608771/">1443608771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608773/">1443608773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608774/">1443608774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608775/">1443608775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608776/">1443608776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608780/">1443608780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608781/">1443608781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608782/">1443608782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608784/">1443608784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608796/">1443608796/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608799/">1443608799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608801/">1443608801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608802/">1443608802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608803/">1443608803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443608804/">1443608804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443609368/">1443609368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443610867/">1443610867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443614048/">1443614048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443615786/">1443615786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443618368/">1443618368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443619747/">1443619747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443620524/">1443620524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443621367/">1443621367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443622526/">1443622526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443622647/">1443622647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443623008/">1443623008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443624331/">1443624331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443625528/">1443625528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443625672/">1443625672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443627213/">1443627213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443627445/">1443627445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443627927/">1443627927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443628108/">1443628108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443628588/">1443628588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443628767/">1443628767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443629906/">1443629906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443630387/">1443630387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443630687/">1443630687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443630989/">1443630989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443631467/">1443631467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443631767/">1443631767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443632546/">1443632546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443632794/">1443632794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443633688/">1443633688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443636747/">1443636747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443644908/">1443644908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443645508/">1443645508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443645747/">1443645747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443645806/">1443645806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443646166/">1443646166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443646768/">1443646768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443647367/">1443647367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443647488/">1443647488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443647549/">1443647549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443648088/">1443648088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443649167/">1443649167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443651448/">1443651448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443651927/">1443651927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443652168/">1443652168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443652290/">1443652290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443653008/">1443653008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443653966/">1443653966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443654266/">1443654266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443654688/">1443654688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443655048/">1443655048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443655049/">1443655049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443655527/">1443655527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443655833/">1443655833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443655885/">1443655885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443655949/">1443655949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443656368/">1443656368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443656668/">1443656668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443657745/">1443657745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443667587/">1443667587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443669148/">1443669148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443671907/">1443671907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443671967/">1443671967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443673107/">1443673107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443677847/">1443677847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443678207/">1443678207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443679107/">1443679107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443680547/">1443680547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443681028/">1443681028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443681328/">1443681328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443683252/">1443683252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443683906/">1443683906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443684991/">1443684991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443687508/">1443687508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443687868/">1443687868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443689608/">1443689608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443690211/">1443690211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443690625/">1443690625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443692306/">1443692306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443692487/">1443692487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443693568/">1443693568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443696148/">1443696148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443697468/">1443697468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443698188/">1443698188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443698488/">1443698488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443699207/">1443699207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443699867/">1443699867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443700408/">1443700408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443701189/">1443701189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443701788/">1443701788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443702747/">1443702747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443705628/">1443705628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443705808/">1443705808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443710068/">1443710068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712222/">1443712222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712223/">1443712223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712224/">1443712224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712225/">1443712225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712292/">1443712292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712293/">1443712293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712294/">1443712294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712297/">1443712297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712298/">1443712298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712299/">1443712299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712300/">1443712300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712301/">1443712301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712306/">1443712306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712307/">1443712307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712309/">1443712309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712310/">1443712310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712312/">1443712312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712315/">1443712315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712316/">1443712316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712317/">1443712317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712319/">1443712319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712323/">1443712323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712324/">1443712324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712325/">1443712325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712326/">1443712326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712328/">1443712328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712332/">1443712332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712334/">1443712334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712335/">1443712335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712336/">1443712336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712343/">1443712343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712344/">1443712344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712345/">1443712345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712348/">1443712348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712605/">1443712605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712606/">1443712606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712607/">1443712607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712660/">1443712660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712661/">1443712661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712664/">1443712664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712665/">1443712665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712667/">1443712667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712670/">1443712670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712671/">1443712671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712672/">1443712672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712673/">1443712673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712677/">1443712677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712678/">1443712678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712683/">1443712683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712684/">1443712684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712687/">1443712687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712690/">1443712690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712691/">1443712691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712692/">1443712692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712697/">1443712697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712698/">1443712698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443712701/">1443712701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443713309/">1443713309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443714568/">1443714568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443716246/">1443716246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443719366/">1443719366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443719488/">1443719488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443719548/">1443719548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443720928/">1443720928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443724408/">1443724408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443724468/">1443724468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443725428/">1443725428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443728128/">1443728128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443728188/">1443728188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443728368/">1443728368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443728966/">1443728966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443731788/">1443731788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443734619/">1443734619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443735278/">1443735278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443739718/">1443739718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443740137/">1443740137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443743137/">1443743137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443747759/">1443747759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443750458/">1443750458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443750765/">1443750765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443751058/">1443751058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443753758/">1443753758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443753878/">1443753878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443755258/">1443755258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443759578/">1443759578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443766358/">1443766358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443767738/">1443767738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443768938/">1443768938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443769178/">1443769178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443770078/">1443770078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443770558/">1443770558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443773438/">1443773438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443774518/">1443774518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443775598/">1443775598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443777938/">1443777938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443778779/">1443778779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443781598/">1443781598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443784060/">1443784060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443785919/">1443785919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443792772/">1443792772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443794558/">1443794558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443795594/">1443795594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443796358/">1443796358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443796957/">1443796957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443798363/">1443798363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443798639/">1443798639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443799420/">1443799420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443800440/">1443800440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443800919/">1443800919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443801877/">1443801877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443802538/">1443802538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443802897/">1443802897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443804696/">1443804696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443806678/">1443806678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443807339/">1443807339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443807518/">1443807518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443807759/">1443807759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443808238/">1443808238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443810338/">1443810338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443810518/">1443810518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443812677/">1443812677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443816818/">1443816818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443817961/">1443817961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443818498/">1443818498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443818619/">1443818619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443819851/">1443819851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443820418/">1443820418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443820419/">1443820419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443822039/">1443822039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443822518/">1443822518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443823359/">1443823359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443823780/">1443823780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443825159/">1443825159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443827320/">1443827320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443827860/">1443827860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828261/">1443828261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828262/">1443828262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828263/">1443828263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828318/">1443828318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828319/">1443828319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828320/">1443828320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828322/">1443828322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828323/">1443828323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828324/">1443828324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828329/">1443828329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828330/">1443828330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828331/">1443828331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828335/">1443828335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828337/">1443828337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828338/">1443828338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828342/">1443828342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828343/">1443828343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828344/">1443828344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828346/">1443828346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828349/">1443828349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828350/">1443828350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828351/">1443828351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828353/">1443828353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828356/">1443828356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828357/">1443828357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828358/">1443828358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828359/">1443828359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828486/">1443828486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828487/">1443828487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828490/">1443828490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828541/">1443828541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828542/">1443828542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828543/">1443828543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828545/">1443828545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828546/">1443828546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828548/">1443828548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828550/">1443828550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828551/">1443828551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828552/">1443828552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828553/">1443828553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828557/">1443828557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828558/">1443828558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828559/">1443828559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828563/">1443828563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828564/">1443828564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828565/">1443828565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828570/">1443828570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828571/">1443828571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828573/">1443828573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828577/">1443828577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828578/">1443828578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828579/">1443828579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828580/">1443828580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828795/">1443828795/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828796/">1443828796/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828797/">1443828797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828851/">1443828851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828852/">1443828852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828853/">1443828853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828855/">1443828855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828856/">1443828856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828857/">1443828857/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828858/">1443828858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828861/">1443828861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828862/">1443828862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828863/">1443828863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828865/">1443828865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828867/">1443828867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828868/">1443828868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828869/">1443828869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828870/">1443828870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828872/">1443828872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828873/">1443828873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828874/">1443828874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828875/">1443828875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828879/">1443828879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828880/">1443828880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828881/">1443828881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828885/">1443828885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828886/">1443828886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443828888/">1443828888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829108/">1443829108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829109/">1443829109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829110/">1443829110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829170/">1443829170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829171/">1443829171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829172/">1443829172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829174/">1443829174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829175/">1443829175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829176/">1443829176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829181/">1443829181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829182/">1443829182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829183/">1443829183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829187/">1443829187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829188/">1443829188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829189/">1443829189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829190/">1443829190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829194/">1443829194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829195/">1443829195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829198/">1443829198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829201/">1443829201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829202/">1443829202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829204/">1443829204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829207/">1443829207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829208/">1443829208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829209/">1443829209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829210/">1443829210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443829780/">1443829780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443834758/">1443834758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443841794/">1443841794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443843577/">1443843577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443849098/">1443849098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443885938/">1443885938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443886058/">1443886058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443886899/">1443886899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443888218/">1443888218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443897098/">1443897098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443909637/">1443909637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443912939/">1443912939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443917200/">1443917200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443969458/">1443969458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443978039/">1443978039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443979838/">1443979838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1443999878/">1443999878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444008999/">1444008999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444010758/">1444010758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444014698/">1444014698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444015598/">1444015598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444015658/">1444015658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444015718/">1444015718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444020458/">1444020458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444023998/">1444023998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444024057/">1444024057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444024898/">1444024898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444025258/">1444025258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444025918/">1444025918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444026897/">1444026897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444027358/">1444027358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444027538/">1444027538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444027838/">1444027838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444028138/">1444028138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444029098/">1444029098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444030238/">1444030238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444030501/">1444030501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444032579/">1444032579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444035218/">1444035218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444035698/">1444035698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444037860/">1444037860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444037978/">1444037978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444038818/">1444038818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444039899/">1444039899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444044698/">1444044698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444046020/">1444046020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444048536/">1444048536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444052858/">1444052858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444055099/">1444055099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444055511/">1444055511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444057179/">1444057179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444057298/">1444057298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444057552/">1444057552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444058921/">1444058921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444059339/">1444059339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444059578/">1444059578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444059878/">1444059878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444060240/">1444060240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444060482/">1444060482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444060898/">1444060898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444061138/">1444061138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444061798/">1444061798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444061919/">1444061919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444062161/">1444062161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444062399/">1444062399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444063121/">1444063121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444066655/">1444066655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444075959/">1444075959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444076078/">1444076078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444076138/">1444076138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444076380/">1444076380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444076381/">1444076381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444076678/">1444076678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444077012/">1444077012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444078778/">1444078778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444080518/">1444080518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444080623/">1444080623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444080774/">1444080774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444081075/">1444081075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444082035/">1444082035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444083713/">1444083713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444084016/">1444084016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444084436/">1444084436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444085216/">1444085216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444085816/">1444085816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444087139/">1444087139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444087827/">1444087827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444095715/">1444095715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444096256/">1444096256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444097101/">1444097101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444097335/">1444097335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444097755/">1444097755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444097935/">1444097935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444098534/">1444098534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444098711/">1444098711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444101596/">1444101596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444103633/">1444103633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444105075/">1444105075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444109425/">1444109425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444110894/">1444110894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444113356/">1444113356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444113715/">1444113715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444115815/">1444115815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444116415/">1444116415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444116596/">1444116596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444120216/">1444120216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444120555/">1444120555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444121336/">1444121336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444121814/">1444121814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444122223/">1444122223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444122536/">1444122536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444123375/">1444123375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444125654/">1444125654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444125955/">1444125955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444126973/">1444126973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444131040/">1444131040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444132436/">1444132436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444133996/">1444133996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444137357/">1444137357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444137536/">1444137536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444138674/">1444138674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444139227/">1444139227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444139589/">1444139589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444139639/">1444139639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444139753/">1444139753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444140574/">1444140574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444141629/">1444141629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444141630/">1444141630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444141631/">1444141631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444141632/">1444141632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444141716/">1444141716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444141717/">1444141717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444141973/">1444141973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444143033/">1444143033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444145374/">1444145374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444146576/">1444146576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444152611/">1444152611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444156714/">1444156714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444156892/">1444156892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444157074/">1444157074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444157196/">1444157196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444157314/">1444157314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444158874/">1444158874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444159052/">1444159052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444159239/">1444159239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444159475/">1444159475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444160613/">1444160613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444162891/">1444162891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163010/">1444163010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163011/">1444163011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163012/">1444163012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163091/">1444163091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163092/">1444163092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163093/">1444163093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163376/">1444163376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163377/">1444163377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163378/">1444163378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163379/">1444163379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163455/">1444163455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163456/">1444163456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444163457/">1444163457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444164936/">1444164936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444167152/">1444167152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444167692/">1444167692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444168834/">1444168834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444170212/">1444170212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444170993/">1444170993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444171172/">1444171172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444172135/">1444172135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444174204/">1444174204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444174952/">1444174952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444177650/">1444177650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444179092/">1444179092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444182452/">1444182452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444187195/">1444187195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444190132/">1444190132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444190312/">1444190312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444195814/">1444195814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444195952/">1444195952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444196555/">1444196555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444197032/">1444197032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444197393/">1444197393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444197632/">1444197632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444198530/">1444198530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444199852/">1444199852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444200091/">1444200091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444201509/">1444201509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444202792/">1444202792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444205672/">1444205672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444211492/">1444211492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444211792/">1444211792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444211972/">1444211972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444212391/">1444212391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444212392/">1444212392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444212691/">1444212691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444212991/">1444212991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213113/">1444213113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213198/">1444213198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213411/">1444213411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213592/">1444213592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213891/">1444213891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213996/">1444213996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213997/">1444213997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444213998/">1444213998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214001/">1444214001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214079/">1444214079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214080/">1444214080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214083/">1444214083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214252/">1444214252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214278/">1444214278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214400/">1444214400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214403/">1444214403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214406/">1444214406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214481/">1444214481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214483/">1444214483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214486/">1444214486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214492/">1444214492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214506/">1444214506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214510/">1444214510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444214513/">1444214513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444215271/">1444215271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444216350/">1444216350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444217423/">1444217423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444217912/">1444217912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444219473/">1444219473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444222592/">1444222592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444222832/">1444222832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444223552/">1444223552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444224751/">1444224751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444224870/">1444224870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444224931/">1444224931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444226135/">1444226135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444226372/">1444226372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444227994/">1444227994/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444228246/">1444228246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444229972/">1444229972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444231112/">1444231112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444233931/">1444233931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444237112/">1444237112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444237292/">1444237292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444238611/">1444238611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444239932/">1444239932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444240714/">1444240714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444241073/">1444241073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444241507/">1444241507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444241801/">1444241801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444242511/">1444242511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444243592/">1444243592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444244432/">1444244432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444244915/">1444244915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444245093/">1444245093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444246414/">1444246414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444246532/">1444246532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444247132/">1444247132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444247432/">1444247432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444248692/">1444248692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444250130/">1444250130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444251833/">1444251833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444253372/">1444253372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444253671/">1444253671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444254872/">1444254872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444255349/">1444255349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444255650/">1444255650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444256911/">1444256911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444257212/">1444257212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444257453/">1444257453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444257872/">1444257872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444260032/">1444260032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444260635/">1444260635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444260932/">1444260932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444261233/">1444261233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444261652/">1444261652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444262627/">1444262627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444264170/">1444264170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444264312/">1444264312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444274671/">1444274671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444277972/">1444277972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444281212/">1444281212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444281392/">1444281392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444281452/">1444281452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444282171/">1444282171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444283612/">1444283612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444284394/">1444284394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444284512/">1444284512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444284932/">1444284932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444286970/">1444286970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444288591/">1444288591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444290213/">1444290213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444291712/">1444291712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444292072/">1444292072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444292972/">1444292972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444296272/">1444296272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444296392/">1444296392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444296452/">1444296452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444296512/">1444296512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444296898/">1444296898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444296993/">1444296993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444297352/">1444297352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444297470/">1444297470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444297711/">1444297711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444297892/">1444297892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444298311/">1444298311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444300171/">1444300171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444300232/">1444300232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444303805/">1444303805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444305151/">1444305151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444305835/">1444305835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444308216/">1444308216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444309056/">1444309056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444312896/">1444312896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444313557/">1444313557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444314154/">1444314154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444314743/">1444314743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444318055/">1444318055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444319673/">1444319673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444321537/">1444321537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444322793/">1444322793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444323337/">1444323337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444324293/">1444324293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444324589/">1444324589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444324954/">1444324954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444325413/">1444325413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444325733/">1444325733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444326035/">1444326035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444330757/">1444330757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444330947/">1444330947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444331719/">1444331719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444332197/">1444332197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444332621/">1444332621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444334764/">1444334764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444335200/">1444335200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444336220/">1444336220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444336699/">1444336699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444337839/">1444337839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444338154/">1444338154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444339158/">1444339158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444340179/">1444340179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444340658/">1444340658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444341137/">1444341137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444341558/">1444341558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444342038/">1444342038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444342699/">1444342699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444342998/">1444342998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444343359/">1444343359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444344320/">1444344320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444347015/">1444347015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444348851/">1444348851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444349035/">1444349035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444349270/">1444349270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444350947/">1444350947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444354727/">1444354727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444356108/">1444356108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444357881/">1444357881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444359948/">1444359948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444363728/">1444363728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444383767/">1444383767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444384727/">1444384727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444385012/">1444385012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444388088/">1444388088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444393608/">1444393608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444395048/">1444395048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444402437/">1444402437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444402612/">1444402612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444403268/">1444403268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444404467/">1444404467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444407827/">1444407827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444408488/">1444408488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444408716/">1444408716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444409751/">1444409751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444411308/">1444411308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444411489/">1444411489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444412267/">1444412267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444416167/">1444416167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444417248/">1444417248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444417249/">1444417249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444417573/">1444417573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444421087/">1444421087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444422169/">1444422169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444422649/">1444422649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444422828/">1444422828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444422890/">1444422890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444423248/">1444423248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444423788/">1444423788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444424148/">1444424148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444425348/">1444425348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444425408/">1444425408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444434229/">1444434229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444434348/">1444434348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444439747/">1444439747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444440048/">1444440048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444440114/">1444440114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444460749/">1444460749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444462187/">1444462187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444465067/">1444465067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444477367/">1444477367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444495547/">1444495547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444509947/">1444509947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444518396/">1444518396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444537727/">1444537727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444538987/">1444538987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444550867/">1444550867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444565327/">1444565327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444567607/">1444567607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444574568/">1444574568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444579068/">1444579068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444579127/">1444579127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444579187/">1444579187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444580149/">1444580149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444592568/">1444592568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444604773/">1444604773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444609513/">1444609513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444611970/">1444611970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444612573/">1444612573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444616952/">1444616952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444621812/">1444621812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444622294/">1444622294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444623253/">1444623253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444630873/">1444630873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444634233/">1444634233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444634413/">1444634413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444634533/">1444634533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444635672/">1444635672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444639633/">1444639633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444642152/">1444642152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444643954/">1444643954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444644862/">1444644862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444646053/">1444646053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444647192/">1444647192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444650850/">1444650850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444652598/">1444652598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444653254/">1444653254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444656013/">1444656013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444663332/">1444663332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444667053/">1444667053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444667533/">1444667533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444668974/">1444668974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444669692/">1444669692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444670893/">1444670893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444671793/">1444671793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444672154/">1444672154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444672573/">1444672573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444673235/">1444673235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444674133/">1444674133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444674373/">1444674373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444674793/">1444674793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444675812/">1444675812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444677013/">1444677013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678622/">1444678622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678623/">1444678623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678624/">1444678624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678625/">1444678625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678626/">1444678626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678627/">1444678627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678628/">1444678628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678629/">1444678629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678630/">1444678630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678631/">1444678631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678632/">1444678632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678634/">1444678634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678635/">1444678635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678637/">1444678637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444678638/">1444678638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444680492/">1444680492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681358/">1444681358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681652/">1444681652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681653/">1444681653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681654/">1444681654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681655/">1444681655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681656/">1444681656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681657/">1444681657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681658/">1444681658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681659/">1444681659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681661/">1444681661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681662/">1444681662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681663/">1444681663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681665/">1444681665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681666/">1444681666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681668/">1444681668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444681898/">1444681898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683229/">1444683229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683230/">1444683230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683233/">1444683233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683310/">1444683310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683311/">1444683311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683312/">1444683312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683313/">1444683313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683518/">1444683518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683519/">1444683519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683520/">1444683520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683521/">1444683521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683525/">1444683525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683607/">1444683607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683608/">1444683608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683609/">1444683609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444683612/">1444683612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444687479/">1444687479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444692114/">1444692114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444693735/">1444693735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444697275/">1444697275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444699017/">1444699017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444702375/">1444702375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444705560/">1444705560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444714555/">1444714555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444716295/">1444716295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444719895/">1444719895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444720079/">1444720079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444720194/">1444720194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444720675/">1444720675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444732917/">1444732917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444733036/">1444733036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444734383/">1444734383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444734534/">1444734534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444735315/">1444735315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444736274/">1444736274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444737657/">1444737657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444739456/">1444739456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444740596/">1444740596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444746704/">1444746704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444746750/">1444746750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444747349/">1444747349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444747769/">1444747769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444748429/">1444748429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444749328/">1444749328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444753588/">1444753588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444754609/">1444754609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444756109/">1444756109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444756468/">1444756468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444764089/">1444764089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444765948/">1444765948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444766813/">1444766813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444769131/">1444769131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444769188/">1444769188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444769249/">1444769249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444770029/">1444770029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444770629/">1444770629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444771289/">1444771289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444771949/">1444771949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444772669/">1444772669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444772788/">1444772788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444773449/">1444773449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444777109/">1444777109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444777429/">1444777429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444778369/">1444778369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444779509/">1444779509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444779988/">1444779988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444780409/">1444780409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444780949/">1444780949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444782029/">1444782029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444783109/">1444783109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444783771/">1444783771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444786532/">1444786532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444788929/">1444788929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444790476/">1444790476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444791689/">1444791689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444794020/">1444794020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444797440/">1444797440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444799899/">1444799899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444801939/">1444801939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444802752/">1444802752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444803140/">1444803140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444803799/">1444803799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444804641/">1444804641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444804879/">1444804879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444804999/">1444804999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444805419/">1444805419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444806269/">1444806269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444807640/">1444807640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444807819/">1444807819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444807939/">1444807939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444808178/">1444808178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444808599/">1444808599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444809620/">1444809620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444810462/">1444810462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444812379/">1444812379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444815322/">1444815322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444815440/">1444815440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444815560/">1444815560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444818680/">1444818680/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444819159/">1444819159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444819702/">1444819702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444824861/">1444824861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444825818/">1444825818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444825941/">1444825941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444825996/">1444825996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444827200/">1444827200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444828817/">1444828817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444832538/">1444832538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444832960/">1444832960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444834895/">1444834895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444835262/">1444835262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444835610/">1444835610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444836510/">1444836510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444837171/">1444837171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444838768/">1444838768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444839208/">1444839208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444841551/">1444841551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444845331/">1444845331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444845691/">1444845691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444845828/">1444845828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444845871/">1444845871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444845991/">1444845991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444846230/">1444846230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444847011/">1444847011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444847491/">1444847491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444847671/">1444847671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444847730/">1444847730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444849287/">1444849287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444849591/">1444849591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444853251/">1444853251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444853371/">1444853371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444853727/">1444853727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444854871/">1444854871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444855347/">1444855347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444855768/">1444855768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444855769/">1444855769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444855892/">1444855892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444856628/">1444856628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444857529/">1444857529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444857649/">1444857649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444857708/">1444857708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444858186/">1444858186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444860109/">1444860109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444860963/">1444860963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444861966/">1444861966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444865031/">1444865031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444865274/">1444865274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444866757/">1444866757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444867248/">1444867248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444867249/">1444867249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444870034/">1444870034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444873969/">1444873969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444875349/">1444875349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444877027/">1444877027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444877544/">1444877544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444879368/">1444879368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444880989/">1444880989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444881166/">1444881166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444881167/">1444881167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444882790/">1444882790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444888431/">1444888431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444889329/">1444889329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444889809/">1444889809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444889989/">1444889989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444890169/">1444890169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444890410/">1444890410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444893356/">1444893356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444894010/">1444894010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444894369/">1444894369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444894370/">1444894370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444895938/">1444895938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444896648/">1444896648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444899827/">1444899827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444900170/">1444900170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444901618/">1444901618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444903184/">1444903184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444909546/">1444909546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444910929/">1444910929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444914889/">1444914889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444917025/">1444917025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444917108/">1444917108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444917229/">1444917229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444917912/">1444917912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444918787/">1444918787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444918914/">1444918914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444922556/">1444922556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444923714/">1444923714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444924553/">1444924553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444924790/">1444924790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444925388/">1444925388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444925448/">1444925448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444925630/">1444925630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444929950/">1444929950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444934259/">1444934259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444935876/">1444935876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444936115/">1444936115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444936370/">1444936370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444936921/">1444936921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444938355/">1444938355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444938639/">1444938639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444939955/">1444939955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444941817/">1444941817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444942536/">1444942536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444943259/">1444943259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444943323/">1444943323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444944815/">1444944815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444945058/">1444945058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444948890/">1444948890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444949490/">1444949490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444952731/">1444952731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444953690/">1444953690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444956878/">1444956878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444961105/">1444961105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444973010/">1444973010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444973191/">1444973191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444973310/">1444973310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444974810/">1444974810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444975471/">1444975471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444975830/">1444975830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444976610/">1444976610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444977090/">1444977090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444977210/">1444977210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444977269/">1444977269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444977810/">1444977810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444977931/">1444977931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444978110/">1444978110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444978650/">1444978650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444981350/">1444981350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444984228/">1444984228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444986031/">1444986031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444989870/">1444989870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444993053/">1444993053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444995012/">1444995012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444999591/">1444999591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1444999710/">1444999710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445001751/">1445001751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445002246/">1445002246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445002651/">1445002651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445002833/">1445002833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445003286/">1445003286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445003437/">1445003437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445004098/">1445004098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445004595/">1445004595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445005911/">1445005911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445008836/">1445008836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445009313/">1445009313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445009492/">1445009492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445011170/">1445011170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445011583/">1445011583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445013031/">1445013031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445013871/">1445013871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445014170/">1445014170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445014725/">1445014725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445015845/">1445015845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445015910/">1445015910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445015911/">1445015911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445016510/">1445016510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445016612/">1445016612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445018855/">1445018855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445020594/">1445020594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445020651/">1445020651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445021009/">1445021009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445021731/">1445021731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445021920/">1445021920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445022606/">1445022606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445023891/">1445023891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445024501/">1445024501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445025358/">1445025358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445026560/">1445026560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445026797/">1445026797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445027337/">1445027337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445027525/">1445027525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445028993/">1445028993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445031631/">1445031631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445032050/">1445032050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445032231/">1445032231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445034105/">1445034105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445034692/">1445034692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445038215/">1445038215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445038591/">1445038591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445043031/">1445043031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445045490/">1445045490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445046211/">1445046211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445049051/">1445049051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445052150/">1445052150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445052451/">1445052451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445052813/">1445052813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445064630/">1445064630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445067750/">1445067750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445068468/">1445068468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445070624/">1445070624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445096228/">1445096228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445102989/">1445102989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445106330/">1445106330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445117195/">1445117195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445117475/">1445117475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445124603/">1445124603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445141972/">1445141972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445143291/">1445143291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445145390/">1445145390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445146206/">1445146206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445166990/">1445166990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445174431/">1445174431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445192610/">1445192610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445194296/">1445194296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445199570/">1445199570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445208030/">1445208030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445214810/">1445214810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445216190/">1445216190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445217932/">1445217932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445219070/">1445219070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445221110/">1445221110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445221230/">1445221230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445221350/">1445221350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445222610/">1445222610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445224290/">1445224290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445224830/">1445224830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445224890/">1445224890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445234250/">1445234250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445234370/">1445234370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445235150/">1445235150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445235570/">1445235570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445243440/">1445243440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445244990/">1445244990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445245770/">1445245770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445247691/">1445247691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445248230/">1445248230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445248830/">1445248830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445250872/">1445250872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445252011/">1445252011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445252555/">1445252555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445253811/">1445253811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445254207/">1445254207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445254890/">1445254890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445260231/">1445260231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445261971/">1445261971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445262658/">1445262658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445262755/">1445262755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445264014/">1445264014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445264542/">1445264542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445265235/">1445265235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445265249/">1445265249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445265529/">1445265529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445267337/">1445267337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445267711/">1445267711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445267888/">1445267888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445269176/">1445269176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445269889/">1445269889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445270791/">1445270791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445273972/">1445273972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445274100/">1445274100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445274272/">1445274272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445276223/">1445276223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445276729/">1445276729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445277229/">1445277229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445277394/">1445277394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445277395/">1445277395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445278189/">1445278189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445278490/">1445278490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445279009/">1445279009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445279369/">1445279369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445279851/">1445279851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445280597/">1445280597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445282190/">1445282190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445283679/">1445283679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445284123/">1445284123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445284529/">1445284529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445284709/">1445284709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445284765/">1445284765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445285792/">1445285792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445286269/">1445286269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445286614/">1445286614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445287829/">1445287829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445288250/">1445288250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445289272/">1445289272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445289453/">1445289453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445292232/">1445292232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445293051/">1445293051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445293289/">1445293289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445293470/">1445293470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445293650/">1445293650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445294265/">1445294265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445295750/">1445295750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445296006/">1445296006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445296247/">1445296247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445296632/">1445296632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445296930/">1445296930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445297647/">1445297647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445300347/">1445300347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445302586/">1445302586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445302760/">1445302760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445304626/">1445304626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445305699/">1445305699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445306146/">1445306146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445306171/">1445306171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445307671/">1445307671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445309032/">1445309032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445309300/">1445309300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445310462/">1445310462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445311749/">1445311749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445312695/">1445312695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445314989/">1445314989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445315107/">1445315107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445318168/">1445318168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445318482/">1445318482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445318609/">1445318609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445319367/">1445319367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445323207/">1445323207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445329387/">1445329387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445329567/">1445329567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445330167/">1445330167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445330707/">1445330707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445330766/">1445330766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445331189/">1445331189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445332086/">1445332086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445332807/">1445332807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445333647/">1445333647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445334007/">1445334007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445334386/">1445334386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445334488/">1445334488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445336419/">1445336419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445337188/">1445337188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445337308/">1445337308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445337486/">1445337486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445337668/">1445337668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445338874/">1445338874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445340667/">1445340667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445340907/">1445340907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445341517/">1445341517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445341567/">1445341567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445342891/">1445342891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445343254/">1445343254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445344929/">1445344929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445345955/">1445345955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445347632/">1445347632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445348887/">1445348887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445350268/">1445350268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445350695/">1445350695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445351056/">1445351056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445351126/">1445351126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445352014/">1445352014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445357527/">1445357527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445359327/">1445359327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445359449/">1445359449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445359574/">1445359574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445361488/">1445361488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445362628/">1445362628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445362749/">1445362749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445364009/">1445364009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445364151/">1445364151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445365874/">1445365874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445366709/">1445366709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445368210/">1445368210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445369264/">1445369264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445369599/">1445369599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445370608/">1445370608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445370912/">1445370912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445371648/">1445371648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445371987/">1445371987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445373737/">1445373737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445373897/">1445373897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445373952/">1445373952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445374431/">1445374431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445379019/">1445379019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445380241/">1445380241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445386501/">1445386501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445400721/">1445400721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445406422/">1445406422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445406423/">1445406423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445406481/">1445406481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445406540/">1445406540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445407020/">1445407020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445407080/">1445407080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445407501/">1445407501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445407560/">1445407560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445408101/">1445408101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445408884/">1445408884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445409420/">1445409420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445410209/">1445410209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445411793/">1445411793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445413648/">1445413648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445413709/">1445413709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445413710/">1445413710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445413853/">1445413853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445413854/">1445413854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445413855/">1445413855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445413942/">1445413942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445415302/">1445415302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445415615/">1445415615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445415847/">1445415847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445418538/">1445418538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445418975/">1445418975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445423402/">1445423402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445434502/">1445434502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445435731/">1445435731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445435732/">1445435732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445435758/">1445435758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445436081/">1445436081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445436649/">1445436649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445436781/">1445436781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445436945/">1445436945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445437158/">1445437158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445437621/">1445437621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445442564/">1445442564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445444015/">1445444015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445444016/">1445444016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445444688/">1445444688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445446501/">1445446501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445448238/">1445448238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445451750/">1445451750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445454474/">1445454474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445457312/">1445457312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445458080/">1445458080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445459649/">1445459649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445460183/">1445460183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445463061/">1445463061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445463122/">1445463122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445463458/">1445463458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445466067/">1445466067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445470866/">1445470866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445471398/">1445471398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445472017/">1445472017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445472860/">1445472860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445472865/">1445472865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445473640/">1445473640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445475256/">1445475256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445477296/">1445477296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445478658/">1445478658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445478917/">1445478917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445480280/">1445480280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445480462/">1445480462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445481490/">1445481490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445484239/">1445484239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445486458/">1445486458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445489040/">1445489040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445494698/">1445494698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445495841/">1445495841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445496002/">1445496002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445496240/">1445496240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445497082/">1445497082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445501655/">1445501655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445501777/">1445501777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445502081/">1445502081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445502211/">1445502211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445506240/">1445506240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445507821/">1445507821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445516703/">1445516703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445517551/">1445517551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445520298/">1445520298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445521082/">1445521082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445521387/">1445521387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445521719/">1445521719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445522709/">1445522709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445523189/">1445523189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445527581/">1445527581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445530642/">1445530642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445531151/">1445531151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445531648/">1445531648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445531827/">1445531827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445533214/">1445533214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445534906/">1445534906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445537313/">1445537313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445537314/">1445537314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445538232/">1445538232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445538239/">1445538239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445546341/">1445546341/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445546578/">1445546578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445546808/">1445546808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445549580/">1445549580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445549640/">1445549640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445550003/">1445550003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445550485/">1445550485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445554220/">1445554220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445554339/">1445554339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445555300/">1445555300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445556919/">1445556919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445557158/">1445557158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445558254/">1445558254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445558334/">1445558334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445558553/">1445558553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445562766/">1445562766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445563863/">1445563863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445565996/">1445565996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445569017/">1445569017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445571885/">1445571885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445572591/">1445572591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445572772/">1445572772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445574875/">1445574875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445578588/">1445578588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445579504/">1445579504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445580569/">1445580569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445581232/">1445581232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445581833/">1445581833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445582072/">1445582072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445584708/">1445584708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445584899/">1445584899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445585074/">1445585074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445585128/">1445585128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445585247/">1445585247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445590352/">1445590352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445593773/">1445593773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445594681/">1445594681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445599234/">1445599234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445603790/">1445603790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445605203/">1445605203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445605374/">1445605374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445608978/">1445608978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445610222/">1445610222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445612011/">1445612011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445613808/">1445613808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445620112/">1445620112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445626474/">1445626474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445626955/">1445626955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445627435/">1445627435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445629001/">1445629001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445629175/">1445629175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445629236/">1445629236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445629549/">1445629549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445632833/">1445632833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445633261/">1445633261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445635536/">1445635536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445637215/">1445637215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445637876/">1445637876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445640938/">1445640938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445641973/">1445641973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445648735/">1445648735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445649095/">1445649095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445655635/">1445655635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445661635/">1445661635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445661755/">1445661755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445665475/">1445665475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445679398/">1445679398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445679574/">1445679574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445679694/">1445679694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445689116/">1445689116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445694756/">1445694756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445695175/">1445695175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445695497/">1445695497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445699495/">1445699495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445707355/">1445707355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445713297/">1445713297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445713671/">1445713671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445715515/">1445715515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445717223/">1445717223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445775517/">1445775517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445785297/">1445785297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445804134/">1445804134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445805934/">1445805934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445811093/">1445811093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445816314/">1445816314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445819492/">1445819492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445835860/">1445835860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445838160/">1445838160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445839487/">1445839487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445848414/">1445848414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445852613/">1445852613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445853641/">1445853641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445853866/">1445853866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445853867/">1445853867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445853876/">1445853876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445854295/">1445854295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445854894/">1445854894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445855386/">1445855386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445856516/">1445856516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445856709/">1445856709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445856932/">1445856932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445857054/">1445857054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445858617/">1445858617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445858736/">1445858736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445859514/">1445859514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445864674/">1445864674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445866824/">1445866824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445867182/">1445867182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445873218/">1445873218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445873553/">1445873553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445873665/">1445873665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445873789/">1445873789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445874895/">1445874895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445876570/">1445876570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445877501/">1445877501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445878342/">1445878342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445880487/">1445880487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445880625/">1445880625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445880866/">1445880866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445880867/">1445880867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445881357/">1445881357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445882871/">1445882871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445883022/">1445883022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445883144/">1445883144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445883500/">1445883500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445883802/">1445883802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445885606/">1445885606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445888379/">1445888379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445888813/">1445888813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445889621/">1445889621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445893787/">1445893787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445893789/">1445893789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445895570/">1445895570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445898204/">1445898204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445898482/">1445898482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445899503/">1445899503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445900518/">1445900518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445900522/">1445900522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445904925/">1445904925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445906926/">1445906926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445908384/">1445908384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445912542/">1445912542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445913680/">1445913680/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445914882/">1445914882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445915089/">1445915089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445918002/">1445918002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445920761/">1445920761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445924659/">1445924659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445929101/">1445929101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445933361/">1445933361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445933541/">1445933541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445933962/">1445933962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445934082/">1445934082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445934562/">1445934562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445935881/">1445935881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445936001/">1445936001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445939903/">1445939903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445946624/">1445946624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445950402/">1445950402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445950524/">1445950524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445950525/">1445950525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445950761/">1445950761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445951122/">1445951122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445951779/">1445951779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445952982/">1445952982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445955145/">1445955145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445956827/">1445956827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445957485/">1445957485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445960182/">1445960182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445961575/">1445961575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445963080/">1445963080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445964278/">1445964278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445966193/">1445966193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445966550/">1445966550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445967873/">1445967873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445968352/">1445968352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445968591/">1445968591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445970151/">1445970151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445970750/">1445970750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445971051/">1445971051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445972010/">1445972010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445977533/">1445977533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445977534/">1445977534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445979761/">1445979761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445980412/">1445980412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445980591/">1445980591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445982148/">1445982148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445982451/">1445982451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445983170/">1445983170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445984311/">1445984311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445989651/">1445989651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1445998892/">1445998892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446000637/">1446000637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446001759/">1446001759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446004472/">1446004472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446005088/">1446005088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446007712/">1446007712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446008434/">1446008434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446009332/">1446009332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446010352/">1446010352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446011852/">1446011852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446023431/">1446023431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446025953/">1446025953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446027632/">1446027632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446029252/">1446029252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446030340/">1446030340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446031834/">1446031834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446032617/">1446032617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446032912/">1446032912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446035454/">1446035454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446036453/">1446036453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446036636/">1446036636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446038972/">1446038972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446039216/">1446039216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446039453/">1446039453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446040986/">1446040986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446041254/">1446041254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446041492/">1446041492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446043059/">1446043059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446044315/">1446044315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446047311/">1446047311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446047372/">1446047372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446048360/">1446048360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446051277/">1446051277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446053013/">1446053013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446054692/">1446054692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446055498/">1446055498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446056193/">1446056193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446056792/">1446056792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446058113/">1446058113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446058353/">1446058353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446058772/">1446058772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446059493/">1446059493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446059613/">1446059613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446061114/">1446061114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446061233/">1446061233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446062559/">1446062559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446065012/">1446065012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446065073/">1446065073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446068012/">1446068012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446068254/">1446068254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446068793/">1446068793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446069274/">1446069274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446069332/">1446069332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446070178/">1446070178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446070653/">1446070653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446071436/">1446071436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446073475/">1446073475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446074435/">1446074435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446074793/">1446074793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446075394/">1446075394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446075753/">1446075753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446075932/">1446075932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446077135/">1446077135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446077279/">1446077279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446077672/">1446077672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446078934/">1446078934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446080027/">1446080027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446080563/">1446080563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446080711/">1446080711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446082125/">1446082125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446084409/">1446084409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446085665/">1446085665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446086090/">1446086090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446098325/">1446098325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446098927/">1446098927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446099465/">1446099465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446099644/">1446099644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446099704/">1446099704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446099764/">1446099764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446100904/">1446100904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446103007/">1446103007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446103425/">1446103425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446103726/">1446103726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446103844/">1446103844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446104024/">1446104024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446104167/">1446104167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446108524/">1446108524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446109007/">1446109007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446109666/">1446109666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446110745/">1446110745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446112001/">1446112001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446112301/">1446112301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446117344/">1446117344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446120688/">1446120688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446123088/">1446123088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446123204/">1446123204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446124408/">1446124408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446157812/">1446157812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446158173/">1446158173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446158412/">1446158412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446160512/">1446160512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446160573/">1446160573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446160815/">1446160815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446161776/">1446161776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446161952/">1446161952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446161953/">1446161953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446162673/">1446162673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446163092/">1446163092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446164233/">1446164233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446166333/">1446166333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446166981/">1446166981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446167775/">1446167775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446168135/">1446168135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446171617/">1446171617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446172634/">1446172634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446173659/">1446173659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446175649/">1446175649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446177093/">1446177093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446177901/">1446177901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446178712/">1446178712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446179021/">1446179021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446179070/">1446179070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446185551/">1446185551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446185790/">1446185790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446186390/">1446186390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446190231/">1446190231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446192570/">1446192570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446192810/">1446192810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446192990/">1446192990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446194011/">1446194011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446194668/">1446194668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446195890/">1446195890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446199051/">1446199051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446199493/">1446199493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446199661/">1446199661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446200371/">1446200371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446200491/">1446200491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446202349/">1446202349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446203971/">1446203971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446207992/">1446207992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446208831/">1446208831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446209490/">1446209490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446211290/">1446211290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446211665/">1446211665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446212910/">1446212910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446214438/">1446214438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446215732/">1446215732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446215970/">1446215970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446216966/">1446216966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446217053/">1446217053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446217832/">1446217832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446219763/">1446219763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446223163/">1446223163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446223401/">1446223401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446223526/">1446223526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446224062/">1446224062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446224306/">1446224306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446224444/">1446224444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446225335/">1446225335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446225993/">1446225993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446227072/">1446227072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446231335/">1446231335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446233251/">1446233251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446234273/">1446234273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446234570/">1446234570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446235366/">1446235366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446235506/">1446235506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446235771/">1446235771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446235891/">1446235891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446236432/">1446236432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446237507/">1446237507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446245610/">1446245610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446246297/">1446246297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446247710/">1446247710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446247949/">1446247949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446248070/">1446248070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446249510/">1446249510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446252870/">1446252870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446256470/">1446256470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446258210/">1446258210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446261652/">1446261652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446262613/">1446262613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446269093/">1446269093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446273833/">1446273833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446281153/">1446281153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446288473/">1446288473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446292561/">1446292561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446294593/">1446294593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446296753/">1446296753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446297114/">1446297114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446298133/">1446298133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446304973/">1446304973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446305872/">1446305872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446310313/">1446310313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446310492/">1446310492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446312173/">1446312173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446312473/">1446312473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446312892/">1446312892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446312952/">1446312952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446317933/">1446317933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446318951/">1446318951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446323692/">1446323692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446325072/">1446325072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446326092/">1446326092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446328913/">1446328913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446332992/">1446332992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446334069/">1446334069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446337553/">1446337553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446343064/">1446343064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446376357/">1446376357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446394477/">1446394477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446399998/">1446399998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446400717/">1446400717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446411698/">1446411698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446420870/">1446420870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446422130/">1446422130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446423391/">1446423391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446427291/">1446427291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446431311/">1446431311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446431909/">1446431909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446433708/">1446433708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446434311/">1446434311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446434880/">1446434880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446439712/">1446439712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446443671/">1446443671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446449611/">1446449611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446449792/">1446449792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446449971/">1446449971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446450091/">1446450091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446450751/">1446450751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446451772/">1446451772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446453449/">1446453449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446456638/">1446456638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446456721/">1446456721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446456807/">1446456807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446457122/">1446457122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446461106/">1446461106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446462307/">1446462307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446467886/">1446467886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446469027/">1446469027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446470886/">1446470886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446473105/">1446473105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446476424/">1446476424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446476474/">1446476474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446477127/">1446477127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446477307/">1446477307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446477965/">1446477965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446480009/">1446480009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446480561/">1446480561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446481085/">1446481085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446482288/">1446482288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446483395/">1446483395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446484329/">1446484329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446486247/">1446486247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446486615/">1446486615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446486729/">1446486729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446486849/">1446486849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446487809/">1446487809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446488227/">1446488227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446488407/">1446488407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446489307/">1446489307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446489428/">1446489428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446491527/">1446491527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446491528/">1446491528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446491886/">1446491886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446494707/">1446494707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446496806/">1446496806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446498308/">1446498308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446499207/">1446499207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446499327/">1446499327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446499810/">1446499810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446499866/">1446499866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446500228/">1446500228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446501187/">1446501187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446501426/">1446501426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446504366/">1446504366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446507007/">1446507007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446507368/">1446507368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446507907/">1446507907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446508687/">1446508687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446510078/">1446510078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446511327/">1446511327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446520104/">1446520104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446524227/">1446524227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446524406/">1446524406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446525368/">1446525368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446526148/">1446526148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446531007/">1446531007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446531186/">1446531186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446531366/">1446531366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446531548/">1446531548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446532387/">1446532387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446535086/">1446535086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446536347/">1446536347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446537727/">1446537727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446537955/">1446537955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446539047/">1446539047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446539285/">1446539285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446539466/">1446539466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446543185/">1446543185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446543849/">1446543849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446545796/">1446545796/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446549210/">1446549210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446549932/">1446549932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446550172/">1446550172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446556232/">1446556232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446558751/">1446558751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446559481/">1446559481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446560312/">1446560312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446563852/">1446563852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446565172/">1446565172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446566716/">1446566716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446567214/">1446567214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446569612/">1446569612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446569672/">1446569672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446571651/">1446571651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446574174/">1446574174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446575010/">1446575010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446575192/">1446575192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446576751/">1446576751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446578492/">1446578492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446578552/">1446578552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446579152/">1446579152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446583591/">1446583591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446585152/">1446585152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446586472/">1446586472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446587731/">1446587731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446588696/">1446588696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446592776/">1446592776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446595660/">1446595660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446597333/">1446597333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446603215/">1446603215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446603936/">1446603936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446604895/">1446604895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446605634/">1446605634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446605736/">1446605736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446606996/">1446606996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446607055/">1446607055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446609096/">1446609096/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446610775/">1446610775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446611914/">1446611914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446615814/">1446615814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446619236/">1446619236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446620678/">1446620678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446620913/">1446620913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446624375/">1446624375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446631115/">1446631115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446631775/">1446631775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446634836/">1446634836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446635975/">1446635975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446636696/">1446636696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446637895/">1446637895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446641255/">1446641255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446643475/">1446643475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446643655/">1446643655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446645035/">1446645035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446646655/">1446646655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446649823/">1446649823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446652293/">1446652293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446654455/">1446654455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446656558/">1446656558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446660755/">1446660755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446661896/">1446661896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446662197/">1446662197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446663454/">1446663454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446665075/">1446665075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446665914/">1446665914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446666155/">1446666155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446671555/">1446671555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446672214/">1446672214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446673114/">1446673114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446673235/">1446673235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446674855/">1446674855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446675035/">1446675035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446676355/">1446676355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446676836/">1446676836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446678437/">1446678437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679471/">1446679471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679475/">1446679475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679479/">1446679479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679481/">1446679481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679486/">1446679486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679487/">1446679487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679490/">1446679490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446679536/">1446679536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446680435/">1446680435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446680615/">1446680615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446682655/">1446682655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683461/">1446683461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683467/">1446683467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683469/">1446683469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683470/">1446683470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683474/">1446683474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683524/">1446683524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683622/">1446683622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683627/">1446683627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683629/">1446683629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446683632/">1446683632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446685295/">1446685295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446685472/">1446685472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446688295/">1446688295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446688835/">1446688835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446690155/">1446690155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446692315/">1446692315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446692835/">1446692835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446704315/">1446704315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446705214/">1446705214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446708335/">1446708335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446710555/">1446710555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446710860/">1446710860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446711095/">1446711095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446712055/">1446712055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446712056/">1446712056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446712850/">1446712850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446712896/">1446712896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446713021/">1446713021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446713134/">1446713134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446713374/">1446713374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446713432/">1446713432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446715655/">1446715655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446717575/">1446717575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446718300/">1446718300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446718357/">1446718357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446718955/">1446718955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446719374/">1446719374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446720156/">1446720156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446721894/">1446721894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446722196/">1446722196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446726641/">1446726641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446727415/">1446727415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446728255/">1446728255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446728494/">1446728494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446731917/">1446731917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446733954/">1446733954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446734075/">1446734075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446735002/">1446735002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446737195/">1446737195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446737514/">1446737514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446737522/">1446737522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446737976/">1446737976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446744874/">1446744874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446747936/">1446747936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446748245/">1446748245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446754295/">1446754295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446757836/">1446757836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446757955/">1446757955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446758255/">1446758255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446759008/">1446759008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446759335/">1446759335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446759755/">1446759755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446760595/">1446760595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446760896/">1446760896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446763832/">1446763832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446766115/">1446766115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446766175/">1446766175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446767738/">1446767738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446768816/">1446768816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446769715/">1446769715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446770555/">1446770555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446770975/">1446770975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446771455/">1446771455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446771635/">1446771635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446772297/">1446772297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446773135/">1446773135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446774574/">1446774574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446775664/">1446775664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446777999/">1446777999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446780516/">1446780516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446780807/">1446780807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446781056/">1446781056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446781294/">1446781294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446782320/">1446782320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446783754/">1446783754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446784356/">1446784356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446787174/">1446787174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446787773/">1446787773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446788322/">1446788322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446794495/">1446794495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446798935/">1446798935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446799174/">1446799174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446802257/">1446802257/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446809674/">1446809674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446809854/">1446809854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446809914/">1446809914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446809974/">1446809974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446810035/">1446810035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446810154/">1446810154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446810454/">1446810454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446814774/">1446814774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446815735/">1446815735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446815914/">1446815914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446817237/">1446817237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446817655/">1446817655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446817954/">1446817954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446818435/">1446818435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446821799/">1446821799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446821877/">1446821877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446821914/">1446821914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446822704/">1446822704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446823721/">1446823721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446823935/">1446823935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446825454/">1446825454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446826176/">1446826176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446826715/">1446826715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446826895/">1446826895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446828948/">1446828948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446831154/">1446831154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446831277/">1446831277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446831459/">1446831459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446832529/">1446832529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446833246/">1446833246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446833254/">1446833254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446834671/">1446834671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446836195/">1446836195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446836376/">1446836376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446836561/">1446836561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446837216/">1446837216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446837336/">1446837336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446838118/">1446838118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446841594/">1446841594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446842434/">1446842434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446844413/">1446844413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446844414/">1446844414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446845426/">1446845426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446846635/">1446846635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446847716/">1446847716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446848557/">1446848557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446848855/">1446848855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446849156/">1446849156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446850295/">1446850295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446850835/">1446850835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446851676/">1446851676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446852034/">1446852034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446852755/">1446852755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446854376/">1446854376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446854666/">1446854666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446855333/">1446855333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446855875/">1446855875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446856225/">1446856225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446860615/">1446860615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446867935/">1446867935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446870712/">1446870712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446873755/">1446873755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446888035/">1446888035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446888215/">1446888215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446899914/">1446899914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446904595/">1446904595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446907115/">1446907115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446907294/">1446907294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446925175/">1446925175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1446941266/">1446941266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447007195/">1447007195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447007315/">1447007315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447014335/">1447014335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447014514/">1447014514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447030295/">1447030295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447030535/">1447030535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447030775/">1447030775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447038696/">1447038696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447042355/">1447042355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447045551/">1447045551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447046613/">1447046613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447046735/">1447046735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447050611/">1447050611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447054955/">1447054955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447055194/">1447055194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447055374/">1447055374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447055674/">1447055674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447055974/">1447055974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447056172/">1447056172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447058751/">1447058751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447077578/">1447077578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447078896/">1447078896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447079797/">1447079797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447081174/">1447081174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447083325/">1447083325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447090952/">1447090952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447092458/">1447092458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447092690/">1447092690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447093292/">1447093292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447095634/">1447095634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447096417/">1447096417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447099650/">1447099650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447101034/">1447101034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447104093/">1447104093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447104615/">1447104615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447104933/">1447104933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447105411/">1447105411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447105833/">1447105833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447106855/">1447106855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447107454/">1447107454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447114831/">1447114831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447114951/">1447114951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447115793/">1447115793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447116331/">1447116331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447118791/">1447118791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447119211/">1447119211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447119271/">1447119271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447119331/">1447119331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447120052/">1447120052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447121431/">1447121431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447123785/">1447123785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447124011/">1447124011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447124372/">1447124372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447125090/">1447125090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447126305/">1447126305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447131092/">1447131092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447133917/">1447133917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447134515/">1447134515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447137016/">1447137016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447142763/">1447142763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447143036/">1447143036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447144655/">1447144655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447145855/">1447145855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447146515/">1447146515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447146815/">1447146815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447147355/">1447147355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447149335/">1447149335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447150535/">1447150535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447151675/">1447151675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447154013/">1447154013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447156715/">1447156715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447158035/">1447158035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447158605/">1447158605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447159475/">1447159475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447163015/">1447163015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447164403/">1447164403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447169082/">1447169082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447169581/">1447169581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447170995/">1447170995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447171657/">1447171657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447172382/">1447172382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447173214/">1447173214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447173397/">1447173397/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447173514/">1447173514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447174953/">1447174953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447175194/">1447175194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447175317/">1447175317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447175675/">1447175675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447176396/">1447176396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447177717/">1447177717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447178373/">1447178373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447180259/">1447180259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447181555/">1447181555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447181974/">1447181974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447183114/">1447183114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447184914/">1447184914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447188094/">1447188094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447188275/">1447188275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447188995/">1447188995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447189115/">1447189115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447189892/">1447189892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447192349/">1447192349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447193069/">1447193069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447193911/">1447193911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447195052/">1447195052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447195951/">1447195951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447198051/">1447198051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447208492/">1447208492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447209811/">1447209811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447212570/">1447212570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447213117/">1447213117/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447214880/">1447214880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447215272/">1447215272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447216169/">1447216169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447216535/">1447216535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447218641/">1447218641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447219051/">1447219051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447219591/">1447219591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447219831/">1447219831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447220731/">1447220731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447221211/">1447221211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447225712/">1447225712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447225895/">1447225895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447228651/">1447228651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447228954/">1447228954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447234245/">1447234245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447237111/">1447237111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447237832/">1447237832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447238072/">1447238072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447238851/">1447238851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447239152/">1447239152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447241552/">1447241552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447241670/">1447241670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447241911/">1447241911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447242164/">1447242164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447242334/">1447242334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447242634/">1447242634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447244252/">1447244252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447245015/">1447245015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447251151/">1447251151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447252052/">1447252052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447253311/">1447253311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447254508/">1447254508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447254752/">1447254752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447254991/">1447254991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447255109/">1447255109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447255173/">1447255173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447255291/">1447255291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447255878/">1447255878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447257153/">1447257153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447258235/">1447258235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447260632/">1447260632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447260992/">1447260992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447261232/">1447261232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447261349/">1447261349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447261712/">1447261712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447263454/">1447263454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447266152/">1447266152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447266654/">1447266654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447275451/">1447275451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447277372/">1447277372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447277373/">1447277373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447277492/">1447277492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447277551/">1447277551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447277793/">1447277793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447278391/">1447278391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447278870/">1447278870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447279891/">1447279891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447280371/">1447280371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447280913/">1447280913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447281571/">1447281571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447281811/">1447281811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447285307/">1447285307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447287887/">1447287887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447287950/">1447287950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447288206/">1447288206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447288908/">1447288908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447290768/">1447290768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447293888/">1447293888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447295027/">1447295027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447298267/">1447298267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447299227/">1447299227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447299828/">1447299828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447305707/">1447305707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447315007/">1447315007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447320370/">1447320370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447322858/">1447322858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447322925/">1447322925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447325868/">1447325868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447328268/">1447328268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447339248/">1447339248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447339967/">1447339967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447340864/">1447340864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447340865/">1447340865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447342965/">1447342965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447344108/">1447344108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447344585/">1447344585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447344746/">1447344746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447345253/">1447345253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447347285/">1447347285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447348310/">1447348310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447350348/">1447350348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447351480/">1447351480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447351661/">1447351661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447352988/">1447352988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447353063/">1447353063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447358254/">1447358254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447374397/">1447374397/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447376079/">1447376079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447376139/">1447376139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447376197/">1447376197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447376256/">1447376256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447377097/">1447377097/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447377880/">1447377880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447381776/">1447381776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447383770/">1447383770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447388376/">1447388376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447389999/">1447389999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447394976/">1447394976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447395278/">1447395278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447400265/">1447400265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447407036/">1447407036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447407042/">1447407042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447407511/">1447407511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447408173/">1447408173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447408354/">1447408354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447408711/">1447408711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447409132/">1447409132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447409433/">1447409433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447416332/">1447416332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447417808/">1447417808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447421851/">1447421851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447424373/">1447424373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447424793/">1447424793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447425395/">1447425395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447425696/">1447425696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447426259/">1447426259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447426774/">1447426774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447427689/">1447427689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447428109/">1447428109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447428212/">1447428212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447428450/">1447428450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447429130/">1447429130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447431346/">1447431346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447432958/">1447432958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447433192/">1447433192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447433372/">1447433372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447434302/">1447434302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447435776/">1447435776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447437032/">1447437032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447439194/">1447439194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447440511/">1447440511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447440815/">1447440815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447442013/">1447442013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447444710/">1447444710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447445491/">1447445491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447445552/">1447445552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447446211/">1447446211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447448132/">1447448132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447450713/">1447450713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447451313/">1447451313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447452869/">1447452869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447454372/">1447454372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447456113/">1447456113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447456531/">1447456531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447457852/">1447457852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447458152/">1447458152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447460132/">1447460132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447465352/">1447465352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447466552/">1447466552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447467812/">1447467812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447467991/">1447467991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447472033/">1447472033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447477451/">1447477451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447477893/">1447477893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447490911/">1447490911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447507833/">1447507833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447515173/">1447515173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447516772/">1447516772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447524633/">1447524633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447536992/">1447536992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447542276/">1447542276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447547409/">1447547409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447554033/">1447554033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447554632/">1447554632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447559253/">1447559253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447576052/">1447576052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447576936/">1447576936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447588173/">1447588173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447588713/">1447588713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447595734/">1447595734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447599633/">1447599633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447606472/">1447606472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447610253/">1447610253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447610373/">1447610373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447612052/">1447612052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447612412/">1447612412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447622552/">1447622552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447627292/">1447627292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447627293/">1447627293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447628253/">1447628253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447631613/">1447631613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447638092/">1447638092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447638453/">1447638453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447643854/">1447643854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447646861/">1447646861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447650547/">1447650547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447652079/">1447652079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447657896/">1447657896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447658496/">1447658496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447661797/">1447661797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447665517/">1447665517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447670076/">1447670076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447677004/">1447677004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447679616/">1447679616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447682014/">1447682014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447685988/">1447685988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447686043/">1447686043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447686306/">1447686306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447686515/">1447686515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447686875/">1447686875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447687922/">1447687922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447688198/">1447688198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447688681/">1447688681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447688736/">1447688736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447688977/">1447688977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447689873/">1447689873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447690426/">1447690426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447691613/">1447691613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447692154/">1447692154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447693446/">1447693446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447695400/">1447695400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447696417/">1447696417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447696699/">1447696699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447696700/">1447696700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447696704/">1447696704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447696708/">1447696708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447697194/">1447697194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447697736/">1447697736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447698697/">1447698697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447698714/">1447698714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447700499/">1447700499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447701276/">1447701276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447710996/">1447710996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447711237/">1447711237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447711296/">1447711296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447711297/">1447711297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447711475/">1447711475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447711656/">1447711656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447712136/">1447712136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447713636/">1447713636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447713758/">1447713758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447714295/">1447714295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447714537/">1447714537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447714655/">1447714655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447715079/">1447715079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447715442/">1447715442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447718885/">1447718885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447719724/">1447719724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447719843/">1447719843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447719905/">1447719905/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447720023/">1447720023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447720231/">1447720231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447720503/">1447720503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447721523/">1447721523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447722149/">1447722149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447722545/">1447722545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447723687/">1447723687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447723923/">1447723923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447724523/">1447724523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447728064/">1447728064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447731302/">1447731302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447733286/">1447733286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447736284/">1447736284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447738384/">1447738384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447738744/">1447738744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447740165/">1447740165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447740342/">1447740342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447745283/">1447745283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447746483/">1447746483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447747864/">1447747864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447750264/">1447750264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447750948/">1447750948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447751464/">1447751464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447752658/">1447752658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447753383/">1447753383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447754826/">1447754826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447756444/">1447756444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447760104/">1447760104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447762507/">1447762507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447763413/">1447763413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447764065/">1447764065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447766644/">1447766644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447767843/">1447767843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447768872/">1447768872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447772808/">1447772808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447773024/">1447773024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447774083/">1447774083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447774411/">1447774411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447775344/">1447775344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447776311/">1447776311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447779544/">1447779544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447781402/">1447781402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447781945/">1447781945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447782184/">1447782184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447782912/">1447782912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447783503/">1447783503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447783702/">1447783702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447784704/">1447784704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447785029/">1447785029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447785240/">1447785240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447788664/">1447788664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447788904/">1447788904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447789863/">1447789863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447790403/">1447790403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447790825/">1447790825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447795449/">1447795449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447796523/">1447796523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447797484/">1447797484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447797743/">1447797743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447800123/">1447800123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447800783/">1447800783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447801024/">1447801024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447802193/">1447802193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447803576/">1447803576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447803814/">1447803814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447805734/">1447805734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447806095/">1447806095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447808645/">1447808645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447809274/">1447809274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447810294/">1447810294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447811974/">1447811974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447813354/">1447813354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447813475/">1447813475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447817481/">1447817481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447818035/">1447818035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447818394/">1447818394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447821214/">1447821214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447823269/">1447823269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447854565/">1447854565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447854745/">1447854745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447854984/">1447854984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447856961/">1447856961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447858406/">1447858406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447861765/">1447861765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447862006/">1447862006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447862431/">1447862431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447862906/">1447862906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447863087/">1447863087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447863159/">1447863159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447863206/">1447863206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447863622/">1447863622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447864881/">1447864881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447865245/">1447865245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447865487/">1447865487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447866865/">1447866865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447867347/">1447867347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447867582/">1447867582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447869205/">1447869205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447870960/">1447870960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447872762/">1447872762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447872818/">1447872818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447872819/">1447872819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447873720/">1447873720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447874678/">1447874678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447877561/">1447877561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447877562/">1447877562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447877629/">1447877629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447878276/">1447878276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447880195/">1447880195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447884459/">1447884459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447884700/">1447884700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447884815/">1447884815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447884938/">1447884938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447886019/">1447886019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447886379/">1447886379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447887881/">1447887881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447888059/">1447888059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447888060/">1447888060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447889199/">1447889199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447889619/">1447889619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447889916/">1447889916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447890099/">1447890099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447890162/">1447890162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447890457/">1447890457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447890519/">1447890519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447890639/">1447890639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447890823/">1447890823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447890879/">1447890879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447891184/">1447891184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447891241/">1447891241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447892438/">1447892438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447893279/">1447893279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447893461/">1447893461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447893763/">1447893763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447894296/">1447894296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447895091/">1447895091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447895496/">1447895496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447901506/">1447901506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447902640/">1447902640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447903539/">1447903539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447909897/">1447909897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447912179/">1447912179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447912539/">1447912539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447917279/">1447917279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447920339/">1447920339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447921120/">1447921120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447923459/">1447923459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447924419/">1447924419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447925379/">1447925379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447925556/">1447925556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447926040/">1447926040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447926280/">1447926280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447926399/">1447926399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447926459/">1447926459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447926579/">1447926579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447926696/">1447926696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447935560/">1447935560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447939819/">1447939819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447940119/">1447940119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447941986/">1447941986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447942087/">1447942087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447942088/">1447942088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447943046/">1447943046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447944559/">1447944559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447945054/">1447945054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447945147/">1447945147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447945989/">1447945989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447948205/">1447948205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447949345/">1447949345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447949469/">1447949469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447951867/">1447951867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447954867/">1447954867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447955404/">1447955404/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447957539/">1447957539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447958319/">1447958319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447960475/">1447960475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447960716/">1447960716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447961555/">1447961555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447961735/">1447961735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447962879/">1447962879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447963235/">1447963235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447964195/">1447964195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447964798/">1447964798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447965218/">1447965218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447967771/">1447967771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447968430/">1447968430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447968550/">1447968550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447969753/">1447969753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447969993/">1447969993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447970348/">1447970348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447970852/">1447970852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447971035/">1447971035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447973374/">1447973374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447973433/">1447973433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447974399/">1447974399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447983755/">1447983755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447985435/">1447985435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447985916/">1447985916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447987236/">1447987236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447990408/">1447990408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447992095/">1447992095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447993362/">1447993362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447993921/">1447993921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447994196/">1447994196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447994674/">1447994674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447996175/">1447996175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447998514/">1447998514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1447999714/">1447999714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448000434/">1448000434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448000554/">1448000554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448000974/">1448000974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448003050/">1448003050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448004515/">1448004515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448005475/">1448005475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448005714/">1448005714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448010456/">1448010456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448010932/">1448010932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448011114/">1448011114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448013575/">1448013575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448019756/">1448019756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448019874/">1448019874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448020835/">1448020835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448024802/">1448024802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448025815/">1448025815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448025934/">1448025934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448026533/">1448026533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448028265/">1448028265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448028935/">1448028935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448029294/">1448029294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448029534/">1448029534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448030973/">1448030973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448034343/">1448034343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448034996/">1448034996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448035656/">1448035656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448039376/">1448039376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448039431/">1448039431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448039972/">1448039972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448040572/">1448040572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448042494/">1448042494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448043751/">1448043751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448044657/">1448044657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448047470/">1448047470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448048195/">1448048195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448049633/">1448049633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448053593/">1448053593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448055033/">1448055033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448055210/">1448055210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448056232/">1448056232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448056533/">1448056533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448057195/">1448057195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448059056/">1448059056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448059474/">1448059474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448059597/">1448059597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448060493/">1448060493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448068007/">1448068007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448068233/">1448068233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448079993/">1448079993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448080267/">1448080267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448087612/">1448087612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448090913/">1448090913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448112934/">1448112934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448135711/">1448135711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448156113/">1448156113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448185631/">1448185631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448200691/">1448200691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448205311/">1448205311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448205431/">1448205431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448220613/">1448220613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448226011/">1448226011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448230271/">1448230271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448233271/">1448233271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448244791/">1448244791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448248811/">1448248811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448251231/">1448251231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448251946/">1448251946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448255425/">1448255425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448256745/">1448256745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448256985/">1448256985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448258621/">1448258621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448262624/">1448262624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448269764/">1448269764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448278465/">1448278465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448278645/">1448278645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448279426/">1448279426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448279725/">1448279725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448280265/">1448280265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448283744/">1448283744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448285546/">1448285546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448286507/">1448286507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448287551/">1448287551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448287922/">1448287922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448288126/">1448288126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448291268/">1448291268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448291492/">1448291492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448292093/">1448292093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448292204/">1448292204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448292865/">1448292865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448293105/">1448293105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448293343/">1448293343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448293644/">1448293644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448293705/">1448293705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448293952/">1448293952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448294006/">1448294006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448294127/">1448294127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448294128/">1448294128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448294424/">1448294424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448295505/">1448295505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448299045/">1448299045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448301267/">1448301267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448306366/">1448306366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448307559/">1448307559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448309184/">1448309184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448313019/">1448313019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448313080/">1448313080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448314279/">1448314279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448314459/">1448314459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448314579/">1448314579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448315419/">1448315419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448316224/">1448316224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448316560/">1448316560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448317519/">1448317519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448317759/">1448317759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448319499/">1448319499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448320173/">1448320173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448321012/">1448321012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448321372/">1448321372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448324072/">1448324072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448326413/">1448326413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448328452/">1448328452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448332060/">1448332060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448332173/">1448332173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448333254/">1448333254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448337576/">1448337576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448339855/">1448339855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448341534/">1448341534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448344592/">1448344592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448348643/">1448348643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448351132/">1448351132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448351252/">1448351252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448351313/">1448351313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448358692/">1448358692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448359234/">1448359234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448361573/">1448361573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448364093/">1448364093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448369013/">1448369013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448374232/">1448374232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448374892/">1448374892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448375974/">1448375974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448378672/">1448378672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448380233/">1448380233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448380593/">1448380593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448380654/">1448380654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448381152/">1448381152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448381493/">1448381493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448382814/">1448382814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448383359/">1448383359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448383777/">1448383777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448384675/">1448384675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448386003/">1448386003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448386172/">1448386172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448387013/">1448387013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448387252/">1448387252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448387433/">1448387433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448388452/">1448388452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448390314/">1448390314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448390372/">1448390372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448391094/">1448391094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448391872/">1448391872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448392532/">1448392532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448392772/">1448392772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448394514/">1448394514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448394752/">1448394752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448395893/">1448395893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448396253/">1448396253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448399733/">1448399733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448400333/">1448400333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448401593/">1448401593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448405673/">1448405673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448406390/">1448406390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448407593/">1448407593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448409213/">1448409213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448409333/">1448409333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448411672/">1448411672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448413234/">1448413234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448414012/">1448414012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448415992/">1448415992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448422001/">1448422001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448422653/">1448422653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448424942/">1448424942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448425296/">1448425296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448426193/">1448426193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448426913/">1448426913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448428233/">1448428233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448429312/">1448429312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448435255/">1448435255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448435856/">1448435856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448436105/">1448436105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448436654/">1448436654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448436752/">1448436752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448442813/">1448442813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448445092/">1448445092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448445971/">1448445971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448448814/">1448448814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448451572/">1448451572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448451933/">1448451933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448452951/">1448452951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448454391/">1448454391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448456313/">1448456313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448457333/">1448457333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448458498/">1448458498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448458499/">1448458499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448459553/">1448459553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448462431/">1448462431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448462616/">1448462616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448463275/">1448463275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448463640/">1448463640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448465588/">1448465588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448467610/">1448467610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448467835/">1448467835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448470655/">1448470655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448471793/">1448471793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448472574/">1448472574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448472694/">1448472694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448473652/">1448473652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448473773/">1448473773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448482893/">1448482893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448483672/">1448483672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448484033/">1448484033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448484516/">1448484516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448485533/">1448485533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448489470/">1448489470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448491090/">1448491090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448492950/">1448492950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448494813/">1448494813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448496315/">1448496315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448496610/">1448496610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448496853/">1448496853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448499130/">1448499130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448499131/">1448499131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448511190/">1448511190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448514260/">1448514260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448524509/">1448524509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448528350/">1448528350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448529250/">1448529250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448529371/">1448529371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448529849/">1448529849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448530453/">1448530453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448530570/">1448530570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448530750/">1448530750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448533211/">1448533211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448535069/">1448535069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448535130/">1448535130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448535670/">1448535670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448536210/">1448536210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448538469/">1448538469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448539349/">1448539349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448540229/">1448540229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448547850/">1448547850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448549531/">1448549531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448551450/">1448551450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448553250/">1448553250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448553431/">1448553431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448555110/">1448555110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448555530/">1448555530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448555590/">1448555590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448555840/">1448555840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448557451/">1448557451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448557813/">1448557813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448557931/">1448557931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448564896/">1448564896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448566390/">1448566390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448566571/">1448566571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448567832/">1448567832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448568972/">1448568972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448570234/">1448570234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448570292/">1448570292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448571492/">1448571492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448572034/">1448572034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448574852/">1448574852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448577072/">1448577072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448578212/">1448578212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448578512/">1448578512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448580492/">1448580492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448581572/">1448581572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448583672/">1448583672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448588832/">1448588832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448589968/">1448589968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448591236/">1448591236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448591772/">1448591772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448592732/">1448592732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448597173/">1448597173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448597713/">1448597713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448607132/">1448607132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448609292/">1448609292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448617092/">1448617092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448619132/">1448619132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448628252/">1448628252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448628312/">1448628312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448629093/">1448629093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448630292/">1448630292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448631972/">1448631972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448635092/">1448635092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448635452/">1448635452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448639832/">1448639832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448641234/">1448641234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448643272/">1448643272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448647892/">1448647892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448650653/">1448650653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448652453/">1448652453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448655453/">1448655453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448659952/">1448659952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448664932/">1448664932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448666493/">1448666493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448667635/">1448667635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448669673/">1448669673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448672310/">1448672310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448683355/">1448683355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448684732/">1448684732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448693612/">1448693612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448693973/">1448693973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448707772/">1448707772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448743112/">1448743112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448743232/">1448743232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448759312/">1448759312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448759672/">1448759672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448816372/">1448816372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448824292/">1448824292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448829585/">1448829585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448831610/">1448831610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448835752/">1448835752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448836832/">1448836832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448839592/">1448839592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448842054/">1448842054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448844572/">1448844572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448859811/">1448859811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448860056/">1448860056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448860173/">1448860173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448861312/">1448861312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448867853/">1448867853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448868334/">1448868334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448874093/">1448874093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448875112/">1448875112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448876492/">1448876492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448876735/">1448876735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448882312/">1448882312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448885252/">1448885252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448886273/">1448886273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448886813/">1448886813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448886933/">1448886933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448887608/">1448887608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448894853/">1448894853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448898633/">1448898633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448903972/">1448903972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448904393/">1448904393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448908211/">1448908211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448912793/">1448912793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448912913/">1448912913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448913121/">1448913121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448913159/">1448913159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448914592/">1448914592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448916334/">1448916334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448916453/">1448916453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448917558/">1448917558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448917595/">1448917595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448918193/">1448918193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448918313/">1448918313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448918854/">1448918854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448919633/">1448919633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448921026/">1448921026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448921171/">1448921171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448921553/">1448921553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448921734/">1448921734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448922936/">1448922936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448923233/">1448923233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448925093/">1448925093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448925392/">1448925392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448925992/">1448925992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448926053/">1448926053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448926054/">1448926054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448926354/">1448926354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448926712/">1448926712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448927253/">1448927253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448927312/">1448927312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448928933/">1448928933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448928993/">1448928993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448929053/">1448929053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448929472/">1448929472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448929773/">1448929773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448931963/">1448931963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448933073/">1448933073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448933734/">1448933734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448933911/">1448933911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448934455/">1448934455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448934516/">1448934516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448936490/">1448936490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448938773/">1448938773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448939147/">1448939147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448939148/">1448939148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448944414/">1448944414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448944596/">1448944596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448945973/">1448945973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448946154/">1448946154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448946936/">1448946936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448947412/">1448947412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448947593/">1448947593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448948432/">1448948432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448949212/">1448949212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448954435/">1448954435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448956113/">1448956113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448959593/">1448959593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448959951/">1448959951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448960552/">1448960552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448960771/">1448960771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448962352/">1448962352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448962953/">1448962953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448963552/">1448963552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448963974/">1448963974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448964154/">1448964154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448964225/">1448964225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448964392/">1448964392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448964512/">1448964512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448964573/">1448964573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448964692/">1448964692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448965533/">1448965533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448967333/">1448967333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448973933/">1448973933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448974592/">1448974592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448976092/">1448976092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448976694/">1448976694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448978373/">1448978373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448978628/">1448978628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448978912/">1448978912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448980113/">1448980113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448981022/">1448981022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448981314/">1448981314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448981673/">1448981673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448983298/">1448983298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448984226/">1448984226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448984553/">1448984553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448989441/">1448989441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448990392/">1448990392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448994208/">1448994208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448995522/">1448995522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448996065/">1448996065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448996248/">1448996248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448997685/">1448997685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448997801/">1448997801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448999006/">1448999006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448999246/">1448999246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1448999545/">1448999545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449002785/">1449002785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449003145/">1449003145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449003324/">1449003324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449005910/">1449005910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449006630/">1449006630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449006810/">1449006810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449008491/">1449008491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449008790/">1449008790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449009210/">1449009210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449010230/">1449010230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449012420/">1449012420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449012960/">1449012960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449013259/">1449013259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449013320/">1449013320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449014160/">1449014160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449015061/">1449015061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449021240/">1449021240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449021984/">1449021984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449024060/">1449024060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449026581/">1449026581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449030061/">1449030061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449031441/">1449031441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449032667/">1449032667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449039120/">1449039120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449041581/">1449041581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449042181/">1449042181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449042360/">1449042360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449043201/">1449043201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449044700/">1449044700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449045660/">1449045660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449048180/">1449048180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449050636/">1449050636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449051120/">1449051120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449061140/">1449061140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449061500/">1449061500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449061622/">1449061622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449061981/">1449061981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449064381/">1449064381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449066206/">1449066206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449066981/">1449066981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449069385/">1449069385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449069804/">1449069804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449071981/">1449071981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449073539/">1449073539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449074126/">1449074126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449074541/">1449074541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449076347/">1449076347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449076702/">1449076702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449078206/">1449078206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449082042/">1449082042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449082466/">1449082466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449083121/">1449083121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449084385/">1449084385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449084866/">1449084866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449086301/">1449086301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449086421/">1449086421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449089961/">1449089961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449090921/">1449090921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449092301/">1449092301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449092721/">1449092721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449093381/">1449093381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449093561/">1449093561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449094641/">1449094641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449094881/">1449094881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449094941/">1449094941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449095425/">1449095425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449095841/">1449095841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449096202/">1449096202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449096381/">1449096381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449096501/">1449096501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449097281/">1449097281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449100465/">1449100465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449104661/">1449104661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449104783/">1449104783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449105026/">1449105026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449105381/">1449105381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449105802/">1449105802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449106701/">1449106701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449106761/">1449106761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449108981/">1449108981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449109102/">1449109102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449113592/">1449113592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449114563/">1449114563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449116905/">1449116905/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449117321/">1449117321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449123745/">1449123745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449124405/">1449124405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449125245/">1449125245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449125601/">1449125601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449128723/">1449128723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449131002/">1449131002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449131061/">1449131061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449131301/">1449131301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449132141/">1449132141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449134841/">1449134841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449136526/">1449136526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449136645/">1449136645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449136761/">1449136761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449136822/">1449136822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449137191/">1449137191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449138141/">1449138141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449140903/">1449140903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449141445/">1449141445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449141622/">1449141622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449141801/">1449141801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449143781/">1449143781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449149122/">1449149122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449150506/">1449150506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449151822/">1449151822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449151941/">1449151941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449156265/">1449156265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449158122/">1449158122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449158242/">1449158242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449159759/">1449159759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449163344/">1449163344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449167241/">1449167241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449171024/">1449171024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449171444/">1449171444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449171921/">1449171921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449173962/">1449173962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449174381/">1449174381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449177638/">1449177638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449178902/">1449178902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449180281/">1449180281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449180822/">1449180822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449183881/">1449183881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449184004/">1449184004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449185442/">1449185442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449185742/">1449185742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449185921/">1449185921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449185981/">1449185981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449187122/">1449187122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449187938/">1449187938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449189614/">1449189614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449190815/">1449190815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449191175/">1449191175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449191475/">1449191475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449191775/">1449191775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449192555/">1449192555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449195493/">1449195493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449197295/">1449197295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449198015/">1449198015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449199277/">1449199277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449199767/">1449199767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449200716/">1449200716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449202695/">1449202695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449203055/">1449203055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449203234/">1449203234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449203895/">1449203895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449205037/">1449205037/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449206956/">1449206956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449209595/">1449209595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449211038/">1449211038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449224055/">1449224055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449224655/">1449224655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449225437/">1449225437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449227012/">1449227012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449237015/">1449237015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449238755/">1449238755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449238996/">1449238996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449239596/">1449239596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449240740/">1449240740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449240915/">1449240915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449241356/">1449241356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449241560/">1449241560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449241570/">1449241570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449248851/">1449248851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449249316/">1449249316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449251656/">1449251656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449253275/">1449253275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449254288/">1449254288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449255069/">1449255069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449256035/">1449256035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449256209/">1449256209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449259433/">1449259433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449263848/">1449263848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449263968/">1449263968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449264808/">1449264808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449264869/">1449264869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449265289/">1449265289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449266129/">1449266129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449271346/">1449271346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449273566/">1449273566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449280890/">1449280890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449281071/">1449281071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449281206/">1449281206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449283587/">1449283587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449287190/">1449287190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449292047/">1449292047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449296971/">1449296971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449299607/">1449299607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449305008/">1449305008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449308371/">1449308371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449309151/">1449309151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449320910/">1449320910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449329910/">1449329910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449331961/">1449331961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449346591/">1449346591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449347371/">1449347371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449348031/">1449348031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449351268/">1449351268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449393691/">1449393691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449420691/">1449420691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449428549/">1449428549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449436170/">1449436170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449444871/">1449444871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449453690/">1449453690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449454588/">1449454588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449454770/">1449454770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449463950/">1449463950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449466235/">1449466235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449467490/">1449467490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449483690/">1449483690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449487470/">1449487470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449491610/">1449491610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449496295/">1449496295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449497252/">1449497252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449500723/">1449500723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449504810/">1449504810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449506139/">1449506139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449516031/">1449516031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449527847/">1449527847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449529647/">1449529647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449530122/">1449530122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449531626/">1449531626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449531805/">1449531805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449534206/">1449534206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449560242/">1449560242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449562105/">1449562105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449564684/">1449564684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449564744/">1449564744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449570385/">1449570385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449570505/">1449570505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449593961/">1449593961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449597342/">1449597342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449597942/">1449597942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449599422/">1449599422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449604062/">1449604062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449604662/">1449604662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449605443/">1449605443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449606520/">1449606520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449606700/">1449606700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449613004/">1449613004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449614261/">1449614261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449614982/">1449614982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449615161/">1449615161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449626601/">1449626601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630141/">1449630141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630163/">1449630163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630165/">1449630165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630362/">1449630362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630373/">1449630373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630374/">1449630374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630377/">1449630377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630452/">1449630452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630455/">1449630455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630538/">1449630538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630586/">1449630586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630608/">1449630608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630681/">1449630681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630697/">1449630697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449630857/">1449630857/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449635864/">1449635864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449654342/">1449654342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449654457/">1449654457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449656742/">1449656742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449660222/">1449660222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449660523/">1449660523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449668443/">1449668443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449673002/">1449673002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449673522/">1449673522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449674142/">1449674142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449676422/">1449676422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449676901/">1449676901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449677080/">1449677080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449677440/">1449677440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449678042/">1449678042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449678939/">1449678939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449679064/">1449679064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449679303/">1449679303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449680383/">1449680383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449684896/">1449684896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449685363/">1449685363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449695922/">1449695922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449696582/">1449696582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449697480/">1449697480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449697660/">1449697660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449700182/">1449700182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449700299/">1449700299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449700663/">1449700663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449701258/">1449701258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449703303/">1449703303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449714103/">1449714103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449715959/">1449715959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449719985/">1449719985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449737982/">1449737982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449738357/">1449738357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449738643/">1449738643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449742483/">1449742483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449747283/">1449747283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449750605/">1449750605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449752615/">1449752615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449761022/">1449761022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449761872/">1449761872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449763253/">1449763253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449766793/">1449766793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449767182/">1449767182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449767330/">1449767330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449767509/">1449767509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449767873/">1449767873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449771769/">1449771769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449772213/">1449772213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449772913/">1449772913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449773332/">1449773332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449774592/">1449774592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449775073/">1449775073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449779152/">1449779152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449779332/">1449779332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449779393/">1449779393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449779573/">1449779573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449780589/">1449780589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449780712/">1449780712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449782136/">1449782136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449782320/">1449782320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449782797/">1449782797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449783032/">1449783032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449783877/">1449783877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449784415/">1449784415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449788918/">1449788918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449793805/">1449793805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449797078/">1449797078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449802268/">1449802268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449804738/">1449804738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449814597/">1449814597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449815419/">1449815419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449818557/">1449818557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449821618/">1449821618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449821678/">1449821678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449821978/">1449821978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449822157/">1449822157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449822277/">1449822277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449824438/">1449824438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449824499/">1449824499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449826207/">1449826207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449830558/">1449830558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449837002/">1449837002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449845498/">1449845498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449847293/">1449847293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449847652/">1449847652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449847986/">1449847986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449848493/">1449848493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449850710/">1449850710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449852152/">1449852152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449852812/">1449852812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449853710/">1449853710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449855394/">1449855394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449857733/">1449857733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449858617/">1449858617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449858812/">1449858812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449861511/">1449861511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449862292/">1449862292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449863611/">1449863611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449869406/">1449869406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449898351/">1449898351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449901810/">1449901810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449930032/">1449930032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449934446/">1449934446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449946351/">1449946351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449947611/">1449947611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449948931/">1449948931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449954394/">1449954394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449955806/">1449955806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449968432/">1449968432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449976232/">1449976232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1449977511/">1449977511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450045471/">1450045471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450045651/">1450045651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450053002/">1450053002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450054711/">1450054711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450056092/">1450056092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450056751/">1450056751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450056811/">1450056811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450060651/">1450060651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450061241/">1450061241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450074993/">1450074993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450079851/">1450079851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450080201/">1450080201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450080512/">1450080512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450081532/">1450081532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450082132/">1450082132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450085403/">1450085403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450087055/">1450087055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450091251/">1450091251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450096212/">1450096212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450103371/">1450103371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450103731/">1450103731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450103853/">1450103853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450104751/">1450104751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450105764/">1450105764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450107217/">1450107217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450109209/">1450109209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450112013/">1450112013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450112611/">1450112611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450116332/">1450116332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450117471/">1450117471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450117808/">1450117808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450119332/">1450119332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450122151/">1450122151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450122513/">1450122513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450122744/">1450122744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450123165/">1450123165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450123464/">1450123464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450123824/">1450123824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450125866/">1450125866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450126464/">1450126464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450128667/">1450128667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450128805/">1450128805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450129285/">1450129285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450129945/">1450129945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450130061/">1450130061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450132232/">1450132232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450133121/">1450133121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450133305/">1450133305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450134741/">1450134741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450137140/">1450137140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450137556/">1450137556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450137684/">1450137684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450138391/">1450138391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450138509/">1450138509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450139430/">1450139430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450144211/">1450144211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450144329/">1450144329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450150364/">1450150364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450150931/">1450150931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450151710/">1450151710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450153331/">1450153331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450157650/">1450157650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450160590/">1450160590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450173807/">1450173807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450182606/">1450182606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450186691/">1450186691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450187591/">1450187591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450187830/">1450187830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450188790/">1450188790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450188850/">1450188850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450189090/">1450189090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450190848/">1450190848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450191030/">1450191030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450192109/">1450192109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450192227/">1450192227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450195852/">1450195852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450201769/">1450201769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450204595/">1450204595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450205309/">1450205309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450205369/">1450205369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450207409/">1450207409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450214913/">1450214913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450217998/">1450217998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450218298/">1450218298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450218477/">1450218477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450219377/">1450219377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450219858/">1450219858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450220875/">1450220875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450221115/">1450221115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450223638/">1450223638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450223998/">1450223998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450224968/">1450224968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450226709/">1450226709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450228746/">1450228746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450230366/">1450230366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450230846/">1450230846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450236486/">1450236486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450239846/">1450239846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450245486/">1450245486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450254546/">1450254546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450256608/">1450256608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450257426/">1450257426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450259766/">1450259766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450259948/">1450259948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450260967/">1450260967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450262706/">1450262706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450263246/">1450263246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450263846/">1450263846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450263847/">1450263847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450264624/">1450264624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450264748/">1450264748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450265346/">1450265346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450266966/">1450266966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450269484/">1450269484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450270999/">1450270999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450273394/">1450273394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450276266/">1450276266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450276567/">1450276567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450278919/">1450278919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450280646/">1450280646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450282000/">1450282000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450282117/">1450282117/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450282539/">1450282539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450286917/">1450286917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450287877/">1450287877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450298460/">1450298460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450298519/">1450298519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450298699/">1450298699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450298939/">1450298939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450298940/">1450298940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450299120/">1450299120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450299360/">1450299360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450300260/">1450300260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450300319/">1450300319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450300619/">1450300619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450301160/">1450301160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450301698/">1450301698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450301939/">1450301939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450302360/">1450302360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450302420/">1450302420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450303258/">1450303258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450304159/">1450304159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450305360/">1450305360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450306140/">1450306140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450306261/">1450306261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450306620/">1450306620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450306739/">1450306739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450307017/">1450307017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450310039/">1450310039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450310339/">1450310339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450311360/">1450311360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450312379/">1450312379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450313156/">1450313156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450314237/">1450314237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450317153/">1450317153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450317995/">1450317995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450320932/">1450320932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450321952/">1450321952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450322072/">1450322072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450322193/">1450322193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450322312/">1450322312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450323149/">1450323149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450324712/">1450324712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450328192/">1450328192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450337431/">1450337431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450337911/">1450337911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450338272/">1450338272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450338392/">1450338392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450338632/">1450338632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450339433/">1450339433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450344572/">1450344572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450346312/">1450346312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450350271/">1450350271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450353690/">1450353690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450358070/">1450358070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450364806/">1450364806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450365150/">1450365150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450366831/">1450366831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450369590/">1450369590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450370012/">1450370012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450370248/">1450370248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450370435/">1450370435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450373731/">1450373731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450373911/">1450373911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450377952/">1450377952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450379752/">1450379752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450380113/">1450380113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450381618/">1450381618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450381972/">1450381972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450383235/">1450383235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450383355/">1450383355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450386413/">1450386413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450387912/">1450387912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450390791/">1450390791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450390792/">1450390792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450391519/">1450391519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450391631/">1450391631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450392175/">1450392175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450393672/">1450393672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450397933/">1450397933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450399191/">1450399191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450400617/">1450400617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450404053/">1450404053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450404218/">1450404218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450405552/">1450405552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450408974/">1450408974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450410368/">1450410368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450410711/">1450410711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450421752/">1450421752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450421876/">1450421876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450426132/">1450426132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450426312/">1450426312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450427272/">1450427272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450428292/">1450428292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450429132/">1450429132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450429852/">1450429852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450433636/">1450433636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450434591/">1450434591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450440833/">1450440833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450443833/">1450443833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450445571/">1450445571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450447311/">1450447311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450448812/">1450448812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450448936/">1450448936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450450194/">1450450194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450451874/">1450451874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450452358/">1450452358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450454391/">1450454391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450454769/">1450454769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450455472/">1450455472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450456374/">1450456374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450457516/">1450457516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450457875/">1450457875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450457991/">1450457991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450464330/">1450464330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450464510/">1450464510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450465054/">1450465054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450465470/">1450465470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450466250/">1450466250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450467693/">1450467693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450468350/">1450468350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450469191/">1450469191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450471470/">1450471470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450473570/">1450473570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450473810/">1450473810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450475250/">1450475250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450476351/">1450476351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450476810/">1450476810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450477650/">1450477650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450478610/">1450478610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450480412/">1450480412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450481190/">1450481190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450481609/">1450481609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450487850/">1450487850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450488030/">1450488030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450489231/">1450489231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450495470/">1450495470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450536090/">1450536090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450559850/">1450559850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450572990/">1450572990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450622970/">1450622970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450665509/">1450665509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450674689/">1450674689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450685429/">1450685429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450687287/">1450687287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450688489/">1450688489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450690109/">1450690109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450690289/">1450690289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450690409/">1450690409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450690949/">1450690949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450691129/">1450691129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450692629/">1450692629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450693829/">1450693829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450694370/">1450694370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450695510/">1450695510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450697126/">1450697126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450699765/">1450699765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450708648/">1450708648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450708769/">1450708769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450710871/">1450710871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450711290/">1450711290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450711348/">1450711348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450715069/">1450715069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450716689/">1450716689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450718606/">1450718606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450721486/">1450721486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450721546/">1450721546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450722509/">1450722509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450723589/">1450723589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450724549/">1450724549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450727429/">1450727429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450729769/">1450729769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450730609/">1450730609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450732271/">1450732271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450732512/">1450732512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450733121/">1450733121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450733589/">1450733589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450734550/">1450734550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450737224/">1450737224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450737284/">1450737284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450737645/">1450737645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450737823/">1450737823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450738631/">1450738631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450738688/">1450738688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450743738/">1450743738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450744398/">1450744398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450744518/">1450744518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450745655/">1450745655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450745898/">1450745898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450746557/">1450746557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450748898/">1450748898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450753567/">1450753567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450753938/">1450753938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450760898/">1450760898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450763959/">1450763959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450765278/">1450765278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450766178/">1450766178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450766958/">1450766958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450767378/">1450767378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450767678/">1450767678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450773737/">1450773737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450776378/">1450776378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450779136/">1450779136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450781301/">1450781301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450784839/">1450784839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450786636/">1450786636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450789938/">1450789938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450790599/">1450790599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450791258/">1450791258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450792699/">1450792699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450793727/">1450793727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450793787/">1450793787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450795347/">1450795347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450797327/">1450797327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450798320/">1450798320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450800928/">1450800928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450801647/">1450801647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450802127/">1450802127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450802309/">1450802309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450803208/">1450803208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450804107/">1450804107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450805126/">1450805126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450808847/">1450808847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450809016/">1450809016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450814247/">1450814247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450814738/">1450814738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450817067/">1450817067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450818214/">1450818214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450819527/">1450819527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450820067/">1450820067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450821267/">1450821267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450821867/">1450821867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450822646/">1450822646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450826667/">1450826667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450827267/">1450827267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450830613/">1450830613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450831527/">1450831527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450832733/">1450832733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450836388/">1450836388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450837887/">1450837887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450838307/">1450838307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450843947/">1450843947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450846827/">1450846827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450852212/">1450852212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450853368/">1450853368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450855288/">1450855288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450860627/">1450860627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450863005/">1450863005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450863507/">1450863507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450867048/">1450867048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450871308/">1450871308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450873803/">1450873803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450879887/">1450879887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450880428/">1450880428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450881867/">1450881867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450882286/">1450882286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450882527/">1450882527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450882955/">1450882955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450883271/">1450883271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450886490/">1450886490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450886804/">1450886804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450887869/">1450887869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450888046/">1450888046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450889732/">1450889732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450889909/">1450889909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450892372/">1450892372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450895450/">1450895450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450897769/">1450897769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450900651/">1450900651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450901129/">1450901129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450901549/">1450901549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450902089/">1450902089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450902628/">1450902628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450903949/">1450903949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450904430/">1450904430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450904743/">1450904743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450904909/">1450904909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450906215/">1450906215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450906587/">1450906587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450907112/">1450907112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450908371/">1450908371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450908433/">1450908433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450914432/">1450914432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450916772/">1450916772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450917032/">1450917032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450918392/">1450918392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450918941/">1450918941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450922538/">1450922538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450923851/">1450923851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450925712/">1450925712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450926491/">1450926491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450927903/">1450927903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450928111/">1450928111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450928173/">1450928173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450930451/">1450930451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450933151/">1450933151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450933812/">1450933812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450937291/">1450937291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450938613/">1450938613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450949403/">1450949403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450953191/">1450953191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450956990/">1450956990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450960204/">1450960204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450975427/">1450975427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450978847/">1450978847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450979207/">1450979207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450979807/">1450979807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450981127/">1450981127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450981832/">1450981832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450984065/">1450984065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450984547/">1450984547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450984847/">1450984847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450986287/">1450986287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450991387/">1450991387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1450993727/">1450993727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451003407/">1451003407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451011667/">1451011667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451014304/">1451014304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451082167/">1451082167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451089804/">1451089804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451094708/">1451094708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451128247/">1451128247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451133004/">1451133004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451155367/">1451155367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451165405/">1451165405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451172047/">1451172047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451176547/">1451176547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451187131/">1451187131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451212967/">1451212967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451219425/">1451219425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451229107/">1451229107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451229707/">1451229707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451230127/">1451230127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451230479/">1451230479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451245427/">1451245427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451251787/">1451251787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451251805/">1451251805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451253107/">1451253107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451255627/">1451255627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451260547/">1451260547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451295887/">1451295887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451296067/">1451296067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451297267/">1451297267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451300867/">1451300867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451304767/">1451304767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451305804/">1451305804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451311392/">1451311392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451315406/">1451315406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451316527/">1451316527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451328452/">1451328452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451332352/">1451332352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451333242/">1451333242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451338101/">1451338101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451342375/">1451342375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451342558/">1451342558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451342978/">1451342978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451343910/">1451343910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451345558/">1451345558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451346758/">1451346758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451349038/">1451349038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451349576/">1451349576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451349644/">1451349644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451349758/">1451349758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451354258/">1451354258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451354377/">1451354377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451357378/">1451357378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451364878/">1451364878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451377478/">1451377478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451378200/">1451378200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451380178/">1451380178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451380238/">1451380238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451380358/">1451380358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451382163/">1451382163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451383178/">1451383178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451386538/">1451386538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451387678/">1451387678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451388038/">1451388038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451389298/">1451389298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451390800/">1451390800/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451395778/">1451395778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451397638/">1451397638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451397910/">1451397910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451399078/">1451399078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451399738/">1451399738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451400515/">1451400515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451403338/">1451403338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451403997/">1451403997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451404176/">1451404176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451405199/">1451405199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451407422/">1451407422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451412218/">1451412218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451412219/">1451412219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451412641/">1451412641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451412815/">1451412815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451414630/">1451414630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451421399/">1451421399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451421818/">1451421818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451424038/">1451424038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451428298/">1451428298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451429078/">1451429078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451431238/">1451431238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451432976/">1451432976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451435498/">1451435498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451445460/">1451445460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451446418/">1451446418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451454880/">1451454880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451463038/">1451463038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451465678/">1451465678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451469338/">1451469338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451469398/">1451469398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451471918/">1451471918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451474199/">1451474199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451476598/">1451476598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451478218/">1451478218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451478698/">1451478698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451482478/">1451482478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451484095/">1451484095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451484998/">1451484998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451495918/">1451495918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451497418/">1451497418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451500778/">1451500778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451501798/">1451501798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451502098/">1451502098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451503298/">1451503298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451505964/">1451505964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451506718/">1451506718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451506837/">1451506837/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451507678/">1451507678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451510136/">1451510136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451511338/">1451511338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451511818/">1451511818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451513125/">1451513125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451513319/">1451513319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451514757/">1451514757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451515718/">1451515718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451515775/">1451515775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451515958/">1451515958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451516815/">1451516815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451517098/">1451517098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451517518/">1451517518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451518476/">1451518476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451519018/">1451519018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451519498/">1451519498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451519738/">1451519738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451521118/">1451521118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451521238/">1451521238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451521779/">1451521779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451522138/">1451522138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451526158/">1451526158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451528499/">1451528499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451531558/">1451531558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451532578/">1451532578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451533958/">1451533958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451536658/">1451536658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451538098/">1451538098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451540797/">1451540797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451547518/">1451547518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451549618/">1451549618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451557959/">1451557959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451565173/">1451565173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451565234/">1451565234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451565237/">1451565237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451565240/">1451565240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451565243/">1451565243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451565246/">1451565246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451568278/">1451568278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451569718/">1451569718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451585378/">1451585378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451587239/">1451587239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451590418/">1451590418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451591440/">1451591440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451594378/">1451594378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451597918/">1451597918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451598458/">1451598458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451601998/">1451601998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451602298/">1451602298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451604278/">1451604278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451604758/">1451604758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451606018/">1451606018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451606594/">1451606594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451606799/">1451606799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451607219/">1451607219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451617718/">1451617718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451642196/">1451642196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451645499/">1451645499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451657018/">1451657018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451665720/">1451665720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451681018/">1451681018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451686718/">1451686718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451694700/">1451694700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451699018/">1451699018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451786198/">1451786198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451791118/">1451791118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451858618/">1451858618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451858918/">1451858918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451859638/">1451859638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451859998/">1451859998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451863538/">1451863538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451864856/">1451864856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451872598/">1451872598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451876316/">1451876316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451877578/">1451877578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451887478/">1451887478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451887718/">1451887718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451894582/">1451894582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451894703/">1451894703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451894856/">1451894856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451895098/">1451895098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451895218/">1451895218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451895398/">1451895398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451908776/">1451908776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451910605/">1451910605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451911418/">1451911418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451911838/">1451911838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451915498/">1451915498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451918378/">1451918378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451919218/">1451919218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451920835/">1451920835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451921589/">1451921589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451923010/">1451923010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451929598/">1451929598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451932217/">1451932217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451932479/">1451932479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451934211/">1451934211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451934331/">1451934331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451934570/">1451934570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451935231/">1451935231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451936129/">1451936129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451936791/">1451936791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451937090/">1451937090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451937448/">1451937448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451937810/">1451937810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451940028/">1451940028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451941950/">1451941950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451942071/">1451942071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451943040/">1451943040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451943051/">1451943051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451943450/">1451943450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451943873/">1451943873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451944291/">1451944291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451945111/">1451945111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451945610/">1451945610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451945852/">1451945852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451946570/">1451946570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451947291/">1451947291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451947708/">1451947708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451948479/">1451948479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451949019/">1451949019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451949978/">1451949978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451953538/">1451953538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451953826/">1451953826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451954860/">1451954860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451954981/">1451954981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451955460/">1451955460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451956360/">1451956360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451959396/">1451959396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451962903/">1451962903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451963861/">1451963861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451964521/">1451964521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451964686/">1451964686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451966389/">1451966389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451966440/">1451966440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451966741/">1451966741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451973396/">1451973396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451978138/">1451978138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451985340/">1451985340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451986228/">1451986228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451986840/">1451986840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451987141/">1451987141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451987559/">1451987559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451988100/">1451988100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451988581/">1451988581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451991802/">1451991802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451991924/">1451991924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451992901/">1451992901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451997029/">1451997029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451998300/">1451998300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1451999108/">1451999108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452003581/">1452003581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452005377/">1452005377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452007004/">1452007004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452008007/">1452008007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452008010/">1452008010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452008445/">1452008445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452010120/">1452010120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452010419/">1452010419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452012041/">1452012041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452012697/">1452012697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452012764/">1452012764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452012881/">1452012881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452013964/">1452013964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452014261/">1452014261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452014560/">1452014560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452014750/">1452014750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452017849/">1452017849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452018651/">1452018651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452021210/">1452021210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452021270/">1452021270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452024149/">1452024149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452024317/">1452024317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452024332/">1452024332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452024388/">1452024388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452025501/">1452025501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452025561/">1452025561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452025683/">1452025683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452026161/">1452026161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452026405/">1452026405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452027300/">1452027300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452028052/">1452028052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452028647/">1452028647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452029310/">1452029310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452029967/">1452029967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452032852/">1452032852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452035731/">1452035731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452040232/">1452040232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452041406/">1452041406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452041588/">1452041588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452045367/">1452045367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452045609/">1452045609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452045745/">1452045745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452045814/">1452045814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452045928/">1452045928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452046148/">1452046148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452047168/">1452047168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452047588/">1452047588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452048128/">1452048128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452048186/">1452048186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452048904/">1452048904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452049148/">1452049148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452049350/">1452049350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452051084/">1452051084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452053015/">1452053015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452053348/">1452053348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452055267/">1452055267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452055509/">1452055509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452055868/">1452055868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452056944/">1452056944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452060010/">1452060010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452060667/">1452060667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452062285/">1452062285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452065468/">1452065468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452069247/">1452069247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452069429/">1452069429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452072643/">1452072643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452074605/">1452074605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452075067/">1452075067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452082147/">1452082147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452082208/">1452082208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452085027/">1452085027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452088447/">1452088447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452089647/">1452089647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452091148/">1452091148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452091813/">1452091813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452091870/">1452091870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452092168/">1452092168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452094407/">1452094407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452094866/">1452094866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452095064/">1452095064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452097211/">1452097211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452099184/">1452099184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452099366/">1452099366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452100035/">1452100035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452100165/">1452100165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452104226/">1452104226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452104646/">1452104646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452104708/">1452104708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452107647/">1452107647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452108968/">1452108968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452109987/">1452109987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452110227/">1452110227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452110644/">1452110644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452110768/">1452110768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452111365/">1452111365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452111606/">1452111606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452113887/">1452113887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452114205/">1452114205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452114546/">1452114546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452115149/">1452115149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452115812/">1452115812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452116288/">1452116288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452117808/">1452117808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452118987/">1452118987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452119227/">1452119227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452119527/">1452119527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452119947/">1452119947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452121808/">1452121808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452121866/">1452121866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452123187/">1452123187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452123368/">1452123368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452123727/">1452123727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452124746/">1452124746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452126007/">1452126007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452126609/">1452126609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452127024/">1452127024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452130146/">1452130146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452131827/">1452131827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452135124/">1452135124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452136268/">1452136268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452136508/">1452136508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452137530/">1452137530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452137534/">1452137534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452142507/">1452142507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452143167/">1452143167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452143407/">1452143407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452143767/">1452143767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452146106/">1452146106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452147730/">1452147730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452148362/">1452148362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452150550/">1452150550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452151627/">1452151627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452153188/">1452153188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452158467/">1452158467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452159027/">1452159027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452159424/">1452159424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452160507/">1452160507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452161887/">1452161887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452165607/">1452165607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452165670/">1452165670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452167885/">1452167885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452169747/">1452169747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452169804/">1452169804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452170107/">1452170107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452171668/">1452171668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452172508/">1452172508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452173764/">1452173764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452176708/">1452176708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452180798/">1452180798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452181227/">1452181227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452182067/">1452182067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452182127/">1452182127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452182365/">1452182365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452182545/">1452182545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452184302/">1452184302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452184707/">1452184707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452185365/">1452185365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452188005/">1452188005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452189266/">1452189266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452189745/">1452189745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452190528/">1452190528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452190707/">1452190707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452190885/">1452190885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452191479/">1452191479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452191496/">1452191496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452192387/">1452192387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452194247/">1452194247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452194727/">1452194727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452195088/">1452195088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452197788/">1452197788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452198626/">1452198626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452199050/">1452199050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452202227/">1452202227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452204505/">1452204505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452205046/">1452205046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452207805/">1452207805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452209873/">1452209873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452210591/">1452210591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452211548/">1452211548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452212931/">1452212931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452215812/">1452215812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452215872/">1452215872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452218091/">1452218091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452219291/">1452219291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452221031/">1452221031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452223984/">1452223984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452224091/">1452224091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452224811/">1452224811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225231/">1452225231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225516/">1452225516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225568/">1452225568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225823/">1452225823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225832/">1452225832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225833/">1452225833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225846/">1452225846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452225990/">1452225990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452226194/">1452226194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452226195/">1452226195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452226272/">1452226272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452226286/">1452226286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452226772/">1452226772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452226860/">1452226860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227001/">1452227001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227134/">1452227134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227136/">1452227136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227138/">1452227138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227141/">1452227141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227143/">1452227143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227144/">1452227144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227145/">1452227145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227188/">1452227188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227255/">1452227255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227363/">1452227363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227391/">1452227391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227433/">1452227433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227652/">1452227652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227767/">1452227767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452227769/">1452227769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452228007/">1452228007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452228028/">1452228028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452228158/">1452228158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452228159/">1452228159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452228161/">1452228161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452228164/">1452228164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452229131/">1452229131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452231113/">1452231113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452234616/">1452234616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452234831/">1452234831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452239691/">1452239691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452241011/">1452241011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452242031/">1452242031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452242271/">1452242271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452245391/">1452245391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452245407/">1452245407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452247070/">1452247070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452247554/">1452247554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452248271/">1452248271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452249111/">1452249111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452256214/">1452256214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452257812/">1452257812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452263035/">1452263035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452264353/">1452264353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452266451/">1452266451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452267293/">1452267293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452269331/">1452269331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452269514/">1452269514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452269750/">1452269750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452272620/">1452272620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452277891/">1452277891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452279152/">1452279152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452279213/">1452279213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452280653/">1452280653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452280712/">1452280712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452280889/">1452280889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452284012/">1452284012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452285333/">1452285333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452285873/">1452285873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452286174/">1452286174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452287070/">1452287070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452288091/">1452288091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452288619/">1452288619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452289173/">1452289173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452290732/">1452290732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452292293/">1452292293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452299407/">1452299407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452307713/">1452307713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452310772/">1452310772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311234/">1452311234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311238/">1452311238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311266/">1452311266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311269/">1452311269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311270/">1452311270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311273/">1452311273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311274/">1452311274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311276/">1452311276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311280/">1452311280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311435/">1452311435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311437/">1452311437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311438/">1452311438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311440/">1452311440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311442/">1452311442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311445/">1452311445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311446/">1452311446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311471/">1452311471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311481/">1452311481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311596/">1452311596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311601/">1452311601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311602/">1452311602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311605/">1452311605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311636/">1452311636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311726/">1452311726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311752/">1452311752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311772/">1452311772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311775/">1452311775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311817/">1452311817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311834/">1452311834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311838/">1452311838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311857/">1452311857/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452311922/">1452311922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312016/">1452312016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312036/">1452312036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312125/">1452312125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312131/">1452312131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312137/">1452312137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312196/">1452312196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312302/">1452312302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312303/">1452312303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312304/">1452312304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312305/">1452312305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312309/">1452312309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312312/">1452312312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312588/">1452312588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312596/">1452312596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312602/">1452312602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312605/">1452312605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312608/">1452312608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312614/">1452312614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312650/">1452312650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312672/">1452312672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312674/">1452312674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312800/">1452312800/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312885/">1452312885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312952/">1452312952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312972/">1452312972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312979/">1452312979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312982/">1452312982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312985/">1452312985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452312986/">1452312986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313183/">1452313183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313283/">1452313283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313365/">1452313365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313370/">1452313370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313409/">1452313409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313413/">1452313413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313511/">1452313511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313529/">1452313529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313771/">1452313771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313779/">1452313779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313824/">1452313824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313835/">1452313835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313855/">1452313855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313943/">1452313943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313946/">1452313946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313948/">1452313948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452313966/">1452313966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452314013/">1452314013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452314050/">1452314050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452314058/">1452314058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452314063/">1452314063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452319413/">1452319413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452320792/">1452320792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452321046/">1452321046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452326630/">1452326630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452338012/">1452338012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452342606/">1452342606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452369393/">1452369393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452369634/">1452369634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452372932/">1452372932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452378754/">1452378754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452380852/">1452380852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452382893/">1452382893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452383972/">1452383972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452385811/">1452385811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452401253/">1452401253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452403593/">1452403593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452434613/">1452434613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452435212/">1452435212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452439962/">1452439962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452462993/">1452462993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452463233/">1452463233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452465632/">1452465632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452468512/">1452468512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452469352/">1452469352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452469772/">1452469772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452472207/">1452472207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452478660/">1452478660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452480634/">1452480634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452483321/">1452483321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452486896/">1452486896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452489569/">1452489569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452492232/">1452492232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452493652/">1452493652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452493834/">1452493834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452496353/">1452496353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452500794/">1452500794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452502233/">1452502233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452504607/">1452504607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452507093/">1452507093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452509913/">1452509913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452511052/">1452511052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452511173/">1452511173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452515406/">1452515406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452515612/">1452515612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452521133/">1452521133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452521613/">1452521613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452524179/">1452524179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452525330/">1452525330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452525447/">1452525447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452526463/">1452526463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452526465/">1452526465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452529524/">1452529524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452532529/">1452532529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452534928/">1452534928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452536068/">1452536068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452536548/">1452536548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452537079/">1452537079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452537394/">1452537394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452537690/">1452537690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452537994/">1452537994/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452538048/">1452538048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452538468/">1452538468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452540092/">1452540092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452542496/">1452542496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452543509/">1452543509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452544412/">1452544412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452545250/">1452545250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452545852/">1452545852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452547712/">1452547712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452547819/">1452547819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452547829/">1452547829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452549452/">1452549452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452550114/">1452550114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452550228/">1452550228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452550892/">1452550892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452556877/">1452556877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452557180/">1452557180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452557241/">1452557241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452557299/">1452557299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452557359/">1452557359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452558440/">1452558440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452558664/">1452558664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452560360/">1452560360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452561502/">1452561502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452563899/">1452563899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452564619/">1452564619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452566121/">1452566121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452568557/">1452568557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452568675/">1452568675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452568793/">1452568793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452569573/">1452569573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452571559/">1452571559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452572093/">1452572093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452572395/">1452572395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452575335/">1452575335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452577974/">1452577974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452579355/">1452579355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452579536/">1452579536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452580214/">1452580214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452582955/">1452582955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452583316/">1452583316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452585056/">1452585056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452585175/">1452585175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452585295/">1452585295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452585475/">1452585475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452588474/">1452588474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452589315/">1452589315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452590456/">1452590456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452591534/">1452591534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452592555/">1452592555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452594295/">1452594295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452595491/">1452595491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452596217/">1452596217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452597115/">1452597115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452597896/">1452597896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452598974/">1452598974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452601736/">1452601736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452601814/">1452601814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452604916/">1452604916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452605215/">1452605215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452607546/">1452607546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452608146/">1452608146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452608387/">1452608387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452609045/">1452609045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452610183/">1452610183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452612977/">1452612977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617026/">1452617026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617146/">1452617146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617276/">1452617276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617332/">1452617332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617333/">1452617333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617385/">1452617385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617445/">1452617445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452617684/">1452617684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452618235/">1452618235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452618405/">1452618405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452618465/">1452618465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452618587/">1452618587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452619125/">1452619125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452619246/">1452619246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452619845/">1452619845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452619965/">1452619965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452620084/">1452620084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452621465/">1452621465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452622653/">1452622653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452623070/">1452623070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452623498/">1452623498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452625596/">1452625596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452625709/">1452625709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452625832/">1452625832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452626130/">1452626130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452626370/">1452626370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452629730/">1452629730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452630390/">1452630390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452630451/">1452630451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452632745/">1452632745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452633751/">1452633751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452634214/">1452634214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452634349/">1452634349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452634473/">1452634473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452634651/">1452634651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452635070/">1452635070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452636031/">1452636031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452637650/">1452637650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452637711/">1452637711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452638130/">1452638130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452638910/">1452638910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452639391/">1452639391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452639750/">1452639750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452641373/">1452641373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452641491/">1452641491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452641969/">1452641969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452642031/">1452642031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452643520/">1452643520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452643531/">1452643531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452643532/">1452643532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452644311/">1452644311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452645008/">1452645008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452645930/">1452645930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452646291/">1452646291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452646532/">1452646532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452647193/">1452647193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452647430/">1452647430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452647491/">1452647491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452649831/">1452649831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452650310/">1452650310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452651690/">1452651690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452653791/">1452653791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452656730/">1452656730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452659970/">1452659970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452666708/">1452666708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452669210/">1452669210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452674611/">1452674611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452676410/">1452676410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452676950/">1452676950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452677251/">1452677251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452677314/">1452677314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452677451/">1452677451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452680311/">1452680311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452680850/">1452680850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452682771/">1452682771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452684510/">1452684510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452686070/">1452686070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452688221/">1452688221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452690090/">1452690090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452690991/">1452690991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452693921/">1452693921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452696454/">1452696454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452697172/">1452697172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452699113/">1452699113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452699451/">1452699451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452699990/">1452699990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452700833/">1452700833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452704911/">1452704911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452705930/">1452705930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452706231/">1452706231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452709531/">1452709531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452709875/">1452709875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452711511/">1452711511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452711631/">1452711631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452712291/">1452712291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452712891/">1452712891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452714031/">1452714031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452714271/">1452714271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452714454/">1452714454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452715292/">1452715292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452716311/">1452716311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452717930/">1452717930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452717991/">1452717991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452718051/">1452718051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452718232/">1452718232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452718711/">1452718711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452718831/">1452718831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452719072/">1452719072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452719970/">1452719970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452720626/">1452720626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452722970/">1452722970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452724471/">1452724471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452726223/">1452726223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452726870/">1452726870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452727050/">1452727050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452728551/">1452728551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452728611/">1452728611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452728791/">1452728791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452729093/">1452729093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452730535/">1452730535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452731370/">1452731370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452732752/">1452732752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452734792/">1452734792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452734912/">1452734912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452735271/">1452735271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452738935/">1452738935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452739469/">1452739469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452740672/">1452740672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452742482/">1452742482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452742778/">1452742778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452744092/">1452744092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452745030/">1452745030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452746796/">1452746796/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452748834/">1452748834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452750751/">1452750751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452751231/">1452751231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452751599/">1452751599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452753511/">1452753511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452754833/">1452754833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452755253/">1452755253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452755371/">1452755371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452755611/">1452755611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452756992/">1452756992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452758848/">1452758848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452761071/">1452761071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452762991/">1452762991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452763111/">1452763111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452763233/">1452763233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452763651/">1452763651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452763769/">1452763769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452763841/">1452763841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452764071/">1452764071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452764732/">1452764732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452768391/">1452768391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452769112/">1452769112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452770191/">1452770191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452774618/">1452774618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452774930/">1452774930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452777272/">1452777272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452780150/">1452780150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452780931/">1452780931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452781111/">1452781111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452781352/">1452781352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452781471/">1452781471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452781530/">1452781530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452781951/">1452781951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452785653/">1452785653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452787591/">1452787591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452788006/">1452788006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452788009/">1452788009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452788013/">1452788013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452788016/">1452788016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452789718/">1452789718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452793498/">1452793498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452794936/">1452794936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452796281/">1452796281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452796796/">1452796796/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452797036/">1452797036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452797396/">1452797396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452798718/">1452798718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452799436/">1452799436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452800456/">1452800456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452800996/">1452800996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452801056/">1452801056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452803817/">1452803817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452803876/">1452803876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452803936/">1452803936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452804956/">1452804956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452805437/">1452805437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452806457/">1452806457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452807012/">1452807012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452807116/">1452807116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452807956/">1452807956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452808017/">1452808017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452808617/">1452808617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452809276/">1452809276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452809576/">1452809576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452811494/">1452811494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452811619/">1452811619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452812217/">1452812217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452812516/">1452812516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452812635/">1452812635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452813837/">1452813837/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452817853/">1452817853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452818759/">1452818759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452820902/">1452820902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452825462/">1452825462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452826782/">1452826782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452828697/">1452828697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452839201/">1452839201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452842022/">1452842022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452842200/">1452842200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452843341/">1452843341/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452844962/">1452844962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452845441/">1452845441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452846401/">1452846401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452846821/">1452846821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452847601/">1452847601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452848621/">1452848621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452850231/">1452850231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452851322/">1452851322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452853721/">1452853721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452854985/">1452854985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452861581/">1452861581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452866381/">1452866381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452868000/">1452868000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452870502/">1452870502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452872060/">1452872060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452872062/">1452872062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452872451/">1452872451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452874903/">1452874903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452875564/">1452875564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452876821/">1452876821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452876941/">1452876941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452878730/">1452878730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452879561/">1452879561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452880342/">1452880342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452881145/">1452881145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452881482/">1452881482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452881601/">1452881601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452881722/">1452881722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452881901/">1452881901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452882084/">1452882084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452882659/">1452882659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452882676/">1452882676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452882867/">1452882867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452883044/">1452883044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452883163/">1452883163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452884784/">1452884784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452886101/">1452886101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452887962/">1452887962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452888021/">1452888021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452888562/">1452888562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452888865/">1452888865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452889161/">1452889161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452891142/">1452891142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452891204/">1452891204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452892102/">1452892102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452892881/">1452892881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452893122/">1452893122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452896001/">1452896001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452896481/">1452896481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452899663/">1452899663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452902121/">1452902121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452902181/">1452902181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452904161/">1452904161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452904209/">1452904209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452905722/">1452905722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452908001/">1452908001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452915169/">1452915169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452918441/">1452918441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452918781/">1452918781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452926962/">1452926962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452927321/">1452927321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452927621/">1452927621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452936637/">1452936637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452938546/">1452938546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452947410/">1452947410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452961101/">1452961101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452969084/">1452969084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452982461/">1452982461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452983602/">1452983602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452984260/">1452984260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1452996861/">1452996861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453000021/">1453000021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453001784/">1453001784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453050211/">1453050211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453051401/">1453051401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453053321/">1453053321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453055490/">1453055490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453055601/">1453055601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453056322/">1453056322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453058381/">1453058381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453061242/">1453061242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453066220/">1453066220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453068147/">1453068147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453071021/">1453071021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453077001/">1453077001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453079061/">1453079061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453086025/">1453086025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453086981/">1453086981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453087221/">1453087221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453087893/">1453087893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453092082/">1453092082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453094840/">1453094840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453098141/">1453098141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453102042/">1453102042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453102161/">1453102161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453103724/">1453103724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453105486/">1453105486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453108161/">1453108161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453108401/">1453108401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453109842/">1453109842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453115063/">1453115063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453117162/">1453117162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453119202/">1453119202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453120205/">1453120205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453123462/">1453123462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453125083/">1453125083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453133791/">1453133791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453133910/">1453133910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453137270/">1453137270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453138050/">1453138050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453138172/">1453138172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453138533/">1453138533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453138592/">1453138592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453139611/">1453139611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453140271/">1453140271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453141865/">1453141865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453141951/">1453141951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453142188/">1453142188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453147471/">1453147471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453148675/">1453148675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453148732/">1453148732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453152614/">1453152614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453153230/">1453153230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453155331/">1453155331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453157013/">1453157013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453157196/">1453157196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453157255/">1453157255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453158396/">1453158396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453158635/">1453158635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453160257/">1453160257/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453167276/">1453167276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453171656/">1453171656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453179216/">1453179216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453180834/">1453180834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453182517/">1453182517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453185222/">1453185222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453185399/">1453185399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453189657/">1453189657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453190136/">1453190136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453190378/">1453190378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453191876/">1453191876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453193376/">1453193376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453193436/">1453193436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453198427/">1453198427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453199442/">1453199442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453201536/">1453201536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453205135/">1453205135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453207956/">1453207956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453212696/">1453212696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453212756/">1453212756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453213835/">1453213835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453213896/">1453213896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453214136/">1453214136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453216372/">1453216372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453216906/">1453216906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453217815/">1453217815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453219437/">1453219437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453220711/">1453220711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453221872/">1453221872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453223792/">1453223792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453225055/">1453225055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453225892/">1453225892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453226553/">1453226553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453226672/">1453226672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453228172/">1453228172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453228535/">1453228535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453230250/">1453230250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453230813/">1453230813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453230932/">1453230932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453231294/">1453231294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453232256/">1453232256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453232676/">1453232676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453235496/">1453235496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453236577/">1453236577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453237595/">1453237595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453239576/">1453239576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453240476/">1453240476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453240536/">1453240536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453240655/">1453240655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453242152/">1453242152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453242632/">1453242632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453243237/">1453243237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453243595/">1453243595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453243895/">1453243895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453244072/">1453244072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453244195/">1453244195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453244255/">1453244255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453244315/">1453244315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453244737/">1453244737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453247297/">1453247297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453248377/">1453248377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453248438/">1453248438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453248439/">1453248439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453249757/">1453249757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453250057/">1453250057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453251742/">1453251742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453251977/">1453251977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453252277/">1453252277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453256897/">1453256897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453261349/">1453261349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453261425/">1453261425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453261428/">1453261428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453261584/">1453261584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453261586/">1453261586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453261620/">1453261620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453261921/">1453261921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262008/">1453262008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262011/">1453262011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262013/">1453262013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262157/">1453262157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262159/">1453262159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262162/">1453262162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262165/">1453262165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262179/">1453262179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262195/">1453262195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262245/">1453262245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262249/">1453262249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262251/">1453262251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262252/">1453262252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262365/">1453262365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262443/">1453262443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262444/">1453262444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262447/">1453262447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262463/">1453262463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262680/">1453262680/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262865/">1453262865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262879/">1453262879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453262936/">1453262936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263144/">1453263144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263425/">1453263425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263495/">1453263495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263497/">1453263497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263591/">1453263591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263695/">1453263695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263702/">1453263702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263705/">1453263705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263706/">1453263706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263771/">1453263771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263783/">1453263783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453263832/">1453263832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264024/">1453264024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264047/">1453264047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264222/">1453264222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264486/">1453264486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264676/">1453264676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264683/">1453264683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264751/">1453264751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264784/">1453264784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264832/">1453264832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453264867/">1453264867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453279457/">1453279457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453279877/">1453279877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453280057/">1453280057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453280345/">1453280345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453280356/">1453280356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453280357/">1453280357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453280926/">1453280926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453280927/">1453280927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453281197/">1453281197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453281437/">1453281437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453281497/">1453281497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453282098/">1453282098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453282337/">1453282337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453282937/">1453282937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453283057/">1453283057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453283298/">1453283298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453284917/">1453284917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453286240/">1453286240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453286777/">1453286777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453286897/">1453286897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453287437/">1453287437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453287681/">1453287681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453289479/">1453289479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453294577/">1453294577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453295357/">1453295357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453295717/">1453295717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453296137/">1453296137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453297397/">1453297397/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453297638/">1453297638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453297699/">1453297699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453298838/">1453298838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453299737/">1453299737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453300639/">1453300639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453300817/">1453300817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453300937/">1453300937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453302087/">1453302087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453302744/">1453302744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453303165/">1453303165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453304130/">1453304130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453305565/">1453305565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453310726/">1453310726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453312466/">1453312466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453312825/">1453312825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453313016/">1453313016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453313666/">1453313666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453314446/">1453314446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453314687/">1453314687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453314749/">1453314749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453314803/">1453314803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453314923/">1453314923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453315227/">1453315227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453315282/">1453315282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453315794/">1453315794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453316760/">1453316760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453317357/">1453317357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453317717/">1453317717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453322066/">1453322066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453322906/">1453322906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453323026/">1453323026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453323265/">1453323265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453323682/">1453323682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453323683/">1453323683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453325245/">1453325245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453326505/">1453326505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453335621/">1453335621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453335681/">1453335681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453335739/">1453335739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453335740/">1453335740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453335860/">1453335860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453335861/">1453335861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453335918/">1453335918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453336101/">1453336101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453336520/">1453336520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453344200/">1453344200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453344288/">1453344288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453347708/">1453347708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453352002/">1453352002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453352120/">1453352120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453352183/">1453352183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453352241/">1453352241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453353505/">1453353505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453354641/">1453354641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453357460/">1453357460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453358780/">1453358780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453358960/">1453358960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453359082/">1453359082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453362500/">1453362500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453363220/">1453363220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453363354/">1453363354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453364550/">1453364550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453364720/">1453364720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453364780/">1453364780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453364900/">1453364900/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453371020/">1453371020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453374500/">1453374500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453375520/">1453375520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453375821/">1453375821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453377205/">1453377205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453378400/">1453378400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453380083/">1453380083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453381886/">1453381886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453382000/">1453382000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453382480/">1453382480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453382660/">1453382660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453382780/">1453382780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453382842/">1453382842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453382960/">1453382960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453383860/">1453383860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453384520/">1453384520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453385720/">1453385720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453388000/">1453388000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453388122/">1453388122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453390558/">1453390558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453390782/">1453390782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453391486/">1453391486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453395202/">1453395202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453395861/">1453395861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453396101/">1453396101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453396410/">1453396410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453396525/">1453396525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453399100/">1453399100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453399944/">1453399944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453400299/">1453400299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453400423/">1453400423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453403481/">1453403481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453403683/">1453403683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453405221/">1453405221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453406902/">1453406902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453408040/">1453408040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453408160/">1453408160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453412489/">1453412489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453413330/">1453413330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453413630/">1453413630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453413689/">1453413689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453414231/">1453414231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453414769/">1453414769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453414949/">1453414949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453415550/">1453415550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453416269/">1453416269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453418430/">1453418430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453418431/">1453418431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453418491/">1453418491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453418730/">1453418730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453419690/">1453419690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453420829/">1453420829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453421789/">1453421789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453422210/">1453422210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453424190/">1453424190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453424969/">1453424969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426028/">1453426028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426170/">1453426170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426467/">1453426467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426471/">1453426471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426475/">1453426475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426479/">1453426479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426483/">1453426483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453426649/">1453426649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453427249/">1453427249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453427250/">1453427250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453428870/">1453428870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453430808/">1453430808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453430885/">1453430885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453431091/">1453431091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453432769/">1453432769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453432953/">1453432953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453433669/">1453433669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453435586/">1453435586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453435710/">1453435710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453444356/">1453444356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453445069/">1453445069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453445973/">1453445973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453446569/">1453446569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453447049/">1453447049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453448429/">1453448429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453448669/">1453448669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453448968/">1453448968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453449389/">1453449389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453450169/">1453450169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453453109/">1453453109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453453888/">1453453888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453454069/">1453454069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453454551/">1453454551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453454729/">1453454729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453455206/">1453455206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453457729/">1453457729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453457907/">1453457907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453459649/">1453459649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453463849/">1453463849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453465649/">1453465649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453465839/">1453465839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453465949/">1453465949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453466549/">1453466549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453467869/">1453467869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453468169/">1453468169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453468349/">1453468349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453470389/">1453470389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453472069/">1453472069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453473449/">1453473449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453474289/">1453474289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453474352/">1453474352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453477892/">1453477892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453477949/">1453477949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453478189/">1453478189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453478325/">1453478325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453478909/">1453478909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453479208/">1453479208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453480288/">1453480288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453480530/">1453480530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453481250/">1453481250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453482929/">1453482929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453483470/">1453483470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453483833/">1453483833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453484432/">1453484432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453485870/">1453485870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453485953/">1453485953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453486827/">1453486827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453487792/">1453487792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453487967/">1453487967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453488153/">1453488153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453488267/">1453488267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453488389/">1453488389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453489650/">1453489650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453490250/">1453490250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453494390/">1453494390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453494449/">1453494449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453494569/">1453494569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453494629/">1453494629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453494869/">1453494869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453496849/">1453496849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453498229/">1453498229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453498829/">1453498829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453499249/">1453499249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453499730/">1453499730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453500509/">1453500509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453502550/">1453502550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453502609/">1453502609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453505009/">1453505009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453506269/">1453506269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453507470/">1453507470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453508909/">1453508909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453511670/">1453511670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453512509/">1453512509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453513349/">1453513349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453513409/">1453513409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453513950/">1453513950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453514787/">1453514787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453518749/">1453518749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453520247/">1453520247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453527509/">1453527509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453527749/">1453527749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453532669/">1453532669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453533211/">1453533211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453534049/">1453534049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453535849/">1453535849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453552049/">1453552049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453563989/">1453563989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453564229/">1453564229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453565969/">1453565969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453568069/">1453568069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453569269/">1453569269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453578269/">1453578269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453583249/">1453583249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453589820/">1453589820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453589962/">1453589962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453589965/">1453589965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453589976/">1453589976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453589978/">1453589978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590006/">1453590006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590014/">1453590014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590018/">1453590018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590144/">1453590144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590260/">1453590260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590265/">1453590265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590266/">1453590266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590269/">1453590269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590271/">1453590271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590293/">1453590293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590501/">1453590501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590571/">1453590571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590640/">1453590640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590713/">1453590713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590764/">1453590764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590766/">1453590766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590849/">1453590849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590852/">1453590852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590856/">1453590856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590880/">1453590880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590998/">1453590998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453590999/">1453590999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591010/">1453591010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591015/">1453591015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591016/">1453591016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591019/">1453591019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591022/">1453591022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591023/">1453591023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591148/">1453591148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591237/">1453591237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591340/">1453591340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591496/">1453591496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591562/">1453591562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591566/">1453591566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591724/">1453591724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591745/">1453591745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591759/">1453591759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591836/">1453591836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591838/">1453591838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591862/">1453591862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591893/">1453591893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591898/">1453591898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453591989/">1453591989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592013/">1453592013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592093/">1453592093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592211/">1453592211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592215/">1453592215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592217/">1453592217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592243/">1453592243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592267/">1453592267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592270/">1453592270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592273/">1453592273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592274/">1453592274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592278/">1453592278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592280/">1453592280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592283/">1453592283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592286/">1453592286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592341/">1453592341/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592342/">1453592342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592607/">1453592607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592637/">1453592637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592697/">1453592697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592753/">1453592753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592830/">1453592830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592867/">1453592867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453592964/">1453592964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593000/">1453593000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593003/">1453593003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593005/">1453593005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593008/">1453593008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593010/">1453593010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593133/">1453593133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593228/">1453593228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593253/">1453593253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593256/">1453593256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593258/">1453593258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593263/">1453593263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593285/">1453593285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593291/">1453593291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593295/">1453593295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593320/">1453593320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593325/">1453593325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593327/">1453593327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593338/">1453593338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593417/">1453593417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593471/">1453593471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593493/">1453593493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453593653/">1453593653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453598407/">1453598407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453600508/">1453600508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453600687/">1453600687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453602008/">1453602008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453602487/">1453602487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453603746/">1453603746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453623787/">1453623787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453629826/">1453629826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453647608/">1453647608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453670107/">1453670107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453670287/">1453670287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453672808/">1453672808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453675627/">1453675627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453677427/">1453677427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453680068/">1453680068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453682407/">1453682407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453682707/">1453682707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453683547/">1453683547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453683826/">1453683826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453685602/">1453685602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453690267/">1453690267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453691707/">1453691707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453693747/">1453693747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453693987/">1453693987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453695247/">1453695247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453695307/">1453695307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453696569/">1453696569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453697826/">1453697826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453704427/">1453704427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453705208/">1453705208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453705687/">1453705687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453711566/">1453711566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453721105/">1453721105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453722309/">1453722309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453732464/">1453732464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453733904/">1453733904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453734931/">1453734931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453734985/">1453734985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453735105/">1453735105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453737926/">1453737926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453738105/">1453738105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453739305/">1453739305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453739907/">1453739907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453743922/">1453743922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453747585/">1453747585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453748066/">1453748066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453748707/">1453748707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453748837/">1453748837/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453752266/">1453752266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453752267/">1453752267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453752805/">1453752805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453753225/">1453753225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453756466/">1453756466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453757604/">1453757604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453759164/">1453759164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453759642/">1453759642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453760065/">1453760065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453762345/">1453762345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453763667/">1453763667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453763788/">1453763788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453768709/">1453768709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453769129/">1453769129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453771709/">1453771709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453772067/">1453772067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453775725/">1453775725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453779264/">1453779264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453787549/">1453787549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453790191/">1453790191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453793968/">1453793968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453794568/">1453794568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453795108/">1453795108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453795480/">1453795480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453800750/">1453800750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453802189/">1453802189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453802550/">1453802550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453802667/">1453802667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453802968/">1453802968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453803628/">1453803628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453804348/">1453804348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453804529/">1453804529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453805428/">1453805428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453806389/">1453806389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453807589/">1453807589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453807645/">1453807645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453808789/">1453808789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453809089/">1453809089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453809333/">1453809333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453811067/">1453811067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453812326/">1453812326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453812627/">1453812627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453813109/">1453813109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453813768/">1453813768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453814247/">1453814247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453815148/">1453815148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453816526/">1453816526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453818029/">1453818029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453818989/">1453818989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453821147/">1453821147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453822090/">1453822090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453824088/">1453824088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453824290/">1453824290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453826001/">1453826001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453826671/">1453826671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453827275/">1453827275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453828648/">1453828648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453829369/">1453829369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453830685/">1453830685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453830989/">1453830989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453831355/">1453831355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453831958/">1453831958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453833154/">1453833154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453833205/">1453833205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453833569/">1453833569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453834469/">1453834469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453835370/">1453835370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453835547/">1453835547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453835969/">1453835969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453838188/">1453838188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453838487/">1453838487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453838646/">1453838646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453840290/">1453840290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453841248/">1453841248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453841365/">1453841365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453841789/">1453841789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453842158/">1453842158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453842248/">1453842248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453842808/">1453842808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453843470/">1453843470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453844488/">1453844488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453844851/">1453844851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453848621/">1453848621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453850541/">1453850541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453851321/">1453851321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453857985/">1453857985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453862002/">1453862002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453862299/">1453862299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453862960/">1453862960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453865646/">1453865646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453867500/">1453867500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453867522/">1453867522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453873289/">1453873289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453873370/">1453873370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453873829/">1453873829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453874249/">1453874249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453874369/">1453874369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453874972/">1453874972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875389/">1453875389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875485/">1453875485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875626/">1453875626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875630/">1453875630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875631/">1453875631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875633/">1453875633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875634/">1453875634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875637/">1453875637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875642/">1453875642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875702/">1453875702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875879/">1453875879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453875918/">1453875918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876207/">1453876207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876212/">1453876212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876213/">1453876213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876291/">1453876291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876490/">1453876490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876503/">1453876503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876720/">1453876720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876815/">1453876815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876818/">1453876818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876925/">1453876925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876928/">1453876928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876929/">1453876929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453876934/">1453876934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453877198/">1453877198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453877283/">1453877283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453877392/">1453877392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453877851/">1453877851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453877855/">1453877855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453877856/">1453877856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453877864/">1453877864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453878299/">1453878299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453878509/">1453878509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453878552/">1453878552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453879769/">1453879769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453881929/">1453881929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453883009/">1453883009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453883549/">1453883549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453884209/">1453884209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453889129/">1453889129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453889261/">1453889261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453889549/">1453889549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890029/">1453890029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890328/">1453890328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890449/">1453890449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890631/">1453890631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890749/">1453890749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890808/">1453890808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890929/">1453890929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453890990/">1453890990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453891051/">1453891051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453891169/">1453891169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453891409/">1453891409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453892069/">1453892069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453892370/">1453892370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453892371/">1453892371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453892550/">1453892550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453893089/">1453893089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453897409/">1453897409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453898189/">1453898189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453898489/">1453898489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453899509/">1453899509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453905029/">1453905029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453905660/">1453905660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453906740/">1453906740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453907640/">1453907640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453907880/">1453907880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453908702/">1453908702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453908703/">1453908703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453909587/">1453909587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453910651/">1453910651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453910707/">1453910707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453911660/">1453911660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453915020/">1453915020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453915218/">1453915218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453915861/">1453915861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453916217/">1453916217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453916476/">1453916476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453916519/">1453916519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453916820/">1453916820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453917063/">1453917063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453917960/">1453917960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453919340/">1453919340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453922820/">1453922820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453922880/">1453922880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453926480/">1453926480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453927560/">1453927560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453927620/">1453927620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453927860/">1453927860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453929360/">1453929360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453929600/">1453929600/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453930320/">1453930320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453933199/">1453933199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453933560/">1453933560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453934699/">1453934699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453935240/">1453935240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453935420/">1453935420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453935599/">1453935599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453936199/">1453936199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453936320/">1453936320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453937519/">1453937519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453938000/">1453938000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453939920/">1453939920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453942437/">1453942437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453944239/">1453944239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453944360/">1453944360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453945320/">1453945320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453945618/">1453945618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453947001/">1453947001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453948620/">1453948620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453949820/">1453949820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453951200/">1453951200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453951970/">1453951970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453955439/">1453955439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453955879/">1453955879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453959540/">1453959540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453960740/">1453960740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453967221/">1453967221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453967699/">1453967699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453968480/">1453968480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453968960/">1453968960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453969440/">1453969440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453969859/">1453969859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453969980/">1453969980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453971238/">1453971238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453973457/">1453973457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453973520/">1453973520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453976937/">1453976937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453977599/">1453977599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453977780/">1453977780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453978560/">1453978560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453980540/">1453980540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453988520/">1453988520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453989480/">1453989480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453994640/">1453994640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453994940/">1453994940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453995280/">1453995280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453995659/">1453995659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453995840/">1453995840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453997100/">1453997100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1453999440/">1453999440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454001806/">1454001806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454003426/">1454003426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454003667/">1454003667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454004086/">1454004086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454006426/">1454006426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454011230/">1454011230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454011706/">1454011706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454012366/">1454012366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454014706/">1454014706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454014885/">1454014885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454015908/">1454015908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454018065/">1454018065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454021005/">1454021005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454021486/">1454021486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454021545/">1454021545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454022505/">1454022505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454022984/">1454022984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454023825/">1454023825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454024125/">1454024125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454027005/">1454027005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454029285/">1454029285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454031565/">1454031565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454032287/">1454032287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454032465/">1454032465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454033163/">1454033163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454033164/">1454033164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454036365/">1454036365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454037326/">1454037326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454037874/">1454037874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454039665/">1454039665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454042965/">1454042965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454043025/">1454043025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454043445/">1454043445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454044347/">1454044347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454049924/">1454049924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454053705/">1454053705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454054426/">1454054426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454056166/">1454056166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454057908/">1454057908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454059106/">1454059106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454064565/">1454064565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454064805/">1454064805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454064926/">1454064926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454065047/">1454065047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454065445/">1454065445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454066185/">1454066185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454071944/">1454071944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454072365/">1454072365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454075605/">1454075605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454077945/">1454077945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454078787/">1454078787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454081129/">1454081129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454081725/">1454081725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454082025/">1454082025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454086707/">1454086707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454091386/">1454091386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454091756/">1454091756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454092310/">1454092310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100274/">1454100274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100334/">1454100334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100373/">1454100373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100389/">1454100389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100393/">1454100393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100400/">1454100400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100428/">1454100428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100444/">1454100444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100448/">1454100448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100526/">1454100526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100556/">1454100556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100616/">1454100616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100630/">1454100630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100635/">1454100635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100637/">1454100637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100640/">1454100640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100665/">1454100665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100675/">1454100675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100733/">1454100733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100739/">1454100739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100829/">1454100829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100849/">1454100849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100855/">1454100855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100870/">1454100870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100871/">1454100871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100911/">1454100911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100964/">1454100964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100966/">1454100966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100967/">1454100967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100970/">1454100970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100972/">1454100972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454100981/">1454100981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101016/">1454101016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101091/">1454101091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101125/">1454101125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101234/">1454101234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101467/">1454101467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101527/">1454101527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101641/">1454101641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101715/">1454101715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101833/">1454101833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101854/">1454101854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454101986/">1454101986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102162/">1454102162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102165/">1454102165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102504/">1454102504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102571/">1454102571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102581/">1454102581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102685/">1454102685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102687/">1454102687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454102765/">1454102765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454103805/">1454103805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454104105/">1454104105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454104705/">1454104705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454105246/">1454105246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454106085/">1454106085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454106325/">1454106325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454106626/">1454106626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454106985/">1454106985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454107886/">1454107886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454108305/">1454108305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454108769/">1454108769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454108937/">1454108937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454109089/">1454109089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454109505/">1454109505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454109587/">1454109587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454109795/">1454109795/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110105/">1454110105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110108/">1454110108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110112/">1454110112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110115/">1454110115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110118/">1454110118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110123/">1454110123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110464/">1454110464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110656/">1454110656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110667/">1454110667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110672/">1454110672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110677/">1454110677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454110885/">1454110885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454111159/">1454111159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454111171/">1454111171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454113849/">1454113849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454115385/">1454115385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454118806/">1454118806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454125765/">1454125765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454126185/">1454126185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454126366/">1454126366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454142085/">1454142085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454146232/">1454146232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454171616/">1454171616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454173646/">1454173646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454175265/">1454175265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454175866/">1454175866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454177905/">1454177905/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454178505/">1454178505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185346/">1454185346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185349/">1454185349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185403/">1454185403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185413/">1454185413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185475/">1454185475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185478/">1454185478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185524/">1454185524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185591/">1454185591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185593/">1454185593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185596/">1454185596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185604/">1454185604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185609/">1454185609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185693/">1454185693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185702/">1454185702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185704/">1454185704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185757/">1454185757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185760/">1454185760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185794/">1454185794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185797/">1454185797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185800/">1454185800/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185804/">1454185804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185805/">1454185805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185807/">1454185807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185810/">1454185810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185814/">1454185814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185943/">1454185943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454185957/">1454185957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186058/">1454186058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186071/">1454186071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186109/">1454186109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186182/">1454186182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186184/">1454186184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186187/">1454186187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186189/">1454186189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186257/">1454186257/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186277/">1454186277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186284/">1454186284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186285/">1454186285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186317/">1454186317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186444/">1454186444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186447/">1454186447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186593/">1454186593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186670/">1454186670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186758/">1454186758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186763/">1454186763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186766/">1454186766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186768/">1454186768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186787/">1454186787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186823/">1454186823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186827/">1454186827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186829/">1454186829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186930/">1454186930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186963/">1454186963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186969/">1454186969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186986/">1454186986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454186991/">1454186991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187038/">1454187038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187067/">1454187067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187113/">1454187113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187123/">1454187123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187198/">1454187198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187259/">1454187259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187312/">1454187312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187432/">1454187432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187454/">1454187454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187524/">1454187524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187576/">1454187576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187589/">1454187589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187626/">1454187626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187629/">1454187629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187636/">1454187636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187706/">1454187706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187740/">1454187740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187741/">1454187741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187742/">1454187742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187745/">1454187745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187748/">1454187748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187837/">1454187837/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187839/">1454187839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187843/">1454187843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187845/">1454187845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187846/">1454187846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187855/">1454187855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187858/">1454187858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187860/">1454187860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187862/">1454187862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187863/">1454187863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454187961/">1454187961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454202745/">1454202745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454228906/">1454228906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454230165/">1454230165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454245286/">1454245286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454252065/">1454252065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454253206/">1454253206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454263886/">1454263886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454269586/">1454269586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454270366/">1454270366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454281586/">1454281586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454281765/">1454281765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454285485/">1454285485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454286685/">1454286685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454289625/">1454289625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454290045/">1454290045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454291968/">1454291968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454295026/">1454295026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454295446/">1454295446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454297305/">1454297305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454301324/">1454301324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454309607/">1454309607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454313827/">1454313827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454314225/">1454314225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454314465/">1454314465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454314945/">1454314945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454315065/">1454315065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454315245/">1454315245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454316025/">1454316025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454316085/">1454316085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454317228/">1454317228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454317765/">1454317765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454317886/">1454317886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454329526/">1454329526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454337028/">1454337028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454337204/">1454337204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454339484/">1454339484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454339906/">1454339906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454339964/">1454339964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454340805/">1454340805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454342366/">1454342366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454343629/">1454343629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454345666/">1454345666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454345726/">1454345726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454345786/">1454345786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454346685/">1454346685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454348785/">1454348785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454351906/">1454351906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454354186/">1454354186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454356165/">1454356165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454358627/">1454358627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454360253/">1454360253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454360431/">1454360431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454361751/">1454361751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454362775/">1454362775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454363370/">1454363370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454364030/">1454364030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454364272/">1454364272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454364396/">1454364396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454366119/">1454366119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454366899/">1454366899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454367199/">1454367199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454367319/">1454367319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454367975/">1454367975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454369838/">1454369838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454371579/">1454371579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454371580/">1454371580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454371698/">1454371698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454371879/">1454371879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454372479/">1454372479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454372719/">1454372719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454373015/">1454373015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454374095/">1454374095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454374817/">1454374817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380574/">1454380574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380587/">1454380587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380588/">1454380588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380590/">1454380590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380591/">1454380591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380602/">1454380602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380606/">1454380606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380610/">1454380610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380630/">1454380630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380641/">1454380641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380642/">1454380642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380654/">1454380654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380655/">1454380655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380658/">1454380658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380659/">1454380659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380666/">1454380666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380668/">1454380668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380674/">1454380674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380680/">1454380680/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380681/">1454380681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380685/">1454380685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380686/">1454380686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380693/">1454380693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380694/">1454380694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380700/">1454380700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380701/">1454380701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380702/">1454380702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380713/">1454380713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380715/">1454380715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380716/">1454380716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380725/">1454380725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380726/">1454380726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380727/">1454380727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380728/">1454380728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380729/">1454380729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380730/">1454380730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380745/">1454380745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380746/">1454380746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380748/">1454380748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380749/">1454380749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380755/">1454380755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380757/">1454380757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380761/">1454380761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380769/">1454380769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380782/">1454380782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380785/">1454380785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380789/">1454380789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380798/">1454380798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380818/">1454380818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380819/">1454380819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380820/">1454380820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380821/">1454380821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380822/">1454380822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380823/">1454380823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380824/">1454380824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380831/">1454380831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380834/">1454380834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380858/">1454380858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380861/">1454380861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380864/">1454380864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380867/">1454380867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380870/">1454380870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380876/">1454380876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380884/">1454380884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380885/">1454380885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380886/">1454380886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380896/">1454380896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380899/">1454380899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380930/">1454380930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380938/">1454380938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380939/">1454380939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380945/">1454380945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380951/">1454380951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380957/">1454380957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380971/">1454380971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380981/">1454380981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454380982/">1454380982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454381007/">1454381007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454381030/">1454381030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454381032/">1454381032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454381045/">1454381045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454381064/">1454381064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454382020/">1454382020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454382379/">1454382379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454385259/">1454385259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454388319/">1454388319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454390958/">1454390958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454392699/">1454392699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454400145/">1454400145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454400435/">1454400435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454403016/">1454403016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454403199/">1454403199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454404875/">1454404875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454405659/">1454405659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454406379/">1454406379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454406859/">1454406859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454408055/">1454408055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454409743/">1454409743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454410278/">1454410278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454410460/">1454410460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454410461/">1454410461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454411360/">1454411360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454411555/">1454411555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454418859/">1454418859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454418979/">1454418979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454419519/">1454419519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454423359/">1454423359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454426773/">1454426773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454427435/">1454427435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454427792/">1454427792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454431933/">1454431933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454436290/">1454436290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454436291/">1454436291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454436331/">1454436331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454436611/">1454436611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454437149/">1454437149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454437270/">1454437270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454440392/">1454440392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454440512/">1454440512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454443692/">1454443692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454445434/">1454445434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454447050/">1454447050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454447712/">1454447712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454449514/">1454449514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454450113/">1454450113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454450752/">1454450752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454451190/">1454451190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454451852/">1454451852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454453383/">1454453383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454453564/">1454453564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454454103/">1454454103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454454883/">1454454883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454456684/">1454456684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454457224/">1454457224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454458543/">1454458543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454460223/">1454460223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454460582/">1454460582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454461723/">1454461723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454464423/">1454464423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467286/">1454467286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467290/">1454467290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467357/">1454467357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467375/">1454467375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467381/">1454467381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467430/">1454467430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467431/">1454467431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467454/">1454467454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467455/">1454467455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467456/">1454467456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467495/">1454467495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467496/">1454467496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467502/">1454467502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467506/">1454467506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467518/">1454467518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467536/">1454467536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467540/">1454467540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467551/">1454467551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467559/">1454467559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467563/">1454467563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467568/">1454467568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467582/">1454467582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467584/">1454467584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467599/">1454467599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467654/">1454467654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467655/">1454467655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467743/">1454467743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467750/">1454467750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467757/">1454467757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467766/">1454467766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467768/">1454467768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467773/">1454467773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454467840/">1454467840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468324/">1454468324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468825/">1454468825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468834/">1454468834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468890/">1454468890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468912/">1454468912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468921/">1454468921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468930/">1454468930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468944/">1454468944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468963/">1454468963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468964/">1454468964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468977/">1454468977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468978/">1454468978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468980/">1454468980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468997/">1454468997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454468998/">1454468998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469011/">1454469011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469013/">1454469013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469021/">1454469021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469025/">1454469025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469031/">1454469031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469036/">1454469036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469041/">1454469041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469053/">1454469053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469054/">1454469054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469060/">1454469060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469064/">1454469064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469068/">1454469068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469075/">1454469075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469090/">1454469090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469145/">1454469145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469147/">1454469147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469149/">1454469149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454469403/">1454469403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454470003/">1454470003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454474924/">1454474924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454481099/">1454481099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454491852/">1454491852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454493103/">1454493103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454493583/">1454493583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454496403/">1454496403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454496464/">1454496464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454496583/">1454496583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454498682/">1454498682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454498864/">1454498864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454499283/">1454499283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454499763/">1454499763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454499889/">1454499889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454499890/">1454499890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454500003/">1454500003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454500124/">1454500124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454500243/">1454500243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454507684/">1454507684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454508367/">1454508367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454509067/">1454509067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454509183/">1454509183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454509963/">1454509963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454510864/">1454510864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454513141/">1454513141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454513338/">1454513338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454513731/">1454513731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454514463/">1454514463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454516547/">1454516547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454517154/">1454517154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454517628/">1454517628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454521527/">1454521527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454522488/">1454522488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454522778/">1454522778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454524245/">1454524245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454525128/">1454525128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454526330/">1454526330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454526510/">1454526510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454526566/">1454526566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454526868/">1454526868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454526985/">1454526985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454527533/">1454527533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454530646/">1454530646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454531309/">1454531309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454531667/">1454531667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454531907/">1454531907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454532028/">1454532028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454532384/">1454532384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454535050/">1454535050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454535066/">1454535066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454535088/">1454535088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454535327/">1454535327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454536288/">1454536288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454536839/">1454536839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454536842/">1454536842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454536853/">1454536853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454537907/">1454537907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454539405/">1454539405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454539467/">1454539467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454541388/">1454541388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454543487/">1454543487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454544687/">1454544687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454544867/">1454544867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454545407/">1454545407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454545764/">1454545764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454545832/">1454545832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454546124/">1454546124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454546427/">1454546427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454546667/">1454546667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454547627/">1454547627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454548647/">1454548647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454549906/">1454549906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454551947/">1454551947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454553086/">1454553086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454556767/">1454556767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454556986/">1454556986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454557889/">1454557889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454558007/">1454558007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454559387/">1454559387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454559567/">1454559567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454560452/">1454560452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454564788/">1454564788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454565868/">1454565868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454566470/">1454566470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454567464/">1454567464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454570667/">1454570667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454570788/">1454570788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454571208/">1454571208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454572228/">1454572228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454573307/">1454573307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454573727/">1454573727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454573847/">1454573847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454575228/">1454575228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454575767/">1454575767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454578229/">1454578229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454580630/">1454580630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454580744/">1454580744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454583993/">1454583993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454585784/">1454585784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454587709/">1454587709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454589063/">1454589063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454594429/">1454594429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454595748/">1454595748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454595868/">1454595868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454596708/">1454596708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454598273/">1454598273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454599168/">1454599168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454599768/">1454599768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454600184/">1454600184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454600484/">1454600484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454601627/">1454601627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454606437/">1454606437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454606788/">1454606788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609620/">1454609620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609621/">1454609621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609622/">1454609622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609625/">1454609625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609626/">1454609626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609627/">1454609627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609628/">1454609628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609629/">1454609629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609630/">1454609630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609631/">1454609631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609632/">1454609632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609633/">1454609633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609634/">1454609634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609635/">1454609635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609636/">1454609636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609637/">1454609637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609639/">1454609639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609640/">1454609640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609641/">1454609641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609643/">1454609643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609644/">1454609644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609646/">1454609646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609647/">1454609647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609648/">1454609648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609650/">1454609650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609651/">1454609651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609652/">1454609652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609654/">1454609654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609655/">1454609655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609656/">1454609656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609657/">1454609657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609658/">1454609658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609659/">1454609659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609660/">1454609660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609661/">1454609661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609662/">1454609662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609663/">1454609663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609665/">1454609665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609666/">1454609666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609667/">1454609667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609669/">1454609669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609670/">1454609670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609671/">1454609671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609673/">1454609673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609674/">1454609674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609676/">1454609676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609677/">1454609677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609678/">1454609678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609680/">1454609680/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609711/">1454609711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609712/">1454609712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609713/">1454609713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609715/">1454609715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609716/">1454609716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609718/">1454609718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609719/">1454609719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609720/">1454609720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609721/">1454609721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609723/">1454609723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609724/">1454609724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609725/">1454609725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609729/">1454609729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609730/">1454609730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609731/">1454609731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609734/">1454609734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609735/">1454609735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609737/">1454609737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609738/">1454609738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609739/">1454609739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609740/">1454609740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609741/">1454609741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609742/">1454609742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609743/">1454609743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609744/">1454609744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609746/">1454609746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609747/">1454609747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609748/">1454609748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609750/">1454609750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609751/">1454609751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609752/">1454609752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609754/">1454609754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609755/">1454609755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609756/">1454609756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609757/">1454609757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609759/">1454609759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609761/">1454609761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609762/">1454609762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609814/">1454609814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609815/">1454609815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609817/">1454609817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609818/">1454609818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454609819/">1454609819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454610199/">1454610199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454610329/">1454610329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454610380/">1454610380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454610641/">1454610641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454610718/">1454610718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454610780/">1454610780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454610829/">1454610829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611262/">1454611262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611297/">1454611297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611314/">1454611314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611398/">1454611398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611401/">1454611401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611605/">1454611605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611781/">1454611781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611787/">1454611787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611792/">1454611792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454611835/">1454611835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612048/">1454612048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612050/">1454612050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612051/">1454612051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612052/">1454612052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612057/">1454612057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612062/">1454612062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612065/">1454612065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454612109/">1454612109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454615008/">1454615008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454621361/">1454621361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454621980/">1454621980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454621985/">1454621985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454622605/">1454622605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454622697/">1454622697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454635374/">1454635374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454641390/">1454641390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454641932/">1454641932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454642109/">1454642109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454642890/">1454642890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454643729/">1454643729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454643969/">1454643969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454644332/">1454644332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454644509/">1454644509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454647452/">1454647452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454649429/">1454649429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454652530/">1454652530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454653871/">1454653871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454653872/">1454653872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454653890/">1454653890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454656467/">1454656467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454658489/">1454658489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454662210/">1454662210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454664650/">1454664650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454667610/">1454667610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454668030/">1454668030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454668150/">1454668150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454668390/">1454668390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454668450/">1454668450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454669410/">1454669410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454670070/">1454670070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454671450/">1454671450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454672710/">1454672710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454675437/">1454675437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454676851/">1454676851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454677150/">1454677150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454677252/">1454677252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454677255/">1454677255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454677692/">1454677692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454678350/">1454678350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454679970/">1454679970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454681109/">1454681109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454682790/">1454682790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454683930/">1454683930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454685019/">1454685019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454686151/">1454686151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454686352/">1454686352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454686355/">1454686355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454686929/">1454686929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454687469/">1454687469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454687532/">1454687532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454689154/">1454689154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454689453/">1454689453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454695929/">1454695929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454696049/">1454696049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454696050/">1454696050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454697050/">1454697050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454697129/">1454697129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454697794/">1454697794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454701494/">1454701494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454704013/">1454704013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454707819/">1454707819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454707914/">1454707914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454708033/">1454708033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454708094/">1454708094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454708274/">1454708274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454708574/">1454708574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454708935/">1454708935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454709054/">1454709054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454710133/">1454710133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454710254/">1454710254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454712953/">1454712953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454713793/">1454713793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454714274/">1454714274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454715578/">1454715578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454715637/">1454715637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454716834/">1454716834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454719358/">1454719358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454719418/">1454719418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454720317/">1454720317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454720680/">1454720680/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454724038/">1454724038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454728358/">1454728358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454729599/">1454729599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454729681/">1454729681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454730220/">1454730220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454732917/">1454732917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454733227/">1454733227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454734717/">1454734717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454736399/">1454736399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454740240/">1454740240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454740837/">1454740837/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454741018/">1454741018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454742638/">1454742638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454751001/">1454751001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454767958/">1454767958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454780378/">1454780378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454781219/">1454781219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454787660/">1454787660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454794010/">1454794010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454794353/">1454794353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454795940/">1454795940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454805001/">1454805001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454812980/">1454812980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454814961/">1454814961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454818140/">1454818140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454820120/">1454820120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454821691/">1454821691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454827861/">1454827861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454840580/">1454840580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454841061/">1454841061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454842320/">1454842320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454847360/">1454847360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454856060/">1454856060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454863503/">1454863503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454865001/">1454865001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454872080/">1454872080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454872920/">1454872920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454874420/">1454874420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454874720/">1454874720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454890140/">1454890140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454892240/">1454892240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454893980/">1454893980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454894580/">1454894580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454897821/">1454897821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454899980/">1454899980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454902259/">1454902259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454910240/">1454910240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454911140/">1454911140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454912880/">1454912880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454913421/">1454913421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454921280/">1454921280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454921460/">1454921460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454925600/">1454925600/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454926920/">1454926920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454929080/">1454929080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454929438/">1454929438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454933176/">1454933176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454933939/">1454933939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454934541/">1454934541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454937060/">1454937060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454937424/">1454937424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454937722/">1454937722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454941081/">1454941081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454941141/">1454941141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454941501/">1454941501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454941621/">1454941621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454942702/">1454942702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454943182/">1454943182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454943244/">1454943244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454944634/">1454944634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454944811/">1454944811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454949252/">1454949252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454949360/">1454949360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454949963/">1454949963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454950582/">1454950582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454952604/">1454952604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454953212/">1454953212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454954044/">1454954044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454957641/">1454957641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454959286/">1454959286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454959527/">1454959527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454961395/">1454961395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454962167/">1454962167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454974284/">1454974284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454975184/">1454975184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454975364/">1454975364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454976564/">1454976564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454977044/">1454977044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454978724/">1454978724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454980047/">1454980047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454981064/">1454981064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454981964/">1454981964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454984665/">1454984665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454987135/">1454987135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454988176/">1454988176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1454994476/">1454994476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455000657/">1455000657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455002698/">1455002698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455003536/">1455003536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455004136/">1455004136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455006776/">1455006776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455011516/">1455011516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455011577/">1455011577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455014816/">1455014816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455015356/">1455015356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455016136/">1455016136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455019857/">1455019857/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455022136/">1455022136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455023222/">1455023222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455025196/">1455025196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455025316/">1455025316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455026696/">1455026696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455028676/">1455028676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455028794/">1455028794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455029276/">1455029276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455029396/">1455029396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455031435/">1455031435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455032217/">1455032217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455034739/">1455034739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455036298/">1455036298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455037640/">1455037640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455056357/">1455056357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455056717/">1455056717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455056778/">1455056778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455056895/">1455056895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455056958/">1455056958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455057158/">1455057158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455057639/">1455057639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455057698/">1455057698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455057817/">1455057817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455057878/">1455057878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455058356/">1455058356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455058597/">1455058597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455058658/">1455058658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455059030/">1455059030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455059617/">1455059617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455059858/">1455059858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455060097/">1455060097/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455060217/">1455060217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455060755/">1455060755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455060831/">1455060831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455061058/">1455061058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455062805/">1455062805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455063158/">1455063158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455065678/">1455065678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455065975/">1455065975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455066636/">1455066636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455067058/">1455067058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455067355/">1455067355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455067657/">1455067657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455068314/">1455068314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455072278/">1455072278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455072338/">1455072338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455072459/">1455072459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455072577/">1455072577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455073183/">1455073183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455075261/">1455075261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455076582/">1455076582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455077182/">1455077182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455080902/">1455080902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455081802/">1455081802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455085225/">1455085225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455088642/">1455088642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455088763/">1455088763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455088882/">1455088882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455089063/">1455089063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455089725/">1455089725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455090982/">1455090982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455091942/">1455091942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455095302/">1455095302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455095539/">1455095539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455097400/">1455097400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455097642/">1455097642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455098793/">1455098793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455099682/">1455099682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455100583/">1455100583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455100823/">1455100823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455103343/">1455103343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455105502/">1455105502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455105922/">1455105922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455106462/">1455106462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455106882/">1455106882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455107663/">1455107663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455108742/">1455108742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455110302/">1455110302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455110543/">1455110543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455111023/">1455111023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455114143/">1455114143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455114520/">1455114520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455115043/">1455115043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455115102/">1455115102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455116063/">1455116063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455116299/">1455116299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455117530/">1455117530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455117531/">1455117531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455117923/">1455117923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455118549/">1455118549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455119859/">1455119859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455119978/">1455119978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455120098/">1455120098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455121978/">1455121978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455121979/">1455121979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455122499/">1455122499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455123098/">1455123098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455123640/">1455123640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455124961/">1455124961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455125082/">1455125082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455125263/">1455125263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455125801/">1455125801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455126580/">1455126580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455127358/">1455127358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455128918/">1455128918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455128979/">1455128979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455129278/">1455129278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455130478/">1455130478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455130538/">1455130538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455130659/">1455130659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455132162/">1455132162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455134258/">1455134258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455137498/">1455137498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455137679/">1455137679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455137918/">1455137918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455139778/">1455139778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455139958/">1455139958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455140498/">1455140498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455143978/">1455143978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455143979/">1455143979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455144038/">1455144038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455144458/">1455144458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455144818/">1455144818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455144878/">1455144878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455145778/">1455145778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455146672/">1455146672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455148113/">1455148113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455156874/">1455156874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455157053/">1455157053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455163892/">1455163892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455165251/">1455165251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455165335/">1455165335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455172652/">1455172652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455178712/">1455178712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455179132/">1455179132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455179192/">1455179192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455179617/">1455179617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455180752/">1455180752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455180992/">1455180992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455185852/">1455185852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455186871/">1455186871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455188253/">1455188253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455188644/">1455188644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455189632/">1455189632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455191612/">1455191612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455191672/">1455191672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455197132/">1455197132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455198392/">1455198392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455200612/">1455200612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455203262/">1455203262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455204453/">1455204453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455204879/">1455204879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455205712/">1455205712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455206013/">1455206013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455206432/">1455206432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455206498/">1455206498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455206880/">1455206880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455210815/">1455210815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455212553/">1455212553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455212851/">1455212851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455213095/">1455213095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455213214/">1455213214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455214794/">1455214794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455215314/">1455215314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455216152/">1455216152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455216213/">1455216213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455217233/">1455217233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455217832/">1455217832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455218193/">1455218193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455218613/">1455218613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455219214/">1455219214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455219692/">1455219692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455219815/">1455219815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455219999/">1455219999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455220295/">1455220295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455221559/">1455221559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455222817/">1455222817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455222932/">1455222932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455224733/">1455224733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455225325/">1455225325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455225865/">1455225865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455226230/">1455226230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455226705/">1455226705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455227006/">1455227006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455227428/">1455227428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455228675/">1455228675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455228676/">1455228676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455228677/">1455228677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455229049/">1455229049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455229169/">1455229169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455229227/">1455229227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455229406/">1455229406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455230126/">1455230126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455230308/">1455230308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455230367/">1455230367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455230967/">1455230967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455231089/">1455231089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455231090/">1455231090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455233608/">1455233608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455234209/">1455234209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455236609/">1455236609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455237329/">1455237329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455237869/">1455237869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455238349/">1455238349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455239665/">1455239665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455250768/">1455250768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455251309/">1455251309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455252493/">1455252493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455252746/">1455252746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455252988/">1455252988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455255029/">1455255029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455255688/">1455255688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455259333/">1455259333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455262275/">1455262275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455262814/">1455262814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455262935/">1455262935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455263415/">1455263415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455263715/">1455263715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455264315/">1455264315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455265215/">1455265215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455265458/">1455265458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455265573/">1455265573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455270555/">1455270555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455270734/">1455270734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455271202/">1455271202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455271203/">1455271203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455275835/">1455275835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455279495/">1455279495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455281236/">1455281236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455283811/">1455283811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455283875/">1455283875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455284294/">1455284294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455284775/">1455284775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455285554/">1455285554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455286881/">1455286881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455287774/">1455287774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455291613/">1455291613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455293423/">1455293423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455296846/">1455296846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455297685/">1455297685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455298403/">1455298403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455317425/">1455317425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455317665/">1455317665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455317725/">1455317725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455317965/">1455317965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455320965/">1455320965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455322945/">1455322945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455323425/">1455323425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455327745/">1455327745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455330625/">1455330625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455334475/">1455334475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455342865/">1455342865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455346705/">1455346705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455365605/">1455365605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455370465/">1455370465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455375445/">1455375445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455379765/">1455379765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455385285/">1455385285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455395896/">1455395896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455400036/">1455400036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455413357/">1455413357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455420377/">1455420377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455456681/">1455456681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455460697/">1455460697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455482963/">1455482963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455489623/">1455489623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455496523/">1455496523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455500903/">1455500903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455520283/">1455520283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455523041/">1455523041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455525863/">1455525863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455527303/">1455527303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455531743/">1455531743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455533003/">1455533003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455535883/">1455535883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455537863/">1455537863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455539422/">1455539422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455542671/">1455542671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455545361/">1455545361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455546145/">1455546145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455546924/">1455546924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455550040/">1455550040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455551060/">1455551060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455556648/">1455556648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455560004/">1455560004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455561572/">1455561572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455562880/">1455562880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455564562/">1455564562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455564984/">1455564984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455582050/">1455582050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455595850/">1455595850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455603290/">1455603290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455603409/">1455603409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455603529/">1455603529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455603649/">1455603649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455606528/">1455606528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455608089/">1455608089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455609538/">1455609538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455609854/">1455609854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455611029/">1455611029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455613431/">1455613431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455616129/">1455616129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455616369/">1455616369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455619670/">1455619670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455621650/">1455621650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455623090/">1455623090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455623871/">1455623871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455629449/">1455629449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455633649/">1455633649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455634069/">1455634069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455637489/">1455637489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455637609/">1455637609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455637670/">1455637670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455637851/">1455637851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455638150/">1455638150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455638930/">1455638930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455639469/">1455639469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455640009/">1455640009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455640970/">1455640970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455642532/">1455642532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455643490/">1455643490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455645289/">1455645289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455646549/">1455646549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455646853/">1455646853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455649733/">1455649733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455650330/">1455650330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455651410/">1455651410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455651774/">1455651774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455652790/">1455652790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455653031/">1455653031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455653810/">1455653810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455654469/">1455654469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455655189/">1455655189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455656209/">1455656209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455658129/">1455658129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455658611/">1455658611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455658669/">1455658669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455659210/">1455659210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455659330/">1455659330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455659809/">1455659809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455661249/">1455661249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455661969/">1455661969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455663110/">1455663110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455663529/">1455663529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455664433/">1455664433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455669649/">1455669649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455669710/">1455669710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455670131/">1455670131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455671040/">1455671040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455674150/">1455674150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455676443/">1455676443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455677870/">1455677870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455678589/">1455678589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455678832/">1455678832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455681532/">1455681532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455682717/">1455682717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455683929/">1455683929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455684529/">1455684529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455685909/">1455685909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455686149/">1455686149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455689930/">1455689930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455697609/">1455697609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455697789/">1455697789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455698149/">1455698149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455699470/">1455699470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455701030/">1455701030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455701390/">1455701390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455702710/">1455702710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455703262/">1455703262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455704990/">1455704990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455705170/">1455705170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455706131/">1455706131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455707693/">1455707693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455707810/">1455707810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455708471/">1455708471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455710750/">1455710750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455716270/">1455716270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455717530/">1455717530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455717531/">1455717531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455721009/">1455721009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455722330/">1455722330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455723279/">1455723279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455723348/">1455723348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455723710/">1455723710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455723775/">1455723775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455724378/">1455724378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455729702/">1455729702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455730370/">1455730370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455731515/">1455731515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455731870/">1455731870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455732771/">1455732771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455732949/">1455732949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455733190/">1455733190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455733250/">1455733250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455733731/">1455733731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455736527/">1455736527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455737129/">1455737129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455737247/">1455737247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455738506/">1455738506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455739529/">1455739529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455739647/">1455739647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455741451/">1455741451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455743128/">1455743128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455743427/">1455743427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455744026/">1455744026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455745047/">1455745047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455745348/">1455745348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455746648/">1455746648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455749305/">1455749305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455753567/">1455753567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455755006/">1455755006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455755727/">1455755727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455756626/">1455756626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455758368/">1455758368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455759206/">1455759206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455759506/">1455759506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455764127/">1455764127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455764750/">1455764750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455765567/">1455765567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455767066/">1455767066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455767966/">1455767966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455769526/">1455769526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455772466/">1455772466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455772706/">1455772706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455776366/">1455776366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455778526/">1455778526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455780928/">1455780928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455782187/">1455782187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455783746/">1455783746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455784466/">1455784466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455784586/">1455784586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455788065/">1455788065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455788192/">1455788192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455788426/">1455788426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455788487/">1455788487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455788726/">1455788726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455788966/">1455788966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455789146/">1455789146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455791847/">1455791847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455791966/">1455791966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455792273/">1455792273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455792568/">1455792568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455794966/">1455794966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455795086/">1455795086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455799293/">1455799293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455801686/">1455801686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455803726/">1455803726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455810147/">1455810147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455810386/">1455810386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455810506/">1455810506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455810747/">1455810747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455810806/">1455810806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455811052/">1455811052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455811649/">1455811649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455812546/">1455812546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455812785/">1455812785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455813328/">1455813328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455813506/">1455813506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455813986/">1455813986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455815449/">1455815449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455815613/">1455815613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455817646/">1455817646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455818254/">1455818254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455821666/">1455821666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455821907/">1455821907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455822749/">1455822749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455823589/">1455823589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455824246/">1455824246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455825324/">1455825324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455825394/">1455825394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455826169/">1455826169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455826646/">1455826646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455826767/">1455826767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455828806/">1455828806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455831387/">1455831387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455831986/">1455831986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455832887/">1455832887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455833127/">1455833127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455833487/">1455833487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455835768/">1455835768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455836304/">1455836304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455836642/">1455836642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455837747/">1455837747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455838586/">1455838586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455838707/">1455838707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455843485/">1455843485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455844385/">1455844385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455844805/">1455844805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455845284/">1455845284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455845703/">1455845703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455847085/">1455847085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455847208/">1455847208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455860880/">1455860880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455862137/">1455862137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455863759/">1455863759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455866460/">1455866460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455867540/">1455867540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455868199/">1455868199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455869160/">1455869160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455869639/">1455869639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455870657/">1455870657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455871200/">1455871200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455871979/">1455871979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455873000/">1455873000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455875580/">1455875580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455878460/">1455878460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455878519/">1455878519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455878640/">1455878640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455878940/">1455878940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455879059/">1455879059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455880140/">1455880140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455881939/">1455881939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455882779/">1455882779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455888664/">1455888664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455889860/">1455889860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455891299/">1455891299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455892019/">1455892019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455896716/">1455896716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455898946/">1455898946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455899366/">1455899366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455899858/">1455899858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455900685/">1455900685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455905126/">1455905126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455905546/">1455905546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455905788/">1455905788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455906867/">1455906867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455907345/">1455907345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455912363/">1455912363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455912364/">1455912364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455912366/">1455912366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455912370/">1455912370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455912423/">1455912423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455912626/">1455912626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455914246/">1455914246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455914725/">1455914725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455915085/">1455915085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455915145/">1455915145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455916819/">1455916819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455916882/">1455916882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455917181/">1455917181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455917722/">1455917722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455918201/">1455918201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455922401/">1455922401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455922945/">1455922945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455923361/">1455923361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455923479/">1455923479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455934821/">1455934821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455934881/">1455934881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455936441/">1455936441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455937377/">1455937377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455937714/">1455937714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455938061/">1455938061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455945022/">1455945022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455964462/">1455964462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1455976042/">1455976042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456012211/">1456012211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456018572/">1456018572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456019832/">1456019832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456020433/">1456020433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456026090/">1456026090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456033572/">1456033572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456047671/">1456047671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456056433/">1456056433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456076832/">1456076832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456078634/">1456078634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456085771/">1456085771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456087570/">1456087570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456089551/">1456089551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456094577/">1456094577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456101957/">1456101957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456110697/">1456110697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456113358/">1456113358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456127097/">1456127097/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456131839/">1456131839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456136638/">1456136638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456139757/">1456139757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456145818/">1456145818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456148517/">1456148517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456152297/">1456152297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456153087/">1456153087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456156136/">1456156136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456156698/">1456156698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456156859/">1456156859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456157050/">1456157050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456158059/">1456158059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456158740/">1456158740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456159375/">1456159375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456160915/">1456160915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456161428/">1456161428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456161949/">1456161949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456162573/">1456162573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456163030/">1456163030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456164155/">1456164155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456164752/">1456164752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456167621/">1456167621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456173898/">1456173898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456174318/">1456174318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456175075/">1456175075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456175076/">1456175076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456175288/">1456175288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456175460/">1456175460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456176718/">1456176718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456178278/">1456178278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456178545/">1456178545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456179290/">1456179290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456182472/">1456182472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456183130/">1456183130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456185050/">1456185050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456186370/">1456186370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456186851/">1456186851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456186911/">1456186911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456189990/">1456189990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456195869/">1456195869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456209847/">1456209847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456237267/">1456237267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456237327/">1456237327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456237388/">1456237388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456237447/">1456237447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456238947/">1456238947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456239248/">1456239248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456239490/">1456239490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456239607/">1456239607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456239747/">1456239747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456239886/">1456239886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456240149/">1456240149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456240747/">1456240747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456241285/">1456241285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456241709/">1456241709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456243028/">1456243028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456243510/">1456243510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456246150/">1456246150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456246579/">1456246579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456249214/">1456249214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456249407/">1456249407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456250698/">1456250698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456251367/">1456251367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456251547/">1456251547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456251848/">1456251848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456252806/">1456252806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456253407/">1456253407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456254967/">1456254967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456255568/">1456255568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456256471/">1456256471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456256828/">1456256828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456257431/">1456257431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456257854/">1456257854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456257916/">1456257916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456261866/">1456261866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456262167/">1456262167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456262287/">1456262287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456262407/">1456262407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456262468/">1456262468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456262527/">1456262527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456262707/">1456262707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456265985/">1456265985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456268505/">1456268505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456270005/">1456270005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456270847/">1456270847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456271087/">1456271087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456271205/">1456271205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456272765/">1456272765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456273485/">1456273485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456273905/">1456273905/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456274444/">1456274444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456276424/">1456276424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456276609/">1456276609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456277864/">1456277864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456285845/">1456285845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456291725/">1456291725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456294965/">1456294965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456297374/">1456297374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456301685/">1456301685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456303729/">1456303729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456304565/">1456304565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456305645/">1456305645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456307085/">1456307085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456307450/">1456307450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456308045/">1456308045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456310147/">1456310147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456312428/">1456312428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456312547/">1456312547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456312729/">1456312729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456312911/">1456312911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456313026/">1456313026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456314827/">1456314827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456314887/">1456314887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456315065/">1456315065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456315494/">1456315494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456315764/">1456315764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456316870/">1456316870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456318908/">1456318908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456321065/">1456321065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456322565/">1456322565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456323289/">1456323289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456323525/">1456323525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456324185/">1456324185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456328398/">1456328398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456329405/">1456329405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456330196/">1456330196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456331025/">1456331025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456331709/">1456331709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456333125/">1456333125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456337054/">1456337054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456337445/">1456337445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456338054/">1456338054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456338405/">1456338405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456339305/">1456339305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456340565/">1456340565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456340885/">1456340885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456340886/">1456340886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456342784/">1456342784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456343567/">1456343567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456343685/">1456343685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456343686/">1456343686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456344049/">1456344049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456344314/">1456344314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456345067/">1456345067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456345186/">1456345186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456345305/">1456345305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456345846/">1456345846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456347106/">1456347106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456347107/">1456347107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456347404/">1456347404/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456347465/">1456347465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456347585/">1456347585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456347765/">1456347765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456348785/">1456348785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456349085/">1456349085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456350707/">1456350707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456351770/">1456351770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456352011/">1456352011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456352668/">1456352668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456352847/">1456352847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456355065/">1456355065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456357589/">1456357589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456358369/">1456358369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456358968/">1456358968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456358969/">1456358969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456359326/">1456359326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456359568/">1456359568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456361669/">1456361669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456362029/">1456362029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456364129/">1456364129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456365089/">1456365089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456366412/">1456366412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456366528/">1456366528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456366586/">1456366586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456366710/">1456366710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456367008/">1456367008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456368208/">1456368208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456369367/">1456369367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456369713/">1456369713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456370548/">1456370548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456371625/">1456371625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456372469/">1456372469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456377268/">1456377268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456381588/">1456381588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456381709/">1456381709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456386028/">1456386028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456386388/">1456386388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456386689/">1456386689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456386749/">1456386749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456386868/">1456386868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456388132/">1456388132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456388670/">1456388670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456388908/">1456388908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456389808/">1456389808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456393290/">1456393290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456393484/">1456393484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456397968/">1456397968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456402229/">1456402229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456402709/">1456402709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456402768/">1456402768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456403456/">1456403456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456404688/">1456404688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456406189/">1456406189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456408886/">1456408886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456409670/">1456409670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456410149/">1456410149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456410929/">1456410929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456414953/">1456414953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456415059/">1456415059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456415615/">1456415615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456416450/">1456416450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456416830/">1456416830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456417009/">1456417009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456417195/">1456417195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456417591/">1456417591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456418789/">1456418789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456419148/">1456419148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456419750/">1456419750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456422749/">1456422749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456424005/">1456424005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456424610/">1456424610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456424669/">1456424669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456425266/">1456425266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456425446/">1456425446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456425926/">1456425926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456426286/">1456426286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456427486/">1456427486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456427786/">1456427786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456428446/">1456428446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456429106/">1456429106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456429167/">1456429167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456429706/">1456429706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456431387/">1456431387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456432825/">1456432825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456434474/">1456434474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456434987/">1456434987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456435046/">1456435046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456436547/">1456436547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456437627/">1456437627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456438405/">1456438405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456439904/">1456439904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456440927/">1456440927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456442187/">1456442187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456442311/">1456442311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456444704/">1456444704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456446149/">1456446149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456446506/">1456446506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456447346/">1456447346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456447767/">1456447767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456451306/">1456451306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456451486/">1456451486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456453646/">1456453646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456453824/">1456453824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456453946/">1456453946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456455023/">1456455023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456455207/">1456455207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456455461/">1456455461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456456587/">1456456587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456458927/">1456458927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456459536/">1456459536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456461771/">1456461771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456462529/">1456462529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456462586/">1456462586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456468827/">1456468827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456472728/">1456472728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456475547/">1456475547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456476446/">1456476446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456478665/">1456478665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456481846/">1456481846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456482206/">1456482206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456484845/">1456484845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456489828/">1456489828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456491687/">1456491687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456494566/">1456494566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456494806/">1456494806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456496546/">1456496546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456497083/">1456497083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456497708/">1456497708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456498228/">1456498228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456498766/">1456498766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456498888/">1456498888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456500385/">1456500385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456500927/">1456500927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456502066/">1456502066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456502246/">1456502246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456502691/">1456502691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456502791/">1456502791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456503183/">1456503183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456504352/">1456504352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456504353/">1456504353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456504648/">1456504648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456504766/">1456504766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456505364/">1456505364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456505678/">1456505678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456506146/">1456506146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456508004/">1456508004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456508831/">1456508831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456509686/">1456509686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456509925/">1456509925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456510119/">1456510119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456510120/">1456510120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456511533/">1456511533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456511534/">1456511534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456511905/">1456511905/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456512325/">1456512325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456515124/">1456515124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456515566/">1456515566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456516890/">1456516890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456517056/">1456517056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456517067/">1456517067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456518265/">1456518265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456518566/">1456518566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456519165/">1456519165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456519647/">1456519647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456519703/">1456519703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456519886/">1456519886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456519946/">1456519946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456520545/">1456520545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456521264/">1456521264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456521926/">1456521926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456523554/">1456523554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456524091/">1456524091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456526124/">1456526124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456526548/">1456526548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456527512/">1456527512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456528531/">1456528531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456529554/">1456529554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456530445/">1456530445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456535727/">1456535727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456537046/">1456537046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456537586/">1456537586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456539386/">1456539386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456541006/">1456541006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456549466/">1456549466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456550786/">1456550786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456557806/">1456557806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456563758/">1456563758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456565487/">1456565487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456569986/">1456569986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456570466/">1456570466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456577786/">1456577786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456583786/">1456583786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456590806/">1456590806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456599326/">1456599326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456604546/">1456604546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456609226/">1456609226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456613186/">1456613186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456613306/">1456613306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456613966/">1456613966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456614852/">1456614852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456623673/">1456623673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456631233/">1456631233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456633092/">1456633092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456647973/">1456647973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456650012/">1456650012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456677101/">1456677101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456690452/">1456690452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456696945/">1456696945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456699452/">1456699452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456699513/">1456699513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456700293/">1456700293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456702513/">1456702513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456703053/">1456703053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456705873/">1456705873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456706652/">1456706652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456717693/">1456717693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456722317/">1456722317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456722977/">1456722977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456728492/">1456728492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456729033/">1456729033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456729573/">1456729573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456729812/">1456729812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456731493/">1456731493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456733473/">1456733473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456734134/">1456734134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456737678/">1456737678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456740244/">1456740244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456742714/">1456742714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456744632/">1456744632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456750332/">1456750332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456750393/">1456750393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456751474/">1456751474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456751952/">1456751952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456752073/">1456752073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456752673/">1456752673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456753453/">1456753453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456753813/">1456753813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456755913/">1456755913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456756933/">1456756933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456757171/">1456757171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456757651/">1456757651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456758399/">1456758399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456758587/">1456758587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456758914/">1456758914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456759513/">1456759513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456760353/">1456760353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456760773/">1456760773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456761973/">1456761973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456763835/">1456763835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456764013/">1456764013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456764571/">1456764571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456764853/">1456764853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456765154/">1456765154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456765656/">1456765656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456766969/">1456766969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456767806/">1456767806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456767992/">1456767992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456769065/">1456769065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456769788/">1456769788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456770026/">1456770026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456770086/">1456770086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456770146/">1456770146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456770209/">1456770209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456770805/">1456770805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456771167/">1456771167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456772146/">1456772146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456772426/">1456772426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456773087/">1456773087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456773447/">1456773447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456773505/">1456773505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456774190/">1456774190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456774191/">1456774191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456774842/">1456774842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456775132/">1456775132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456775488/">1456775488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456775614/">1456775614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456777709/">1456777709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456778006/">1456778006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456778306/">1456778306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456779386/">1456779386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456779747/">1456779747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456781067/">1456781067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456781607/">1456781607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456781667/">1456781667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456781727/">1456781727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456782806/">1456782806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456783047/">1456783047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456783706/">1456783706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456784666/">1456784666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456784785/">1456784785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456787486/">1456787486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456790246/">1456790246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456790903/">1456790903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456792474/">1456792474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456794150/">1456794150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456801173/">1456801173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456810833/">1456810833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456811493/">1456811493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456814433/">1456814433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456815333/">1456815333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456817013/">1456817013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456820253/">1456820253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456820314/">1456820314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456821642/">1456821642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456822481/">1456822481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456823682/">1456823682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456823742/">1456823742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456825662/">1456825662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456826141/">1456826141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456832681/">1456832681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456832742/">1456832742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456833221/">1456833221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456834181/">1456834181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456838621/">1456838621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456841802/">1456841802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456841921/">1456841921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456842102/">1456842102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456842402/">1456842402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456842521/">1456842521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456842821/">1456842821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456842881/">1456842881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456843241/">1456843241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456844995/">1456844995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456845402/">1456845402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456847567/">1456847567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456847739/">1456847739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456847803/">1456847803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456848259/">1456848259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456848381/">1456848381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456853425/">1456853425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456855730/">1456855730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456856017/">1456856017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456856671/">1456856671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456857111/">1456857111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456858170/">1456858170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456858231/">1456858231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456859195/">1456859195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456860032/">1456860032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456860871/">1456860871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456860931/">1456860931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456861053/">1456861053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456862491/">1456862491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456862880/">1456862880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456867471/">1456867471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456867771/">1456867771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456869271/">1456869271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456869993/">1456869993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456873531/">1456873531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456874552/">1456874552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456875034/">1456875034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456875271/">1456875271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456875811/">1456875811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456878812/">1456878812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456882532/">1456882532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456883851/">1456883851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456884811/">1456884811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456888059/">1456888059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456889791/">1456889791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456891292/">1456891292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456892975/">1456892975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456893331/">1456893331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456902031/">1456902031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456903474/">1456903474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456904071/">1456904071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456907431/">1456907431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456910071/">1456910071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456910971/">1456910971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456914151/">1456914151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456916131/">1456916131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456916311/">1456916311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456916431/">1456916431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456916562/">1456916562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456916671/">1456916671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456917518/">1456917518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456918351/">1456918351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456920273/">1456920273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456920991/">1456920991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456929572/">1456929572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456930771/">1456930771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456930882/">1456930882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456931420/">1456931420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456931658/">1456931658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456932562/">1456932562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456934885/">1456934885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456935861/">1456935861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456936639/">1456936639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456937421/">1456937421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456937481/">1456937481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456938579/">1456938579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456940359/">1456940359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456941019/">1456941019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456941201/">1456941201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456941833/">1456941833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456942588/">1456942588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456943659/">1456943659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456944561/">1456944561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456947381/">1456947381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456947502/">1456947502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456949541/">1456949541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456949661/">1456949661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456949841/">1456949841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456950201/">1456950201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456950501/">1456950501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456951342/">1456951342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456951821/">1456951821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456952241/">1456952241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456953081/">1456953081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456953140/">1456953140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456954645/">1456954645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456954764/">1456954764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456954883/">1456954883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456955964/">1456955964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456956258/">1456956258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456956989/">1456956989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456957226/">1456957226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456958014/">1456958014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456958429/">1456958429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456959931/">1456959931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456960468/">1456960468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456960530/">1456960530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456960769/">1456960769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456961787/">1456961787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456964789/">1456964789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456965807/">1456965807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456967664/">1456967664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456970064/">1456970064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456973063/">1456973063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456973123/">1456973123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456976304/">1456976304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456977144/">1456977144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456977203/">1456977203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456978104/">1456978104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456979520/">1456979520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456980504/">1456980504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456983201/">1456983201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456986384/">1456986384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456986564/">1456986564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456992278/">1456992278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456993883/">1456993883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456994965/">1456994965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456995143/">1456995143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456995267/">1456995267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1456999111/">1456999111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457002711/">1457002711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457003272/">1457003272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457003431/">1457003431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457004575/">1457004575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457005351/">1457005351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457006311/">1457006311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457009191/">1457009191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457012558/">1457012558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457013571/">1457013571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457014651/">1457014651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457015792/">1457015792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457018195/">1457018195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457019452/">1457019452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457020065/">1457020065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457020594/">1457020594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457021291/">1457021291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457021335/">1457021335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457022067/">1457022067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457023171/">1457023171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457026291/">1457026291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457026362/">1457026362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457026414/">1457026414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457027433/">1457027433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457028346/">1457028346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457028698/">1457028698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457028995/">1457028995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457030013/">1457030013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457030311/">1457030311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457031859/">1457031859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457034095/">1457034095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457034151/">1457034151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457034272/">1457034272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457035565/">1457035565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457036252/">1457036252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457038712/">1457038712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457039043/">1457039043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457039851/">1457039851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457040031/">1457040031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457040755/">1457040755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457041314/">1457041314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457041494/">1457041494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457041615/">1457041615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457041798/">1457041798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457041974/">1457041974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457042158/">1457042158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457042275/">1457042275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457045633/">1457045633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457047494/">1457047494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457047613/">1457047613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457051938/">1457051938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457051939/">1457051939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457053708/">1457053708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457053947/">1457053947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457054187/">1457054187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457057486/">1457057486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457057727/">1457057727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457059408/">1457059408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457060225/">1457060225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457060226/">1457060226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457061928/">1457061928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457064413/">1457064413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457064414/">1457064414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457065285/">1457065285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457066306/">1457066306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457067266/">1457067266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457068947/">1457068947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457070031/">1457070031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457076387/">1457076387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457083839/">1457083839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457084547/">1457084547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457084968/">1457084968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457088988/">1457088988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457090486/">1457090486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457095044/">1457095044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457095229/">1457095229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457096550/">1457096550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457097508/">1457097508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457105849/">1457105849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457107105/">1457107105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457108304/">1457108304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457110584/">1457110584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457110879/">1457110879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457111180/">1457111180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457111674/">1457111674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457112564/">1457112564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457113763/">1457113763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457120879/">1457120879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457130987/">1457130987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131048/">1457131048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131106/">1457131106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131226/">1457131226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131286/">1457131286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131347/">1457131347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131407/">1457131407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131467/">1457131467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131526/">1457131526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131586/">1457131586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131706/">1457131706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131767/">1457131767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131889/">1457131889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457131890/">1457131890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457132427/">1457132427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457132559/">1457132559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457132907/">1457132907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457133028/">1457133028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457133087/">1457133087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457133985/">1457133985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457135367/">1457135367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457135431/">1457135431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457135908/">1457135908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457136339/">1457136339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457142987/">1457142987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457144247/">1457144247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457149406/">1457149406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457149707/">1457149707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457150071/">1457150071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457152373/">1457152373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457152520/">1457152520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457154860/">1457154860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457163320/">1457163320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457164640/">1457164640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457171420/">1457171420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457184973/">1457184973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457185018/">1457185018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457185072/">1457185072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457186691/">1457186691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457186732/">1457186732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457186808/">1457186808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457186842/">1457186842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457186892/">1457186892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457186935/">1457186935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457188634/">1457188634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457188743/">1457188743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457188853/">1457188853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457188959/">1457188959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457190177/">1457190177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457190314/">1457190314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457191280/">1457191280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457198600/">1457198600/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457198840/">1457198840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457200824/">1457200824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457200960/">1457200960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457201421/">1457201421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457202380/">1457202380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457205140/">1457205140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457211260/">1457211260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457211320/">1457211320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457212340/">1457212340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457222528/">1457222528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457222948/">1457222948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457223788/">1457223788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457223849/">1457223849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457224329/">1457224329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457236208/">1457236208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457236748/">1457236748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457248148/">1457248148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457262069/">1457262069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457271488/">1457271488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457289672/">1457289672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457294650/">1457294650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457295008/">1457295008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457295308/">1457295308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457296209/">1457296209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457298009/">1457298009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457301311/">1457301311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457303950/">1457303950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457308148/">1457308148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457308869/">1457308869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457311928/">1457311928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457321770/">1457321770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457322668/">1457322668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457325444/">1457325444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457329508/">1457329508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457329869/">1457329869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457334278/">1457334278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457337860/">1457337860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457341608/">1457341608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457342229/">1457342229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457343019/">1457343019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457344869/">1457344869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457347089/">1457347089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457349549/">1457349549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457351529/">1457351529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457355669/">1457355669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457358068/">1457358068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457359210/">1457359210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457359641/">1457359641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457362347/">1457362347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457363307/">1457363307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457364203/">1457364203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457364560/">1457364560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457364980/">1457364980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457365340/">1457365340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457365760/">1457365760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457366360/">1457366360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457367084/">1457367084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457368467/">1457368467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457369844/">1457369844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457370446/">1457370446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457370602/">1457370602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457371227/">1457371227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457374107/">1457374107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457374167/">1457374167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457374407/">1457374407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457374707/">1457374707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457375607/">1457375607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457380047/">1457380047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457385627/">1457385627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457385747/">1457385747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457385807/">1457385807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457385868/">1457385868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457385869/">1457385869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457385987/">1457385987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457386107/">1457386107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457386287/">1457386287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457386347/">1457386347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457386466/">1457386466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457386527/">1457386527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457387187/">1457387187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457387188/">1457387188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457388507/">1457388507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457388747/">1457388747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457389047/">1457389047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457389228/">1457389228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457389527/">1457389527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457389964/">1457389964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457399181/">1457399181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457400648/">1457400648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457401008/">1457401008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457401487/">1457401487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457401728/">1457401728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457402147/">1457402147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457402267/">1457402267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457402387/">1457402387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457405990/">1457405990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457407069/">1457407069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457407428/">1457407428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457418386/">1457418386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457419409/">1457419409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457419475/">1457419475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457419587/">1457419587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457421621/">1457421621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457422881/">1457422881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457423605/">1457423605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457423725/">1457423725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457423841/">1457423841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457424506/">1457424506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457424685/">1457424685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457424925/">1457424925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457425042/">1457425042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457425704/">1457425704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457428166/">1457428166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457430561/">1457430561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457431127/">1457431127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457431728/">1457431728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457431943/">1457431943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457432573/">1457432573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457432574/">1457432574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457436564/">1457436564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457437523/">1457437523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457437884/">1457437884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457438364/">1457438364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457438967/">1457438967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457440523/">1457440523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457441304/">1457441304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457441425/">1457441425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457441784/">1457441784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457442925/">1457442925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457443224/">1457443224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457443704/">1457443704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457444665/">1457444665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457444845/">1457444845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457446826/">1457446826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457447003/">1457447003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457447124/">1457447124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457447185/">1457447185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457448984/">1457448984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457449396/">1457449396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457449823/">1457449823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457449944/">1457449944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457450367/">1457450367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457451506/">1457451506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457451507/">1457451507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457452467/">1457452467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457453070/">1457453070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457453964/">1457453964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457453965/">1457453965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457454983/">1457454983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457455285/">1457455285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457457144/">1457457144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457458402/">1457458402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457459546/">1457459546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457461152/">1457461152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457461211/">1457461211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457462413/">1457462413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457464095/">1457464095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457464515/">1457464515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457465053/">1457465053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457465113/">1457465113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457465537/">1457465537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457465712/">1457465712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457466553/">1457466553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457468593/">1457468593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457470513/">1457470513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457470873/">1457470873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457472012/">1457472012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457472972/">1457472972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457474656/">1457474656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457474895/">1457474895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457475136/">1457475136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457476753/">1457476753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457480473/">1457480473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457482093/">1457482093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457482157/">1457482157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457482871/">1457482871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457482933/">1457482933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457482992/">1457482992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457483052/">1457483052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457483173/">1457483173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457483293/">1457483293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457485153/">1457485153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457488873/">1457488873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457490253/">1457490253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457490973/">1457490973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457491094/">1457491094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457491330/">1457491330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457492173/">1457492173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457493076/">1457493076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457494417/">1457494417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457495716/">1457495716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457495773/">1457495773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457496502/">1457496502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457496925/">1457496925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457498177/">1457498177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457498233/">1457498233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457498292/">1457498292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457500213/">1457500213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457501254/">1457501254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457502193/">1457502193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457507833/">1457507833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457509453/">1457509453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457509692/">1457509692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457512217/">1457512217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457514793/">1457514793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457516532/">1457516532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457518046/">1457518046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457518453/">1457518453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457518573/">1457518573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457520496/">1457520496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457521693/">1457521693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457522411/">1457522411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457522713/">1457522713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457528654/">1457528654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457530066/">1457530066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457530335/">1457530335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457530679/">1457530679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457531233/">1457531233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457531953/">1457531953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457532011/">1457532011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457532916/">1457532916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457538081/">1457538081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457538564/">1457538564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457538682/">1457538682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457539351/">1457539351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457539449/">1457539449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457543481/">1457543481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457543665/">1457543665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457547924/">1457547924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457548764/">1457548764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457549906/">1457549906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457549965/">1457549965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457550625/">1457550625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457551764/">1457551764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457552784/">1457552784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457552964/">1457552964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457553443/">1457553443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457554824/">1457554824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457555548/">1457555548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457556923/">1457556923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457558124/">1457558124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457558243/">1457558243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457559743/">1457559743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457559744/">1457559744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457560045/">1457560045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457560401/">1457560401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457560643/">1457560643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457561363/">1457561363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457562202/">1457562202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457562503/">1457562503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457562564/">1457562564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457562625/">1457562625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457562803/">1457562803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457563584/">1457563584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457563703/">1457563703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457563883/">1457563883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457564243/">1457564243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457565324/">1457565324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457566824/">1457566824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457566944/">1457566944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457567061/">1457567061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457567843/">1457567843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457567905/">1457567905/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457568264/">1457568264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457569462/">1457569462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457571504/">1457571504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457573425/">1457573425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457575464/">1457575464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457579602/">1457579602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457580444/">1457580444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457580512/">1457580512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457582333/">1457582333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457583505/">1457583505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457583827/">1457583827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457585604/">1457585604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457585964/">1457585964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457587524/">1457587524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457588186/">1457588186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457588543/">1457588543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457591904/">1457591904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457597423/">1457597423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457597485/">1457597485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457597663/">1457597663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457600797/">1457600797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457601923/">1457601923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457603483/">1457603483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457603544/">1457603544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457603782/">1457603782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457603962/">1457603962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457604402/">1457604402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457606542/">1457606542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457607333/">1457607333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457608405/">1457608405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457611224/">1457611224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457612304/">1457612304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457612664/">1457612664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457613149/">1457613149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457614464/">1457614464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457616864/">1457616864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457617644/">1457617644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457621484/">1457621484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457621724/">1457621724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457622447/">1457622447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457623764/">1457623764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457627906/">1457627906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457629106/">1457629106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457630723/">1457630723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457633486/">1457633486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457634026/">1457634026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457634264/">1457634264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457637862/">1457637862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457642847/">1457642847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457647764/">1457647764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457648126/">1457648126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457648366/">1457648366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457648784/">1457648784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457648906/">1457648906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457649024/">1457649024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457649145/">1457649145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457649386/">1457649386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457649506/">1457649506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457650155/">1457650155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457650635/">1457650635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457650872/">1457650872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457650958/">1457650958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457651293/">1457651293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457651373/">1457651373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457652256/">1457652256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457652435/">1457652435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457653575/">1457653575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457654472/">1457654472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457655615/">1457655615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457656033/">1457656033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457656995/">1457656995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457657835/">1457657835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457658916/">1457658916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457659274/">1457659274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457662875/">1457662875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457662932/">1457662932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457663054/">1457663054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457663176/">1457663176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457663352/">1457663352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457663655/">1457663655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457664434/">1457664434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457665094/">1457665094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457666610/">1457666610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457667516/">1457667516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457669716/">1457669716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457672116/">1457672116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457672175/">1457672175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457674404/">1457674404/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457677216/">1457677216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457678112/">1457678112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457680334/">1457680334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457683452/">1457683452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457683813/">1457683813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457685287/">1457685287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457688314/">1457688314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457690724/">1457690724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457690801/">1457690801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457691316/">1457691316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457692395/">1457692395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457694675/">1457694675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457696292/">1457696292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457697372/">1457697372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457697555/">1457697555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457699593/">1457699593/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457702234/">1457702234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457704275/">1457704275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457704276/">1457704276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457706255/">1457706255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457707333/">1457707333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457707692/">1457707692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457707812/">1457707812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457708823/">1457708823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457708847/">1457708847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457709257/">1457709257/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457711954/">1457711954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457712915/">1457712915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457714175/">1457714175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457714176/">1457714176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457714415/">1457714415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457715614/">1457715614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457715750/">1457715750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457716936/">1457716936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457719035/">1457719035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457719095/">1457719095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457721567/">1457721567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457721872/">1457721872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457722109/">1457722109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457722168/">1457722168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457723079/">1457723079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457723908/">1457723908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457724027/">1457724027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457724989/">1457724989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457725348/">1457725348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457726865/">1457726865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457728047/">1457728047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457728589/">1457728589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457730928/">1457730928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457732667/">1457732667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457733373/">1457733373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457733374/">1457733374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457733567/">1457733567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457733997/">1457733997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457736268/">1457736268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457736388/">1457736388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457737828/">1457737828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457738428/">1457738428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457739027/">1457739027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457739390/">1457739390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457739567/">1457739567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457741427/">1457741427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457741908/">1457741908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457742988/">1457742988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457743947/">1457743947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457745447/">1457745447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457748448/">1457748448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457748687/">1457748687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457749888/">1457749888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457750488/">1457750488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457758827/">1457758827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457760507/">1457760507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457761467/">1457761467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457769208/">1457769208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457772147/">1457772147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457792967/">1457792967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457798928/">1457798928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457799867/">1457799867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457803347/">1457803347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457811029/">1457811029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457812648/">1457812648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457814329/">1457814329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457821108/">1457821108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457825487/">1457825487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457826507/">1457826507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457832989/">1457832989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457842227/">1457842227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457876007/">1457876007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457876727/">1457876727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457890887/">1457890887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457896707/">1457896707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457910567/">1457910567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457923647/">1457923647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457924629/">1457924629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457933964/">1457933964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457938285/">1457938285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457939248/">1457939248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457945010/">1457945010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457949201/">1457949201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457951908/">1457951908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457953223/">1457953223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457954964/">1457954964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457955813/">1457955813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457962230/">1457962230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457963011/">1457963011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457963310/">1457963310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457963384/">1457963384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457966552/">1457966552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457969123/">1457969123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457970146/">1457970146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457970803/">1457970803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457970864/">1457970864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457971894/">1457971894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457972910/">1457972910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457973087/">1457973087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457974163/">1457974163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457976151/">1457976151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457976387/">1457976387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457976525/">1457976525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457977232/">1457977232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457977471/">1457977471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457977644/">1457977644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457978914/">1457978914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457979083/">1457979083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457979330/">1457979330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457980051/">1457980051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457980762/">1457980762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457980825/">1457980825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457982239/">1457982239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457982270/">1457982270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457982387/">1457982387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457983064/">1457983064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457985641/">1457985641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457985833/">1457985833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457985874/">1457985874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457986180/">1457986180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457986657/">1457986657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457986902/">1457986902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457987910/">1457987910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457988819/">1457988819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457988997/">1457988997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457989840/">1457989840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457990675/">1457990675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457990676/">1457990676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457990917/">1457990917/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457991034/">1457991034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457991880/">1457991880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457993080/">1457993080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457994555/">1457994555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457994621/">1457994621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457994973/">1457994973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457995450/">1457995450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457997762/">1457997762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1457999253/">1457999253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458000236/">1458000236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458001238/">1458001238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458002372/">1458002372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458002503/">1458002503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458002554/">1458002554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458002683/">1458002683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458003156/">1458003156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458003277/">1458003277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458006758/">1458006758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458007466/">1458007466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458007467/">1458007467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458007468/">1458007468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458007780/">1458007780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458008073/">1458008073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458008918/">1458008918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458009221/">1458009221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458009518/">1458009518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458010297/">1458010297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458012223/">1458012223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458012458/">1458012458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458014380/">1458014380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458014439/">1458014439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458014677/">1458014677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458014922/">1458014922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458016063/">1458016063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458017980/">1458017980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458018881/">1458018881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458019234/">1458019234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458019362/">1458019362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458021850/">1458021850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458025116/">1458025116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458027040/">1458027040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458027241/">1458027241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458027822/">1458027822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458027880/">1458027880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458028538/">1458028538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458030393/">1458030393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458033455/">1458033455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458035135/">1458035135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458036099/">1458036099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458036231/">1458036231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458037959/">1458037959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458038134/">1458038134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458040658/">1458040658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458044193/">1458044193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458047673/">1458047673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458049057/">1458049057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458050504/">1458050504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458050567/">1458050567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458051758/">1458051758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458051820/">1458051820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458052270/">1458052270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458052442/">1458052442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458054273/">1458054273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458054336/">1458054336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458054755/">1458054755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458056801/">1458056801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458057092/">1458057092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458058470/">1458058470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458058528/">1458058528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458058956/">1458058956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458060268/">1458060268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458060816/">1458060816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458060926/">1458060926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458060994/">1458060994/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458061228/">1458061228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458061532/">1458061532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458062864/">1458062864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458063934/">1458063934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458064299/">1458064299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458064598/">1458064598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458064838/">1458064838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458064884/">1458064884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458065318/">1458065318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458067107/">1458067107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458067169/">1458067169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458067228/">1458067228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458067471/">1458067471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458068185/">1458068185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458068662/">1458068662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458069209/">1458069209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458069448/">1458069448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458069507/">1458069507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458073843/">1458073843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458076053/">1458076053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458077016/">1458077016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458077194/">1458077194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458077982/">1458077982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458079910/">1458079910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458080024/">1458080024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458080502/">1458080502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458081110/">1458081110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458081169/">1458081169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458081472/">1458081472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458081701/">1458081701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458082368/">1458082368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458083444/">1458083444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458084470/">1458084470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458085547/">1458085547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458086438/">1458086438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458087104/">1458087104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458087288/">1458087288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458088302/">1458088302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458088423/">1458088423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458090282/">1458090282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458092082/">1458092082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458093541/">1458093541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458093603/">1458093603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458095022/">1458095022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458099528/">1458099528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458099697/">1458099697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458103723/">1458103723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458103840/">1458103840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458107442/">1458107442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458107859/">1458107859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458108043/">1458108043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458108882/">1458108882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458109060/">1458109060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458111409/">1458111409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458114158/">1458114158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458114715/">1458114715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458115180/">1458115180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458115784/">1458115784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458116626/">1458116626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458117817/">1458117817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458120579/">1458120579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458120884/">1458120884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458121421/">1458121421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458121778/">1458121778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458122271/">1458122271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458122562/">1458122562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458123047/">1458123047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458123108/">1458123108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458123167/">1458123167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458124062/">1458124062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458128080/">1458128080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458129850/">1458129850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458132405/">1458132405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458132582/">1458132582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458134503/">1458134503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458135342/">1458135342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458135762/">1458135762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458136425/">1458136425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458136842/">1458136842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458137121/">1458137121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458137621/">1458137621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458138047/">1458138047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458138224/">1458138224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458138817/">1458138817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458140209/">1458140209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458140836/">1458140836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458141943/">1458141943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458142968/">1458142968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458143316/">1458143316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458143624/">1458143624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458144358/">1458144358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458145237/">1458145237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458145300/">1458145300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458145301/">1458145301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458145420/">1458145420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458146219/">1458146219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458146450/">1458146450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458146496/">1458146496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458147401/">1458147401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458147854/">1458147854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458149685/">1458149685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458152375/">1458152375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458152504/">1458152504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458152856/">1458152856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458153284/">1458153284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458153412/">1458153412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458154368/">1458154368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458155503/">1458155503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458156702/">1458156702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458158255/">1458158255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458158387/">1458158387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458158502/">1458158502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458160006/">1458160006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458160426/">1458160426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458160670/">1458160670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458160908/">1458160908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458161028/">1458161028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458161146/">1458161146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458161208/">1458161208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458161323/">1458161323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458163959/">1458163959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458165040/">1458165040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458165588/">1458165588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458166989/">1458166989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458167957/">1458167957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458168186/">1458168186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458168672/">1458168672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458169095/">1458169095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458169211/">1458169211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458169267/">1458169267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458169461/">1458169461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458172338/">1458172338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458176950/">1458176950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458178330/">1458178330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458179708/">1458179708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458181091/">1458181091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458181390/">1458181390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458181571/">1458181571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458183008/">1458183008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458183675/">1458183675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458184048/">1458184048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458185169/">1458185169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458185776/">1458185776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458190689/">1458190689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458191475/">1458191475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458195494/">1458195494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458196099/">1458196099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458199810/">1458199810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458200835/">1458200835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458204367/">1458204367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458204554/">1458204554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458204797/">1458204797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458205147/">1458205147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458205459/">1458205459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458206953/">1458206953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458207910/">1458207910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458208818/">1458208818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458209255/">1458209255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458209718/">1458209718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458213137/">1458213137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458215299/">1458215299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458216016/">1458216016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458216475/">1458216475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458216855/">1458216855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458218949/">1458218949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458221352/">1458221352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458223211/">1458223211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458223987/">1458223987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458226508/">1458226508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458227254/">1458227254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458227745/">1458227745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458230545/">1458230545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458230681/">1458230681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458232156/">1458232156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458232516/">1458232516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458232631/">1458232631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458234687/">1458234687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458236773/">1458236773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458240975/">1458240975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458242061/">1458242061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458243192/">1458243192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458243609/">1458243609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458243673/">1458243673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458243851/">1458243851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458244513/">1458244513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458246367/">1458246367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458246492/">1458246492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458247219/">1458247219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458249137/">1458249137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458249378/">1458249378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458251232/">1458251232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458252488/">1458252488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458254467/">1458254467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458254589/">1458254589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458255316/">1458255316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458257647/">1458257647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458258369/">1458258369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458259763/">1458259763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458259764/">1458259764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458259937/">1458259937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458260894/">1458260894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458262030/">1458262030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458262954/">1458262954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458263891/">1458263891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458265875/">1458265875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458271507/">1458271507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458272172/">1458272172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458273873/">1458273873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458275288/">1458275288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458275419/">1458275419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458276372/">1458276372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458280033/">1458280033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458280959/">1458280959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458284477/">1458284477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458284678/">1458284678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458285794/">1458285794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458289757/">1458289757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458290347/">1458290347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458291006/">1458291006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458291222/">1458291222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458291432/">1458291432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458291620/">1458291620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458294491/">1458294491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458294859/">1458294859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458296168/">1458296168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458296778/">1458296778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458297912/">1458297912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458300833/">1458300833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458301271/">1458301271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458305109/">1458305109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458306446/">1458306446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458308352/">1458308352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458308840/">1458308840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458311484/">1458311484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458312256/">1458312256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458313643/">1458313643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458313869/">1458313869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458314046/">1458314046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458314227/">1458314227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458315070/">1458315070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458315372/">1458315372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458317293/">1458317293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458317888/">1458317888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319394/">1458319394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319531/">1458319531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319709/">1458319709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319865/">1458319865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319868/">1458319868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319870/">1458319870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319877/">1458319877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319878/">1458319878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458319883/">1458319883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320043/">1458320043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320048/">1458320048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320052/">1458320052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320055/">1458320055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320057/">1458320057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320061/">1458320061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320205/">1458320205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320207/">1458320207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320210/">1458320210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320217/">1458320217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320360/">1458320360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320361/">1458320361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320364/">1458320364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320370/">1458320370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320372/">1458320372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320374/">1458320374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320508/">1458320508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320553/">1458320553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320557/">1458320557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320560/">1458320560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320565/">1458320565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320572/">1458320572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320580/">1458320580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320709/">1458320709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320793/">1458320793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320829/">1458320829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320831/">1458320831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320919/">1458320919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320922/">1458320922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458320928/">1458320928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458321063/">1458321063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458321065/">1458321065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458321068/">1458321068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458321076/">1458321076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458321080/">1458321080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458321088/">1458321088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458322258/">1458322258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458322575/">1458322575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458323115/">1458323115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458323241/">1458323241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458323545/">1458323545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458323720/">1458323720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458324500/">1458324500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458325333/">1458325333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458325512/">1458325512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458327188/">1458327188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458327491/">1458327491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458328525/">1458328525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458329660/">1458329660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458330434/">1458330434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458330616/">1458330616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458331293/">1458331293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458331772/">1458331772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458332087/">1458332087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458332502/">1458332502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458332561/">1458332561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458333058/">1458333058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458333159/">1458333159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458333349/">1458333349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458334416/">1458334416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458334772/">1458334772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458335863/">1458335863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458336758/">1458336758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458337423/">1458337423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458338733/">1458338733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458341322/">1458341322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458342105/">1458342105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458342333/">1458342333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458342490/">1458342490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458344676/">1458344676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458346361/">1458346361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458346475/">1458346475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458347499/">1458347499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458348344/">1458348344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458351097/">1458351097/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458351344/">1458351344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458351638/">1458351638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458354867/">1458354867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458356865/">1458356865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458357697/">1458357697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458360153/">1458360153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458361838/">1458361838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458372993/">1458372993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458382244/">1458382244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458385775/">1458385775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458387084/">1458387084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458394606/">1458394606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458401189/">1458401189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458402994/">1458402994/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458404909/">1458404909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458408673/">1458408673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458409177/">1458409177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458415105/">1458415105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458419430/">1458419430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458423087/">1458423087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458430202/">1458430202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458436769/">1458436769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458437312/">1458437312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458440311/">1458440311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458441932/">1458441932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458447996/">1458447996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458451992/">1458451992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458461850/">1458461850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458466710/">1458466710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458468731/">1458468731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458471633/">1458471633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458473297/">1458473297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458473405/">1458473405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458490167/">1458490167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458519938/">1458519938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458520293/">1458520293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458520356/">1458520356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458527193/">1458527193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458527547/">1458527547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458528392/">1458528392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458531460/">1458531460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458534029/">1458534029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458534637/">1458534637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458538250/">1458538250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458538530/">1458538530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458538602/">1458538602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458538830/">1458538830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458539194/">1458539194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458539436/">1458539436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458539551/">1458539551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458540513/">1458540513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458543511/">1458543511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458544597/">1458544597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458545487/">1458545487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458548493/">1458548493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458549022/">1458549022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458552454/">1458552454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458557560/">1458557560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458560793/">1458560793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458562023/">1458562023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458562661/">1458562661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458562707/">1458562707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458562828/">1458562828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458562887/">1458562887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458565231/">1458565231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458565449/">1458565449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458566679/">1458566679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458566916/">1458566916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458569385/">1458569385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458569971/">1458569971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458570268/">1458570268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458570939/">1458570939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458571737/">1458571737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458571854/">1458571854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458572547/">1458572547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458574475/">1458574475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458575555/">1458575555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458575791/">1458575791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458576319/">1458576319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458576595/">1458576595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458576699/">1458576699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458576810/">1458576810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458577110/">1458577110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458577295/">1458577295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458577528/">1458577528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458577987/">1458577987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458579159/">1458579159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458580655/">1458580655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458581358/">1458581358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458581493/">1458581493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458581673/">1458581673/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458581850/">1458581850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458582693/">1458582693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458583070/">1458583070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458583236/">1458583236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458583447/">1458583447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458584195/">1458584195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458584373/">1458584373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458584548/">1458584548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458588167/">1458588167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458589416/">1458589416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458589594/">1458589594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458589989/">1458589989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458590667/">1458590667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458591148/">1458591148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458591538/">1458591538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458592307/">1458592307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458593439/">1458593439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458595023/">1458595023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458595472/">1458595472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458596021/">1458596021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458596456/">1458596456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458597753/">1458597753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458601276/">1458601276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458601651/">1458601651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458602247/">1458602247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458602899/">1458602899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458603041/">1458603041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458603805/">1458603805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458604346/">1458604346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458604395/">1458604395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458605417/">1458605417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458606444/">1458606444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458609049/">1458609049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458610783/">1458610783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458611872/">1458611872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458613728/">1458613728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458615160/">1458615160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458616357/">1458616357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458616849/">1458616849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458619836/">1458619836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458620379/">1458620379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458622846/">1458622846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458624679/">1458624679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458626740/">1458626740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458627345/">1458627345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458631723/">1458631723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458632855/">1458632855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458633276/">1458633276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458633459/">1458633459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458636936/">1458636936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458641261/">1458641261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458641324/">1458641324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458642199/">1458642199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458642530/">1458642530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458642645/">1458642645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458645661/">1458645661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458646252/">1458646252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458646357/">1458646357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458649365/">1458649365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458650076/">1458650076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458650373/">1458650373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458653014/">1458653014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458653078/">1458653078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458655126/">1458655126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458656076/">1458656076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458656978/">1458656978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458658306/">1458658306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458659465/">1458659465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458663145/">1458663145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458667893/">1458667893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458668726/">1458668726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458669560/">1458669560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458669924/">1458669924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458677543/">1458677543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458678026/">1458678026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458678083/">1458678083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458678139/">1458678139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458678140/">1458678140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458678198/">1458678198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458678320/">1458678320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458679217/">1458679217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458680360/">1458680360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458680426/">1458680426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458680712/">1458680712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458681220/">1458681220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458681405/">1458681405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458681876/">1458681876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458682314/">1458682314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458683445/">1458683445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458683449/">1458683449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458683570/">1458683570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458684766/">1458684766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458687632/">1458687632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458687870/">1458687870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458688039/">1458688039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458688401/">1458688401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458688702/">1458688702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458689301/">1458689301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458689431/">1458689431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458690320/">1458690320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458690979/">1458690979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458691096/">1458691096/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458692482/">1458692482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458692899/">1458692899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458693082/">1458693082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458693685/">1458693685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458694106/">1458694106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458695612/">1458695612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458696978/">1458696978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458697722/">1458697722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458698870/">1458698870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458699654/">1458699654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458700398/">1458700398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458701258/">1458701258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458707241/">1458707241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458708201/">1458708201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458708976/">1458708976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458709158/">1458709158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458709505/">1458709505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458710068/">1458710068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458711083/">1458711083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458713428/">1458713428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458716944/">1458716944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458718876/">1458718876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458721858/">1458721858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458722252/">1458722252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458723449/">1458723449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458725848/">1458725848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458726267/">1458726267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458727025/">1458727025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458729559/">1458729559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458730198/">1458730198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458730470/">1458730470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458731319/">1458731319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458732708/">1458732708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458732724/">1458732724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458733769/">1458733769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458737079/">1458737079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458738987/">1458738987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458739350/">1458739350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458739544/">1458739544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458739720/">1458739720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458740451/">1458740451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458742958/">1458742958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458743535/">1458743535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458743553/">1458743553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458743977/">1458743977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458744040/">1458744040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458745527/">1458745527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458746278/">1458746278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458746969/">1458746969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458747655/">1458747655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458747656/">1458747656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458747940/">1458747940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458748185/">1458748185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458748515/">1458748515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458748767/">1458748767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458749142/">1458749142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458750687/">1458750687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458750936/">1458750936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458752564/">1458752564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458752863/">1458752863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458754001/">1458754001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458754262/">1458754262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458754477/">1458754477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458754596/">1458754596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458755938/">1458755938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458756239/">1458756239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458756451/">1458756451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458757060/">1458757060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458757309/">1458757309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458757541/">1458757541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458759883/">1458759883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458760650/">1458760650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458761259/">1458761259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458763183/">1458763183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458765022/">1458765022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458766246/">1458766246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458766866/">1458766866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458769176/">1458769176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458769177/">1458769177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458769370/">1458769370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458769483/">1458769483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458769668/">1458769668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458769956/">1458769956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458770198/">1458770198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458770650/">1458770650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458771589/">1458771589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458771641/">1458771641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458771830/">1458771830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458773738/">1458773738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458775960/">1458775960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458777186/">1458777186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458777189/">1458777189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458777192/">1458777192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458777317/">1458777317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458777407/">1458777407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458778070/">1458778070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458778118/">1458778118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458779558/">1458779558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458780442/">1458780442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458780810/">1458780810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458781487/">1458781487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458781750/">1458781750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458782051/">1458782051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458782499/">1458782499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458783585/">1458783585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458783703/">1458783703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458785746/">1458785746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458786432/">1458786432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458786863/">1458786863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458791567/">1458791567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458792528/">1458792528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458792529/">1458792529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458792726/">1458792726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458793209/">1458793209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458794674/">1458794674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458794684/">1458794684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458795283/">1458795283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458795797/">1458795797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458796101/">1458796101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458796419/">1458796419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458796880/">1458796880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458797390/">1458797390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458797737/">1458797737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458798343/">1458798343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458798715/">1458798715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458798893/">1458798893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458801518/">1458801518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458801949/">1458801949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458802372/">1458802372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458805012/">1458805012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458805540/">1458805540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458805705/">1458805705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458808007/">1458808007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458808187/">1458808187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458808283/">1458808283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458811666/">1458811666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458813224/">1458813224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458815578/">1458815578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458816562/">1458816562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819088/">1458819088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819427/">1458819427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819436/">1458819436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819562/">1458819562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819567/">1458819567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819714/">1458819714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819718/">1458819718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819720/">1458819720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819840/">1458819840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819846/">1458819846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819985/">1458819985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819989/">1458819989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458819990/">1458819990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820115/">1458820115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820120/">1458820120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820127/">1458820127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820247/">1458820247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820255/">1458820255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820380/">1458820380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820384/">1458820384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820520/">1458820520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820522/">1458820522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820525/">1458820525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820643/">1458820643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458820645/">1458820645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458825471/">1458825471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458826674/">1458826674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458827208/">1458827208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458827342/">1458827342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458827383/">1458827383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458827736/">1458827736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458828639/">1458828639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458829001/">1458829001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458829592/">1458829592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458830852/">1458830852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458831335/">1458831335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458832169/">1458832169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458832534/">1458832534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458832843/">1458832843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458833679/">1458833679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458833803/">1458833803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458834158/">1458834158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458834330/">1458834330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458834399/">1458834399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458834577/">1458834577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458834632/">1458834632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458834722/">1458834722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458834838/">1458834838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458835379/">1458835379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458835512/">1458835512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458836034/">1458836034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458839021/">1458839021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458839460/">1458839460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458839461/">1458839461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458839462/">1458839462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458840666/">1458840666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458841311/">1458841311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458841759/">1458841759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458845239/">1458845239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458845868/">1458845868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458846090/">1458846090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458846346/">1458846346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458847538/">1458847538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458848017/">1458848017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458848496/">1458848496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458849935/">1458849935/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458850084/">1458850084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458850262/">1458850262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458850669/">1458850669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458851437/">1458851437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458851737/">1458851737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458853539/">1458853539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458856291/">1458856291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458856536/">1458856536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458857976/">1458857976/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458858574/">1458858574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458859232/">1458859232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458860137/">1458860137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458860434/">1458860434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458860672/">1458860672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458862234/">1458862234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458863678/">1458863678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458864518/">1458864518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458873093/">1458873093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458877894/">1458877894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458884498/">1458884498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892328/">1458892328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892333/">1458892333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892334/">1458892334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892337/">1458892337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892344/">1458892344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892473/">1458892473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892475/">1458892475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892481/">1458892481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892484/">1458892484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892490/">1458892490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892626/">1458892626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892631/">1458892631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892632/">1458892632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892637/">1458892637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892638/">1458892638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458892646/">1458892646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458894647/">1458894647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458895267/">1458895267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458895536/">1458895536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458895915/">1458895915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458900940/">1458900940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458905254/">1458905254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458905406/">1458905406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458908848/">1458908848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458912270/">1458912270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458913230/">1458913230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458913289/">1458913289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458913349/">1458913349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458916474/">1458916474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458918637/">1458918637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458918759/">1458918759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458920309/">1458920309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458921468/">1458921468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458921471/">1458921471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458921472/">1458921472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458924819/">1458924819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458925183/">1458925183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458927070/">1458927070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458927582/">1458927582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458928076/">1458928076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458931061/">1458931061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458931666/">1458931666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458931868/">1458931868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458932252/">1458932252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458933170/">1458933170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458936327/">1458936327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458937719/">1458937719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458937827/">1458937827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458937840/">1458937840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458938375/">1458938375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458939208/">1458939208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458939450/">1458939450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458939938/">1458939938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458941008/">1458941008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458941249/">1458941249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458942006/">1458942006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458942633/">1458942633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458943121/">1458943121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458943774/">1458943774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458944380/">1458944380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458945093/">1458945093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458945535/">1458945535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458948668/">1458948668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458948738/">1458948738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458952972/">1458952972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458956203/">1458956203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458957037/">1458957037/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458957697/">1458957697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458958304/">1458958304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458958536/">1458958536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458970060/">1458970060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458970420/">1458970420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458970532/">1458970532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458972272/">1458972272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458974140/">1458974140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458980023/">1458980023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458995008/">1458995008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458995737/">1458995737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458995790/">1458995790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458995912/">1458995912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458995913/">1458995913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458996091/">1458996091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458996216/">1458996216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458996696/">1458996696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1458999150/">1458999150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459001727/">1459001727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459002710/">1459002710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459008489/">1459008489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459009660/">1459009660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459013497/">1459013497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459018530/">1459018530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459036764/">1459036764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459046092/">1459046092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459047090/">1459047090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459056206/">1459056206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459061676/">1459061676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459067413/">1459067413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459069231/">1459069231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459080027/">1459080027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459089227/">1459089227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459108052/">1459108052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459108585/">1459108585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459110387/">1459110387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459114407/">1459114407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459115553/">1459115553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459120049/">1459120049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459121401/">1459121401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459122693/">1459122693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459122748/">1459122748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459132303/">1459132303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459132984/">1459132984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459143066/">1459143066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459147230/">1459147230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459155204/">1459155204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459155272/">1459155272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459162115/">1459162115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459162191/">1459162191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459164606/">1459164606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459170417/">1459170417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459170931/">1459170931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459171172/">1459171172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459172844/">1459172844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459175501/">1459175501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459178967/">1459178967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459180822/">1459180822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459182142/">1459182142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459184071/">1459184071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459186172/">1459186172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459186315/">1459186315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459186897/">1459186897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459187374/">1459187374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459188563/">1459188563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459188811/">1459188811/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459192407/">1459192407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459192826/">1459192826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459194929/">1459194929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459195502/">1459195502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459195797/">1459195797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459196613/">1459196613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459197031/">1459197031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459197571/">1459197571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459202427/">1459202427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459202859/">1459202859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459203098/">1459203098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459203281/">1459203281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459203519/">1459203519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459204724/">1459204724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459207881/">1459207881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459208266/">1459208266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459209275/">1459209275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459209877/">1459209877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459213302/">1459213302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459213887/">1459213887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459214873/">1459214873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459220157/">1459220157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459229476/">1459229476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459231609/">1459231609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459232010/">1459232010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459232514/">1459232514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459232810/">1459232810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459233217/">1459233217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459237900/">1459237900/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459239818/">1459239818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459240291/">1459240291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459241071/">1459241071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459242463/">1459242463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459245692/">1459245692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459247138/">1459247138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459248915/">1459248915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459251023/">1459251023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459251216/">1459251216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459253551/">1459253551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459254625/">1459254625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459256738/">1459256738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459257698/">1459257698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459259070/">1459259070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459259640/">1459259640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459259729/">1459259729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459261295/">1459261295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459264503/">1459264503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459264652/">1459264652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459265128/">1459265128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459265614/">1459265614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459268160/">1459268160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459272710/">1459272710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459274314/">1459274314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459275931/">1459275931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459276530/">1459276530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459278633/">1459278633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459279801/">1459279801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459281293/">1459281293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459283083/">1459283083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459283376/">1459283376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459283478/">1459283478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459284692/">1459284692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459286375/">1459286375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459287356/">1459287356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459289380/">1459289380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459289438/">1459289438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459289494/">1459289494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459289560/">1459289560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459289801/">1459289801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459290157/">1459290157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459290582/">1459290582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459293556/">1459293556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459294222/">1459294222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459294657/">1459294657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459294716/">1459294716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459295074/">1459295074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459295262/">1459295262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459298017/">1459298017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459305169/">1459305169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459305945/">1459305945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459306123/">1459306123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459306420/">1459306420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459306472/">1459306472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459310980/">1459310980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459313914/">1459313914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459315061/">1459315061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459315930/">1459315930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459317034/">1459317034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459318773/">1459318773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459320456/">1459320456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459320698/">1459320698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459320813/">1459320813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459321061/">1459321061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459321175/">1459321175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459321371/">1459321371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459321594/">1459321594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459333657/">1459333657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459333760/">1459333760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459333881/">1459333881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459334000/">1459334000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459334048/">1459334048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459334114/">1459334114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459334184/">1459334184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459334228/">1459334228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459334336/">1459334336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459334443/">1459334443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459335690/">1459335690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459335876/">1459335876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459336357/">1459336357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459336835/">1459336835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459337798/">1459337798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459338038/">1459338038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459338701/">1459338701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459338764/">1459338764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459338873/">1459338873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459338982/">1459338982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459339089/">1459339089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459339472/">1459339472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459339656/">1459339656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459339710/">1459339710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459340986/">1459340986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459341403/">1459341403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459344117/">1459344117/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459344752/">1459344752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459346710/">1459346710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459347047/">1459347047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459347544/">1459347544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459349317/">1459349317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459350142/">1459350142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459350474/">1459350474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459351057/">1459351057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459352141/">1459352141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459353520/">1459353520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459354931/">1459354931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459355621/">1459355621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459355995/">1459355995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459356266/">1459356266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459356541/">1459356541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459357216/">1459357216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459357425/">1459357425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459358629/">1459358629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459359059/">1459359059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438828/">1459438828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438831/">1459438831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438834/">1459438834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438835/">1459438835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438836/">1459438836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438840/">1459438840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438841/">1459438841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438842/">1459438842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438847/">1459438847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438848/">1459438848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438849/">1459438849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438851/">1459438851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438853/">1459438853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438854/">1459438854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438855/">1459438855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438856/">1459438856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438861/">1459438861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438862/">1459438862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459438864/">1459438864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459454977/">1459454977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459456539/">1459456539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459458519/">1459458519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459459719/">1459459719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459460676/">1459460676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459467011/">1459467011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469022/">1459469022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469196/">1459469196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469263/">1459469263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469264/">1459469264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469319/">1459469319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469441/">1459469441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469554/">1459469554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469675/">1459469675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469737/">1459469737/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469738/">1459469738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469793/">1459469793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469881/">1459469881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469937/">1459469937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459469982/">1459469982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459470096/">1459470096/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459470153/">1459470153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459470275/">1459470275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459470336/">1459470336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459470393/">1459470393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459470455/">1459470455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459470523/">1459470523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459471479/">1459471479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459471667/">1459471667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459471719/">1459471719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459472082/">1459472082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459473279/">1459473279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459473757/">1459473757/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459473818/">1459473818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459474537/">1459474537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459474719/">1459474719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459475006/">1459475006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459477041/">1459477041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459478075/">1459478075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459478209/">1459478209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459478933/">1459478933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459479289/">1459479289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459480167/">1459480167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459480240/">1459480240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459482763/">1459482763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459483000/">1459483000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459484378/">1459484378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459487324/">1459487324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459491110/">1459491110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459491227/">1459491227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459491760/">1459491760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459492119/">1459492119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459492602/">1459492602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459493567/">1459493567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459496575/">1459496575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459496926/">1459496926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459497107/">1459497107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459497157/">1459497157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459497224/">1459497224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459498362/">1459498362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459500462/">1459500462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459503588/">1459503588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459504720/">1459504720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459506047/">1459506047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459507123/">1459507123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459507850/">1459507850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459510668/">1459510668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459512236/">1459512236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459512281/">1459512281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459512602/">1459512602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459512715/">1459512715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459512884/">1459512884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459513661/">1459513661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459513799/">1459513799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459516907/">1459516907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459517199/">1459517199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459517322/">1459517322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459517443/">1459517443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459517995/">1459517995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459522544/">1459522544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459522667/">1459522667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459522724/">1459522724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459523330/">1459523330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459523390/">1459523390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459523868/">1459523868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459527175/">1459527175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459527942/">1459527942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459529872/">1459529872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459529983/">1459529983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459531250/">1459531250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459531359/">1459531359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459531482/">1459531482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459531665/">1459531665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459532687/">1459532687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459532741/">1459532741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459533165/">1459533165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459533220/">1459533220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459533347/">1459533347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459534360/">1459534360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459534841/">1459534841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459535034/">1459535034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459535383/">1459535383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459537187/">1459537187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459538261/">1459538261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459538461/">1459538461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459541143/">1459541143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459542169/">1459542169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459543365/">1459543365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459544929/">1459544929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459546298/">1459546298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459547440/">1459547440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459549180/">1459549180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459552005/">1459552005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459552423/">1459552423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459552909/">1459552909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459553430/">1459553430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459558800/">1459558800/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459560228/">1459560228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459563347/">1459563347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459564365/">1459564365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459568274/">1459568274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459575221/">1459575221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459580089/">1459580089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459601449/">1459601449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459612600/">1459612600/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459618012/">1459618012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459618306/">1459618306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459620224/">1459620224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459623228/">1459623228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459625630/">1459625630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459631200/">1459631200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459631861/">1459631861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459658174/">1459658174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459658223/">1459658223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459658224/">1459658224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459661233/">1459661233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459665254/">1459665254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459668682/">1459668682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459671134/">1459671134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459681036/">1459681036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459683435/">1459683435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459686861/">1459686861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459691632/">1459691632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459693960/">1459693960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459694835/">1459694835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459703720/">1459703720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459719915/">1459719915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459729340/">1459729340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459730478/">1459730478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459732517/">1459732517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459734132/">1459734132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459742417/">1459742417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459756818/">1459756818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459757059/">1459757059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459759099/">1459759099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459764212/">1459764212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459765282/">1459765282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459767532/">1459767532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459767920/">1459767920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459770676/">1459770676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459774115/">1459774115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459776747/">1459776747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459785448/">1459785448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459786478/">1459786478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459788456/">1459788456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459789711/">1459789711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459789829/">1459789829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459789889/">1459789889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459790133/">1459790133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459790190/">1459790190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459790247/">1459790247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459792044/">1459792044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459792289/">1459792289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459795823/">1459795823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459796965/">1459796965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459798167/">1459798167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459798414/">1459798414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459799364/">1459799364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459800253/">1459800253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459800254/">1459800254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459800256/">1459800256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459800323/">1459800323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801015/">1459801015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801132/">1459801132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801278/">1459801278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801390/">1459801390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801465/">1459801465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801498/">1459801498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801541/">1459801541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801615/">1459801615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801725/">1459801725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801779/">1459801779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459801910/">1459801910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459802734/">1459802734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459804845/">1459804845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459807067/">1459807067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459807562/">1459807562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459807778/">1459807778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459808595/">1459808595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459809765/">1459809765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459810183/">1459810183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459811795/">1459811795/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459820011/">1459820011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459820012/">1459820012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459821311/">1459821311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459824198/">1459824198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459825512/">1459825512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459827499/">1459827499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459827620/">1459827620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459828217/">1459828217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459828337/">1459828337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459830250/">1459830250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459830432/">1459830432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459830556/">1459830556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459830612/">1459830612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459830665/">1459830665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459830856/">1459830856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459831266/">1459831266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459836959/">1459836959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459838057/">1459838057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459843214/">1459843214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459844108/">1459844108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459844225/">1459844225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459845309/">1459845309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459849218/">1459849218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459849337/">1459849337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459849820/">1459849820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459856533/">1459856533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459861089/">1459861089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459866563/">1459866563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459868116/">1459868116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459868229/">1459868229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459869621/">1459869621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459870394/">1459870394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459871771/">1459871771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459879033/">1459879033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459879223/">1459879223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459880113/">1459880113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459880168/">1459880168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459880286/">1459880286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459881315/">1459881315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459881919/">1459881919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459882326/">1459882326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459883711/">1459883711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459886353/">1459886353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459886416/">1459886416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459887191/">1459887191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459887312/">1459887312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459887376/">1459887376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459888929/">1459888929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459889834/">1459889834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459891869/">1459891869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459892118/">1459892118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459892179/">1459892179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459892957/">1459892957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459894275/">1459894275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459894337/">1459894337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459895226/">1459895226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459896078/">1459896078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459897880/">1459897880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459899487/">1459899487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459899613/">1459899613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459900866/">1459900866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459903751/">1459903751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459905076/">1459905076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459912929/">1459912929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459913614/">1459913614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459914500/">1459914500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459915871/">1459915871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459917628/">1459917628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459924040/">1459924040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459925466/">1459925466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459926435/">1459926435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459926676/">1459926676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459927697/">1459927697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459929306/">1459929306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459931597/">1459931597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459932554/">1459932554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459934890/">1459934890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459935625/">1459935625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459937239/">1459937239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459937288/">1459937288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459938611/">1459938611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459940952/">1459940952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459942276/">1459942276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459943836/">1459943836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459944140/">1459944140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459949047/">1459949047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459949647/">1459949647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459950493/">1459950493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459951394/">1459951394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459951767/">1459951767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459952300/">1459952300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459953442/">1459953442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459956493/">1459956493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459958653/">1459958653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459959070/">1459959070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459959616/">1459959616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459960027/">1459960027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459960211/">1459960211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459963340/">1459963340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459963928/">1459963928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459969986/">1459969986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459970359/">1459970359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459971071/">1459971071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459971650/">1459971650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459971851/">1459971851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459973420/">1459973420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459974475/">1459974475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459975784/">1459975784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459977290/">1459977290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459977708/">1459977708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459978966/">1459978966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459979925/">1459979925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459982383/">1459982383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459984734/">1459984734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459986773/">1459986773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459987915/">1459987915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459988028/">1459988028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459991657/">1459991657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459993190/">1459993190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459993493/">1459993493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459997631/">1459997631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1459997992/">1459997992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460009865/">1460009865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460012142/">1460012142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460013302/">1460013302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460014195/">1460014195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460014914/">1460014914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460015212/">1460015212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460017565/">1460017565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460017729/">1460017729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460017970/">1460017970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460018630/">1460018630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460018870/">1460018870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460019835/">1460019835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460022109/">1460022109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460023043/">1460023043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460023491/">1460023491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460026185/">1460026185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460028352/">1460028352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460028582/">1460028582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029195/">1460029195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029196/">1460029196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029198/">1460029198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029199/">1460029199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029200/">1460029200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029202/">1460029202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029206/">1460029206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029207/">1460029207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029210/">1460029210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029211/">1460029211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029213/">1460029213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029218/">1460029218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029221/">1460029221/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029222/">1460029222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029223/">1460029223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029224/">1460029224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029225/">1460029225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029228/">1460029228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029229/">1460029229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029232/">1460029232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029234/">1460029234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029236/">1460029236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029279/">1460029279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029281/">1460029281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029287/">1460029287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029288/">1460029288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029291/">1460029291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029297/">1460029297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029298/">1460029298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029299/">1460029299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029300/">1460029300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029311/">1460029311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029312/">1460029312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029318/">1460029318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029320/">1460029320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029321/">1460029321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460029615/">1460029615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460030685/">1460030685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460031537/">1460031537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460032604/">1460032604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460034225/">1460034225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460034830/">1460034830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460035850/">1460035850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460035975/">1460035975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460036747/">1460036747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460036880/">1460036880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460036970/">1460036970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460037050/">1460037050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460043217/">1460043217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460044513/">1460044513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460044980/">1460044980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460044982/">1460044982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460044986/">1460044986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460044988/">1460044988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460044989/">1460044989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460044996/">1460044996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460045001/">1460045001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460045005/">1460045005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460047913/">1460047913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054333/">1460054333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054334/">1460054334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054335/">1460054335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054340/">1460054340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054343/">1460054343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054346/">1460054346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054347/">1460054347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054348/">1460054348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054349/">1460054349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054372/">1460054372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054374/">1460054374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460054375/">1460054375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460055533/">1460055533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460056552/">1460056552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460061541/">1460061541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460062192/">1460062192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460062999/">1460062999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460064010/">1460064010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460065621/">1460065621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460065751/">1460065751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460066641/">1460066641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460066966/">1460066966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460071321/">1460071321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460071396/">1460071396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460071478/">1460071478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460071847/">1460071847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460074598/">1460074598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460078137/">1460078137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460079253/">1460079253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460080787/">1460080787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460080844/">1460080844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460083424/">1460083424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460084148/">1460084148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460088646/">1460088646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460096079/">1460096079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460101362/">1460101362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460102925/">1460102925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460105500/">1460105500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460106947/">1460106947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460108627/">1460108627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460109240/">1460109240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460110918/">1460110918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460111219/">1460111219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460112239/">1460112239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460115540/">1460115540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460118418/">1460118418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460118966/">1460118966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460119205/">1460119205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460119679/">1460119679/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460121238/">1460121238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460123519/">1460123519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460125741/">1460125741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460126177/">1460126177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460126639/">1460126639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460127248/">1460127248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460128318/">1460128318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460130233/">1460130233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460133418/">1460133418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460134079/">1460134079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460134799/">1460134799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460136956/">1460136956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460141766/">1460141766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460142775/">1460142775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460142989/">1460142989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460142992/">1460142992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460143988/">1460143988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460145153/">1460145153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460145193/">1460145193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460145204/">1460145204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460146446/">1460146446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148025/">1460148025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148366/">1460148366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148367/">1460148367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148369/">1460148369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148371/">1460148371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148372/">1460148372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148373/">1460148373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148375/">1460148375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148390/">1460148390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148391/">1460148391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148394/">1460148394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148426/">1460148426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148427/">1460148427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148428/">1460148428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148429/">1460148429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148437/">1460148437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148438/">1460148438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148440/">1460148440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148448/">1460148448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148449/">1460148449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148451/">1460148451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148453/">1460148453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148454/">1460148454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148455/">1460148455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460148457/">1460148457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460149808/">1460149808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460150580/">1460150580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460152136/">1460152136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460153630/">1460153630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460154349/">1460154349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460154770/">1460154770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460155426/">1460155426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460156685/">1460156685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460156805/">1460156805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460157015/">1460157015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460158250/">1460158250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460159295/">1460159295/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460159712/">1460159712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460161780/">1460161780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460165218/">1460165218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460171750/">1460171750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460206305/">1460206305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460209916/">1460209916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460211035/">1460211035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460211881/">1460211881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460225448/">1460225448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460225915/">1460225915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460228685/">1460228685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460229824/">1460229824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460230717/">1460230717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460231075/">1460231075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460231959/">1460231959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460233906/">1460233906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460256610/">1460256610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460258057/">1460258057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460258175/">1460258175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460263626/">1460263626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460319731/">1460319731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460320204/">1460320204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460320566/">1460320566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460322975/">1460322975/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460327597/">1460327597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460328915/">1460328915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460334192/">1460334192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460335152/">1460335152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460336114/">1460336114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460339832/">1460339832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460339892/">1460339892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460340554/">1460340554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460343124/">1460343124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460349966/">1460349966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460353872/">1460353872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460354286/">1460354286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460355919/">1460355919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460360719/">1460360719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460360895/">1460360895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460363056/">1460363056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460366053/">1460366053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460367130/">1460367130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460367854/">1460367854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460368086/">1460368086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460370981/">1460370981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460371578/">1460371578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460373196/">1460373196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460380384/">1460380384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460380567/">1460380567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460381896/">1460381896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460382557/">1460382557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460382856/">1460382856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460383336/">1460383336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460385681/">1460385681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460385852/">1460385852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460386873/">1460386873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460387350/">1460387350/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460387839/">1460387839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460388081/">1460388081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460390364/">1460390364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460390590/">1460390590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460390954/">1460390954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460391435/">1460391435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460391555/">1460391555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460391918/">1460391918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460393121/">1460393121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460393651/">1460393651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460393778/">1460393778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460394019/">1460394019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460395272/">1460395272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460395571/">1460395571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460396704/">1460396704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460398956/">1460398956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460398958/">1460398958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460399156/">1460399156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460399662/">1460399662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460399920/">1460399920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460400150/">1460400150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460401189/">1460401189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460401832/">1460401832/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460401963/">1460401963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460403921/">1460403921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460404365/">1460404365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460408754/">1460408754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460409654/">1460409654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460410658/">1460410658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460412175/">1460412175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460412920/">1460412920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460413075/">1460413075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460413488/">1460413488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460414631/">1460414631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460415955/">1460415955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460418105/">1460418105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460418232/">1460418232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460418770/">1460418770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460425007/">1460425007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460425244/">1460425244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460426036/">1460426036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460427472/">1460427472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460430112/">1460430112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460430524/">1460430524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460430767/">1460430767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460431248/">1460431248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460431551/">1460431551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460432096/">1460432096/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460434069/">1460434069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460434557/">1460434557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460438814/">1460438814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460438984/">1460438984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460440431/">1460440431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460441267/">1460441267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460441385/">1460441385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460442892/">1460442892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460446250/">1460446250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460447030/">1460447030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460450094/">1460450094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460450400/">1460450400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460450689/">1460450689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460450813/">1460450813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460452544/">1460452544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460455973/">1460455973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460457533/">1460457533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460459206/">1460459206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460462035/">1460462035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460464909/">1460464909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460465574/">1460465574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460466712/">1460466712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460466948/">1460466948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460468457/">1460468457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460469591/">1460469591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460470123/">1460470123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460470548/">1460470548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460470735/">1460470735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460470736/">1460470736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460470850/">1460470850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460471215/">1460471215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460472945/">1460472945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460474529/">1460474529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460476618/">1460476618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460477214/">1460477214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460477692/">1460477692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460479604/">1460479604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460481476/">1460481476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460481648/">1460481648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460482733/">1460482733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460485193/">1460485193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460486036/">1460486036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460488556/">1460488556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460488967/">1460488967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460489564/">1460489564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460490587/">1460490587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460491791/">1460491791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460492210/">1460492210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460492267/">1460492267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460492867/">1460492867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460493528/">1460493528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460494123/">1460494123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460494369/">1460494369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460495564/">1460495564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460495983/">1460495983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460497208/">1460497208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460498152/">1460498152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460498153/">1460498153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460498333/">1460498333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460498766/">1460498766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460499468/">1460499468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460500614/">1460500614/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460500845/">1460500845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460505054/">1460505054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460506969/">1460506969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460507206/">1460507206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460509785/">1460509785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460509907/">1460509907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460510749/">1460510749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460511890/">1460511890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460516267/">1460516267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460521072/">1460521072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460521906/">1460521906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460526170/">1460526170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460530247/">1460530247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460530664/">1460530664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460532893/">1460532893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460534212/">1460534212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460535165/">1460535165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460536727/">1460536727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460537687/">1460537687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460538888/">1460538888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460539014/">1460539014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460541357/">1460541357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460542074/">1460542074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460544047/">1460544047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460544406/">1460544406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460546207/">1460546207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460547950/">1460547950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548386/">1460548386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548390/">1460548390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548394/">1460548394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548396/">1460548396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548618/">1460548618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548619/">1460548619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548620/">1460548620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548621/">1460548621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548622/">1460548622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548774/">1460548774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548775/">1460548775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548776/">1460548776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548778/">1460548778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548779/">1460548779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460548782/">1460548782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460550114/">1460550114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460550895/">1460550895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460551904/">1460551904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460552096/">1460552096/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460552756/">1460552756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460552987/">1460552987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460553165/">1460553165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554401/">1460554401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554402/">1460554402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554403/">1460554403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554404/">1460554404/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554407/">1460554407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554408/">1460554408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554409/">1460554409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554410/">1460554410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554420/">1460554420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554421/">1460554421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554422/">1460554422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554424/">1460554424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554427/">1460554427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554428/">1460554428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554446/">1460554446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554447/">1460554447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554449/">1460554449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554499/">1460554499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554500/">1460554500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554501/">1460554501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554505/">1460554505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554506/">1460554506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554507/">1460554507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554508/">1460554508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554513/">1460554513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554514/">1460554514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554515/">1460554515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554516/">1460554516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554520/">1460554520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554521/">1460554521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554522/">1460554522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554524/">1460554524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554529/">1460554529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554530/">1460554530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554531/">1460554531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554533/">1460554533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554534/">1460554534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554536/">1460554536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554537/">1460554537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554538/">1460554538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554539/">1460554539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554542/">1460554542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554543/">1460554543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554544/">1460554544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554545/">1460554545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554885/">1460554885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460554904/">1460554904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460555157/">1460555157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460555452/">1460555452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460556396/">1460556396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460556592/">1460556592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460557378/">1460557378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460557956/">1460557956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460559409/">1460559409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460561023/">1460561023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460562460/">1460562460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460567861/">1460567861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460568821/">1460568821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460570144/">1460570144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460571470/">1460571470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460572484/">1460572484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460573146/">1460573146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460579683/">1460579683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460579988/">1460579988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460581841/">1460581841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460582022/">1460582022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460582319/">1460582319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460583287/">1460583287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460583476/">1460583476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460584604/">1460584604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460585451/">1460585451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460585748/">1460585748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460586520/">1460586520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460586702/">1460586702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460587002/">1460587002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460587250/">1460587250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460587600/">1460587600/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460590127/">1460590127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460590314/">1460590314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460590362/">1460590362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460591803/">1460591803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460591869/">1460591869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460592643/">1460592643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460600858/">1460600858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460600986/">1460600986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460606269/">1460606269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460607828/">1460607828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460609449/">1460609449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460609571/">1460609571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460613880/">1460613880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460614839/">1460614839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460615089/">1460615089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460617120/">1460617120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460617515/">1460617515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460619402/">1460619402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460619762/">1460619762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460621629/">1460621629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460623846/">1460623846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460625703/">1460625703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460627084/">1460627084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460627507/">1460627507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460628374/">1460628374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460630395/">1460630395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460634051/">1460634051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460634471/">1460634471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460636203/">1460636203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460643222/">1460643222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460645138/">1460645138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460646945/">1460646945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460648025/">1460648025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460648151/">1460648151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460648742/">1460648742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460649228/">1460649228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460649287/">1460649287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460650246/">1460650246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460651042/">1460651042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460653368/">1460653368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460655283/">1460655283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460655476/">1460655476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460655698/">1460655698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460656000/">1460656000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460657183/">1460657183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460657184/">1460657184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460659120/">1460659120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460660084/">1460660084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460660505/">1460660505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460662069/">1460662069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460662487/">1460662487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460663441/">1460663441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460664710/">1460664710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460665242/">1460665242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460665300/">1460665300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460666269/">1460666269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460666869/">1460666869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460667041/">1460667041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460670948/">1460670948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460672321/">1460672321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460675741/">1460675741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460675927/">1460675927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460677609/">1460677609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460681259/">1460681259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460681678/">1460681678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460689059/">1460689059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460689306/">1460689306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460689544/">1460689544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460689798/">1460689798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460691114/">1460691114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460691459/">1460691459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460692373/">1460692373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460693143/">1460693143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460693988/">1460693988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460695149/">1460695149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460695543/">1460695543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460695730/">1460695730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460700642/">1460700642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460701671/">1460701671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460701966/">1460701966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460704073/">1460704073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460705454/">1460705454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460711499/">1460711499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460711989/">1460711989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460712411/">1460712411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460714629/">1460714629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460715170/">1460715170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460716839/">1460716839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460718764/">1460718764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460719738/">1460719738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460721461/">1460721461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460721588/">1460721588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460723536/">1460723536/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460723554/">1460723554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460723555/">1460723555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460726146/">1460726146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460726328/">1460726328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460727711/">1460727711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460729424/">1460729424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460729764/">1460729764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460729918/">1460729918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460730000/">1460730000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460730017/">1460730017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460731733/">1460731733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460734122/">1460734122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460737121/">1460737121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460738750/">1460738750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460739224/">1460739224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460743850/">1460743850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460746839/">1460746839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460747208/">1460747208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460748882/">1460748882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460752647/">1460752647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460757255/">1460757255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460758461/">1460758461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460758944/">1460758944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460760017/">1460760017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460760641/">1460760641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460761095/">1460761095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460763314/">1460763314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460763615/">1460763615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460764165/">1460764165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460765901/">1460765901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460766076/">1460766076/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460771361/">1460771361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460772365/">1460772365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460775991/">1460775991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460776346/">1460776346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460790320/">1460790320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460798657/">1460798657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460802202/">1460802202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460811759/">1460811759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460823438/">1460823438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460824345/">1460824345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460828959/">1460828959/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460864844/">1460864844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460865142/">1460865142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460897846/">1460897846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460903062/">1460903062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460907501/">1460907501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460911047/">1460911047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460929635/">1460929635/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460930478/">1460930478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460938038/">1460938038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460938698/">1460938698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460959215/">1460959215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460962340/">1460962340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460964140/">1460964140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460964861/">1460964861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460966558/">1460966558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460966775/">1460966775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460966971/">1460966971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460968104/">1460968104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460968878/">1460968878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460969126/">1460969126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460970559/">1460970559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460970903/">1460970903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460971225/">1460971225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460971760/">1460971760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460975189/">1460975189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460978115/">1460978115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460978183/">1460978183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460982198/">1460982198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460982498/">1460982498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460982801/">1460982801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460983398/">1460983398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460984803/">1460984803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460985874/">1460985874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460988978/">1460988978/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460990302/">1460990302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460991382/">1460991382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460992650/">1460992650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460993136/">1460993136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460993313/">1460993313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460997031/">1460997031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460997222/">1460997222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460998230/">1460998230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1460999565/">1460999565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461000225/">1461000225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461001255/">1461001255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461002926/">1461002926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461003405/">1461003405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461003470/">1461003470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461005383/">1461005383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461006170/">1461006170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461008695/">1461008695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461009992/">1461009992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461010669/">1461010669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461012982/">1461012982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461013107/">1461013107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461013953/">1461013953/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461013954/">1461013954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461014487/">1461014487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461014540/">1461014540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461014603/">1461014603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461014782/">1461014782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461015260/">1461015260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461016894/">1461016894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461017723/">1461017723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461020425/">1461020425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461021687/">1461021687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461021872/">1461021872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461024381/">1461024381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461032840/">1461032840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461033202/">1461033202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461034346/">1461034346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461034835/">1461034835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461035606/">1461035606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461035660/">1461035660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461035962/">1461035962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461044998/">1461044998/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461044999/">1461044999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461045001/">1461045001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461046311/">1461046311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461051480/">1461051480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461051582/">1461051582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461052969/">1461052969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461057043/">1461057043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461058123/">1461058123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461058185/">1461058185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461058314/">1461058314/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461058603/">1461058603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461059203/">1461059203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461059265/">1461059265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461059336/">1461059336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461060109/">1461060109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461060523/">1461060523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461060524/">1461060524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461062212/">1461062212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461063408/">1461063408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461065148/">1461065148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461065273/">1461065273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461067548/">1461067548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461067852/">1461067852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461068508/">1461068508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461068868/">1461068868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461070370/">1461070370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461071742/">1461071742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461074151/">1461074151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461074392/">1461074392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461075395/">1461075395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461075822/">1461075822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461076001/">1461076001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461076060/">1461076060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461076115/">1461076115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461077198/">1461077198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461077434/">1461077434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461079510/">1461079510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461079511/">1461079511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461080313/">1461080313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461081171/">1461081171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461082358/">1461082358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461082474/">1461082474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461085123/">1461085123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461086860/">1461086860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461090161/">1461090161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461091177/">1461091177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461091897/">1461091897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461093572/">1461093572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461096823/">1461096823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461098023/">1461098023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461100120/">1461100120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461100661/">1461100661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461101617/">1461101617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461104795/">1461104795/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461106176/">1461106176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461106416/">1461106416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461107432/">1461107432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461107974/">1461107974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461108211/">1461108211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461108277/">1461108277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461108877/">1461108877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461110017/">1461110017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461111693/">1461111693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461111937/">1461111937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461114267/">1461114267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461114585/">1461114585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461118904/">1461118904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461124178/">1461124178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461124419/">1461124419/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461124594/">1461124594/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461128432/">1461128432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461129049/">1461129049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461133722/">1461133722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461135456/">1461135456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461136291/">1461136291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461136358/">1461136358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461138349/">1461138349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461139054/">1461139054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461142355/">1461142355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461142903/">1461142903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461143265/">1461143265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461143320/">1461143320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461144639/">1461144639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461145233/">1461145233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461145776/">1461145776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461145968/">1461145968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461146260/">1461146260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461147224/">1461147224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461148899/">1461148899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461151122/">1461151122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461153460/">1461153460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461154500/">1461154500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461156511/">1461156511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461157292/">1461157292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461159394/">1461159394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461159698/">1461159698/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461161313/">1461161313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461161317/">1461161317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461161918/">1461161918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461162522/">1461162522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461162858/">1461162858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461165703/">1461165703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461166965/">1461166965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461167621/">1461167621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461170504/">1461170504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461170623/">1461170623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461172249/">1461172249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461172672/">1461172672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461173562/">1461173562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461173924/">1461173924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461174341/">1461174341/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461174828/">1461174828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461176385/">1461176385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461176566/">1461176566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461177888/">1461177888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461178901/">1461178901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461179260/">1461179260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461180464/">1461180464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461181008/">1461181008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461184849/">1461184849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461185622/">1461185622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461186423/">1461186423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461187205/">1461187205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461188099/">1461188099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461189066/">1461189066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461189429/">1461189429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461189545/">1461189545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461190024/">1461190024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461192119/">1461192119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461192370/">1461192370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461196020/">1461196020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461196201/">1461196201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461196262/">1461196262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461200766/">1461200766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461203645/">1461203645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461206463/">1461206463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461206590/">1461206590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461211086/">1461211086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461212828/">1461212828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461213190/">1461213190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461214505/">1461214505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461217124/">1461217124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461217441/">1461217441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461217996/">1461217996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461219423/">1461219423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461224104/">1461224104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461226382/">1461226382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461226981/">1461226981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461230707/">1461230707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461231428/">1461231428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461233218/">1461233218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461233298/">1461233298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461234240/">1461234240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461238802/">1461238802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461238989/">1461238989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461239109/">1461239109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461240549/">1461240549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461245081/">1461245081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461246083/">1461246083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461247028/">1461247028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461247934/">1461247934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461248345/">1461248345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461249445/">1461249445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461250829/">1461250829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461251776/">1461251776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461252490/">1461252490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461252799/">1461252799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461252833/">1461252833/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461252973/">1461252973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461253380/">1461253380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461253572/">1461253572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461253828/">1461253828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461253889/">1461253889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461255193/">1461255193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461257291/">1461257291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461257926/">1461257926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461258000/">1461258000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461258402/">1461258402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461259986/">1461259986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461260100/">1461260100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461260590/">1461260590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461261243/">1461261243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461261617/">1461261617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461262394/">1461262394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461263948/">1461263948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461264241/">1461264241/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461265984/">1461265984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461266882/">1461266882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461267243/">1461267243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461268213/">1461268213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461269185/">1461269185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461272165/">1461272165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461272227/">1461272227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461272656/">1461272656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461273313/">1461273313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461276377/">1461276377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461276378/">1461276378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461277449/">1461277449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461277511/">1461277511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461278527/">1461278527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461280210/">1461280210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461280932/">1461280932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461281524/">1461281524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461283564/">1461283564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461285366/">1461285366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461285548/">1461285548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461285902/">1461285902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461289115/">1461289115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461289632/">1461289632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461290781/">1461290781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461292082/">1461292082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461296586/">1461296586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461297435/">1461297435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461297436/">1461297436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461297904/">1461297904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461300429/">1461300429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461301485/">1461301485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461302284/">1461302284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461304506/">1461304506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461304684/">1461304684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461308219/">1461308219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461309243/">1461309243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461309802/">1461309802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461310510/">1461310510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461310511/">1461310511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461315883/">1461315883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461316035/">1461316035/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461316931/">1461316931/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317804/">1461317804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317805/">1461317805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317806/">1461317806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317808/">1461317808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317809/">1461317809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317814/">1461317814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317815/">1461317815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317816/">1461317816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317817/">1461317817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317818/">1461317818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317821/">1461317821/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317825/">1461317825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317826/">1461317826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317840/">1461317840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317841/">1461317841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317842/">1461317842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317843/">1461317843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461317844/">1461317844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318088/">1461318088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318089/">1461318089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318090/">1461318090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318091/">1461318091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318094/">1461318094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318100/">1461318100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318101/">1461318101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318102/">1461318102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318103/">1461318103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318106/">1461318106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318107/">1461318107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318108/">1461318108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318109/">1461318109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318111/">1461318111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318125/">1461318125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318126/">1461318126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318127/">1461318127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318128/">1461318128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318129/">1461318129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318371/">1461318371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318372/">1461318372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318374/">1461318374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318382/">1461318382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318383/">1461318383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318386/">1461318386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318388/">1461318388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318389/">1461318389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318391/">1461318391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318407/">1461318407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318408/">1461318408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318409/">1461318409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318410/">1461318410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318415/">1461318415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318694/">1461318694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318695/">1461318695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318696/">1461318696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318699/">1461318699/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318706/">1461318706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318708/">1461318708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461318709/">1461318709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461322440/">1461322440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461325259/">1461325259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461327848/">1461327848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461328324/">1461328324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461329286/">1461329286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461330570/">1461330570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461332339/">1461332339/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461333192/">1461333192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461333244/">1461333244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461333543/">1461333543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461334163/">1461334163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461335226/">1461335226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461335887/">1461335887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461337631/">1461337631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461338464/">1461338464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461338644/">1461338644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461338707/">1461338707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461339305/">1461339305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343505/">1461343505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343680/">1461343680/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343748/">1461343748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343749/">1461343749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343862/">1461343862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343922/">1461343922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343923/">1461343923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461343981/">1461343981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461344112/">1461344112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461344284/">1461344284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461344343/">1461344343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461344807/">1461344807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461345547/">1461345547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461347022/">1461347022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461348454/">1461348454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461348692/">1461348692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461349849/">1461349849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461350069/">1461350069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461354830/">1461354830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461355308/">1461355308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461355452/">1461355452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461355579/">1461355579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461356082/">1461356082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461359807/">1461359807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461362452/">1461362452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461365453/">1461365453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461366895/">1461366895/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461369532/">1461369532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461370730/">1461370730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461385912/">1461385912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461390065/">1461390065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461401574/">1461401574/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461401802/">1461401802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461403788/">1461403788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461404082/">1461404082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461404153/">1461404153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461404154/">1461404154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461404155/">1461404155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461405414/">1461405414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461406906/">1461406906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407027/">1461407027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407028/">1461407028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407116/">1461407116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407151/">1461407151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407189/">1461407189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407190/">1461407190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407204/">1461407204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407205/">1461407205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407272/">1461407272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407273/">1461407273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407274/">1461407274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407324/">1461407324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407325/">1461407325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407395/">1461407395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407396/">1461407396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461407453/">1461407453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461408122/">1461408122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461409565/">1461409565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461409965/">1461409965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461414942/">1461414942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461431102/">1461431102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461435115/">1461435115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461442734/">1461442734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461442791/">1461442791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461443692/">1461443692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461445963/">1461445963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461446388/">1461446388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461459230/">1461459230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461459289/">1461459289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461460131/">1461460131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461467206/">1461467206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461467207/">1461467207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461472552/">1461472552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461485155/">1461485155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461507275/">1461507275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461510707/">1461510707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461522883/">1461522883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461524402/">1461524402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461538302/">1461538302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461547074/">1461547074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461548445/">1461548445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461552582/">1461552582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461555069/">1461555069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461556487/">1461556487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461563693/">1461563693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461569027/">1461569027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461569385/">1461569385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461570232/">1461570232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461571363/">1461571363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461576651/">1461576651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461577605/">1461577605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461578266/">1461578266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461580908/">1461580908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461584155/">1461584155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461586308/">1461586308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461588534/">1461588534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461589551/">1461589551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461589743/">1461589743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461589968/">1461589968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461592012/">1461592012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461592401/">1461592401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461594595/">1461594595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461595548/">1461595548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461596106/">1461596106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461596813/">1461596813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461598313/">1461598313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461598915/">1461598915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461599323/">1461599323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461600588/">1461600588/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461602656/">1461602656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461603671/">1461603671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461604277/">1461604277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461605542/">1461605542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461605785/">1461605785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461609070/">1461609070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461609550/">1461609550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461612425/">1461612425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461613395/">1461613395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461614660/">1461614660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461615497/">1461615497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461616516/">1461616516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461618245/">1461618245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461619867/">1461619867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461624550/">1461624550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461625214/">1461625214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461625806/">1461625806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461630185/">1461630185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461630300/">1461630300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461631856/">1461631856/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461632343/">1461632343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461634443/">1461634443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461635819/">1461635819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461635937/">1461635937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461636971/">1461636971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461638084/">1461638084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461638586/">1461638586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461640203/">1461640203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461644701/">1461644701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461645007/">1461645007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461647575/">1461647575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461652694/">1461652694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461653404/">1461653404/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461653576/">1461653576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461657479/">1461657479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461658088/">1461658088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461659045/">1461659045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461660364/">1461660364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461660720/">1461660720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461662105/">1461662105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461662407/">1461662407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461663046/">1461663046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461663246/">1461663246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461666120/">1461666120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461669181/">1461669181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461672258/">1461672258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461672377/">1461672377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461672535/">1461672535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461673211/">1461673211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461674276/">1461674276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461674705/">1461674705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461679612/">1461679612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461681267/">1461681267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461681778/">1461681778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461681913/">1461681913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461682196/">1461682196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461682809/">1461682809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461683103/">1461683103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461683224/">1461683224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461685503/">1461685503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461685615/">1461685615/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461685805/">1461685805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461686516/">1461686516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461688334/">1461688334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461689876/">1461689876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461690559/">1461690559/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461690903/">1461690903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461691628/">1461691628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461692286/">1461692286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461692637/">1461692637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461694573/">1461694573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461694618/">1461694618/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461695465/">1461695465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461695701/">1461695701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461696417/">1461696417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461696724/">1461696724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461699244/">1461699244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461700203/">1461700203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461701582/">1461701582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461702966/">1461702966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461703083/">1461703083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461703204/">1461703204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461703922/">1461703922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461704738/">1461704738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461704888/">1461704888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461705058/">1461705058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461705598/">1461705598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461706138/">1461706138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461706199/">1461706199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461708904/">1461708904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461713413/">1461713413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461723779/">1461723779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461724446/">1461724446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461727558/">1461727558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461728645/">1461728645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461729481/">1461729481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461730985/">1461730985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461736266/">1461736266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461736746/">1461736746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461739204/">1461739204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461740465/">1461740465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461743413/">1461743413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461745284/">1461745284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461746939/">1461746939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461750777/">1461750777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461751627/">1461751627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461753415/">1461753415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461753540/">1461753540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461757616/">1461757616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461758161/">1461758161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461761578/">1461761578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461763375/">1461763375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461763864/">1461763864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461766322/">1461766322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461767773/">1461767773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461767890/">1461767890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461768363/">1461768363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461769088/">1461769088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461769316/">1461769316/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461773131/">1461773131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461773266/">1461773266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461773399/">1461773399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461775020/">1461775020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461775330/">1461775330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461775688/">1461775688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461775885/">1461775885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461776239/">1461776239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461776398/">1461776398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461776827/">1461776827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461777601/">1461777601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461778087/">1461778087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461780591/">1461780591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461781786/">1461781786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461783120/">1461783120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461785510/">1461785510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461785569/">1461785569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461788096/">1461788096/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461790619/">1461790619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461791808/">1461791808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461791930/">1461791930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461792231/">1461792231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461792647/">1461792647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461797512/">1461797512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461799376/">1461799376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461800688/">1461800688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461802018/">1461802018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461811253/">1461811253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461811427/">1461811427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461811793/">1461811793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461813958/">1461813958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461820007/">1461820007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461820132/">1461820132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461822534/">1461822534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461823134/">1461823134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461825534/">1461825534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461826015/">1461826015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461827465/">1461827465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461828048/">1461828048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461831420/">1461831420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461831655/">1461831655/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461832131/">1461832131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461832248/">1461832248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461832442/">1461832442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461832498/">1461832498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461833932/">1461833932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461834266/">1461834266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461834955/">1461834955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461835370/">1461835370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461836290/">1461836290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461838136/">1461838136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461838192/">1461838192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461840892/">1461840892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461841257/">1461841257/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461843595/">1461843595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461844653/">1461844653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461844795/">1461844795/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461845183/">1461845183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461849835/">1461849835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461850246/">1461850246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461852168/">1461852168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461854434/">1461854434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461854435/">1461854435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461854568/">1461854568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461854994/">1461854994/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461857627/">1461857627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461859319/">1461859319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461859933/">1461859933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461861721/">1461861721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461862259/">1461862259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461862918/">1461862918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461863647/">1461863647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461864181/">1461864181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461865802/">1461865802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461871310/">1461871310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461872507/">1461872507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461872697/">1461872697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461874062/">1461874062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461874246/">1461874246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461874789/">1461874789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461875146/">1461875146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461875205/">1461875205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461876287/">1461876287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461877566/">1461877566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461877791/">1461877791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461879881/">1461879881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461880246/">1461880246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461881747/">1461881747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461882293/">1461882293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461883613/">1461883613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461886130/">1461886130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461886133/">1461886133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461886138/">1461886138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461886191/">1461886191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461886192/">1461886192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461886194/">1461886194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461886543/">1461886543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461887564/">1461887564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461891839/">1461891839/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461894601/">1461894601/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461898603/">1461898603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461899301/">1461899301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461899639/">1461899639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461899930/">1461899930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461903705/">1461903705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461906112/">1461906112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461909281/">1461909281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461910303/">1461910303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461910544/">1461910544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461911151/">1461911151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461916730/">1461916730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461917690/">1461917690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461921949/">1461921949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461923321/">1461923321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461927530/">1461927530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461929990/">1461929990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461931246/">1461931246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461931553/">1461931553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461931670/">1461931670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461932813/">1461932813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461934304/">1461934304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461935204/">1461935204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461935501/">1461935501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461935863/">1461935863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461939897/">1461939897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461942282/">1461942282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461945412/">1461945412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461945521/">1461945521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461945970/">1461945970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461945971/">1461945971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461949354/">1461949354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461949779/">1461949779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461950015/">1461950015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461950077/">1461950077/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461950628/">1461950628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461951575/">1461951575/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461952661/">1461952661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461953620/">1461953620/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461955360/">1461955360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461958961/">1461958961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461959431/">1461959431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461962016/">1461962016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461962373/">1461962373/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461964230/">1461964230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461964542/">1461964542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461966510/">1461966510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461967293/">1461967293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461967471/">1461967471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461967780/">1461967780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461967891/">1461967891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461968796/">1461968796/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461969400/">1461969400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461970411/">1461970411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461970907/">1461970907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461972518/">1461972518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461974433/">1461974433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461974665/">1461974665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461975814/">1461975814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461978357/">1461978357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461978522/">1461978522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461982951/">1461982951/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461985666/">1461985666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461986136/">1461986136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1461991838/">1461991838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462002939/">1462002939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462005815/">1462005815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462009649/">1462009649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462019141/">1462019141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462029100/">1462029100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462055982/">1462055982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462085013/">1462085013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462104932/">1462104932/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108238/">1462108238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108239/">1462108239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108290/">1462108290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108291/">1462108291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108292/">1462108292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108293/">1462108293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108294/">1462108294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108356/">1462108356/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108357/">1462108357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108358/">1462108358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108359/">1462108359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108360/">1462108360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108409/">1462108409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108410/">1462108410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108411/">1462108411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108412/">1462108412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108413/">1462108413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108476/">1462108476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108477/">1462108477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108478/">1462108478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108479/">1462108479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108480/">1462108480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108530/">1462108530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108531/">1462108531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108532/">1462108532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108533/">1462108533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108534/">1462108534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108596/">1462108596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108597/">1462108597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108598/">1462108598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108599/">1462108599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108600/">1462108600/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108661/">1462108661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108662/">1462108662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108663/">1462108663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108664/">1462108664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108665/">1462108665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108715/">1462108715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108716/">1462108716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108717/">1462108717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462108718/">1462108718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462112937/">1462112937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462113150/">1462113150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462114721/">1462114721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462114722/">1462114722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462114723/">1462114723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462114724/">1462114724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462114790/">1462114790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462114825/">1462114825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462114826/">1462114826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462124016/">1462124016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462127381/">1462127381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462143449/">1462143449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462143940/">1462143940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462149031/">1462149031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462151913/">1462151913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462153293/">1462153293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462154189/">1462154189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462158509/">1462158509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462160554/">1462160554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462160677/">1462160677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462161454/">1462161454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462164518/">1462164518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462169199/">1462169199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462171126/">1462171126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462172865/">1462172865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462173157/">1462173157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462173391/">1462173391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462177537/">1462177537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462182038/">1462182038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462191030/">1462191030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462198036/">1462198036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462198596/">1462198596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462199678/">1462199678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462203632/">1462203632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462203751/">1462203751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462205439/">1462205439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462205448/">1462205448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462205612/">1462205612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462205960/">1462205960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462208193/">1462208193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462210110/">1462210110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462210495/">1462210495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462211024/">1462211024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462211800/">1462211800/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462211979/">1462211979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462213454/">1462213454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462213722/">1462213722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462214442/">1462214442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462215291/">1462215291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462216254/">1462216254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462216255/">1462216255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462216309/">1462216309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462217206/">1462217206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462218462/">1462218462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462219032/">1462219032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462220613/">1462220613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462221032/">1462221032/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462222782/">1462222782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462224292/">1462224292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462225311/">1462225311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462227342/">1462227342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462228663/">1462228663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462230799/">1462230799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462232904/">1462232904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462234960/">1462234960/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462235261/">1462235261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462235379/">1462235379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462239040/">1462239040/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462242967/">1462242967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462244026/">1462244026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462246727/">1462246727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462247320/">1462247320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462249621/">1462249621/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462249726/">1462249726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462249776/">1462249776/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462250085/">1462250085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462253018/">1462253018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462253516/">1462253516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462257100/">1462257100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462258360/">1462258360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462259269/">1462259269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462259503/">1462259503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462262434/">1462262434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462263641/">1462263641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462263642/">1462263642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462263762/">1462263762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462264721/">1462264721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462264907/">1462264907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462265322/">1462265322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462265564/">1462265564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462266263/">1462266263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462266822/">1462266822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462267957/">1462267957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462268135/">1462268135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462269231/">1462269231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462269957/">1462269957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462270065/">1462270065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462270475/">1462270475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462270703/">1462270703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462271105/">1462271105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462271375/">1462271375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462273002/">1462273002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462274138/">1462274138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462275038/">1462275038/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462275056/">1462275056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462278101/">1462278101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462279069/">1462279069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462280667/">1462280667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462281278/">1462281278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462281418/">1462281418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462281462/">1462281462/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462283330/">1462283330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462284411/">1462284411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462285185/">1462285185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462286075/">1462286075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462286533/">1462286533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462287528/">1462287528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462288656/">1462288656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462291067/">1462291067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462291717/">1462291717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462293053/">1462293053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462293997/">1462293997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462294128/">1462294128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462295747/">1462295747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462296281/">1462296281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462296409/">1462296409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462296538/">1462296538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462296723/">1462296723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462301506/">1462301506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462301868/">1462301868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462303490/">1462303490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462305403/">1462305403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462305950/">1462305950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462306908/">1462306908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462306909/">1462306909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462307318/">1462307318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462307454/">1462307454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462307919/">1462307919/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462307988/">1462307988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462309460/">1462309460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462310395/">1462310395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462310481/">1462310481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462311095/">1462311095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462311340/">1462311340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462313858/">1462313858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462315122/">1462315122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462316261/">1462316261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462316646/">1462316646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462318225/">1462318225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462321716/">1462321716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462321898/">1462321898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462322319/">1462322319/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462322497/">1462322497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462323525/">1462323525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462323701/">1462323701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462328775/">1462328775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462329386/">1462329386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462337388/">1462337388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462338706/">1462338706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462338824/">1462338824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462338877/">1462338877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462339357/">1462339357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462339916/">1462339916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462340323/">1462340323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462340506/">1462340506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462341964/">1462341964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462342907/">1462342907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462346021/">1462346021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462347224/">1462347224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462347522/">1462347522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462347996/">1462347996/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462348658/">1462348658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462349801/">1462349801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462350587/">1462350587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462350624/">1462350624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462351485/">1462351485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462353048/">1462353048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462354957/">1462354957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462355985/">1462355985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462358921/">1462358921/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462361972/">1462361972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462361973/">1462361973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462362952/">1462362952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462366969/">1462366969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462368048/">1462368048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462368247/">1462368247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462371056/">1462371056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462373983/">1462373983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462376086/">1462376086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462376738/">1462376738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462377095/">1462377095/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462377521/">1462377521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462377842/">1462377842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462378128/">1462378128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462378479/">1462378479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462379617/">1462379617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462380396/">1462380396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462381185/">1462381185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462381300/">1462381300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462383079/">1462383079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462383168/">1462383168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462384009/">1462384009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462384303/">1462384303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462384662/">1462384662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462384865/">1462384865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462385077/">1462385077/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462386220/">1462386220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462387296/">1462387296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462393885/">1462393885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462394031/">1462394031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462396418/">1462396418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462396485/">1462396485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462397082/">1462397082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462398157/">1462398157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462398583/">1462398583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462398584/">1462398584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462399847/">1462399847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462401336/">1462401336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462402545/">1462402545/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462404630/">1462404630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462406198/">1462406198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462407458/">1462407458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462408965/">1462408965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462410401/">1462410401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462415700/">1462415700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462415813/">1462415813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462416284/">1462416284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462417692/">1462417692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462417913/">1462417913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462418920/">1462418920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462419822/">1462419822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462420055/">1462420055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462424561/">1462424561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462425695/">1462425695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462425822/">1462425822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462426066/">1462426066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462429914/">1462429914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434852/">1462434852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434853/">1462434853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434854/">1462434854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434859/">1462434859/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434860/">1462434860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434861/">1462434861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434862/">1462434862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434866/">1462434866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434869/">1462434869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434871/">1462434871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434872/">1462434872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434875/">1462434875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434877/">1462434877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434878/">1462434878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434881/">1462434881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434883/">1462434883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434884/">1462434884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434885/">1462434885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434889/">1462434889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434890/">1462434890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434891/">1462434891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434892/">1462434892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434893/">1462434893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434896/">1462434896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434897/">1462434897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434899/">1462434899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462434900/">1462434900/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462439866/">1462439866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462440891/">1462440891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462442324/">1462442324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462442573/">1462442573/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462443586/">1462443586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462445208/">1462445208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462447814/">1462447814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462448393/">1462448393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462449758/">1462449758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462449982/">1462449982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462457395/">1462457395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462457396/">1462457396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462459183/">1462459183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462459299/">1462459299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462459366/">1462459366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462459659/">1462459659/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462460440/">1462460440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462460912/">1462460912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462463312/">1462463312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462465906/">1462465906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462465969/">1462465969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462466394/">1462466394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462466799/">1462466799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462466868/">1462466868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462466987/">1462466987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462466988/">1462466988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462468915/">1462468915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462469390/">1462469390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462469690/">1462469690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462470706/">1462470706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462471002/">1462471002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462472989/">1462472989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462473342/">1462473342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462473822/">1462473822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462477610/">1462477610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462478500/">1462478500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462478862/">1462478862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462479039/">1462479039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462479279/">1462479279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462480546/">1462480546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462480667/">1462480667/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462481686/">1462481686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462483260/">1462483260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462486362/">1462486362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462487862/">1462487862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462488887/">1462488887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462490800/">1462490800/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462493453/">1462493453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462499902/">1462499902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462501042/">1462501042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462501583/">1462501583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462501758/">1462501758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462502712/">1462502712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462503995/">1462503995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462504167/">1462504167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462505125/">1462505125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462506442/">1462506442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462509329/">1462509329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462510646/">1462510646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462518623/">1462518623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462519395/">1462519395/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462519641/">1462519641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462519939/">1462519939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462520443/">1462520443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462526263/">1462526263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462529078/">1462529078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462530504/">1462530504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462536343/">1462536343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462537992/">1462537992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462537993/">1462537993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462539872/">1462539872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462540460/">1462540460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462542741/">1462542741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462545674/">1462545674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462546277/">1462546277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462547484/">1462547484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462549216/">1462549216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462551155/">1462551155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462553722/">1462553722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462555231/">1462555231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462555913/">1462555913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462556262/">1462556262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462557284/">1462557284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462557883/">1462557883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462558611/">1462558611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462560162/">1462560162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462561061/">1462561061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462561980/">1462561980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462562332/">1462562332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462563240/">1462563240/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462563298/">1462563298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462563538/">1462563538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462564190/">1462564190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462564260/">1462564260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462564581/">1462564581/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462564613/">1462564613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462565079/">1462565079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462565192/">1462565192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462565390/">1462565390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462565460/">1462565460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462567490/">1462567490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462567623/">1462567623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462568280/">1462568280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462569718/">1462569718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462570674/">1462570674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462572775/">1462572775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462575120/">1462575120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462575543/">1462575543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462576310/">1462576310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462576666/">1462576666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462578890/">1462578890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462580390/">1462580390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462583057/">1462583057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462584772/">1462584772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462586793/">1462586793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462587102/">1462587102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462596947/">1462596947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462600247/">1462600247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462602285/">1462602285/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462608408/">1462608408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462612660/">1462612660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462623710/">1462623710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462638114/">1462638114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462654554/">1462654554/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462673624/">1462673624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462693497/">1462693497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462694267/">1462694267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462696488/">1462696488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462702732/">1462702732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462708008/">1462708008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462714017/">1462714017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462718515/">1462718515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462732315/">1462732315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462757440/">1462757440/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462759129/">1462759129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462761284/">1462761284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462762846/">1462762846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462765007/">1462765007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462771059/">1462771059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462771604/">1462771604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462772389/">1462772389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462775086/">1462775086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462784082/">1462784082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462784449/">1462784449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462785051/">1462785051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462785101/">1462785101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462785229/">1462785229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462785767/">1462785767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462786781/">1462786781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462787086/">1462787086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462787332/">1462787332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462787509/">1462787509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462787685/">1462787685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462788769/">1462788769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462789853/">1462789853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462791296/">1462791296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462791946/">1462791946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462792907/">1462792907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462794048/">1462794048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462794590/">1462794590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462797583/">1462797583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462798726/">1462798726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462801846/">1462801846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462801972/">1462801972/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462802958/">1462802958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462804360/">1462804360/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462804501/">1462804501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462805183/">1462805183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462806289/">1462806289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462807000/">1462807000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462807428/">1462807428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462808031/">1462808031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462811748/">1462811748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462811981/">1462811981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462812942/">1462812942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462813188/">1462813188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462814021/">1462814021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462814143/">1462814143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462814391/">1462814391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462815460/">1462815460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462816027/">1462816027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462816862/">1462816862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462818365/">1462818365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462818477/">1462818477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462819616/">1462819616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462820282/">1462820282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462820394/">1462820394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462823943/">1462823943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462824753/">1462824753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462824793/">1462824793/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462825148/">1462825148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462825451/">1462825451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462826701/">1462826701/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462827247/">1462827247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462828198/">1462828198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462833365/">1462833365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462837257/">1462837257/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462837738/">1462837738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462840017/">1462840017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462840927/">1462840927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462841405/">1462841405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462842367/">1462842367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462843505/">1462843505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462843622/">1462843622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462844708/">1462844708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462845954/">1462845954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462847226/">1462847226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462850279/">1462850279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462851537/">1462851537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462852820/">1462852820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462853277/">1462853277/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462853418/">1462853418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462854136/">1462854136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462863136/">1462863136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462868944/">1462868944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462869845/">1462869845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462869909/">1462869909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462869970/">1462869970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462870084/">1462870084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462870337/">1462870337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462870444/">1462870444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462871066/">1462871066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462872258/">1462872258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462872552/">1462872552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462873267/">1462873267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462874045/">1462874045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462874884/">1462874884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462874954/">1462874954/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462875365/">1462875365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462875553/">1462875553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462875983/">1462875983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462876930/">1462876930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462877108/">1462877108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462877169/">1462877169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462877587/">1462877587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462877777/">1462877777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462877957/">1462877957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462882089/">1462882089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462883587/">1462883587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462884068/">1462884068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462885595/">1462885595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462889256/">1462889256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462891393/">1462891393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462891572/">1462891572/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462918985/">1462918985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462920064/">1462920064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462920376/">1462920376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462920784/">1462920784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462920908/">1462920908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462921031/">1462921031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462922233/">1462922233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462922357/">1462922357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462923854/">1462923854/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462924819/">1462924819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462927151/">1462927151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462928171/">1462928171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462928299/">1462928299/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462934230/">1462934230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462938194/">1462938194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462939145/">1462939145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462939274/">1462939274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462939884/">1462939884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462939990/">1462939990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462940113/">1462940113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462942956/">1462942956/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462944367/">1462944367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462945987/">1462945987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462949898/">1462949898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462950077/">1462950077/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462952113/">1462952113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462953896/">1462953896/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462953899/">1462953899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462954266/">1462954266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462956190/">1462956190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462956379/">1462956379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462956552/">1462956552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462957792/">1462957792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462958768/">1462958768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462958834/">1462958834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462960027/">1462960027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462960334/">1462960334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462961778/">1462961778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462961885/">1462961885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963420/">1462963420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963421/">1462963421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963423/">1462963423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963424/">1462963424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963425/">1462963425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963426/">1462963426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963427/">1462963427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963429/">1462963429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963430/">1462963430/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963431/">1462963431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963432/">1462963432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963435/">1462963435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963436/">1462963436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963437/">1462963437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963439/">1462963439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963441/">1462963441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963443/">1462963443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963445/">1462963445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963446/">1462963446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963449/">1462963449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963450/">1462963450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963451/">1462963451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963452/">1462963452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963453/">1462963453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963455/">1462963455/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963456/">1462963456/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963457/">1462963457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963458/">1462963458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963459/">1462963459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462963461/">1462963461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462964297/">1462964297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462964353/">1462964353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462965005/">1462965005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462965006/">1462965006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462965376/">1462965376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462967174/">1462967174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462968148/">1462968148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462968730/">1462968730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462971491/">1462971491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462972988/">1462972988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462973297/">1462973297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462974551/">1462974551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462974731/">1462974731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462975583/">1462975583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462975929/">1462975929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462976178/">1462976178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462976534/">1462976534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462977784/">1462977784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462977966/">1462977966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462978029/">1462978029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462980907/">1462980907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462981147/">1462981147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462981337/">1462981337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462981421/">1462981421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462982111/">1462982111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462983308/">1462983308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462984474/">1462984474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462984477/">1462984477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462987843/">1462987843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462987985/">1462987985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462988110/">1462988110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462988111/">1462988111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462988647/">1462988647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462989250/">1462989250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462990735/">1462990735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462991351/">1462991351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462992016/">1462992016/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462992425/">1462992425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462993331/">1462993331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462993692/">1462993692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462993929/">1462993929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462994052/">1462994052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1462999270/">1462999270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463000766/">1463000766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463002144/">1463002144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463002874/">1463002874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463003407/">1463003407/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463005925/">1463005925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463006595/">1463006595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463010378/">1463010378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463010974/">1463010974/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463011873/">1463011873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463012175/">1463012175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463013006/">1463013006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463018046/">1463018046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463019134/">1463019134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463020994/">1463020994/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463021706/">1463021706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463025244/">1463025244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463025496/">1463025496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463025676/">1463025676/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463026153/">1463026153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463027047/">1463027047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463027657/">1463027657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463028844/">1463028844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463030831/">1463030831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463030850/">1463030850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463031507/">1463031507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463034078/">1463034078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463035336/">1463035336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463036516/">1463036516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463036790/">1463036790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463039767/">1463039767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463041338/">1463041338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463041934/">1463041934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463041992/">1463041992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463042467/">1463042467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463043258/">1463043258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463043754/">1463043754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463044036/">1463044036/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463044988/">1463044988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463045529/">1463045529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463045778/">1463045778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463045886/">1463045886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463047093/">1463047093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463048390/">1463048390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463052052/">1463052052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463052479/">1463052479/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463052586/">1463052586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463052705/">1463052705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463052767/">1463052767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463053308/">1463053308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463055645/">1463055645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463056433/">1463056433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463056603/">1463056603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463056660/">1463056660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463057920/">1463057920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463058770/">1463058770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463059421/">1463059421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463059547/">1463059547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463059846/">1463059846/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463059907/">1463059907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463060091/">1463060091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463060809/">1463060809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463063073/">1463063073/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463063074/">1463063074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463063694/">1463063694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463063695/">1463063695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463063872/">1463063872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463064048/">1463064048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463064461/">1463064461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463064463/">1463064463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463064946/">1463064946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463065541/">1463065541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463068124/">1463068124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463068731/">1463068731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463073465/">1463073465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463074359/">1463074359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463074957/">1463074957/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463075019/">1463075019/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463075080/">1463075080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463075260/">1463075260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463075321/">1463075321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463075748/">1463075748/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463079098/">1463079098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463079405/">1463079405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463080245/">1463080245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463081735/">1463081735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463086122/">1463086122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463086247/">1463086247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463088396/">1463088396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463088580/">1463088580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463089719/">1463089719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463091345/">1463091345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463091400/">1463091400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463092894/">1463092894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463093505/">1463093505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463093684/">1463093684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463094874/">1463094874/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463095064/">1463095064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463096498/">1463096498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463097514/">1463097514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463098069/">1463098069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463099873/">1463099873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463100101/">1463100101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463103099/">1463103099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463103516/">1463103516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463107421/">1463107421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463108437/">1463108437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463109159/">1463109159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463110656/">1463110656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463111745/">1463111745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463115704/">1463115704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463118466/">1463118466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463118715/">1463118715/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463122598/">1463122598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463125182/">1463125182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463125305/">1463125305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463129558/">1463129558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463132203/">1463132203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463132327/">1463132327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463132495/">1463132495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463134001/">1463134001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463134002/">1463134002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463134070/">1463134070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463137061/">1463137061/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463138024/">1463138024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463139636/">1463139636/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463140431/">1463140431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463142399/">1463142399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463143180/">1463143180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463143968/">1463143968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463146535/">1463146535/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463148643/">1463148643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463149002/">1463149002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463150566/">1463150566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463152006/">1463152006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463152901/">1463152901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463154466/">1463154466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463155302/">1463155302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463157274/">1463157274/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463157997/">1463157997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463158421/">1463158421/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463161359/">1463161359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463162860/">1463162860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463164599/">1463164599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463164728/">1463164728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463165607/">1463165607/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463166412/">1463166412/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463167538/">1463167538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463168139/">1463168139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463169661/">1463169661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463171304/">1463171304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463171431/">1463171431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463172463/">1463172463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463172797/">1463172797/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463174449/">1463174449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463175414/">1463175414/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463175580/">1463175580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463176423/">1463176423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463176660/">1463176660/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463177436/">1463177436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463178049/">1463178049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463179897/">1463179897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463180756/">1463180756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463180979/">1463180979/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463183197/">1463183197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463183810/">1463183810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463184376/">1463184376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463184647/">1463184647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463185596/">1463185596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463188323/">1463188323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463190577/">1463190577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463193161/">1463193161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463209308/">1463209308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463214948/">1463214948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463219566/">1463219566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463227549/">1463227549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463256465/">1463256465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463256875/">1463256875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463258495/">1463258495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463275474/">1463275474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463321066/">1463321066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463333549/">1463333549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463337033/">1463337033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463361331/">1463361331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463361933/">1463361933/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463362353/">1463362353/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463363126/">1463363126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463365047/">1463365047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463365475/">1463365475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463367447/">1463367447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463370152/">1463370152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463371239/">1463371239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463372798/">1463372798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463379517/">1463379517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463380950/">1463380950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463381485/">1463381485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463382330/">1463382330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463384315/">1463384315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463391691/">1463391691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463393313/">1463393313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463399795/">1463399795/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463402198/">1463402198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463405738/">1463405738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463406147/">1463406147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463406330/">1463406330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463408251/">1463408251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463408437/">1463408437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463409072/">1463409072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463409571/">1463409571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463409625/">1463409625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463412986/">1463412986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463414930/">1463414930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463415464/">1463415464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463416784/">1463416784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463418227/">1463418227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463419191/">1463419191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463419547/">1463419547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463419612/">1463419612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463419786/">1463419786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463421345/">1463421345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463421527/">1463421527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463422188/">1463422188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463423802/">1463423802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463423923/">1463423923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463424055/">1463424055/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463424763/">1463424763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463425065/">1463425065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463426861/">1463426861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463430165/">1463430165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463430587/">1463430587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463430604/">1463430604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463433836/">1463433836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463434115/">1463434115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463434175/">1463434175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463434539/">1463434539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463436642/">1463436642/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463436817/">1463436817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463436886/">1463436886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463437124/">1463437124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463437844/">1463437844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463439879/">1463439879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463441403/">1463441403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463441687/">1463441687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463442104/">1463442104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463442819/">1463442819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463445294/">1463445294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463450017/">1463450017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463450318/">1463450318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463451633/">1463451633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463451634/">1463451634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463453207/">1463453207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463453920/">1463453920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463454397/">1463454397/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463463039/">1463463039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463463050/">1463463050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463463460/">1463463460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463467115/">1463467115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463469214/">1463469214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463470838/">1463470838/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463470891/">1463470891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463471014/">1463471014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463471432/">1463471432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463471488/">1463471488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463472276/">1463472276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463472873/">1463472873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463473808/">1463473808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463474081/">1463474081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463475637/">1463475637/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463476529/">1463476529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463477197/">1463477197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463479774/">1463479774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463480069/">1463480069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463485355/">1463485355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463485836/">1463485836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463487697/">1463487697/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463488169/">1463488169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463488651/">1463488651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463488777/">1463488777/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463489672/">1463489672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463490273/">1463490273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463490696/">1463490696/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463490999/">1463490999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463491291/">1463491291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463491773/">1463491773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463495467/">1463495467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463496818/">1463496818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463496992/">1463496992/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463497653/">1463497653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463498023/">1463498023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463499041/">1463499041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463499152/">1463499152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463501191/">1463501191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463501488/">1463501488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463501611/">1463501611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463502162/">1463502162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463502939/">1463502939/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463503720/">1463503720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463505403/">1463505403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463506249/">1463506249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463508475/">1463508475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463508648/">1463508648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463508762/">1463508762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463509716/">1463509716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463510147/">1463510147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463510276/">1463510276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463511111/">1463511111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463511406/">1463511406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463511520/">1463511520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463514105/">1463514105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463514817/">1463514817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515007/">1463515007/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515381/">1463515381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515382/">1463515382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515383/">1463515383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515384/">1463515384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515386/">1463515386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515388/">1463515388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515389/">1463515389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515390/">1463515390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515393/">1463515393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515413/">1463515413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515415/">1463515415/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515416/">1463515416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515418/">1463515418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515438/">1463515438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515439/">1463515439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515441/">1463515441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515442/">1463515442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515602/">1463515602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515717/">1463515717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515861/">1463515861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515862/">1463515862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515863/">1463515863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515866/">1463515866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515867/">1463515867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515868/">1463515868/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515869/">1463515869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515872/">1463515872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515875/">1463515875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515876/">1463515876/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515877/">1463515877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515879/">1463515879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515886/">1463515886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515888/">1463515888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515889/">1463515889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463515892/">1463515892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516205/">1463516205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516206/">1463516206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516208/">1463516208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516210/">1463516210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516211/">1463516211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516212/">1463516212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516216/">1463516216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516217/">1463516217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516218/">1463516218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516220/">1463516220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516223/">1463516223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516228/">1463516228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516230/">1463516230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516231/">1463516231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516233/">1463516233/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463516234/">1463516234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463518059/">1463518059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463519923/">1463519923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463520401/">1463520401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463521247/">1463521247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463522079/">1463522079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463522200/">1463522200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463523337/">1463523337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463525085/">1463525085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463525324/">1463525324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463525685/">1463525685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463525862/">1463525862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463526045/">1463526045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463526654/">1463526654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463526952/">1463526952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463527364/">1463527364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463527830/">1463527830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463527902/">1463527902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463528691/">1463528691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463534745/">1463534745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463534943/">1463534943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463534947/">1463534947/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463535110/">1463535110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463535324/">1463535324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463535460/">1463535460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463535648/">1463535648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463536661/">1463536661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463538767/">1463538767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463539844/">1463539844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463540713/">1463540713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463540714/">1463540714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463540912/">1463540912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463541054/">1463541054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463541349/">1463541349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463542127/">1463542127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463542541/">1463542541/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463543750/">1463543750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463544369/">1463544369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463545408/">1463545408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463547710/">1463547710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463547934/">1463547934/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463549329/">1463549329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463549521/">1463549521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463549681/">1463549681/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463553099/">1463553099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463553700/">1463553700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463554368/">1463554368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463554486/">1463554486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463555037/">1463555037/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463555328/">1463555328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463557480/">1463557480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463559643/">1463559643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463559948/">1463559948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463560219/">1463560219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463560787/">1463560787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463561029/">1463561029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463562877/">1463562877/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463563064/">1463563064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463563302/">1463563302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463563427/">1463563427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463563778/">1463563778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463564567/">1463564567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463566852/">1463566852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463567323/">1463567323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463568590/">1463568590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463569011/">1463569011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463570383/">1463570383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463571020/">1463571020/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463571999/">1463571999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463572186/">1463572186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463572248/">1463572248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463573629/">1463573629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463581604/">1463581604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588085/">1463588085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588389/">1463588389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588447/">1463588447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588448/">1463588448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588505/">1463588505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588561/">1463588561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588627/">1463588627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588682/">1463588682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463588862/">1463588862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594155/">1463594155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594327/">1463594327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594328/">1463594328/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594386/">1463594386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594754/">1463594754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594755/">1463594755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594756/">1463594756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594864/">1463594864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594924/">1463594924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463594925/">1463594925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463595047/">1463595047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463595937/">1463595937/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463601401/">1463601401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463601522/">1463601522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463601945/">1463601945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463602137/">1463602137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463603689/">1463603689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463603815/">1463603815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463606270/">1463606270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463607464/">1463607464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463607525/">1463607525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463607771/">1463607771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463608062/">1463608062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463609801/">1463609801/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463610340/">1463610340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463614058/">1463614058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463615563/">1463615563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463616578/">1463616578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463617725/">1463617725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463618746/">1463618746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463619578/">1463619578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463620484/">1463620484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463622106/">1463622106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463622765/">1463622765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463624438/">1463624438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463624439/">1463624439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463629182/">1463629182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463630557/">1463630557/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463631220/">1463631220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463632189/">1463632189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463632371/">1463632371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463632728/">1463632728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463632908/">1463632908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463633204/">1463633204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463634226/">1463634226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463634343/">1463634343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463638311/">1463638311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463641003/">1463641003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463642200/">1463642200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463642318/">1463642318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463642562/">1463642562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463642981/">1463642981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463643945/">1463643945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463644308/">1463644308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463645145/">1463645145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463647120/">1463647120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463649290/">1463649290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463649700/">1463649700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463650362/">1463650362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463650486/">1463650486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463650604/">1463650604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463650718/">1463650718/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463650910/">1463650910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463651378/">1463651378/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463654560/">1463654560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463655047/">1463655047/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463655287/">1463655287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463657264/">1463657264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463658522/">1463658522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463660806/">1463660806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463661888/">1463661888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463662429/">1463662429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463663146/">1463663146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463663439/">1463663439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463663507/">1463663507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463664284/">1463664284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463664650/">1463664650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463665129/">1463665129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463665303/">1463665303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463666017/">1463666017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463666508/">1463666508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463667049/">1463667049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463667101/">1463667101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463668369/">1463668369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463668778/">1463668778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463670469/">1463670469/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463676645/">1463676645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463678331/">1463678331/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463679467/">1463679467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463679886/">1463679886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463679949/">1463679949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463679999/">1463679999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463680118/">1463680118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463680119/">1463680119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463680238/">1463680238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463680658/">1463680658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463680791/">1463680791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463681139/">1463681139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463682762/">1463682762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463682995/">1463682995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463683072/">1463683072/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463683493/">1463683493/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463683955/">1463683955/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463684199/">1463684199/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463684562/">1463684562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463685464/">1463685464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463689716/">1463689716/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463708651/">1463708651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463723525/">1463723525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737733/">1463737733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737734/">1463737734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737735/">1463737735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737736/">1463737736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737739/">1463737739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737740/">1463737740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737741/">1463737741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737742/">1463737742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737743/">1463737743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737747/">1463737747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737750/">1463737750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737753/">1463737753/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737754/">1463737754/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737755/">1463737755/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737758/">1463737758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737761/">1463737761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737762/">1463737762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737763/">1463737763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737765/">1463737765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737769/">1463737769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737770/">1463737770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737771/">1463737771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737772/">1463737772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737778/">1463737778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737779/">1463737779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737780/">1463737780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463737781/">1463737781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463739706/">1463739706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463739911/">1463739911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463740271/">1463740271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463740464/">1463740464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463740641/">1463740641/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463740853/">1463740853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463780312/">1463780312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463781626/">1463781626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463781751/">1463781751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463782056/">1463782056/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463785591/">1463785591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463788599/">1463788599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463788961/">1463788961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463788962/">1463788962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789008/">1463789008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789077/">1463789077/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789134/">1463789134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789192/">1463789192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789193/">1463789193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789318/">1463789318/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789374/">1463789374/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789375/">1463789375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789499/">1463789499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789500/">1463789500/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789555/">1463789555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789611/">1463789611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463789670/">1463789670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463792435/">1463792435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463793215/">1463793215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463797774/">1463797774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463797872/">1463797872/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463797949/">1463797949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463798013/">1463798013/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463798193/">1463798193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463798372/">1463798372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463798428/">1463798428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463799509/">1463799509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463799692/">1463799692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463800471/">1463800471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463800890/">1463800890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463802458/">1463802458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463802571/">1463802571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463803658/">1463803658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463804553/">1463804553/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463804738/">1463804738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463805583/">1463805583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463805875/">1463805875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463806480/">1463806480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463812721/">1463812721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463819616/">1463819616/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463819914/">1463819914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463822317/">1463822317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463830355/">1463830355/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463838819/">1463838819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463843135/">1463843135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463843195/">1463843195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463843853/">1463843853/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463844029/">1463844029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463845475/">1463845475/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463847037/">1463847037/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463847160/">1463847160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463850273/">1463850273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463850334/">1463850334/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463850450/">1463850450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463851001/">1463851001/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463852256/">1463852256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463852488/">1463852488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463853991/">1463853991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463854293/">1463854293/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463856942/">1463856942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463862511/">1463862511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463868090/">1463868090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463868508/">1463868508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463873677/">1463873677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463906560/">1463906560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463910039/">1463910039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463916212/">1463916212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463920239/">1463920239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463921252/">1463921252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463927313/">1463927313/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463928157/">1463928157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463947891/">1463947891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463949093/">1463949093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463954196/">1463954196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463957738/">1463957738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463958156/">1463958156/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463958209/">1463958209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463958330/">1463958330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463959528/">1463959528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463961880/">1463961880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463963918/">1463963918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463965119/">1463965119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463969675/">1463969675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463970576/">1463970576/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463970700/">1463970700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463974289/">1463974289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463975376/">1463975376/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463977413/">1463977413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463979628/">1463979628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463982160/">1463982160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463982270/">1463982270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463982760/">1463982760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463984492/">1463984492/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463984556/">1463984556/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463985170/">1463985170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463985220/">1463985220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463985993/">1463985993/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463987071/">1463987071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463987668/">1463987668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463988402/">1463988402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463988640/">1463988640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463988700/">1463988700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463989473/">1463989473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463989711/">1463989711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463990380/">1463990380/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463990677/">1463990677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463991091/">1463991091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463991333/">1463991333/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463992159/">1463992159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463992828/">1463992828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463994159/">1463994159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463994275/">1463994275/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463994579/">1463994579/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463995473/">1463995473/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1463996320/">1463996320/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464006271/">1464006271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464012216/">1464012216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464012270/">1464012270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464012336/">1464012336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464012392/">1464012392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464012633/">1464012633/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464012929/">1464012929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464013051/">1464013051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464013234/">1464013234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464013361/">1464013361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464013417/">1464013417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464013654/">1464013654/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464013779/">1464013779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464014317/">1464014317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464015816/">1464015816/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464017069/">1464017069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464017675/">1464017675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464018155/">1464018155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464018394/">1464018394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464021281/">1464021281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464032875/">1464032875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464033713/">1464033713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464044411/">1464044411/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464053744/">1464053744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464054226/">1464054226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464055194/">1464055194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056227/">1464056227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056294/">1464056294/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056410/">1464056410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056464/">1464056464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056526/">1464056526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056597/">1464056597/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056598/">1464056598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056720/">1464056720/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056779/">1464056779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464056836/">1464056836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464057037/">1464057037/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464057106/">1464057106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464057245/">1464057245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464057486/">1464057486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464057675/">1464057675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464057725/">1464057725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464058033/">1464058033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464060139/">1464060139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464061864/">1464061864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464062291/">1464062291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464062465/">1464062465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464063006/">1464063006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464064450/">1464064450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464064503/">1464064503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464064997/">1464064997/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464066006/">1464066006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464068643/">1464068643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464070690/">1464070690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464070807/">1464070807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464073149/">1464073149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464074943/">1464074943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464075555/">1464075555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464075725/">1464075725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464076385/">1464076385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464077709/">1464077709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464078365/">1464078365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464078485/">1464078485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464079205/">1464079205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464079689/">1464079689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464080343/">1464080343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464080531/">1464080531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464080704/">1464080704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464081014/">1464081014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464081303/">1464081303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464081794/">1464081794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464082219/">1464082219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464082220/">1464082220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464082265/">1464082265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464082991/">1464082991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464083178/">1464083178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464083595/">1464083595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464086223/">1464086223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464087424/">1464087424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464087852/">1464087852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464090307/">1464090307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464091566/">1464091566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464092767/">1464092767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464093605/">1464093605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464094986/">1464094986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464095765/">1464095765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464096906/">1464096906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464097148/">1464097148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464097870/">1464097870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464098236/">1464098236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464099497/">1464099497/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464099555/">1464099555/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464099663/">1464099663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464100236/">1464100236/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464101899/">1464101899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464102198/">1464102198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464102434/">1464102434/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464103505/">1464103505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464104175/">1464104175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464107837/">1464107837/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464108008/">1464108008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464110714/">1464110714/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464111315/">1464111315/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464111736/">1464111736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464112276/">1464112276/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464112330/">1464112330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464113142/">1464113142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464113511/">1464113511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464114163/">1464114163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464114289/">1464114289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464114401/">1464114401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464115060/">1464115060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464117099/">1464117099/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464118779/">1464118779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464119264/">1464119264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464123288/">1464123288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464124125/">1464124125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464125137/">1464125137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464125210/">1464125210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464126166/">1464126166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464126401/">1464126401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464126941/">1464126941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464127004/">1464127004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464127185/">1464127185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464127539/">1464127539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464127903/">1464127903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464128207/">1464128207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464129168/">1464129168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464130000/">1464130000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464130129/">1464130129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464130357/">1464130357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464130785/">1464130785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464131024/">1464131024/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464131810/">1464131810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464132351/">1464132351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464132942/">1464132942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464133123/">1464133123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464133358/">1464133358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464134136/">1464134136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464134209/">1464134209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464140803/">1464140803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464141889/">1464141889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464142902/">1464142902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464143025/">1464143025/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464143270/">1464143270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464146090/">1464146090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464150405/">1464150405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464157900/">1464157900/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464158448/">1464158448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464159349/">1464159349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464159461/">1464159461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464159524/">1464159524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464160247/">1464160247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464161812/">1464161812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464162226/">1464162226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464163721/">1464163721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464163899/">1464163899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464164685/">1464164685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464165647/">1464165647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464166190/">1464166190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464167798/">1464167798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464169188/">1464169188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464169847/">1464169847/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464170091/">1464170091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464170630/">1464170630/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464172246/">1464172246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464172487/">1464172487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464172717/">1464172717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464172909/">1464172909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464173023/">1464173023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464174098/">1464174098/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464175361/">1464175361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464176741/">1464176741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464176981/">1464176981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464177227/">1464177227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464177336/">1464177336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464177399/">1464177399/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464178599/">1464178599/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464181067/">1464181067/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464182197/">1464182197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464182198/">1464182198/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464182330/">1464182330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464182443/">1464182443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464182508/">1464182508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464183790/">1464183790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464185812/">1464185812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464185981/">1464185981/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464186279/">1464186279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464186528/">1464186528/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464186829/">1464186829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464188205/">1464188205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464188924/">1464188924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464189530/">1464189530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464189578/">1464189578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464191803/">1464191803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464191861/">1464191861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464192165/">1464192165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464193249/">1464193249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464202064/">1464202064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464204521/">1464204521/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464204769/">1464204769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464204770/">1464204770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464204771/">1464204771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464204827/">1464204827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464205006/">1464205006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464205422/">1464205422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464205786/">1464205786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464205844/">1464205844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464206021/">1464206021/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464207520/">1464207520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464207819/">1464207819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464208730/">1464208730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464208904/">1464208904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464209204/">1464209204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464209392/">1464209392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464210711/">1464210711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464211781/">1464211781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464212149/">1464212149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464212330/">1464212330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464212691/">1464212691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464213952/">1464213952/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464215930/">1464215930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464216041/">1464216041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464218379/">1464218379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464219108/">1464219108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464224089/">1464224089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464224739/">1464224739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464227629/">1464227629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464228109/">1464228109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464232077/">1464232077/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464232190/">1464232190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464233451/">1464233451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464234349/">1464234349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464235191/">1464235191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464235242/">1464235242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464239279/">1464239279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464241789/">1464241789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464244851/">1464244851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464244968/">1464244968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464245738/">1464245738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464246287/">1464246287/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464250961/">1464250961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464251092/">1464251092/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464251634/">1464251634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464251739/">1464251739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464252406/">1464252406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464253129/">1464253129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464254690/">1464254690/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464255409/">1464255409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464257140/">1464257140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464258404/">1464258404/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464259550/">1464259550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464259786/">1464259786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464264290/">1464264290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464264435/">1464264435/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464264532/">1464264532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464265060/">1464265060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464265910/">1464265910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464266746/">1464266746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464269989/">1464269989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464270883/">1464270883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464271312/">1464271312/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464271540/">1464271540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464271779/">1464271779/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464271906/">1464271906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464272029/">1464272029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464272080/">1464272080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464273767/">1464273767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464273884/">1464273884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464274781/">1464274781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464274899/">1464274899/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464275148/">1464275148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464276229/">1464276229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464276460/">1464276460/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464277431/">1464277431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464280540/">1464280540/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464280858/">1464280858/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464280923/">1464280923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464280983/">1464280983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464281268/">1464281268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464282710/">1464282710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464285220/">1464285220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464286606/">1464286606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464291770/">1464291770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464295006/">1464295006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305571/">1464305571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305631/">1464305631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305687/">1464305687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305744/">1464305744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305745/">1464305745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305803/">1464305803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305875/">1464305875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305928/">1464305928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464305984/">1464305984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464306281/">1464306281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464306472/">1464306472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464307789/">1464307789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464307843/">1464307843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464308209/">1464308209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464309403/">1464309403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464313183/">1464313183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464313908/">1464313908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464316485/">1464316485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464317273/">1464317273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464320090/">1464320090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464321346/">1464321346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464322781/">1464322781/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464323093/">1464323093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464326450/">1464326450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464328727/">1464328727/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464329332/">1464329332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464329567/">1464329567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464332750/">1464332750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464333280/">1464333280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464335567/">1464335567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464336831/">1464336831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464340857/">1464340857/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464341154/">1464341154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464343850/">1464343850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464344323/">1464344323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464348881/">1464348881/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464350093/">1464350093/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464353149/">1464353149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464355306/">1464355306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464357345/">1464357345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464358124/">1464358124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464358903/">1464358903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464360651/">1464360651/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464360764/">1464360764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464362688/">1464362688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464364179/">1464364179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464365143/">1464365143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464367004/">1464367004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464368336/">1464368336/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464369107/">1464369107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464370354/">1464370354/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464370969/">1464370969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464371677/">1464371677/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464373368/">1464373368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464375278/">1464375278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464375640/">1464375640/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464380688/">1464380688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464380809/">1464380809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464380866/">1464380866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464381162/">1464381162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464381282/">1464381282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464381409/">1464381409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464383028/">1464383028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464383212/">1464383212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464383513/">1464383513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464383985/">1464383985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464384945/">1464384945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464385426/">1464385426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464386144/">1464386144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464391181/">1464391181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464392389/">1464392389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464394426/">1464394426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464394969/">1464394969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464395267/">1464395267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464405693/">1464405693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464409538/">1464409538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464414760/">1464414760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464422069/">1464422069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464423005/">1464423005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464425010/">1464425010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464425012/">1464425012/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464425558/">1464425558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464426999/">1464426999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464427863/">1464427863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464431763/">1464431763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464431902/">1464431902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464432030/">1464432030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464432153/">1464432153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464432298/">1464432298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464434252/">1464434252/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464440372/">1464440372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464444695/">1464444695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464444820/">1464444820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464448169/">1464448169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464451713/">1464451713/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464476010/">1464476010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464490416/">1464490416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464501217/">1464501217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464501758/">1464501758/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464505900/">1464505900/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464549392/">1464549392/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464557562/">1464557562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464559182/">1464559182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464562665/">1464562665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464564043/">1464564043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464564340/">1464564340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464570464/">1464570464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464575385/">1464575385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464576467/">1464576467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464581684/">1464581684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464584022/">1464584022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464591157/">1464591157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464591789/">1464591789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464591791/">1464591791/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464591990/">1464591990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464592897/">1464592897/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464593873/">1464593873/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464593916/">1464593916/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464597045/">1464597045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464598182/">1464598182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464599084/">1464599084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464599136/">1464599136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464601840/">1464601840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464601969/">1464601969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464602451/">1464602451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464602567/">1464602567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464605925/">1464605925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464607788/">1464607788/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464607900/">1464607900/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464608628/">1464608628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464608693/">1464608693/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464609880/">1464609880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464611678/">1464611678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464612106/">1464612106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464612820/">1464612820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464614685/">1464614685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464615050/">1464615050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464615108/">1464615108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464616306/">1464616306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464616487/">1464616487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464616961/">1464616961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464617200/">1464617200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464618049/">1464618049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464621284/">1464621284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464621520/">1464621520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464624708/">1464624708/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464626203/">1464626203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464627707/">1464627707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464630644/">1464630644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464631369/">1464631369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464635317/">1464635317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464637121/">1464637121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464638146/">1464638146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464643182/">1464643182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464645465/">1464645465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464645518/">1464645518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464651279/">1464651279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464651468/">1464651468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464654161/">1464654161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464654766/">1464654766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464661004/">1464661004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464661302/">1464661302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464663335/">1464663335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464664842/">1464664842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464666278/">1464666278/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464666398/">1464666398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464666586/">1464666586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464666700/">1464666700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464667902/">1464667902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464675224/">1464675224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464676661/">1464676661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464679608/">1464679608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464682180/">1464682180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464684644/">1464684644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464687348/">1464687348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464688726/">1464688726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464690936/">1464690936/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464692148/">1464692148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464695027/">1464695027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464695806/">1464695806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464696165/">1464696165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464696524/">1464696524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464696945/">1464696945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464698623/">1464698623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464701739/">1464701739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464701925/">1464701925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464702515/">1464702515/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464702892/">1464702892/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464703066/">1464703066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464703301/">1464703301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464706547/">1464706547/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464706719/">1464706719/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464708229/">1464708229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464708279/">1464708279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464708584/">1464708584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464709546/">1464709546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464709661/">1464709661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464710506/">1464710506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464710736/">1464710736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464710985/">1464710985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464711886/">1464711886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464712127/">1464712127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464713752/">1464713752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464714052/">1464714052/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464715245/">1464715245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464717819/">1464717819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464718598/">1464718598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464719561/">1464719561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464725375/">1464725375/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464725438/">1464725438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464725562/">1464725562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464725626/">1464725626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464725740/">1464725740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464726396/">1464726396/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464727424/">1464727424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464729049/">1464729049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464729526/">1464729526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464729766/">1464729766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464731139/">1464731139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464731986/">1464731986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464732101/">1464732101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464733357/">1464733357/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464734323/">1464734323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464734438/">1464734438/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464736116/">1464736116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464736604/">1464736604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464738519/">1464738519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464738646/">1464738646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464738815/">1464738815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464739552/">1464739552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464740400/">1464740400/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464742672/">1464742672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464743454/">1464743454/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464745063/">1464745063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464745064/">1464745064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464745706/">1464745706/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464746015/">1464746015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464746731/">1464746731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464747394/">1464747394/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464747817/">1464747817/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464748663/">1464748663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464749571/">1464749571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464752446/">1464752446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464752745/">1464752745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464753051/">1464753051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464755875/">1464755875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464756051/">1464756051/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464756644/">1464756644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464759102/">1464759102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464763008/">1464763008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464763074/">1464763074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464763674/">1464763674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464763845/">1464763845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464763911/">1464763911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464764145/">1464764145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464765649/">1464765649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464766009/">1464766009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464766010/">1464766010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464767928/">1464767928/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464770148/">1464770148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464770209/">1464770209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464770388/">1464770388/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464770752/">1464770752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464773870/">1464773870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464773925/">1464773925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464775134/">1464775134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464775843/">1464775843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464776213/">1464776213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464776332/">1464776332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464776385/">1464776385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464776987/">1464776987/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464777290/">1464777290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464783527/">1464783527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464783586/">1464783586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464784124/">1464784124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464786234/">1464786234/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464787011/">1464787011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464787546/">1464787546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464787664/">1464787664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464787730/">1464787730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464787852/">1464787852/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464788088/">1464788088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464788687/">1464788687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464789044/">1464789044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464789291/">1464789291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464792301/">1464792301/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464796849/">1464796849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464797813/">1464797813/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464797814/">1464797814/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464798044/">1464798044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464798173/">1464798173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464798232/">1464798232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464800145/">1464800145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464801228/">1464801228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464803262/">1464803262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464803569/">1464803569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464803863/">1464803863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464805968/">1464805968/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464806094/">1464806094/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464806151/">1464806151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464807222/">1464807222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464807650/">1464807650/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464808303/">1464808303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464809147/">1464809147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464809565/">1464809565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464810530/">1464810530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464812268/">1464812268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464812335/">1464812335/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464812625/">1464812625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464814674/">1464814674/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464815033/">1464815033/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464815330/">1464815330/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464815875/">1464815875/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464816822/">1464816822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464818812/">1464818812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464819107/">1464819107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464819292/">1464819292/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464821393/">1464821393/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464822774/">1464822774/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464823008/">1464823008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464824812/">1464824812/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464824988/">1464824988/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464826672/">1464826672/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464829364/">1464829364/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464847124/">1464847124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464848085/">1464848085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464849043/">1464849043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464849348/">1464849348/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464850070/">1464850070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464850188/">1464850188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464851027/">1464851027/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464852824/">1464852824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464853123/">1464853123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464853848/">1464853848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464854329/">1464854329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464854930/">1464854930/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464855284/">1464855284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464856612/">1464856612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464856678/">1464856678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464858107/">1464858107/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464859246/">1464859246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464859494/">1464859494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464859670/">1464859670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464860034/">1464860034/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464860204/">1464860204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464861291/">1464861291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464861764/">1464861764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464862552/">1464862552/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464862967/">1464862967/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464864709/">1464864709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464864942/">1464864942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867137/">1464867137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867139/">1464867139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867145/">1464867145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867147/">1464867147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867150/">1464867150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867151/">1464867151/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867152/">1464867152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867153/">1464867153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867157/">1464867157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867160/">1464867160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867162/">1464867162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867163/">1464867163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867164/">1464867164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867166/">1464867166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867167/">1464867167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867169/">1464867169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867170/">1464867170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867172/">1464867172/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867175/">1464867175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867176/">1464867176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867178/">1464867178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867183/">1464867183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867186/">1464867186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867188/">1464867188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867190/">1464867190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867191/">1464867191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867193/">1464867193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867194/">1464867194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867196/">1464867196/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867482/">1464867482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867483/">1464867483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867488/">1464867488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867489/">1464867489/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867490/">1464867490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867494/">1464867494/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867495/">1464867495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867496/">1464867496/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867499/">1464867499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867501/">1464867501/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867502/">1464867502/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867504/">1464867504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867506/">1464867506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867507/">1464867507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867508/">1464867508/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867510/">1464867510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867511/">1464867511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867512/">1464867512/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867513/">1464867513/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867514/">1464867514/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867516/">1464867516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867517/">1464867517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867518/">1464867518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867519/">1464867519/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867520/">1464867520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867522/">1464867522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867523/">1464867523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867524/">1464867524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867525/">1464867525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867526/">1464867526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867527/">1464867527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867531/">1464867531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867533/">1464867533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867534/">1464867534/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867537/">1464867537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867539/">1464867539/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464867542/">1464867542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464868010/">1464868010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464873112/">1464873112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464874903/">1464874903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875326/">1464875326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875722/">1464875722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875723/">1464875723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875728/">1464875728/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875729/">1464875729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875730/">1464875730/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875731/">1464875731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875734/">1464875734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875735/">1464875735/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875736/">1464875736/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875739/">1464875739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875741/">1464875741/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875742/">1464875742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875750/">1464875750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875751/">1464875751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875752/">1464875752/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464875810/">1464875810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464877546/">1464877546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464878146/">1464878146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464878694/">1464878694/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464879231/">1464879231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464879648/">1464879648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464880011/">1464880011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464880366/">1464880366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464880612/">1464880612/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464880785/">1464880785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464881385/">1464881385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464882470/">1464882470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464883836/">1464883836/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464884135/">1464884135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464884436/">1464884436/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464887495/">1464887495/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464888215/">1464888215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464888458/">1464888458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464890794/">1464890794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464890915/">1464890915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464892417/">1464892417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464893317/">1464893317/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464895181/">1464895181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464895596/">1464895596/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464896445/">1464896445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464896915/">1464896915/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464898537/">1464898537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464899617/">1464899617/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464900217/">1464900217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464900457/">1464900457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464900938/">1464900938/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464901057/">1464901057/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464901417/">1464901417/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464904237/">1464904237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464905197/">1464905197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464905377/">1464905377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464905437/">1464905437/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464906397/">1464906397/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464907297/">1464907297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464908017/">1464908017/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464909577/">1464909577/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464910209/">1464910209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464912216/">1464912216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464915370/">1464915370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464915431/">1464915431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464915490/">1464915490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464918912/">1464918912/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464920950/">1464920950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464921371/">1464921371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464921670/">1464921670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464923230/">1464923230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464925090/">1464925090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464930311/">1464930311/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464930368/">1464930368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464930431/">1464930431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464930551/">1464930551/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464931150/">1464931150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464931391/">1464931391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464931450/">1464931450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464933045/">1464933045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464933358/">1464933358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464936310/">1464936310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464936550/">1464936550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464937329/">1464937329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464938710/">1464938710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464940871/">1464940871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464941950/">1464941950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464942687/">1464942687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464943571/">1464943571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464945250/">1464945250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464946090/">1464946090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464946571/">1464946571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464948731/">1464948731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464950591/">1464950591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464952452/">1464952452/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464953531/">1464953531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464953891/">1464953891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464954550/">1464954550/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464957251/">1464957251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464959769/">1464959769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464960008/">1464960008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464960670/">1464960670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464960851/">1464960851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464963610/">1464963610/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464964326/">1464964326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464964391/">1464964391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464964451/">1464964451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464965538/">1464965538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464967870/">1464967870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464971170/">1464971170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464973692/">1464973692/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464973871/">1464973871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464975048/">1464975048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464975078/">1464975078/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464975194/">1464975194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464976571/">1464976571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464976691/">1464976691/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464978206/">1464978206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464979827/">1464979827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464980427/">1464980427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464981267/">1464981267/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464982046/">1464982046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464982466/">1464982466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464982946/">1464982946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464983426/">1464983426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464985818/">1464985818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464985886/">1464985886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464987926/">1464987926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464988166/">1464988166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464988406/">1464988406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464990986/">1464990986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464995546/">1464995546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464996026/">1464996026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464996206/">1464996206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464996604/">1464996604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464996746/">1464996746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1464998365/">1464998365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465001906/">1465001906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465002146/">1465002146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465007457/">1465007457/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465009766/">1465009766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465011506/">1465011506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465025126/">1465025126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465025606/">1465025606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465025786/">1465025786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465025966/">1465025966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465026026/">1465026026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465029014/">1465029014/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465036586/">1465036586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465039803/">1465039803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465043486/">1465043486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465047306/">1465047306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465052046/">1465052046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465055707/">1465055707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465061453/">1465061453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465071066/">1465071066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465078447/">1465078447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465083011/">1465083011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465088286/">1465088286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465090807/">1465090807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465093855/">1465093855/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465112442/">1465112442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465113522/">1465113522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465113762/">1465113762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465113882/">1465113882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465114182/">1465114182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465114302/">1465114302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465118862/">1465118862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465126205/">1465126205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465147602/">1465147602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465155882/">1465155882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465158611/">1465158611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465160682/">1465160682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465168662/">1465168662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465169802/">1465169802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465174062/">1465174062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465175848/">1465175848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465177168/">1465177168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465177948/">1465177948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465179389/">1465179389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465180288/">1465180288/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465183529/">1465183529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465184249/">1465184249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465187468/">1465187468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465188185/">1465188185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465188634/">1465188634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465188809/">1465188809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465190368/">1465190368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465190943/">1465190943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465191520/">1465191520/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465193729/">1465193729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465195108/">1465195108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465196128/">1465196128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465197273/">1465197273/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465200210/">1465200210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465202249/">1465202249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465204468/">1465204468/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465204668/">1465204668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465204771/">1465204771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465204829/">1465204829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465205188/">1465205188/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465206090/">1465206090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465206688/">1465206688/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465207408/">1465207408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465210888/">1465210888/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465212570/">1465212570/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465212638/">1465212638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465212808/">1465212808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465214909/">1465214909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465216349/">1465216349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465217309/">1465217309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465217428/">1465217428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465217488/">1465217488/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465218028/">1465218028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465218088/">1465218088/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465218213/">1465218213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465219048/">1465219048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465221150/">1465221150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465221268/">1465221268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465222890/">1465222890/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465223191/">1465223191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465223790/">1465223790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465224929/">1465224929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465227446/">1465227446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465229186/">1465229186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465229666/">1465229666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465229845/">1465229845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465229906/">1465229906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465230446/">1465230446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465230506/">1465230506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465232491/">1465232491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465233747/">1465233747/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465234271/">1465234271/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465234709/">1465234709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465235427/">1465235427/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465235608/">1465235608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465235668/">1465235668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465236253/">1465236253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465237346/">1465237346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465238187/">1465238187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465240226/">1465240226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465240646/">1465240646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465245030/">1465245030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465248571/">1465248571/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252173/">1465252173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252174/">1465252174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252231/">1465252231/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252290/">1465252290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252410/">1465252410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252472/">1465252472/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252771/">1465252771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465252950/">1465252950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465253130/">1465253130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465253253/">1465253253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465253372/">1465253372/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465253790/">1465253790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465253910/">1465253910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465254871/">1465254871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465255770/">1465255770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465255806/">1465255806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465256370/">1465256370/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465258352/">1465258352/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465258532/">1465258532/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465258592/">1465258592/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465259011/">1465259011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465261711/">1465261711/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465262132/">1465262132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465265138/">1465265138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465265310/">1465265310/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465266155/">1465266155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465266391/">1465266391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465266675/">1465266675/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465268611/">1465268611/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465269091/">1465269091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465269511/">1465269511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465272511/">1465272511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465277250/">1465277250/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465279474/">1465279474/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465279770/">1465279770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465279891/">1465279891/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465280790/">1465280790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465280910/">1465280910/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465281091/">1465281091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465281211/">1465281211/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465284272/">1465284272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465284632/">1465284632/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465285712/">1465285712/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465285831/">1465285831/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465286491/">1465286491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465287991/">1465287991/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465288223/">1465288223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465290091/">1465290091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465291409/">1465291409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465291533/">1465291533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465292851/">1465292851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465292973/">1465292973/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465294290/">1465294290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465295733/">1465295733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465295911/">1465295911/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465298191/">1465298191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465299060/">1465299060/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465299391/">1465299391/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465301070/">1465301070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465301851/">1465301851/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465304671/">1465304671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465305090/">1465305090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465305210/">1465305210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465305511/">1465305511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465305751/">1465305751/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465306296/">1465306296/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465306410/">1465306410/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465307490/">1465307490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465307671/">1465307671/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465309591/">1465309591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465309769/">1465309769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465309878/">1465309878/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465310131/">1465310131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465310190/">1465310190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465310790/">1465310790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465311152/">1465311152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465313068/">1465313068/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465313371/">1465313371/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465313971/">1465313971/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465315470/">1465315470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465315533/">1465315533/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465315830/">1465315830/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465316516/">1465316516/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465317869/">1465317869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465320634/">1465320634/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465320700/">1465320700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465322131/">1465322131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465322431/">1465322431/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465323091/">1465323091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465324110/">1465324110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465324176/">1465324176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465324471/">1465324471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465324591/">1465324591/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465324652/">1465324652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465325010/">1465325010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465326086/">1465326086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465326386/">1465326386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465331467/">1465331467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465334484/">1465334484/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465334485/">1465334485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465334544/">1465334544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465334664/">1465334664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465334665/">1465334665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465334785/">1465334785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465335030/">1465335030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465335147/">1465335147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465335504/">1465335504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465336164/">1465336164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465337064/">1465337064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465337124/">1465337124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465337185/">1465337185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465337304/">1465337304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465338746/">1465338746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465340603/">1465340603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465342045/">1465342045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465347084/">1465347084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465347205/">1465347205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465347564/">1465347564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465351105/">1465351105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465352309/">1465352309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465352429/">1465352429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465352609/">1465352609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465353026/">1465353026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465353629/">1465353629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465356870/">1465356870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465356989/">1465356989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465360049/">1465360049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465361189/">1465361189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465362749/">1465362749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465363108/">1465363108/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465363589/">1465363589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465363805/">1465363805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465368332/">1465368332/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465368451/">1465368451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465371809/">1465371809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465372169/">1465372169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465372589/">1465372589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465373369/">1465373369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465373609/">1465373609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465374869/">1465374869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465375950/">1465375950/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465376969/">1465376969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465378409/">1465378409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465380569/">1465380569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465380870/">1465380870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465380871/">1465380871/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465381109/">1465381109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465381409/">1465381409/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465381531/">1465381531/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465381709/">1465381709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465383031/">1465383031/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465385402/">1465385402/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465385613/">1465385613/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465386511/">1465386511/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465390049/">1465390049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465390770/">1465390770/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465390949/">1465390949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465391729/">1465391729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465392153/">1465392153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465392810/">1465392810/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465393169/">1465393169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465393949/">1465393949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465394429/">1465394429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465395149/">1465395149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465395449/">1465395449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465397969/">1465397969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465400849/">1465400849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465401029/">1465401029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465401449/">1465401449/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465401990/">1465401990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465402109/">1465402109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465402291/">1465402291/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465403849/">1465403849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465404569/">1465404569/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465405289/">1465405289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465406969/">1465406969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465407005/">1465407005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465407989/">1465407989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465407990/">1465407990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465408349/">1465408349/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465408829/">1465408829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465409069/">1465409069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465411529/">1465411529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465412670/">1465412670/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465412969/">1465412969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465413509/">1465413509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465413749/">1465413749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465415849/">1465415849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465416030/">1465416030/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465416209/">1465416209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465418429/">1465418429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465419269/">1465419269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465420229/">1465420229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465421490/">1465421490/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465421909/">1465421909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465422329/">1465422329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465422869/">1465422869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465422929/">1465422929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465423829/">1465423829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465424729/">1465424729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465428606/">1465428606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465428809/">1465428809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465429050/">1465429050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465429351/">1465429351/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465429829/">1465429829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465430970/">1465430970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465431631/">1465431631/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465432169/">1465432169/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465432710/">1465432710/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465434750/">1465434750/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465434989/">1465434989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465436248/">1465436248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465436850/">1465436850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465443450/">1465443450/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465444829/">1465444829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465446268/">1465446268/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465446509/">1465446509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465446629/">1465446629/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465447050/">1465447050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465448729/">1465448729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465449089/">1465449089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465450304/">1465450304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465453048/">1465453048/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465454549/">1465454549/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465454848/">1465454848/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465455329/">1465455329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465455429/">1465455429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465461053/">1465461053/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465464389/">1465464389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465465889/">1465465889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465467270/">1465467270/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465467809/">1465467809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465468049/">1465468049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465468529/">1465468529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465469130/">1465469130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465470929/">1465470929/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465471886/">1465471886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465472069/">1465472069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465473390/">1465473390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465473689/">1465473689/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465474949/">1465474949/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465477649/">1465477649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465478009/">1465478009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465478369/">1465478369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465478969/">1465478969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465480049/">1465480049/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465480050/">1465480050/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465480548/">1465480548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465482682/">1465482682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465482989/">1465482989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465483168/">1465483168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465483590/">1465483590/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465484609/">1465484609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465484850/">1465484850/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465485090/">1465485090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465486530/">1465486530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465486709/">1465486709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465489771/">1465489771/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465490195/">1465490195/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465490729/">1465490729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465491990/">1465491990/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465492769/">1465492769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465493309/">1465493309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465493470/">1465493470/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465497091/">1465497091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465497329/">1465497329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465497509/">1465497509/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465497568/">1465497568/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465498229/">1465498229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465500448/">1465500448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465502011/">1465502011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465502069/">1465502069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465504223/">1465504223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465505790/">1465505790/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465506451/">1465506451/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465507530/">1465507530/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465508369/">1465508369/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465508429/">1465508429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465509149/">1465509149/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465509390/">1465509390/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465512208/">1465512208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465512869/">1465512869/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465513290/">1465513290/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465514008/">1465514008/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465515010/">1465515010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465519649/">1465519649/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465520249/">1465520249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465520309/">1465520309/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465526969/">1465526969/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465532070/">1465532070/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465532491/">1465532491/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465532608/">1465532608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465536652/">1465536652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465539269/">1465539269/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465539989/">1465539989/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465543289/">1465543289/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465543529/">1465543529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465543829/">1465543829/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465548329/">1465548329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465550009/">1465550009/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465552109/">1465552109/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465552589/">1465552589/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465558251/">1465558251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465560090/">1465560090/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465560389/">1465560389/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465565909/">1465565909/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465566089/">1465566089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465568249/">1465568249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465568609/">1465568609/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465569043/">1465569043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465569870/">1465569870/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465575510/">1465575510/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465575749/">1465575749/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465576529/">1465576529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465576709/">1465576709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465577849/">1465577849/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465578384/">1465578384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465579818/">1465579818/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465580246/">1465580246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465581863/">1465581863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465582584/">1465582584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465583604/">1465583604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465583723/">1465583723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465585763/">1465585763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465589304/">1465589304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465590323/">1465590323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465590608/">1465590608/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465591343/">1465591343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465592604/">1465592604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465592783/">1465592783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465592964/">1465592964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465593204/">1465593204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465594284/">1465594284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465594343/">1465594343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465596143/">1465596143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465597283/">1465597283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465599743/">1465599743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465601663/">1465601663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465603283/">1465603283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465606164/">1465606164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465609824/">1465609824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465610664/">1465610664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465610784/">1465610784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465611504/">1465611504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465612366/">1465612366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465612644/">1465612644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465612944/">1465612944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465614684/">1465614684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465623074/">1465623074/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465623075/">1465623075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465631244/">1465631244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465633104/">1465633104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465634543/">1465634543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465639585/">1465639585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465644605/">1465644605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465650323/">1465650323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465650683/">1465650683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465659143/">1465659143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465662503/">1465662503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465662683/">1465662683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465662923/">1465662923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465666255/">1465666255/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465670543/">1465670543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465677043/">1465677043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465692743/">1465692743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465698658/">1465698658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465704444/">1465704444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465713143/">1465713143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465719023/">1465719023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465719563/">1465719563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465723944/">1465723944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465725444/">1465725444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465741464/">1465741464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465741704/">1465741704/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465743744/">1465743744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465743923/">1465743923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465752648/">1465752648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465754723/">1465754723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465758203/">1465758203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465763416/">1465763416/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765731/">1465765731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765732/">1465765732/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765733/">1465765733/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765734/">1465765734/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765739/">1465765739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765740/">1465765740/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765742/">1465765742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765759/">1465765759/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765760/">1465765760/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765761/">1465765761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765772/">1465765772/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765773/">1465765773/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465765775/">1465765775/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766185/">1465766185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766186/">1465766186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766187/">1465766187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766189/">1465766189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766192/">1465766192/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766193/">1465766193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766194/">1465766194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766213/">1465766213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766215/">1465766215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766218/">1465766218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766226/">1465766226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766228/">1465766228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465766229/">1465766229/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465802124/">1465802124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465803924/">1465803924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465804524/">1465804524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465805124/">1465805124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465806263/">1465806263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465814724/">1465814724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465817461/">1465817461/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465821264/">1465821264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465827864/">1465827864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465828260/">1465828260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465829124/">1465829124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465829303/">1465829303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465830503/">1465830503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465831524/">1465831524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465831944/">1465831944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465832184/">1465832184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465832603/">1465832603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465834764/">1465834764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465839044/">1465839044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465845983/">1465845983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465849808/">1465849808/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465854083/">1465854083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465859966/">1465859966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465885884/">1465885884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465895304/">1465895304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465900763/">1465900763/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465901363/">1465901363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465901543/">1465901543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465901723/">1465901723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465902203/">1465902203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465902323/">1465902323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465902446/">1465902446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465903283/">1465903283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465903703/">1465903703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465903806/">1465903806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465904724/">1465904724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465904784/">1465904784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465905381/">1465905381/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465906284/">1465906284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465907663/">1465907663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465907785/">1465907785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465908623/">1465908623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465909707/">1465909707/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465911387/">1465911387/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465911564/">1465911564/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465912045/">1465912045/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465912283/">1465912283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465914756/">1465914756/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465915344/">1465915344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465915583/">1465915583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465915884/">1465915884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465917743/">1465917743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465918248/">1465918248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465919543/">1465919543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465922423/">1465922423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465925429/">1465925429/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465928843/">1465928843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465936254/">1465936254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465938203/">1465938203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465940543/">1465940543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465941265/">1465941265/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465941983/">1465941983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465942283/">1465942283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465944623/">1465944623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465948703/">1465948703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465949063/">1465949063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465949123/">1465949123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465957815/">1465957815/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465960044/">1465960044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465961064/">1465961064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465967483/">1465967483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465968504/">1465968504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465970483/">1465970483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465976281/">1465976281/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465979405/">1465979405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465979483/">1465979483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465980264/">1465980264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465980383/">1465980383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465981644/">1465981644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465981943/">1465981943/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465983084/">1465983084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465984403/">1465984403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465984824/">1465984824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465986383/">1465986383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465987163/">1465987163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465987823/">1465987823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465988003/">1465988003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465988303/">1465988303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465990163/">1465990163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465990204/">1465990204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465995023/">1465995023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465995623/">1465995623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465997844/">1465997844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1465999703/">1465999703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466000244/">1466000244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466001122/">1466001122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466001503/">1466001503/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466005343/">1466005343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466005463/">1466005463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466006003/">1466006003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466006123/">1466006123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466006243/">1466006243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466006543/">1466006543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466008163/">1466008163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466008765/">1466008765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466009964/">1466009964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466011284/">1466011284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466011866/">1466011866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466025083/">1466025083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466025923/">1466025923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466027065/">1466027065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466027363/">1466027363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466029464/">1466029464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466030244/">1466030244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466045063/">1466045063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466046803/">1466046803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466061323/">1466061323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466061443/">1466061443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466062043/">1466062043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466062764/">1466062764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466062834/">1466062834/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466063063/">1466063063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466063303/">1466063303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466068886/">1466068886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466070683/">1466070683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466075424/">1466075424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466075903/">1466075903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466079084/">1466079084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466080043/">1466080043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466081124/">1466081124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466085504/">1466085504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466087064/">1466087064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466087243/">1466087243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466087428/">1466087428/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466087963/">1466087963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466088804/">1466088804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466088863/">1466088863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466095466/">1466095466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466095883/">1466095883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466096003/">1466096003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466098223/">1466098223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466098702/">1466098702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466109005/">1466109005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466118923/">1466118923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466121683/">1466121683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466121804/">1466121804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466124863/">1466124863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466150363/">1466150363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466157563/">1466157563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466159183/">1466159183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466160143/">1466160143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466165483/">1466165483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466166083/">1466166083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466166863/">1466166863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466168063/">1466168063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466169994/">1466169994/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466169995/">1466169995/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466173944/">1466173944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466178329/">1466178329/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466181203/">1466181203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466181863/">1466181863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466187444/">1466187444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466201183/">1466201183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466217383/">1466217383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466221044/">1466221044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466235323/">1466235323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466243663/">1466243663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466247263/">1466247263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466252303/">1466252303/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466254283/">1466254283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466265863/">1466265863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466280923/">1466280923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466287583/">1466287583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466292683/">1466292683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466324903/">1466324903/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466325623/">1466325623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466332645/">1466332645/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466371043/">1466371043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466390543/">1466390543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466410762/">1466410762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466411363/">1466411363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466414003/">1466414003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466414723/">1466414723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466418203/">1466418203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466418323/">1466418323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466419823/">1466419823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466422101/">1466422101/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466423423/">1466423423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466424023/">1466424023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466424203/">1466424203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466424324/">1466424324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466424563/">1466424563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466429347/">1466429347/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466429439/">1466429439/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466429441/">1466429441/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466429444/">1466429444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466429448/">1466429448/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466429537/">1466429537/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466430562/">1466430562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466430924/">1466430924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466432136/">1466432136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466432603/">1466432603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466433145/">1466433145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466433623/">1466433623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466436985/">1466436985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466442023/">1466442023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466442383/">1466442383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466443103/">1466443103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466444422/">1466444422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466445385/">1466445385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466446344/">1466446344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466448684/">1466448684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466448744/">1466448744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466449823/">1466449823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466450483/">1466450483/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466452343/">1466452343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466453183/">1466453183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466453664/">1466453664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466455163/">1466455163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466458923/">1466458923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466461203/">1466461203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466461863/">1466461863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466463783/">1466463783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466471582/">1466471582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466473803/">1466473803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466475603/">1466475603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466482204/">1466482204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466488143/">1466488143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466491739/">1466491739/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466491863/">1466491863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466492342/">1466492342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466494383/">1466494383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466495583/">1466495583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466495883/">1466495883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466497323/">1466497323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466500561/">1466500561/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466500920/">1466500920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466501722/">1466501722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466501723/">1466501723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466504100/">1466504100/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466506743/">1466506743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466506980/">1466506980/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466508244/">1466508244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466509322/">1466509322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466510219/">1466510219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466511125/">1466511125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466512260/">1466512260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466513580/">1466513580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466513820/">1466513820/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466515860/">1466515860/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466516764/">1466516764/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466519595/">1466519595/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466520059/">1466520059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466520844/">1466520844/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466520901/">1466520901/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466521083/">1466521083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466521263/">1466521263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466522403/">1466522403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466523119/">1466523119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466524079/">1466524079/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466529182/">1466529182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466541723/">1466541723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466541840/">1466541840/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466541963/">1466541963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466541964/">1466541964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466542023/">1466542023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466542082/">1466542082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466542144/">1466542144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466542263/">1466542263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466542923/">1466542923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466543041/">1466543041/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466543160/">1466543160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466543463/">1466543463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466543944/">1466543944/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466546042/">1466546042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466547543/">1466547543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466549043/">1466549043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466549220/">1466549220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466552280/">1466552280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466553480/">1466553480/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466554080/">1466554080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466557203/">1466557203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466557619/">1466557619/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466559603/">1466559603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466561280/">1466561280/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466561403/">1466561403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466563623/">1466563623/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466563743/">1466563743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466565603/">1466565603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466567703/">1466567703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466568359/">1466568359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466572563/">1466572563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466574063/">1466574063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466576103/">1466576103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466578143/">1466578143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466579882/">1466579882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466580003/">1466580003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466580902/">1466580902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466581803/">1466581803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466582163/">1466582163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466583783/">1466583783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466583784/">1466583784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466589244/">1466589244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466591523/">1466591523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466594583/">1466594583/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466596081/">1466596081/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466596261/">1466596261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466598363/">1466598363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466600163/">1466600163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466600940/">1466600940/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466601000/">1466601000/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466601359/">1466601359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466603580/">1466603580/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466605889/">1466605889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466616605/">1466616605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466617224/">1466617224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466636425/">1466636425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466636544/">1466636544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466636604/">1466636604/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466636664/">1466636664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466636724/">1466636724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466637444/">1466637444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466637744/">1466637744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466638203/">1466638203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466638404/">1466638404/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466643146/">1466643146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466643325/">1466643325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466646385/">1466646385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466652984/">1466652984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466653224/">1466653224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466653284/">1466653284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466653344/">1466653344/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466653584/">1466653584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466655504/">1466655504/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466656464/">1466656464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466657664/">1466657664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466661624/">1466661624/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466663185/">1466663185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466668164/">1466668164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466668284/">1466668284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466669124/">1466669124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466669683/">1466669683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466669724/">1466669724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466670085/">1466670085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466671403/">1466671403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466672064/">1466672064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466673325/">1466673325/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466674709/">1466674709/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466675964/">1466675964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466678546/">1466678546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466679805/">1466679805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466682263/">1466682263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466683584/">1466683584/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466684964/">1466684964/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466685745/">1466685745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466685925/">1466685925/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466687726/">1466687726/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466687784/">1466687784/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466687906/">1466687906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466688084/">1466688084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466688206/">1466688206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466688446/">1466688446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466688865/">1466688865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466688924/">1466688924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466689765/">1466689765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466693064/">1466693064/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466693307/">1466693307/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466694084/">1466694084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466696063/">1466696063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466697155/">1466697155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466697924/">1466697924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466703046/">1466703046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466724418/">1466724418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466724478/">1466724478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466724538/">1466724538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466724598/">1466724598/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466724646/">1466724646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466724656/">1466724656/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466725259/">1466725259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466726638/">1466726638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466733661/">1466733661/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466734018/">1466734018/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466735458/">1466735458/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466739418/">1466739418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466743558/">1466743558/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466751118/">1466751118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466751238/">1466751238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466751538/">1466751538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466752498/">1466752498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466754358/">1466754358/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466754478/">1466754478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466760304/">1466760304/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466767071/">1466767071/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466767768/">1466767768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466770080/">1466770080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466770678/">1466770678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466771471/">1466771471/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466773499/">1466773499/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466774398/">1466774398/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466774459/">1466774459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466778700/">1466778700/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466783140/">1466783140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466783158/">1466783158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466787058/">1466787058/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466790478/">1466790478/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466791498/">1466791498/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466791738/">1466791738/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466792459/">1466792459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466796538/">1466796538/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466796898/">1466796898/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466797678/">1466797678/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466798159/">1466798159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466799476/">1466799476/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466799778/">1466799778/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466800798/">1466800798/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466800920/">1466800920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466804638/">1466804638/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466811010/">1466811010/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466820264/">1466820264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466821880/">1466821880/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466859384/">1466859384/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466860284/">1466860284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466869884/">1466869884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466874924/">1466874924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466878224/">1466878224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466888664/">1466888664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466890464/">1466890464/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466890644/">1466890644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466894544/">1466894544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466897724/">1466897724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466957184/">1466957184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466958744/">1466958744/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466959884/">1466959884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466961984/">1466961984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466970144/">1466970144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466973084/">1466973084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466978124/">1466978124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466979444/">1466979444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466981544/">1466981544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466982144/">1466982144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466988204/">1466988204/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466988444/">1466988444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1466992824/">1466992824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467000084/">1467000084/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467002363/">1467002363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467003324/">1467003324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467009264/">1467009264/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467010403/">1467010403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467012924/">1467012924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467016825/">1467016825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467020124/">1467020124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467028359/">1467028359/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467029904/">1467029904/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467039113/">1467039113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467039695/">1467039695/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467040279/">1467040279/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467041179/">1467041179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467043517/">1467043517/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467045799/">1467045799/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467046397/">1467046397/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467047177/">1467047177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467047717/">1467047717/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467047837/">1467047837/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467048377/">1467048377/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467049340/">1467049340/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467051077/">1467051077/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467051918/">1467051918/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467051977/">1467051977/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467052238/">1467052238/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467052338/">1467052338/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467052578/">1467052578/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467052999/">1467052999/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467053298/">1467053298/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467053657/">1467053657/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467054259/">1467054259/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467061819/">1467061819/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467065418/">1467065418/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467066140/">1467066140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467066729/">1467066729/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467067602/">1467067602/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467072197/">1467072197/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467072379/">1467072379/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467073639/">1467073639/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467074239/">1467074239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467074780/">1467074780/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467076401/">1467076401/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467077119/">1467077119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467079039/">1467079039/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467080062/">1467080062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467080422/">1467080422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467080962/">1467080962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467081059/">1467081059/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467081443/">1467081443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467081862/">1467081862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467083002/">1467083002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467092011/">1467092011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467092137/">1467092137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467092140/">1467092140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467092963/">1467092963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467093562/">1467093562/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467094222/">1467094222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467097882/">1467097882/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467098242/">1467098242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467099683/">1467099683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467099863/">1467099863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467100823/">1467100823/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467101363/">1467101363/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467101423/">1467101423/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467101542/">1467101542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467101914/">1467101914/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467102323/">1467102323/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467106228/">1467106228/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467108086/">1467108086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467108443/">1467108443/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467114861/">1467114861/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467115644/">1467115644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467118163/">1467118163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467119543/">1467119543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467120863/">1467120863/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467121103/">1467121103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467123324/">1467123324/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467123385/">1467123385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467123984/">1467123984/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467124282/">1467124282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467124403/">1467124403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467125123/">1467125123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467125842/">1467125842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467126080/">1467126080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467126321/">1467126321/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467127224/">1467127224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467127225/">1467127225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467128963/">1467128963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467129803/">1467129803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467129879/">1467129879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467131182/">1467131182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467133284/">1467133284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467134075/">1467134075/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467134970/">1467134970/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467135089/">1467135089/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467136524/">1467136524/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467136643/">1467136643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467137244/">1467137244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467137783/">1467137783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467138444/">1467138444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467138563/">1467138563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467139343/">1467139343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467139883/">1467139883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467140721/">1467140721/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467141983/">1467141983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467142465/">1467142465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467143063/">1467143063/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467143433/">1467143433/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467145104/">1467145104/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467145893/">1467145893/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467148102/">1467148102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467159206/">1467159206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467160286/">1467160286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467164906/">1467164906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467168146/">1467168146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467170668/">1467170668/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467171266/">1467171266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467175226/">1467175226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467177266/">1467177266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467179487/">1467179487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467179906/">1467179906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467180086/">1467180086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467180266/">1467180266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467181406/">1467181406/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467181766/">1467181766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467181886/">1467181886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467182366/">1467182366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467182486/">1467182486/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467186567/">1467186567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467187586/">1467187586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467190889/">1467190889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467191666/">1467191666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467191966/">1467191966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467192927/">1467192927/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467194006/">1467194006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467195447/">1467195447/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467197786/">1467197786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467197906/">1467197906/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467198029/">1467198029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467199769/">1467199769/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467201146/">1467201146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467203054/">1467203054/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467204626/">1467204626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467204687/">1467204687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467205946/">1467205946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467206186/">1467206186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467206426/">1467206426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467210091/">1467210091/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467212308/">1467212308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467213087/">1467213087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467215186/">1467215186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467216566/">1467216566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467216867/">1467216867/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467217226/">1467217226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467225567/">1467225567/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467226886/">1467226886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467227606/">1467227606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467229767/">1467229767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467230366/">1467230366/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467230606/">1467230606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467230908/">1467230908/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467231146/">1467231146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467233246/">1467233246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467233444/">1467233444/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467237926/">1467237926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467240266/">1467240266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467240627/">1467240627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467242787/">1467242787/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467244587/">1467244587/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467244946/">1467244946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467245426/">1467245426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467250467/">1467250467/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467250647/">1467250647/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467252865/">1467252865/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467252986/">1467252986/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467254187/">1467254187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467260666/">1467260666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467261026/">1467261026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467261266/">1467261266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467261746/">1467261746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467262767/">1467262767/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467264386/">1467264386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467268586/">1467268586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467269306/">1467269306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467271346/">1467271346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467272306/">1467272306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467273026/">1467273026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467275966/">1467275966/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467282087/">1467282087/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467283886/">1467283886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467284186/">1467284186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467285746/">1467285746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467288326/">1467288326/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467288686/">1467288686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467288926/">1467288926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467289408/">1467289408/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467297253/">1467297253/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467315809/">1467315809/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467316529/">1467316529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467316648/">1467316648/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467317669/">1467317669/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467317789/">1467317789/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467318029/">1467318029/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467319961/">1467319961/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467320069/">1467320069/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467320249/">1467320249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467320485/">1467320485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467322283/">1467322283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467322822/">1467322822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467323432/">1467323432/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467323731/">1467323731/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467325046/">1467325046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467326183/">1467326183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467326841/">1467326841/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467326900/">1467326900/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467327140/">1467327140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467330920/">1467330920/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467333563/">1467333563/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467338422/">1467338422/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467340702/">1467340702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467341842/">1467341842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467343102/">1467343102/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467345202/">1467345202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467346413/">1467346413/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467354382/">1467354382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467360983/">1467360983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467362300/">1467362300/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467362482/">1467362482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467362722/">1467362722/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467362962/">1467362962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467363080/">1467363080/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467363143/">1467363143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467363560/">1467363560/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467364761/">1467364761/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467370161/">1467370161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467370884/">1467370884/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467371842/">1467371842/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467372085/">1467372085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467375023/">1467375023/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467376222/">1467376222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467376342/">1467376342/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467380662/">1467380662/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467381028/">1467381028/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467383663/">1467383663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467387260/">1467387260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467389242/">1467389242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467391523/">1467391523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467392182/">1467392182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467395420/">1467395420/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467396382/">1467396382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467399382/">1467399382/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467399802/">1467399802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467404183/">1467404183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467417862/">1467417862/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467427582/">1467427582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467427822/">1467427822/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467437481/">1467437481/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467451222/">1467451222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467454762/">1467454762/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467459982/">1467459982/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467506902/">1467506902/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467521362/">1467521362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467564802/">1467564802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467567263/">1467567263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467586942/">1467586942/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467604768/">1467604768/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467604886/">1467604886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467605006/">1467605006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467613226/">1467613226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467614664/">1467614664/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467618146/">1467618146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467618686/">1467618686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467620426/">1467620426/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467621506/">1467621506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467630506/">1467630506/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467630686/">1467630686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467630687/">1467630687/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467637886/">1467637886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467640225/">1467640225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467640297/">1467640297/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467640646/">1467640646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467641546/">1467641546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467642158/">1467642158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467642446/">1467642446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467644308/">1467644308/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467645146/">1467645146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467645626/">1467645626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467648206/">1467648206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467652345/">1467652345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467652948/">1467652948/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467654446/">1467654446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467654507/">1467654507/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467656004/">1467656004/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467656362/">1467656362/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467657922/">1467657922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467661582/">1467661582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467670164/">1467670164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467690622/">1467690622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467690802/">1467690802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467690922/">1467690922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467696142/">1467696142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467697518/">1467697518/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467702022/">1467702022/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467705922/">1467705922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467709644/">1467709644/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467710783/">1467710783/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467714383/">1467714383/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467714742/">1467714742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467722305/">1467722305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467724043/">1467724043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467725962/">1467725962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467726263/">1467726263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467726802/">1467726802/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467727703/">1467727703/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467729682/">1467729682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467730282/">1467730282/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467730523/">1467730523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467731725/">1467731725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467737066/">1467737066/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467739522/">1467739522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467740963/">1467740963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467742548/">1467742548/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467742643/">1467742643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467744683/">1467744683/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467745463/">1467745463/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467746062/">1467746062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467748403/">1467748403/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467748946/">1467748946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467750803/">1467750803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467752424/">1467752424/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467757883/">1467757883/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467759262/">1467759262/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467759742/">1467759742/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467763886/">1467763886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467764002/">1467764002/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467769361/">1467769361/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467773963/">1467773963/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467776962/">1467776962/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467777202/">1467777202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467777622/">1467777622/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467780983/">1467780983/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467787043/">1467787043/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467787523/">1467787523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467796283/">1467796283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467796702/">1467796702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467798322/">1467798322/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467798682/">1467798682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467804746/">1467804746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467812123/">1467812123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467812244/">1467812244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467812782/">1467812782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467813922/">1467813922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467814702/">1467814702/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467816082/">1467816082/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467816201/">1467816201/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467816923/">1467816923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467821123/">1467821123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467824543/">1467824543/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467826284/">1467826284/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467828625/">1467828625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467829582/">1467829582/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467831743/">1467831743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467832824/">1467832824/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467836782/">1467836782/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467842183/">1467842183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467846682/">1467846682/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467848542/">1467848542/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467854843/">1467854843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467855143/">1467855143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467856343/">1467856343/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467858923/">1467858923/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467858924/">1467858924/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467862232/">1467862232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467862958/">1467862958/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467863302/">1467863302/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467863482/">1467863482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467863843/">1467863843/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467864804/">1467864804/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467864922/">1467864922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467865042/">1467865042/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467869477/">1467869477/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467870015/">1467870015/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467872062/">1467872062/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467872182/">1467872182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467872482/">1467872482/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467872522/">1467872522/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467878183/">1467878183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467880044/">1467880044/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467880260/">1467880260/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467882083/">1467882083/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467882922/">1467882922/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467884243/">1467884243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467884724/">1467884724/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467886643/">1467886643/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467891065/">1467891065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467895103/">1467895103/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467895585/">1467895585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467900743/">1467900743/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467901887/">1467901887/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467902663/">1467902663/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467903263/">1467903263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467903684/">1467903684/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467904523/">1467904523/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467906144/">1467906144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908105/">1467908105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908111/">1467908111/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908112/">1467908112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908113/">1467908113/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908114/">1467908114/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908115/">1467908115/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908116/">1467908116/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908117/">1467908117/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908118/">1467908118/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908119/">1467908119/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908120/">1467908120/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908121/">1467908121/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908122/">1467908122/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908123/">1467908123/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908124/">1467908124/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908125/">1467908125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908126/">1467908126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908127/">1467908127/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908128/">1467908128/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908129/">1467908129/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908130/">1467908130/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908131/">1467908131/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908132/">1467908132/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908133/">1467908133/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908134/">1467908134/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908135/">1467908135/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908136/">1467908136/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908137/">1467908137/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908138/">1467908138/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908139/">1467908139/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908140/">1467908140/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908141/">1467908141/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908142/">1467908142/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908143/">1467908143/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908144/">1467908144/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908145/">1467908145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908146/">1467908146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908147/">1467908147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908148/">1467908148/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908150/">1467908150/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908152/">1467908152/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908153/">1467908153/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908154/">1467908154/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908155/">1467908155/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908157/">1467908157/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908158/">1467908158/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908159/">1467908159/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908160/">1467908160/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908161/">1467908161/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908162/">1467908162/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908163/">1467908163/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908164/">1467908164/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908165/">1467908165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908166/">1467908166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908167/">1467908167/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908168/">1467908168/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908170/">1467908170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908171/">1467908171/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908173/">1467908173/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908174/">1467908174/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908175/">1467908175/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908176/">1467908176/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908177/">1467908177/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908178/">1467908178/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908179/">1467908179/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908180/">1467908180/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908181/">1467908181/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908182/">1467908182/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908183/">1467908183/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908184/">1467908184/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908186/">1467908186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908187/">1467908187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908189/">1467908189/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908190/">1467908190/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908191/">1467908191/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908193/">1467908193/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908194/">1467908194/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908200/">1467908200/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908202/">1467908202/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908203/">1467908203/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908205/">1467908205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908206/">1467908206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908207/">1467908207/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908208/">1467908208/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908209/">1467908209/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908210/">1467908210/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908212/">1467908212/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908213/">1467908213/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908214/">1467908214/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908215/">1467908215/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908216/">1467908216/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908217/">1467908217/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908219/">1467908219/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908220/">1467908220/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908222/">1467908222/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908223/">1467908223/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908224/">1467908224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908227/">1467908227/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908230/">1467908230/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908232/">1467908232/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908235/">1467908235/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908237/">1467908237/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908239/">1467908239/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908242/">1467908242/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908244/">1467908244/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908247/">1467908247/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908249/">1467908249/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908251/">1467908251/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908254/">1467908254/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908256/">1467908256/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908258/">1467908258/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908261/">1467908261/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908263/">1467908263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908544/">1467908544/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467908603/">1467908603/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467909803/">1467909803/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467910766/">1467910766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467910941/">1467910941/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467912442/">1467912442/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467912652/">1467912652/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467913224/">1467913224/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467916283/">1467916283/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467917723/">1467917723/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467918685/">1467918685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467919405/">1467919405/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467919466/">1467919466/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467923459/">1467923459/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467923786/">1467923786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467925826/">1467925826/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467926786/">1467926786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467927985/">1467927985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467931886/">1467931886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467934218/">1467934218/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467936806/">1467936806/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467938186/">1467938186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467941666/">1467941666/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467943106/">1467943106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467944725/">1467944725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467945086/">1467945086/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467952106/">1467952106/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467952705/">1467952705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467955585/">1467955585/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467955886/">1467955886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467957505/">1467957505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467958646/">1467958646/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467958885/">1467958885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467961165/">1467961165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467961827/">1467961827/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467962185/">1467962185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467963147/">1467963147/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467965306/">1467965306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467966653/">1467966653/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467968305/">1467968305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467969805/">1467969805/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467973046/">1467973046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467974794/">1467974794/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467978446/">1467978446/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467982886/">1467982886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467986245/">1467986245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467988337/">1467988337/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467991226/">1467991226/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467991945/">1467991945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467993445/">1467993445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467995368/">1467995368/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467998065/">1467998065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1467999625/">1467999625/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468005505/">1468005505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468009894/">1468009894/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468015526/">1468015526/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468016546/">1468016546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468016665/">1468016665/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468016785/">1468016785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468017266/">1468017266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468017807/">1468017807/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468018165/">1468018165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468020085/">1468020085/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468020628/">1468020628/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468022005/">1468022005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468023866/">1468023866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468029686/">1468029686/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468030889/">1468030889/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468031465/">1468031465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468032866/">1468032866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468036165/">1468036165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468038026/">1468038026/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468042263/">1468042263/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468043605/">1468043605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468046605/">1468046605/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468048345/">1468048345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468051346/">1468051346/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468056146/">1468056146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468063225/">1468063225/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468063835/">1468063835/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468076305/">1468076305/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468079185/">1468079185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468080146/">1468080146/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468083505/">1468083505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468099586/">1468099586/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468099765/">1468099765/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468107003/">1468107003/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468107685/">1468107685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468113685/">1468113685/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468117913/">1468117913/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468130965/">1468130965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468139453/">1468139453/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468149145/">1468149145/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468150243/">1468150243/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468189885/">1468189885/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468199546/">1468199546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468203985/">1468203985/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468223965/">1468223965/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468224745/">1468224745/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468225286/">1468225286/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468226126/">1468226126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468226306/">1468226306/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468226425/">1468226425/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468226606/">1468226606/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468227206/">1468227206/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468228766/">1468228766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468229186/">1468229186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468230627/">1468230627/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468233658/">1468233658/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468235546/">1468235546/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468236792/">1468236792/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468237825/">1468237825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468239386/">1468239386/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468239505/">1468239505/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468242746/">1468242746/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468243046/">1468243046/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468243527/">1468243527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468245385/">1468245385/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468245866/">1468245866/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468247125/">1468247125/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468249946/">1468249946/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468251926/">1468251926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468255766/">1468255766/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468258272/">1468258272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468258886/">1468258886/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468259245/">1468259245/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468264465/">1468264465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468265185/">1468265185/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468266266/">1468266266/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468267705/">1468267705/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468267825/">1468267825/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468267945/">1468267945/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468268365/">1468268365/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468268785/">1468268785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468269112/">1468269112/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468270465/">1468270465/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468271367/">1468271367/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468271907/">1468271907/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468273529/">1468273529/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468279879/">1468279879/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468280785/">1468280785/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468285105/">1468285105/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468301845/">1468301845/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468302565/">1468302565/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468304006/">1468304006/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468304725/">1468304725/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468307487/">1468307487/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468308626/">1468308626/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468308926/">1468308926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468312272/">1468312272/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468314445/">1468314445/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468314566/">1468314566/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468314926/">1468314926/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468315170/">1468315170/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468315345/">1468315345/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468315525/">1468315525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468315828/">1468315828/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468316005/">1468316005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468316248/">1468316248/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468316485/">1468316485/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468317126/">1468317126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468320327/">1468320327/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468322011/">1468322011/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468323110/">1468323110/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468325246/">1468325246/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468325905/">1468325905/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468331126/">1468331126/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468331187/">1468331187/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468332864/">1468332864/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468333527/">1468333527/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468334186/">1468334186/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468337065/">1468337065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468339165/">1468339165/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468339166/">1468339166/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468339525/">1468339525/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468344205/">1468344205/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468346005/">1468346005/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468346065/">1468346065/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/1468346786/">1468346786/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+ <tr>
+ <td>Dir</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/latest/">latest/</a></td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound-win32-pgo-bm71-build1-build5.txt.gz">mozilla-inbound-win32-pgo-bm71-build1-build5.txt.gz</a></td>
+ <td>701K</td>
+ <td>15-Jan-2016 22:36</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound-win32-pgo-bm72-build1-build202.txt.gz">mozilla-inbound-win32-pgo-bm72-build1-build202.txt.gz</a></td>
+ <td>574K</td>
+ <td>18-May-2016 14:14</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound-win32-pgo-bm73-build1-build218.txt.gz">mozilla-inbound-win32-pgo-bm73-build1-build218.txt.gz</a></td>
+ <td>614K</td>
+ <td>26-Mar-2016 13:09</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound-win32-pgo-bm74-build1-build140.txt.gz">mozilla-inbound-win32-pgo-bm74-build1-build140.txt.gz</a></td>
+ <td>579K</td>
+ <td>17-May-2016 18:25</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound-win32-pgo-bm91-build1-build191.txt.gz">mozilla-inbound-win32-pgo-bm91-build1-build191.txt.gz</a></td>
+ <td>572K</td>
+ <td>26-May-2016 22:49</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-chromez-bm110-tests1-windows-build2478.txt.gz">mozilla-inbound_win7-ix_test-chromez-bm110-tests1-windows-build2478.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-dromaeojs-e10s-bm109-tests1-windows-build1907.txt.gz">mozilla-inbound_win7-ix_test-dromaeojs-e10s-bm109-tests1-windows-build1907.txt.gz</a></td>
+ <td>46K</td>
+ <td>08-Apr-2016 21:07</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-g1-bm111-tests1-windows-build1066.txt.gz">mozilla-inbound_win7-ix_test-g1-bm111-tests1-windows-build1066.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-mochitest-1-bm126-tests1-windows-build96.txt.gz">mozilla-inbound_win7-ix_test-mochitest-1-bm126-tests1-windows-build96.txt.gz</a></td>
+ <td>84K</td>
+ <td>05-Mar-2016 16:10</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-other_nol64-bm110-tests1-windows-build1297.txt.gz">mozilla-inbound_win7-ix_test-other_nol64-bm110-tests1-windows-build1297.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-other_nol64-bm110-tests1-windows-build1298.txt.gz">mozilla-inbound_win7-ix_test-other_nol64-bm110-tests1-windows-build1298.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-svgr-bm110-tests1-windows-build2614.txt.gz">mozilla-inbound_win7-ix_test-svgr-bm110-tests1-windows-build2614.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-svgr-bm110-tests1-windows-build2616.txt.gz">mozilla-inbound_win7-ix_test-svgr-bm110-tests1-windows-build2616.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-tp5o-bm110-tests1-windows-build2394.txt.gz">mozilla-inbound_win7-ix_test-tp5o-bm110-tests1-windows-build2394.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win7-ix_test-xperf-bm112-tests1-windows-build1264.txt.gz">mozilla-inbound_win7-ix_test-xperf-bm112-tests1-windows-build1264.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-chromez-bm110-tests1-windows-build2478.txt.gz">mozilla-inbound_win8_test-chromez-bm110-tests1-windows-build2478.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-chromez-bm111-tests1-windows-build2156.txt.gz">mozilla-inbound_win8_test-chromez-bm111-tests1-windows-build2156.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-dromaeojs-bm111-tests1-windows-build2156.txt.gz">mozilla-inbound_win8_test-dromaeojs-bm111-tests1-windows-build2156.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-g1-bm112-tests1-windows-build622.txt.gz">mozilla-inbound_win8_test-g1-bm112-tests1-windows-build622.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-other_nol64-bm109-tests1-windows-build1352.txt.gz">mozilla-inbound_win8_test-other_nol64-bm109-tests1-windows-build1352.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-svgr-bm110-tests1-windows-build2417.txt.gz">mozilla-inbound_win8_test-svgr-bm110-tests1-windows-build2417.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-tp5o-bm110-tests1-windows-build2444.txt.gz">mozilla-inbound_win8_test-tp5o-bm110-tests1-windows-build2444.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_win8_test-tp5o-bm110-tests1-windows-build2446.txt.gz">mozilla-inbound_win8_test-tp5o-bm110-tests1-windows-build2446.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-chromez-bm109-tests1-windows-build2126.txt.gz">mozilla-inbound_xp-ix_test-chromez-bm109-tests1-windows-build2126.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-chromez-bm110-tests1-windows-build2446.txt.gz">mozilla-inbound_xp-ix_test-chromez-bm110-tests1-windows-build2446.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g1-bm110-tests1-windows-build1410.txt.gz">mozilla-inbound_xp-ix_test-g1-bm110-tests1-windows-build1410.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm111-tests1-windows-build1705.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm111-tests1-windows-build1705.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:18</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1549.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1549.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:18</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1550.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1550.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:24</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1551.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1551.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:24</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1552.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm112-tests1-windows-build1552.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:25</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm119-tests1-windows-build1752.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm119-tests1-windows-build1752.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:25</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm126-tests1-windows-build852.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm126-tests1-windows-build852.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:23</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-g2-e10s-bm126-tests1-windows-build853.txt.gz">mozilla-inbound_xp-ix_test-g2-e10s-bm126-tests1-windows-build853.txt.gz</a></td>
+ <td>26K</td>
+ <td>24-Mar-2016 15:25</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-other_nol64-bm110-tests1-windows-build1386.txt.gz">mozilla-inbound_xp-ix_test-other_nol64-bm110-tests1-windows-build1386.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-svgr-bm109-tests1-windows-build1998.txt.gz">mozilla-inbound_xp-ix_test-svgr-bm109-tests1-windows-build1998.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-svgr-bm112-tests1-windows-build1122.txt.gz">mozilla-inbound_xp-ix_test-svgr-bm112-tests1-windows-build1122.txt.gz</a></td>
+ <td>3K</td>
+ <td>23-Oct-2015 12:40</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-tp5o-e10s-bm109-tests1-windows-build1892.txt.gz">mozilla-inbound_xp-ix_test-tp5o-e10s-bm109-tests1-windows-build1892.txt.gz</a></td>
+ <td>82K</td>
+ <td>15-Apr-2016 16:04</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-tp5o-e10s-bm110-tests1-windows-build1585.txt.gz">mozilla-inbound_xp-ix_test-tp5o-e10s-bm110-tests1-windows-build1585.txt.gz</a></td>
+ <td>82K</td>
+ <td>04-Apr-2016 23:11</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-tp5o-e10s-bm111-tests1-windows-build1739.txt.gz">mozilla-inbound_xp-ix_test-tp5o-e10s-bm111-tests1-windows-build1739.txt.gz</a></td>
+ <td>82K</td>
+ <td>15-Apr-2016 15:33</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-tp5o-e10s-bm119-tests1-windows-build1674.txt.gz">mozilla-inbound_xp-ix_test-tp5o-e10s-bm119-tests1-windows-build1674.txt.gz</a></td>
+ <td>82K</td>
+ <td>15-Apr-2016 16:04</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-tp5o-e10s-bm127-tests1-windows-build874.txt.gz">mozilla-inbound_xp-ix_test-tp5o-e10s-bm127-tests1-windows-build874.txt.gz</a></td>
+ <td>82K</td>
+ <td>15-Apr-2016 16:04</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp-ix_test-tp5o-e10s-bm127-tests1-windows-build875.txt.gz">mozilla-inbound_xp-ix_test-tp5o-e10s-bm127-tests1-windows-build875.txt.gz</a></td>
+ <td>82K</td>
+ <td>15-Apr-2016 16:06</td>
+ </tr>
+
+
+
+ <tr>
+ <td>File</td>
+ <td><a href="/pub/firefox/tinderbox-builds/mozilla-inbound-win32/mozilla-inbound_xp_ix_test-mochitest-devtools-chrome-1-bm110-tests1-windows-build55.txt.gz">mozilla-inbound_xp_ix_test-mochitest-devtools-chrome-1-bm110-tests1-windows-build55.txt.gz</a></td>
+ <td>58K</td>
+ <td>26-May-2016 14:03</td>
+ </tr>
+
+
+ </table>
+</body></html>
diff --git a/layout/generic/test/file_scroll_position_restore_no_bfcache.html b/layout/generic/test/file_scroll_position_restore_no_bfcache.html
new file mode 100644
index 0000000000..5e55771d42
--- /dev/null
+++ b/layout/generic/test/file_scroll_position_restore_no_bfcache.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<style>
+body {
+ height: 100%;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+}
+#scroll {
+ overflow-y: auto;
+ max-height: 100vh;
+}
+#padding {
+ height: 100vh;
+}
+</style>
+<script>
+ window.addEventListener("unload", () => { /* prevent bfcaching */ });
+ window.addEventListener("pageshow", function(e) {
+ window.opener.handlePageShow(e.persisted);
+ });
+</script>
+<!-- This is important as it delays layout, so that stuff goes through PresShell::Initialize under `mDelayedLayoutStart` instead of ContentAppended -->
+<link rel="stylesheet" href="slow-stylesheet.sjs">
+<div id="scroll">
+ <div id="padding"></div>
+ <a href="t1.html">t1.html</a>
+ <div id="padding"></div>
+</div>
diff --git a/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-1.svg b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-1.svg
new file mode 100644
index 0000000000..1a12888583
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<filter id="f1" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox"
+ x="0" y="0" width="1" height="1">
+ <feFlood flood-color="#00ff00" result="flood"/>
+ <feDisplacementMap x="10%" y="10%" width="80%" height="80%" style="color-interpolation-filters:sRGB"
+ in2="SourceGraphic" in="flood" scale="1" xChannelSelector="R" yChannelSelector="G"/>
+</filter>
+<g filter="url(#f1)">
+ <rect x="0" y="0" width="100" height="100" fill="#ff0000"/>
+</g>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-2.svg b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-2.svg
new file mode 100644
index 0000000000..385d083911
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-2.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<filter id="f1" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox"
+ x="0" y="0" width="1" height="1">
+ <feImage xlink:href="http://example.com/tests/layout/generic/test/file_taintedfilters_red-flood-for-feImage.svg" result="flood"/>
+ <feDisplacementMap x="10%" y="10%" width="80%" height="80%" style="color-interpolation-filters:sRGB"
+ in="SourceGraphic" in2="flood" scale="1" xChannelSelector="R" yChannelSelector="G"/>
+</filter>
+<g filter="url(#f1)">
+ <rect x="0" y="0" width="100" height="100" fill="#00ff00"/>
+</g>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-3.svg b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-3.svg
new file mode 100644
index 0000000000..49990ede06
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-3.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<filter id="f1" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox"
+ x="0" y="0" width="1" height="1">
+ <feImage xlink:href="file_taintedfilters_red-flood-for-feImage.svg" result="flood"/>
+ <feMerge result="map">
+ <feMergeNode in="SourceGraphic"/>
+ <feMergeNode in="flood"/>
+ </feMerge>
+ <feDisplacementMap x="10%" y="10%" width="80%" height="80%" style="color-interpolation-filters:sRGB"
+ in="SourceGraphic" in2="map" scale="1" xChannelSelector="R" yChannelSelector="G"/>
+</filter>
+<g filter="url(#f1)">
+ <rect x="0" y="0" width="100" height="100" fill="#00ff00"/>
+</g>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-ref.svg b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-ref.svg
new file mode 100644
index 0000000000..39a175bba6
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_feDisplacementMap-tainted-ref.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<rect x="10" y="10" width="80" height="80" fill="#00ff00"/>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-1.svg b/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-1.svg
new file mode 100644
index 0000000000..dd52f2644f
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<filter id="f1" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox"
+ x="0" y="0" width="1" height="1">
+ <feImage xlink:href="file_taintedfilters_red-flood-for-feImage.svg" result="flood"/>
+ <feDisplacementMap x="10%" y="10%" width="80%" height="80%" style="color-interpolation-filters:sRGB"
+ in="SourceGraphic" in2="flood" scale="1" xChannelSelector="R" yChannelSelector="G"/>
+</filter>
+<g filter="url(#f1)">
+ <rect x="0" y="0" width="100" height="100" fill="#00ff00"/>
+</g>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-2.svg b/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-2.svg
new file mode 100644
index 0000000000..17f7f7c1fc
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-2.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<filter id="f1" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox"
+ x="0" y="0" width="1" height="1">
+ <feImage xlink:href="http://example.com/tests/layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg" result="flood"/>
+ <feDisplacementMap x="10%" y="10%" width="80%" height="80%" style="color-interpolation-filters:sRGB"
+ in="SourceGraphic" in2="flood" scale="1" xChannelSelector="R" yChannelSelector="G"/>
+</filter>
+<g filter="url(#f1)">
+ <rect x="0" y="0" width="100" height="100" fill="#00ff00"/>
+</g>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-ref.svg b/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-ref.svg
new file mode 100644
index 0000000000..8ac9dff0be
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_feDisplacementMap-untainted-ref.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<rect x="10" y="50" width="40" height="40" fill="#00ff00"/>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg b/layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg
new file mode 100644
index 0000000000..2075943365
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+
+<rect x="0" y="0" width="100" height="100" fill="#ff0000"/>
+
+</svg>
diff --git a/layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg^headers^ b/layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg^headers^
new file mode 100644
index 0000000000..144198c6e1
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_red-flood-for-feImage-cors.svg^headers^
@@ -0,0 +1,4 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
+Access-Control-Allow-Credentials: true
+Content-Type: image/svg+xml
+Cache-Control: no-cache, must-revalidate
diff --git a/layout/generic/test/file_taintedfilters_red-flood-for-feImage.svg b/layout/generic/test/file_taintedfilters_red-flood-for-feImage.svg
new file mode 100644
index 0000000000..2075943365
--- /dev/null
+++ b/layout/generic/test/file_taintedfilters_red-flood-for-feImage.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+
+<rect x="0" y="0" width="100" height="100" fill="#ff0000"/>
+
+</svg>
diff --git a/layout/generic/test/frame_selection_underline-ref.xhtml b/layout/generic/test/frame_selection_underline-ref.xhtml
new file mode 100644
index 0000000000..1dd857bab5
--- /dev/null
+++ b/layout/generic/test/frame_selection_underline-ref.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" class="willBeRemoved">
+<head>
+<link rel="stylesheet" type="text/css" href="frame_selection_underline.css"/>
+<script type="text/javascript">
+<![CDATA[
+
+function init(aTest)
+{
+ var target = document.getElementById("target");
+ var decoration = document.getElementById("decoration");
+ var leftSpacer = document.getElementById("leftspacer");
+ var rightSpacer = document.getElementById("rightspacer");
+
+ var docShell = window.docShell;
+ var controller =
+ docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISelectionDisplay)
+ .QueryInterface(Ci.nsISelectionController);
+
+ const nsISelectionController = Ci.nsISelectionController;
+ if (aTest.selection.isIME) {
+ leftSpacer.style.display = rightSpacer.style.display = "inline-block";
+ } else {
+ leftSpacer.style.display = rightSpacer.style.display = "none";
+ }
+
+ target.style.fontFamily = aTest.font.family;
+ target.style.fontSize = aTest.font.defaultSize;
+
+ decoration.style.textDecorationStyle = aTest.decoration.styleName;
+ decoration.style.textDecorationColor = aTest.selection.decorationColor;
+
+ document.documentElement.removeAttribute("class");
+ setTimeout(function () {
+ document.documentElement.setAttribute("class", "willBeRemoved"); }, 0);
+}
+
+]]>
+</script>
+</head>
+<body class="reference">
+ <div id="target"><span id="decoration"><span id="leftspacer">&nbsp;</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span id="rightspacer"></span>&nbsp;</span></div>
+</body>
+</html>
diff --git a/layout/generic/test/frame_selection_underline.css b/layout/generic/test/frame_selection_underline.css
new file mode 100644
index 0000000000..b64ead8814
--- /dev/null
+++ b/layout/generic/test/frame_selection_underline.css
@@ -0,0 +1,51 @@
+html {
+ font-size: 16px;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+}
+
+@font-face {
+ font-family: "AhemTest";
+ src: url(../../../../tests/fonts/Ahem.ttf);
+}
+
+@font-face {
+ font-family: "mplusTest";
+ src: url(../../../../tests/fonts/mplus/mplus-1p-regular.ttf);
+}
+
+/* For aligning the two spacers (see below) to the left most and the right most,
+ the div must create a new blocking format context. */
+div#target {
+ position: absolute;
+}
+
+span#decoration {
+ margin-left: 0.333em;
+}
+
+body.reference div span#decoration {
+ text-decoration: underline;
+}
+
+/* both ends of selection underlines for IME are clipped for making the
+ boundaries of clauses in composition string clear. These spacers will
+ cover the ends in the reference. */
+span#leftspacer, span#rightspacer {
+ background-color: white;
+ position: absolute;
+ width: 1px;
+ height: 100%;
+ overflow: hidden;
+}
+
+span#leftspacer {
+ left: 0.333em;
+}
+
+span#rightspacer {
+ right: 0;
+}
diff --git a/layout/generic/test/frame_selection_underline.xhtml b/layout/generic/test/frame_selection_underline.xhtml
new file mode 100644
index 0000000000..1e4f6790bb
--- /dev/null
+++ b/layout/generic/test/frame_selection_underline.xhtml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" class="willBeRemoved">
+<head>
+<link rel="stylesheet" type="text/css" href="frame_selection_underline.css"/>
+<script type="text/javascript">
+<![CDATA[
+
+function init(aTest)
+{
+ var docShell = window.docShell;
+ var controller =
+ docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISelectionDisplay)
+ .QueryInterface(Ci.nsISelectionController);
+
+ var selections = [
+ controller.SELECTION_SPELLCHECK,
+ controller.SELECTION_IME_RAWINPUT,
+ controller.SELECTION_IME_SELECTEDRAWTEXT,
+ controller.SELECTION_IME_CONVERTEDTEXT,
+ controller.SELECTION_IME_SELECTEDCONVERTEDTEXT,
+ ];
+ for (var i = 0; i < selections.length; i++) {
+ var sel = controller.getSelection(selections[i]);
+ sel.removeAllRanges();
+ }
+
+ var target = document.getElementById("target");
+ target.style.fontFamily = aTest.font.family;
+ target.style.fontSize = aTest.font.defaultSize;
+
+ var range = document.createRange();
+ range.selectNodeContents(target);
+ controller.getSelection(aTest.selection.type).addRange(range);
+
+ document.documentElement.removeAttribute("class");
+ setTimeout(function () {
+ document.documentElement.setAttribute("class", "willBeRemoved"); }, 0);
+}
+
+]]>
+</script>
+</head>
+<body class="test">
+ <div id="target"><span id="decoration">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></div>
+</body>
+</html>
diff --git a/layout/generic/test/frame_visibility_in_iframe.html b/layout/generic/test/frame_visibility_in_iframe.html
new file mode 100644
index 0000000000..9d5a87a34f
--- /dev/null
+++ b/layout/generic/test/frame_visibility_in_iframe.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Video element visibility in iframe</title>
+</head>
+<body>
+<div style="width: 100%; height: calc(100vh - 100px);"></div>
+<div style="height: 100px; overflow: scroll;">
+ <!-- 2x height of the scroll port to make this test pass with/without Fission -->
+ <div style="width: 100%; height: 200px;"></div>
+ <iframe id="iframe" style="height: 100px; border: none;"></iframe>
+</div>
+<script>
+const add_task = window.opener.add_task;
+const SimpleTest = window.opener.SimpleTest;
+const original_finish = window.opener.SimpleTest.finish;
+
+// add_task calls SimpleTest.finish() when it finished, we want to close this
+// window before SimpleTest.finish() gets called.
+SimpleTest.finish = function finish() {
+ self.close();
+ original_finish();
+}
+
+add_task(async () => {
+ const iframe = document.getElementById("iframe");
+ await new Promise(resolve => {
+ iframe.addEventListener("load", () => { resolve(); }, { once: true });
+ iframe.src = "http://example.org/tests/layout/generic/test/frame_visibility_in_iframe_child.html";
+ });
+
+ await SpecialPowers.spawn(iframe, [], async () => {
+ // With layout.visibility.min-recompute-interval-ms=0 value, flushing layout
+ // and waiting two frames would be sufficient to make sure at least one
+ // RebuildApproximateFrameVisibility call was done. This would avoid the
+ // situation we consider the video element is invisible without doing any
+ // frame visibility tracking stuff.
+ content.document.documentElement.getBoundingClientRect();
+ await new Promise(resolve => content.window.requestAnimationFrame(resolve));
+ await new Promise(resolve => content.window.requestAnimationFrame(resolve));
+ });
+
+ await SpecialPowers.spawn(iframe, [], async () => {
+ const video = content.document.querySelector("video");
+ Assert.ok(!SpecialPowers.wrap(video).isInViewPort,
+ "The video element should not be in the viewport");
+ });
+});
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/frame_visibility_in_iframe_child.html b/layout/generic/test/frame_visibility_in_iframe_child.html
new file mode 100644
index 0000000000..f9c86bcb26
--- /dev/null
+++ b/layout/generic/test/frame_visibility_in_iframe_child.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<video></video>
diff --git a/layout/generic/test/mochitest.toml b/layout/generic/test/mochitest.toml
new file mode 100644
index 0000000000..5c06c90eb7
--- /dev/null
+++ b/layout/generic/test/mochitest.toml
@@ -0,0 +1,294 @@
+[DEFAULT]
+prefs = ["gfx.font_loader.delay=0"]
+support-files = [
+ "../../reftests/backgrounds/blue-32x32.png",
+ "../../reftests/backgrounds/fuchsia-32x32.png",
+ "file_BrokenImageReference.png",
+ "file_Dolske.png",
+ "file_IconTestServer.sjs",
+ "file_LoadingImageReference.png",
+ "file_SlowImage.sjs",
+ "file_SlowPage.sjs",
+ "file_SlowTallImage.sjs",
+ "bug1174521.html",
+ "!/gfx/layers/apz/test/mochitest/apz_test_utils.js",
+]
+
+["test_bug240933.html"]
+
+["test_bug263683.html"]
+
+["test_bug288789.html"]
+
+["test_bug290397.html"]
+
+["test_bug323656.html"]
+
+["test_bug344830.html"]
+support-files = ["bug344830_testembed.svg"]
+
+["test_bug348681.html"]
+
+["test_bug382429.html"]
+
+["test_bug384527.html"]
+
+["test_bug385751.html"]
+
+["test_bug389630.html"]
+
+["test_bug391747.html"]
+
+["test_bug392746.html"]
+
+["test_bug392923.html"]
+
+["test_bug394173.html"]
+
+["test_bug394239.html"]
+
+["test_bug402380.html"]
+
+["test_bug404872.html"]
+
+["test_bug405178.html"]
+
+["test_bug416168.html"]
+
+["test_bug421436.html"]
+
+["test_bug421839-1.html"]
+skip-if = ["true"] # Disabled for calling finish twice
+
+["test_bug421839-2.html"]
+support-files = ["bug421839-2-page.html"]
+
+["test_bug424627.html"]
+
+["test_bug438840.html"]
+
+["test_bug448860.html"]
+
+["test_bug448987.html"]
+support-files = [
+ "file_bug448987.html",
+ "file_bug448987_ref.html",
+ "file_bug448987_notref.html",
+]
+
+["test_bug449653.html"]
+support-files = [
+ "file_bug449653_1.html",
+ "file_bug449653_1_ref.html",
+]
+
+["test_bug460532.html"]
+
+["test_bug468167.html"]
+
+["test_bug470212.html"]
+
+["test_bug488417.html"]
+
+["test_bug496275.html"]
+
+["test_bug503813.html"]
+
+["test_bug507902.html"]
+skip-if = ["true"] # Bug 510001
+
+["test_bug522632.html"]
+
+["test_bug524925.html"]
+
+["test_bug579767.html"]
+support-files = [
+ "file_bug579767_1.html",
+ "file_bug579767_2.html",
+]
+skip-if = ["os == 'android'"]
+
+["test_bug589621.html"]
+
+["test_bug589623.html"]
+
+["test_bug597333.html"]
+
+["test_bug633762.html"]
+support-files = ["bug633762_iframe.html"]
+
+["test_bug666225.html"]
+
+["test_bug719503.html"]
+
+["test_bug719515.html"]
+
+["test_bug719518.html"]
+
+["test_bug719523.html"]
+
+["test_bug735641.html"]
+
+["test_bug748961.html"]
+
+["test_bug756984.html"]
+fail-if = ["os == 'linux' && os_version == '18.04'"] # Bug 1600208 permafail on ubuntu1804
+
+["test_bug784410.html"]
+
+["test_bug785324.html"]
+
+["test_bug791616.html"]
+
+["test_bug831780.html"]
+
+["test_bug841361.html"]
+
+["test_bug904810.html"]
+
+["test_bug938772.html"]
+
+["test_bug970363.html"]
+
+["test_bug1062406.html"]
+
+["test_bug1174521.html"]
+
+["test_bug1198135.html"]
+
+["test_bug1307853.html"]
+support-files = ["file_bug1307853.html"]
+
+["test_bug1408607.html"]
+
+["test_bug1499961.html"]
+
+["test_bug1566783.html"]
+support-files = ["file_bug1566783.html"]
+skip-if = ["!debug"] # Bug 1838577
+
+["test_bug1623764.html"]
+
+["test_bug1642588.html"]
+
+["test_bug1644511.html"]
+
+["test_bug1655135.html"]
+
+["test_bug1756831.html"]
+
+["test_bug1803209.html"]
+
+["test_crash_on_mouse_move.html"]
+
+["test_dynamic_reflow_root_disallowal.html"]
+
+["test_flex_interrupt.html"]
+
+["test_frame_visibility_in_iframe.html"]
+support-files = [
+ "frame_visibility_in_iframe.html",
+ "frame_visibility_in_iframe_child.html",
+]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_grid_track_sizing_algo_001.html"]
+
+["test_grid_track_sizing_algo_002.html"]
+
+["test_image_selection.html"]
+
+["test_image_selection_2.html"]
+
+["test_image_selection_3.html"]
+
+["test_image_selection_in_contenteditable.html"]
+
+["test_intrinsic_size_on_loading.html"]
+
+["test_key_enter_open_second_summary.html"]
+
+["test_key_enter_prevent_default.html"]
+
+["test_key_enter_single_summary.html"]
+
+["test_key_space_single_summary.html"]
+
+["test_movement_by_characters.html"]
+
+["test_movement_by_words.html"]
+# Disable the caret movement by word test on Linux because the shortcut keys
+# are defined in system level. So, it depends on the environment.
+skip-if = ["os == 'linux' && os_version == '18.04'"]
+
+["test_overflow_event.html"]
+
+["test_overlay_scrollbar_position.html"]
+
+["test_page_scroll_with_fixed_pos.html"]
+support-files = ["page_scroll_with_fixed_pos_window.html"]
+
+["test_reframe_for_lazy_load_image.html"]
+support-files = ["file_reframe_for_lazy_load_image.html"]
+
+["test_scroll_animation_restore.html"]
+
+["test_scroll_behavior.html"]
+skip-if = ["os == 'android'"]
+
+["test_scroll_on_display_contents.html"]
+support-files = ["!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"]
+skip-if = [
+ "os == 'android'",
+ "display == 'wayland' && os_version == '22.04'", # Bug 1857078
+]
+
+["test_scroll_position_iframe.html"]
+
+["test_scroll_position_restore.html"]
+support-files = ["file_scroll_position_restore.html"]
+skip-if = ["display == 'wayland' && os_version == '22.04'"] # Bug 1857246
+
+["test_scroll_position_restore_after_stop.html"]
+skip-if = ["os == 'android'"]
+
+["test_scroll_position_restore_no_bfcache.html"]
+support-files = [
+ "file_scroll_position_restore_no_bfcache.html",
+ "slow-stylesheet.sjs",
+]
+
+["test_scrollframe_abspos_interrupt.html"]
+
+["test_selection_changes_with_middle_mouse_button.html"]
+
+["test_selection_doubleclick.html"]
+
+["test_selection_expanding.html"]
+
+["test_selection_multiclick_drag.html"]
+
+["test_selection_preventDefault.html"]
+
+["test_selection_splitText-normalize.html"]
+
+["test_selection_touchevents.html"]
+
+["test_selection_tripleclick.html"]
+
+["test_taintedfilters.html"]
+support-files = [
+ "file_taintedfilters_feDisplacementMap-tainted-1.svg",
+ "file_taintedfilters_feDisplacementMap-tainted-2.svg",
+ "file_taintedfilters_feDisplacementMap-tainted-3.svg",
+ "file_taintedfilters_feDisplacementMap-tainted-ref.svg",
+ "file_taintedfilters_feDisplacementMap-untainted-ref.svg",
+ "file_taintedfilters_feDisplacementMap-untainted-1.svg",
+ "file_taintedfilters_feDisplacementMap-untainted-2.svg",
+ "file_taintedfilters_red-flood-for-feImage-cors.svg",
+ "file_taintedfilters_red-flood-for-feImage-cors.svg^headers^",
+ "file_taintedfilters_red-flood-for-feImage.svg",
+]
diff --git a/layout/generic/test/page_scroll_with_fixed_pos_window.html b/layout/generic/test/page_scroll_with_fixed_pos_window.html
new file mode 100644
index 0000000000..9c2b6586c4
--- /dev/null
+++ b/layout/generic/test/page_scroll_with_fixed_pos_window.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, user-scalable=no" />
+ <title>Scrolling by pages with fixed-pos headers and footers</title>
+ <style>
+ .fp { position:fixed; left:0; width:100%; }
+ .fp2 { position:fixed; left:0; width:100%; }
+ </style>
+</head>
+<body onscroll="didScroll()" onload="test()">
+<div class="fp" id="top" style="top:0; height:10px; background:yellow;"></div>
+<div class="fp2" id="top2" style="top:10px; height:11px; background:blue;"></div>
+<div class="fp" style="top:50%; height:7px; background:cyan;"></div>
+<div class="fp2" id="bottom2" style="bottom:9px; height:12px; background:red;"></div>
+<div class="fp" id="bottom" style="bottom:0; height:13px; background:yellow;"></div>
+<p id="target">Something to click on to get focus
+<div style="height:3000px;"></div>
+<pre id="test">
+<script class="testbody">
+var SimpleTest = window.opener.SimpleTest;
+var SpecialPowers = window.opener.SpecialPowers;
+var is = window.opener.is;
+
+function showElements(show, classname) {
+ var elements = document.getElementsByClassName(classname);
+ for (var i = 0; i < elements.length; ++i) {
+ elements[i].style.display = show ? '' : 'none';
+ }
+ document.documentElement.getBoundingClientRect();
+}
+function showFixedPosElements(show) {
+ showElements(show, "fp");
+}
+function showFixedPosElements2(show) {
+ showElements(show, "fp2");
+}
+
+var nextCont;
+function didScroll() {
+ var c = nextCont;
+ nextCont = null;
+ if (c) {
+ c();
+ }
+}
+
+function resetScrollAndScrollDownOnePageWithContinuation(cont) {
+ if (document.documentElement.scrollTop != 0) {
+ document.documentElement.scrollTop = 0;
+ nextCont = function() {
+ setTimeout(function() { scrollDownOnePageWithContinuation(cont) }, 0);
+ };
+ } else {
+ scrollDownOnePageWithContinuation(cont);
+ }
+}
+
+function scrollDownOnePageWithContinuation(cont) {
+ nextCont = cont;
+ window.scrollByPages(1);
+}
+
+function test() {
+ var smoothScrollPref = "general.smoothScroll";
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, runTest);
+}
+
+function runTest() {
+ showFixedPosElements(false);
+ showFixedPosElements2(false);
+ resetScrollAndScrollDownOnePageWithContinuation(function() {
+ var fullPageScrollDown = document.documentElement.scrollTop;
+
+ showFixedPosElements(true);
+ resetScrollAndScrollDownOnePageWithContinuation(function() {
+ var fullPageScrollDownWithHeaderAndFooter = document.documentElement.scrollTop;
+ is(fullPageScrollDownWithHeaderAndFooter, fullPageScrollDown - (10 + 13),
+ "Reduce scroll distance by size of small header and footer");
+
+ document.getElementById("bottom").style.height = (window.innerHeight - 20) + "px";
+ resetScrollAndScrollDownOnePageWithContinuation(function() {
+ is(document.documentElement.scrollTop, fullPageScrollDown - 10,
+ "Ignore really big elements when reducing scroll size");
+ document.getElementById("bottom").style.height = "13px";
+
+ document.getElementById("top").style.width = "100px";
+ resetScrollAndScrollDownOnePageWithContinuation(function() {
+ is(document.documentElement.scrollTop, fullPageScrollDown - 13,
+ "Ignore elements that don't span the entire viewport side");
+ document.getElementById("top").style.width = "100%";
+
+ showFixedPosElements2(true);
+ resetScrollAndScrollDownOnePageWithContinuation(function() {
+ is(document.documentElement.scrollTop, fullPageScrollDown - (10 + 11 + 9 + 12),
+ "Combine multiple overlapping elements");
+ showFixedPosElements2(false);
+
+ document.getElementById("top").style.width = "400px";
+ resetScrollAndScrollDownOnePageWithContinuation(function() {
+ is(document.documentElement.scrollTop, fullPageScrollDown - (10 + 13),
+ "Don't ignore elements that span more than half the viewport side");
+ document.getElementById("top").style.width = "100%";
+
+ document.getElementById("top").style.top = "-40px";
+ document.getElementById("top").style.transform = "translateY(38px)";
+ resetScrollAndScrollDownOnePageWithContinuation(function() {
+ is(document.documentElement.scrollTop,
+ fullPageScrollDown - (10 + 13 - 40 + 38),
+ "Account for offset and transform");
+ document.getElementById("top").style.width = "100%";
+
+ // Scroll back up so test results are visible
+ document.documentElement.scrollTop = 0;
+ SimpleTest.finish();
+ window.close();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/slow-stylesheet.sjs b/layout/generic/test/slow-stylesheet.sjs
new file mode 100644
index 0000000000..f0c7f94e67
--- /dev/null
+++ b/layout/generic/test/slow-stylesheet.sjs
@@ -0,0 +1,19 @@
+// Make sure our timer stays alive.
+let gTimer;
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/css", false);
+ response.setStatusLine("1.1", 200, "OK");
+ response.processAsync();
+
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // Wait for 1s before responding; this should usually make sure this load comes in last.
+ gTimer.init(
+ () => {
+ response.write("body { color: lime }");
+ response.finish();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/generic/test/test_backspace_delete.xhtml b/layout/generic/test/test_backspace_delete.xhtml
new file mode 100644
index 0000000000..2e914bc727
--- /dev/null
+++ b/layout/generic/test/test_backspace_delete.xhtml
@@ -0,0 +1,325 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Test BackSpace/Delete Keys">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function execTests() {
+ var e = document.getElementById("edit");
+ var doc = e.contentDocument;
+ var win = e.contentWindow;
+ var root = doc.documentElement;
+ var editor = doc.body;
+ var sel = win.getSelection();
+ win.focus();
+
+ function setupTest(html, firstChildOffsetForCaret, node) {
+ // Work around bug 474255 --- we need to have nonempty content before we turn on
+ // editing, or the tests below break because the editor doesn't notice when we
+ // insert non-empty content using innerHTML.
+ doc.designMode = 'off';
+ editor.innerHTML = html;
+ doc.designMode = 'on';
+ var n = editor.firstChild;
+ if (node) {
+ n = node();
+ }
+ sel.collapse(n, firstChildOffsetForCaret);
+ }
+
+ var eatSpace;
+ var deleteImmediately;
+
+ function getPrefs(branch) {
+ const prefSvcContractID = "@mozilla.org/preferences-service;1";
+ const prefSvcIID = Ci.nsIPrefService;
+ return Cc[prefSvcContractID].getService(prefSvcIID)
+ .getBranch(branch);
+ }
+
+ function setPref(branch, pref, newValue) {
+ getPrefs(branch).setBoolPref(pref, newValue);
+ return newValue;
+ }
+
+ function restorePref(branch, pref, newValue) {
+ try {
+ getPrefs(branch).clearUserPref(pref);
+ } catch(ex) {}
+ }
+
+ function setEatSpace(newValue) {
+ eatSpace = setPref("layout.word_select.", "eat_space_to_next_word", newValue);
+ }
+
+ function restoreEatSpace() {
+ restorePref("layout.word_select.", "eat_space_to_next_word");
+ }
+
+ function setDeleteImmediately(newValue) {
+ deleteImmediately = setPref("bidi.edit.", "delete_immediately", newValue);
+ }
+
+ function restoreDeleteImmediately() {
+ restorePref("bidi.edit.", "delete_immediately");
+ }
+
+ function doCommand(cmd) {
+ var controller = document.commandDispatcher.getControllerForCommand(cmd);
+ if (controller) {
+ try {
+ controller.doCommand(cmd);
+ ok(true, 'doCommand(' + cmd + ') succeeded');
+ } catch(ex) {
+ ok(false, 'exception in doCommand(' + cmd + '): ', ex.message);
+ }
+ }
+ }
+
+ function testRight(node, offset) {
+ doCommand("cmd_charNext");
+ var msg = "Right movement broken in \"" + editor.innerHTML + "\", offset " + offset;
+ is(sel.anchorNode, node, msg);
+ is(sel.anchorOffset, offset, msg);
+ }
+
+ function selErrString(dir) {
+ return dir + " selection broken with eatSpace=" + eatSpace + " in \"" + editor.innerHTML + "\"";
+ }
+
+ function testWordSelRight(startNode, startOffset, endNode, endOffset) {
+ doCommand("cmd_selectWordNext");
+ var selRange = sel.getRangeAt(0);
+ is(selRange.startContainer, startNode, selErrString("Word right"));
+ is(selRange.startOffset, startOffset, selErrString("Word right"));
+ is(selRange.endContainer, endNode, selErrString("Word right"));
+ is(selRange.endOffset, endOffset, selErrString("Word right"));
+ }
+
+ function testDelete(node, offset, text, richtext) {
+ doCommand("cmd_deleteCharForward");
+ var msg = "Delete broken in \"" + editor.innerHTML + "\", offset " + offset + " with deleteImmediately=" + deleteImmediately;
+ if(typeof node == 'function'){
+ node = node();
+ }
+ is(sel.anchorNode, node, msg);
+
+ is(sel.anchorOffset, offset, msg);
+ let text_result = richtext ? editor.innerHTML : editor.textContent;
+ is(text_result, text, msg);
+ }
+
+ function testBackspace(node, offset, text) {
+ doCommand("cmd_deleteCharBackward");
+ var msg = "Backspace broken in \"" + editor.innerHTML + "\", offset " + offset + " with deleteImmediately=" + deleteImmediately;
+ is(sel.anchorNode, node, msg);
+
+ is(sel.anchorOffset, offset, msg);
+ is(editor.textContent, text, msg);
+ }
+
+ function testDeletePrevWord(node, offset, text) {
+ doCommand("cmd_deleteWordBackward");
+ var msg = "Delete previous word broken in \"" + editor.innerHTML + "\", offset " + offset;
+ is(sel.anchorNode, node, msg);
+ is(sel.anchorOffset, offset, msg);
+ is(editor.textContent, text, msg);
+ }
+
+ function testDeleteNextWord(node, offset, text) {
+ doCommand("cmd_deleteWordForward");
+ var msg = "Delete next word broken in \"" + editor.innerHTML + "\", offset " + offset;
+ is(sel.anchorNode, node, msg);
+ is(sel.anchorOffset, offset, msg);
+ todo_is(editor.textContent, text, msg);
+ }
+
+ // Test cell-wise deletion of Delete
+ setupTest("สวัสดีพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡", 0);
+ testRight(editor.firstChild, 1);
+ testDelete(editor.firstChild, 1, "สสดีพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 2);
+ testDelete(editor.firstChild, 2, "สสพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 4);
+ testDelete(editor.firstChild, 4, "สสพ่à¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 5);
+ testDelete(editor.firstChild, 5, "สสพ่à¹à¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡", false);
+ testRight(editor.firstChild, 8);
+ testDelete(editor.firstChild, 8, "สสพ่à¹à¸žà¸µà¹ˆà¸­à¸‡", false);
+ testRight(editor.firstChild, 9);
+ testDelete(editor.firstChild, 9, "สสพ่à¹à¸žà¸µà¹ˆà¸­", false);
+
+ // Test character-wise deletion of Backspace
+ setupTest("สวัสดีพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡", 0);
+ testRight(editor.firstChild, 1);
+ testBackspace(editor.firstChild, 0, "วัสดีพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 2);
+ testBackspace(editor.firstChild, 1, "วสดีพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 2);
+ testBackspace(editor.firstChild, 1, "วดีพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 3);
+ testBackspace(editor.firstChild, 2, "วดพ่อà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 4);
+ testBackspace(editor.firstChild, 3, "วดพอà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 4);
+ testBackspace(editor.firstChild, 3, "วดพà¹à¸¡à¹ˆà¸žà¸µà¹ˆà¸™à¹‰à¸­à¸‡");
+ testRight(editor.firstChild, 4);
+ testBackspace(editor.firstChild, 3, "วดพม่พี่น้อง");
+ testRight(editor.firstChild, 5);
+ testBackspace(editor.firstChild, 4, "วดพมพี่น้อง");
+ testRight(editor.firstChild, 7);
+ testBackspace(editor.firstChild, 6, "วดพมพีน้อง");
+ testRight(editor.firstChild, 8);
+ testBackspace(editor.firstChild, 7, "วดพมพีนอง");
+ testRight(editor.firstChild, 8);
+ testBackspace(editor.firstChild, 7, "วดพมพีนง");
+ testRight(editor.firstChild, 8);
+ testBackspace(editor.firstChild, 7, "วดพมพีน");
+
+ // Tests for Bug 417745
+
+ setEatSpace(true);
+
+ setupTest("Quick yellow fox", 0);
+ testWordSelRight(editor.firstChild, 0, editor.firstChild, 6);
+ testDelete(editor.firstChild, 0, "yellow fox");
+ testWordSelRight(editor.firstChild, 0, editor.firstChild, 7);
+ testDelete(editor.firstChild, 0, "fox");
+
+ setEatSpace(false);
+
+ setupTest("Quick yellow fox", 0);
+ testWordSelRight(editor.firstChild, 0, editor.firstChild, 5);
+ // editor converts the leading space to an &nbsp;, otherwise it
+ // wouldn't show up which would confuse users
+ testDelete(editor.firstChild, 0, "\u00A0yellow fox");
+ testWordSelRight(editor.firstChild, 0, editor.firstChild, 7);
+ testDelete(editor.firstChild, 0, "\u00A0fox");
+ testWordSelRight(editor.firstChild, 0, editor.firstChild, 4);
+ testDelete(editor, 0, "");
+
+ restoreEatSpace();
+
+ // Tests for Bug 419217
+
+ setupTest("foo<div>bar</div>", 3);
+ testDelete(function(){return editor.firstChild;}, 3, "foobar", true);
+
+ // Tests for Bug 419406
+ var s = "helloשלו×";
+
+ setDeleteImmediately(true);
+
+ setupTest(s, 4);
+ testRight(editor.firstChild, 5);
+ testDelete(editor.firstChild, 5, "helloלו×");
+
+ setDeleteImmediately(false);
+
+ setupTest(s, 4);
+ testRight(editor.firstChild, 5);
+ testDelete(editor.firstChild, 5, "helloשלו×");
+
+ // Tests for bug 1034337
+ s = "اهلاhello";
+
+ setDeleteImmediately(true);
+
+ setupTest(s, 4);
+ // first delete an ltr character to make sure that the caret is ltr
+ testDelete(editor.firstChild, 4, "اهلاello");
+ testBackspace(editor.firstChild, 3, "اهلello");
+
+ setDeleteImmediately(false);
+
+ setupTest(s, 4);
+ // first delete an ltr character to make sure that the caret is ltr
+ testDelete(editor.firstChild, 4, "اهلاello");
+ testBackspace(editor.firstChild, 4, "اهلاello");
+
+ restoreDeleteImmediately();
+
+ // Tests for Bug 462188
+ setupTest("You should not see this text.", 29);
+ testDeletePrevWord(editor.firstChild, 24, "You should not see this ");
+ testDeletePrevWord(editor.firstChild, 19, "You should not see ");
+ testDeletePrevWord(editor.firstChild, 15, "You should not ");
+ testDeletePrevWord(editor.firstChild, 11, "You should ");
+ testDeletePrevWord(editor.firstChild, 4, "You ");
+ testDeletePrevWord(editor, 0, "");
+
+ setupTest("You should not see this text.", 0);
+ testDeleteNextWord(editor.firstChild, 0, "\u00A0should not see this text.");
+ testDeleteNextWord(editor.firstChild, 0, "\u00A0not see this text.");
+ testDeleteNextWord(editor.firstChild, 0, "\u00A0see this text.");
+ testDeleteNextWord(editor.firstChild, 0, "\u00A0this text.");
+ testDeleteNextWord(editor.firstChild, 0, "\u00A0text.");
+ // testDeleteNextWord(editor, 0, "");
+
+ // Tests for Bug 502259
+ setupTest("<p>Bug</p>\n<p>502259</p>", 1);
+ testDelete(function(){return editor.firstChild.firstChild;}, 3, "<p>Bug502259</p>", true);
+
+ // Tests for Bug 507936
+ var nodecallback = function(){return editor.firstChild.firstChild.lastChild.firstChild.lastChild;};
+ setupTest("<ol><li>one<ol><li>two</li></ol></li></ol>\n<p>three</p>", 3, nodecallback);
+ testDelete(nodecallback, 0, "<ol><li>one<ol><li>twothree</li></ol></li></ol>", true);
+
+ setupTest("<ol><li>one<ol><li>two</li></ol></li></ol>\n<hr>\n<p>three</p>", 3, nodecallback);
+ testDelete(nodecallback, 3,
+ "<ol><li>one<ol><li>two</li></ol></li></ol><p>three</p>", true);
+
+ // Tests for Bug 519751
+ var nodecallback = function(){return editor.firstChild.lastChild;};
+ setupTest("<p>one</p><ol><li>two</li><li>three</li></ol>", 3, nodecallback);
+ testDelete(nodecallback, 0, "<p>onetwo</p><ol><li>three</li></ol>", true);
+
+ nodecallback = function(){return editor.firstChild.childNodes[1].firstChild;};
+ setupTest("<ol><li>one</li><li>two</li></ol><ol><li>three</li><li>four</li></ol>", 3, nodecallback);
+ testDelete(function(){return editor.firstChild.childNodes[2].firstChild;},
+ 0, "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>", true);
+ /*todo_is(false, true, 'The above testDelete should use the same nodecallback' +
+ 'as in the proceeding setupTest: the cursor should stay at the end of "two", while currently it is at the beginning of "three" after delete');*/
+
+ // More Tests for Bug 507936
+ nodecallback = function(){return editor.firstChild.firstChild.firstChild;}
+ setupTest("<div><div>abcdef</div><div>bar</div><div>ghi</div></div>", 5, nodecallback);
+ sel.extend(editor.lastChild.lastChild.lastChild, 1);
+ testDelete(editor.lastChild.lastChild.lastChild, 5, "<div><div>abcdehi</div></div>", true);
+
+ setupTest("<div><div>abcdef</div><div>ghi</div></div>", 5, nodecallback);
+ sel.extend(editor.lastChild.lastChild.lastChild, 1);
+ testDelete(editor.lastChild.lastChild.lastChild, 5, "<div><div>abcdehi</div></div>", true);
+
+ nodecallback = function(){return editor.firstChild.firstChild;}
+ setupTest("<div>abcdef<div><div>bar</div>ghi</div></div>", 5, nodecallback);
+ sel.extend(editor.lastChild.lastChild.lastChild, 1);
+ expectednodecallback = function(){return editor.lastChild.lastChild;}
+ testDelete(expectednodecallback, 0, "<div>abcdehi</div>", true);
+
+ setupTest("<div>abcdef<div>ghi</div></div>", 5, nodecallback);
+ sel.extend(editor.lastChild.lastChild.lastChild, 1);
+ testDelete(expectednodecallback, 0, "<div>abcdehi</div>", true);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(execTests);
+]]>
+</script>
+
+<body id="html_body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462188">Mozilla Bug 462188</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+<iframe id="edit" width="200" height="100" src="about:blank"/>
+</body>
+</window>
diff --git a/layout/generic/test/test_bug1062406.html b/layout/generic/test/test_bug1062406.html
new file mode 100644
index 0000000000..6f758d278b
--- /dev/null
+++ b/layout/generic/test/test_bug1062406.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1062406
+-->
+<head>
+ <title>Test for Bug 1062406</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1062406">Mozilla Bug 1062406</a>
+<div id="content" style="display: none">
+
+</div>
+<div style="width:10000px; height:10000px"></div>
+<pre id="test">
+<script type="application/javascript">
+
+ /** Test for Bug 1062406 **/
+ window.scroll(100, 100);
+ window.scroll(NaN, NaN);
+ is(window.scrollX, 0, "window.scroll x parameter must treat NaN as 0.");
+ is(window.scrollY, 0, "window.scroll y parameter must treat NaN as 0.");
+
+ window.scroll(100, 100);
+ window.scroll(Infinity, Infinity);
+ is(window.scrollX, 0, "window.scroll x parameter must treat Infinity as 0.");
+ is(window.scrollY, 0, "window.scroll y parameter must treat Infinity as 0.");
+
+ window.scroll(100, 100);
+ window.scroll(-Infinity, -Infinity);
+ is(window.scrollX, 0, "window.scroll x parameter must treat -Infinity as 0.");
+ is(window.scrollY, 0, "window.scroll y parameter must treat -Infinity as 0.");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug1174521.html b/layout/generic/test/test_bug1174521.html
new file mode 100644
index 0000000000..7faa6d99b3
--- /dev/null
+++ b/layout/generic/test/test_bug1174521.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1174521
+-->
+<head>
+ <title>Test for Bug 1174521</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1174521">Mozilla Bug 1174521</a>
+<div id="content">
+ <iframe src="bug1174521.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+ var link = doc.querySelector("a");
+ SimpleTest.waitForFocus(function() {
+ synthesizeMouseAtCenter(link, {}, win);
+ }, win);
+ });
+
+ onmessage = function(e) {
+ is(e.data.msg, "DONE", "We should be able to click the link");
+ SimpleTest.finish();
+ };
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug1198135.html b/layout/generic/test/test_bug1198135.html
new file mode 100644
index 0000000000..3b5496d79e
--- /dev/null
+++ b/layout/generic/test/test_bug1198135.html
@@ -0,0 +1,89 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1198135
+-->
+<html><head>
+<title>Test for Bug 1198135</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1198135">Mozilla Bug 1198135</a>
+<style>
+.example {
+ perspective: 100px;
+ height: 300px;
+ width: 300px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ border: 1px solid blue;
+}
+
+.example__group {
+ position: relative;
+ transform-style: preserve-3d;
+ height: 300px;
+ top: 0;
+}
+.example__layer {
+ position: absolute;
+ top:0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+.layer--a {
+ box-shadow: inset 0 0 0 1px red;
+}
+.layer--b {
+ box-shadow: inset 0 0 0 1px blue;
+ transform: translateZ(50px) scale(.45);
+}
+.layer--c {
+ box-shadow: inset 0 0 0 1px green;
+}
+.layer--d {
+ box-shadow: inset 00px 0 0px 1px orange;
+ transform: translateZ(50px) scale(.45);
+}
+.layer--e {
+ box-shadow: inset 00px 0 0px 1px orange;
+ transform: translateZ(50px) scale(.45) translateY(300px);
+}
+</style>
+</head>
+
+<body>
+
+<div class="example" id="first">
+ <div class="example__group">
+ <div class="example__layer layer--a"></div>
+ <div class="example__layer layer--b"></div>
+ </div>
+ <div class="example__group">
+ <div class="example__layer layer--c"></div>
+ <div class="example__layer layer--d"></div>
+ </div>
+</div>
+
+<div class="example" id="second">
+ <div class="example__group">
+ <div class="example__layer layer--a"></div>
+ <div class="example__layer layer--b"></div>
+ </div>
+ <div class="example__group">
+ <div class="example__layer layer--c"></div>
+ <div class="example__layer layer--e"></div>
+ </div>
+</div>
+
+<script>
+ is(document.getElementById("first").scrollHeight, 600, "Scroll height should be computed correctly");
+ document.getElementById("first").scrollTop = 150;
+ is(document.getElementById("first").scrollHeight, 600, "Scroll height should be a constant when scrolling");
+
+ // The true height is 727.5 and we don't always snap the same direction.
+ isfuzzy(document.getElementById("second").scrollHeight, 728, 1, "Scroll height should be computed correctly");
+ document.getElementById("second").scrollTop = 150;
+ isfuzzy(document.getElementById("second").scrollHeight, 728, 1, "Scroll height should be a constant when scrolling");
+</script>
+
+</body></html>
diff --git a/layout/generic/test/test_bug1307853.html b/layout/generic/test/test_bug1307853.html
new file mode 100644
index 0000000000..5dbcf4f102
--- /dev/null
+++ b/layout/generic/test/test_bug1307853.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<title>Test for Mozilla Bug 1307853</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+iframe {
+ margin: 0; padding: 0; border: none;
+}
+</style>
+<body onload="run_tests()">
+<iframe id="i" src="file_bug1307853.html" style="width: 200px; height: 100px"></iframe>
+<script>
+function run_tests() {
+ test(function() {
+ var iframe = document.getElementById("i");
+ var idoc = iframe.contentDocument;
+ var iwin = iframe.contentWindow;
+ var inner = idoc.getElementById("inner");
+
+ assert_equals(inner.clientWidth, 60,
+ "width of element before iframe resize");
+ iframe.style.width = "300px";
+ assert_equals(inner.clientWidth, 40,
+ "width of element after iframe resize");
+ },
+ "resize_iframe_percent_padding_fixed_width_boxsizing_borderbox");
+}
+</script>
diff --git a/layout/generic/test/test_bug1408607.html b/layout/generic/test/test_bug1408607.html
new file mode 100644
index 0000000000..381299752d
--- /dev/null
+++ b/layout/generic/test/test_bug1408607.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1408607
+-->
+<head>
+ <title>Test for Bug 1408607</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1408607">Mozilla Bug 1408607</a>
+<iframe id="display" src="about:blank"></iframe>
+<div id="content" style="display: none">
+
+</div>
+
+<div style="margin-left: 1%; border: 1px solid black;">
+ <div style="height: 200px; overflow: auto; transform: translateZ(0);">
+ <div style="height: 100px;"></div>
+ <a id="x" href="./">Hello</a>
+ <div style="height: 500px;"></div>
+ </div>
+</div
+
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", step1);
+
+
+function step1()
+{
+ var rect = document.getElementById("x").getBoundingClientRect();
+
+ is(document.elementFromPoint(rect.x + rect.width/2, rect.y + rect.height/2), document.getElementById("x"), "Able to hit transformed object");
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug1499961.html b/layout/generic/test/test_bug1499961.html
new file mode 100644
index 0000000000..60f166bab4
--- /dev/null
+++ b/layout/generic/test/test_bug1499961.html
@@ -0,0 +1,409 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1499961
+
+Some tests ported from IntersectionObserver/polyfill/intersection-observer-test.html
+
+Original license header:
+
+Copyright 2016 Google Inc. All Rights Reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1499961</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1499961">Mozilla Bug 1499961</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+ /* eslint-disable no-shadow */
+ var tests = [];
+ var curDescribeMsg = '';
+ var curItMsg = '';
+
+ function beforeEach_fn() { };
+ function afterEach_fn() { };
+
+ function before(fn) {
+ fn();
+ }
+
+ function beforeEach(fn) {
+ beforeEach_fn = fn;
+ }
+
+ function afterEach(fn) {
+ afterEach_fn = fn;
+ }
+
+ function it(msg, fn) {
+ tests.push({
+ msg: `${msg} [${curDescribeMsg}]`,
+ fn: fn
+ });
+ }
+
+ var callbacks = [];
+ function callDelayed(fn) {
+ callbacks.push(fn);
+ }
+
+ requestAnimationFrame(function tick() {
+ var i = callbacks.length;
+ while (i--) {
+ var cb = callbacks[i];
+ SimpleTest.executeSoon(function() { SimpleTest.executeSoon(cb) });
+ callbacks.splice(i, 1);
+ }
+ requestAnimationFrame(tick);
+ });
+
+ function expect(val) {
+ return {
+ to: {
+ throwException: function (regexp) {
+ try {
+ val();
+ ok(false, `${curItMsg} - an exception should have beeen thrown`);
+ } catch (e) {
+ ok(regexp.test(e), `${curItMsg} - supplied regexp should match thrown exception`);
+ }
+ },
+ get be() {
+ var fn = function (expected) {
+ is(val, expected, curItMsg);
+ };
+ fn.ok = function () {
+ ok(val, curItMsg);
+ };
+ fn.greaterThan = function (other) {
+ ok(val > other, `${curItMsg} - ${val} should be greater than ${other}`);
+ };
+ fn.lessThan = function (other) {
+ ok(val < other, `${curItMsg} - ${val} should be less than ${other}`);
+ };
+ return fn;
+ },
+ eql: function (expected) {
+ if (Array.isArray(expected)) {
+ if (!Array.isArray(val)) {
+ ok(false, curItMsg, `${curItMsg} - should be an array,`);
+ return;
+ }
+ is(val.length, expected.length, curItMsg, `${curItMsg} - arrays should be the same length`);
+ if (expected.length != val.length) {
+ return;
+ }
+ for (var i = 0; i < expected.length; i++) {
+ is(val[i], expected[i], `${curItMsg} - array elements at position ${i} should be equal`);
+ if (expected[i] != val[i]) {
+ return;
+ }
+ }
+ ok(true);
+ }
+ },
+ }
+ }
+ }
+
+ function describe(msg, fn) {
+ curDescribeMsg = msg;
+ fn();
+ curDescribeMsg = '';
+ }
+
+ function next() {
+ var test = tests.shift();
+ if (test) {
+ console.log(test.msg);
+ curItMsg = test.msg;
+ var fn = test.fn;
+ beforeEach_fn();
+ if (fn.length) {
+ fn(function () {
+ afterEach_fn();
+ next();
+ });
+ } else {
+ fn();
+ afterEach_fn();
+ next();
+ }
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ var sinon = {
+ spy: function () {
+ var callbacks = [];
+ var fn = function () {
+ fn.callCount++;
+ fn.lastCall = { args: arguments };
+ if (callbacks.length) {
+ callbacks.shift()();
+ }
+ };
+ fn.callCount = 0;
+ fn.lastCall = { args: [] };
+ fn.waitForNotification = (fn) => {
+ callbacks.push(fn);
+ };
+ return fn;
+ }
+ };
+
+ var ASYNC_TIMEOUT = 300;
+
+
+ var io;
+ var noop = function() {};
+
+
+ // References to DOM elements, which are accessible to any test
+ // and reset prior to each test so state isn't shared.
+ var rootEl;
+ var grandParentEl;
+ var parentEl;
+ var targetEl1;
+ var targetEl2;
+ var targetEl3;
+ var targetEl4;
+ var targetEl5;
+
+
+ describe('IntersectionObserver', function() {
+
+ before(function() {
+
+ });
+
+
+ beforeEach(function() {
+ addStyles();
+ addFixtures();
+ });
+
+
+ afterEach(function() {
+ if (io && 'disconnect' in io) io.disconnect();
+ io = null;
+
+ window.onmessage = null;
+
+ removeStyles();
+ removeFixtures();
+ });
+
+
+ describe('constructor', function() {
+
+ it('move iframe and check reflow', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ // Do a first change and wait for its intersection observer
+ // notification, to ensure one full reflow was completed.
+ function(done) {
+ targetEl1.style.top = '0px';
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ },
+ // Do another change, which may trigger an incremental reflow only.
+ function(done) {
+ targetEl4.style.top = '-20px';
+ targetEl4.style.left = '20px';
+ io.observe(targetEl4);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].target).to.be(targetEl4);
+ // After the iframe is moved, reflow should include its parent,
+ // even if the iframe is a reflow root.
+ // If moved correctly (outside of rootEl), the intersection ratio
+ // should now be 0.
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+ });
+
+ });
+
+
+ /**
+ * Runs a sequence of function and when finished invokes the done callback.
+ * Each function in the sequence is invoked with its own done function and
+ * it should call that function once it's complete.
+ * @param {Array<Function>} functions An array of async functions.
+ * @param {Function} done A final callback to be invoked once all function
+ * have run.
+ */
+ function runSequence(functions, done) {
+ var next = functions.shift();
+ if (next) {
+ next(function() {
+ runSequence(functions, done);
+ });
+ } else {
+ done && done();
+ }
+ }
+
+
+ /**
+ * Sorts an array of records alphebetically by ascending ID. Since the current
+ * native implementation doesn't sort change entries by `observe` order, we do
+ * that ourselves for the non-polyfill case. Since all tests call observe
+ * on targets in sequential order, this should always match.
+ * https://crbug.com/613679
+ * @param {Array<IntersectionObserverEntry>} entries The entries to sort.
+ * @return {Array<IntersectionObserverEntry>} The sorted array.
+ */
+ function sortRecords(entries) {
+ entries = entries.sort(function(a, b) {
+ return a.target.id < b.target.id ? -1 : 1;
+ });
+ return entries;
+ }
+
+
+ /**
+ * Adds the common styles used by all tests to the page.
+ */
+ function addStyles() {
+ var styles = document.createElement('style');
+ styles.id = 'styles';
+ document.documentElement.appendChild(styles);
+
+ var cssText =
+ '#root {' +
+ ' position: relative;' +
+ ' width: 400px;' +
+ ' height: 200px;' +
+ ' background: #eee' +
+ '}' +
+ '#grand-parent {' +
+ ' position: relative;' +
+ ' width: 200px;' +
+ ' height: 200px;' +
+ '}' +
+ '#parent {' +
+ ' position: absolute;' +
+ ' top: 0px;' +
+ ' left: 200px;' +
+ ' overflow: hidden;' +
+ ' width: 200px;' +
+ ' height: 200px;' +
+ ' background: #ddd;' +
+ '}' +
+ '#target1, #target2, #target3, #target4 {' +
+ ' position: absolute;' +
+ ' top: 0px;' +
+ ' left: 0px;' +
+ ' width: 20px;' +
+ ' height: 20px;' +
+ ' transform: translateX(0px) translateY(0px);' +
+ ' transition: transform .5s;' +
+ ' background: #f00;' +
+ ' border: none;' +
+ '}';
+
+ styles.innerHTML = cssText;
+ }
+
+
+ /**
+ * Adds the DOM fixtures used by all tests to the page and assigns them to
+ * global variables so they can be referenced within the tests.
+ */
+ function addFixtures() {
+ var fixtures = document.createElement('div');
+ fixtures.id = 'fixtures';
+
+ fixtures.innerHTML =
+ '<div id="root">' +
+ ' <div id="grand-parent">' +
+ ' <div id="parent">' +
+ ' <div id="target1"></div>' +
+ ' <div id="target2"></div>' +
+ ' <div id="target3"></div>' +
+ ' <iframe id="target4"></iframe>' +
+ ' </div>' +
+ ' </div>' +
+ '</div>';
+
+ document.body.appendChild(fixtures);
+
+ rootEl = document.getElementById('root');
+ grandParentEl = document.getElementById('grand-parent');
+ parentEl = document.getElementById('parent');
+ targetEl1 = document.getElementById('target1');
+ targetEl2 = document.getElementById('target2');
+ targetEl3 = document.getElementById('target3');
+ targetEl4 = document.getElementById('target4');
+ }
+
+
+ /**
+ * Removes the common styles from the page.
+ */
+ function removeStyles() {
+ var styles = document.getElementById('styles');
+ styles.remove();
+ }
+
+
+ /**
+ * Removes the DOM fixtures from the page and resets the global references.
+ */
+ function removeFixtures() {
+ var fixtures = document.getElementById('fixtures');
+ fixtures.remove();
+
+ rootEl = null;
+ grandParentEl = null;
+ parentEl = null;
+ targetEl1 = null;
+ targetEl2 = null;
+ targetEl3 = null;
+ targetEl4 = null;
+ }
+
+ function onLoad() {
+ next();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug1566783.html b/layout/generic/test/test_bug1566783.html
new file mode 100644
index 0000000000..4632b0718b
--- /dev/null
+++ b/layout/generic/test/test_bug1566783.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>Test for scroll anchoring adjustments during onload</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ SimpleTest.waitForExplicitFinish();
+</script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<iframe width="300" height="300" src="file_bug1566783.html#slow"></iframe>
diff --git a/layout/generic/test/test_bug1623764.html b/layout/generic/test/test_bug1623764.html
new file mode 100644
index 0000000000..eba1bbf85f
--- /dev/null
+++ b/layout/generic/test/test_bug1623764.html
@@ -0,0 +1,292 @@
+<!DOCTYPE html>
+<title>Test Windows conventional caret movement behavior</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<style>
+ [contenteditable] {
+ font-size: 14px;
+ font-family: monospace;
+ overflow-wrap: normal;
+ outline: none;
+ width: 35ch;
+ }
+ textarea {
+ font-size: 14px;
+ font-family: monospace;
+ }
+
+ .break-word {
+ overflow-wrap: break-word;
+ }
+</style>
+<div id="testDiv" contenteditable></div>
+<textarea id="testTextarea" cols=35></textarea>
+
+<script>
+// Call testEach(cases[i]) on console when debugging
+/**
+ * @type {TestCase[]}
+ *
+ * @typedef {object} TestCase
+ * @property {string} title
+ * @property {string} text Text on which the test runs.
+ * The newlines and spaces will be auto-converted if needed.
+ * @property {boolean} [reverse] Test in both forward and backward directions
+ * @property {boolean} [backward] Move backward by cmd_wordPrevious
+ * @property {ElementSpecific} [div] Can be omitted when there is a bug
+ * @property {ElementSpecific} textarea
+ *
+ * @typedef {object} ElementSpecific
+ * @property {number} expectedOffset Expected offset after a caret move
+ * @property {number} [expectedNodeIndex] The index of child node with focus after a caret move.
+ * -1 means the parent node.
+ * @property {number} [initialOffset=0] initialOffset offset before a caret move
+ * @property {number} [initialNodeIndex]
+ */
+
+const kFirstWordLength = "Supercalifragilisticexpialidocious".length; // 34
+const kParentNodeIndex = -1; // special value
+const cases = [
+ {
+ title: "Eats inline whitespaces after word",
+ text: "Supercalifragilisticexpialidocious foo bar fussball",
+ reverse: true,
+ div: {
+ expectedOffset: kFirstWordLength + 1
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Eats inline whitespaces across a wrapped line after word",
+ text: "Supercalifragilisticexpialidocious foo bar fussball",
+ reverse: true,
+ div: {
+ expectedOffset: kFirstWordLength + 7
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 7
+ }
+ },
+ {
+ title: "Eats inline whitespaces across multiple wrapped lines after word",
+ text: `Supercalifragilisticexpialidocious foo bar fussball`,
+ reverse: true,
+ div: {
+ expectedOffset: kFirstWordLength + 65
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 65
+ }
+ },
+ {
+ title: "Eats inline whitespaces after a whole word and stop before a newline",
+ text: "Supercalifragilisticexpialidocious \nfoo bar fussball",
+ reverse: true,
+ div: {
+ expectedOffset: 1,
+ expectedNodeIndex: kParentNodeIndex
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Eats inline whitespaces after a partial word and stop before a newline",
+ text: "Supercalifragilisticexpialidocious \nfoo bar fussball",
+ div: {
+ initialOffset: 5,
+ expectedOffset: 1,
+ expectedNodeIndex: kParentNodeIndex
+ },
+ textarea: {
+ initialOffset: 5,
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Eats inline whitespaces without a word and stop before a newline",
+ text: "Supercalifragilisticexpialidocious \n foo bar fussball",
+ // TODO(krosylight): Currently ClusterIterator internally skips trailing whitespace
+ // div: {
+ // initialOffset: kFirstWordLength,
+ // expectedOffset: 1,
+ // expectedNodeIndex: kParentNodeIndex
+ // },
+ textarea: {
+ initialOffset: kFirstWordLength,
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Jumps to the next line and eats inline whitespaces",
+ text: "Supercalifragilisticexpialidocious \n foo bar fussball",
+ reverse: true,
+ div: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: 6,
+ expectedNodeIndex: 2
+ },
+ textarea: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: kFirstWordLength + 8
+ }
+ },
+ {
+ title: "Stops on whitespaces after word",
+ text: "Supercalifragilisticexpialidocious \n foo bar fussball",
+ backward: true,
+ div: {
+ initialOffset: 8,
+ initialNodeIndex: 2,
+ expectedOffset: 6,
+ expectedNodeIndex: 2
+ },
+ textarea: {
+ initialOffset: 44, // middle of "foo"
+ expectedOffset: 42
+ }
+ },
+ // TODO(krosylight): Currently no way to tell a word break is from line wrapping
+ // {
+ // title: "Ignore a word break introduced by line wrapping",
+ // text: "Supercalifragilisticexpialidociouspostfix",
+ // className: "narrow break-word",
+ // div: {
+ // expectedOffset: kFirstWordLength + 7
+ // },
+ // textarea: {
+ // expectedOffset: kFirstWordLength + 7
+ // }
+ // }
+ {
+ title: "Jump only one line at an empty line end",
+ text: "Supercalifragilisticexpialidocious \n\nfoo bar fussball",
+ reverse: true,
+ div: {
+ initialOffset: 2,
+ initialNodeIndex: kParentNodeIndex,
+ expectedOffset: 0,
+ expectedNodeIndex: 3
+ },
+ textarea: {
+ initialOffset: kFirstWordLength + 2,
+ expectedOffset: kFirstWordLength + 3
+ }
+ },
+ {
+ title: "Jump only one line at a non-empty line end",
+ text: "Supercalifragilisticexpialidocious \n\nfoo bar fussball",
+ reverse: true,
+ div: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: 2,
+ expectedNodeIndex: -1
+ },
+ textarea: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: kFirstWordLength + 2
+ }
+ },
+];
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["layout.word_select.eat_space_to_next_word", true]]
+ });
+ // Test on div first to prevent Bug 1623413
+ for (const testCase of cases) {
+ if (testCase.div) {
+ testOnDiv(testCase);
+ }
+ }
+ for (const testCase of cases) {
+ testOnTextarea(testCase);
+ }
+ SimpleTest.finish();
+});
+
+/** @param {TestCase} testCase */
+function testOnDiv(testCase) {
+ const {
+ title,
+ backward = false,
+ reverse = false,
+ } = testCase;
+ const {
+ initialOffset = 0,
+ expectedOffset,
+ } = testCase.div;
+ if (expectedOffset === undefined) {
+ throw new Error("`expectedOffset` must be defined.")
+ }
+
+ testDiv.innerHTML = testCase.text.replaceAll(/ {2}/g, " &nbsp;").replaceAll(/\n/g, "<br>");
+ const initialNode = childNode(testDiv, testCase.div.initialNodeIndex);
+ const expectedNode = childNode(testDiv, testCase.div.expectedNodeIndex);
+
+ const selection = getSelection();
+ selection.collapse(initialNode, initialOffset);
+
+ moveByWord(backward);
+
+ is(selection.focusNode, expectedNode, `focusNode in "${title}"`);
+ is(selection.focusOffset, expectedOffset, `focusOffset in "${title}"`);
+
+ if (reverse) {
+ selection.collapse(expectedNode, expectedOffset);
+
+ moveByWord(!backward);
+
+ is(selection.focusNode, initialNode, `focusNode with reversed selection in "${title}"`);
+ is(selection.focusOffset, initialOffset, `focusOffset with reversed selection in "${title}"`);
+ }
+}
+
+function testOnTextarea(testCase) {
+ const {
+ title,
+ backward = false,
+ reverse = false,
+ } = testCase;
+ const {
+ initialOffset = 0,
+ expectedOffset,
+ } = testCase.textarea;
+ if (expectedOffset === undefined) {
+ throw new Error("`expectedOffset` must be defined.")
+ }
+
+ testTextarea.value = testCase.text;
+ testTextarea.selectionStart = testTextarea.selectionEnd = initialOffset;
+ testTextarea.focus();
+
+ moveByWord(backward);
+
+ is(testTextarea.selectionStart, expectedOffset, `selectionStart in "${title}"`);
+
+ if (reverse) {
+ testTextarea.selectionStart = testTextarea.selectionEnd = expectedOffset;
+
+ moveByWord(!backward);
+
+ is(testTextarea.selectionStart, initialOffset, `selectionStart with reversed selection in "${title}"`);
+ }
+}
+
+function childNode(parent, index = 0) {
+ if (index === kParentNodeIndex) {
+ return parent;
+ }
+ return parent.childNodes[index];
+}
+
+/** @param {boolean} backward */
+function moveByWord(backward) {
+ const dir = backward ? "Previous" : "Next";
+ SpecialPowers.doCommand(window, `cmd_word${dir}`);
+}
+</script>
diff --git a/layout/generic/test/test_bug1642588.html b/layout/generic/test/test_bug1642588.html
new file mode 100644
index 0000000000..56ebc6fc1e
--- /dev/null
+++ b/layout/generic/test/test_bug1642588.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<title>Test for Bug 1642588</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ [contenteditable] {
+ padding: .5em 40%;
+ }
+</style>
+
+<div>
+ <p>
+ Intentionally not in WPT as triple click is not guaranteed to have same
+ behavior on every browser.
+ </p>
+ <span contenteditable></span><hr>
+ <div contenteditable style="display: inline;"></div><hr>
+ <div contenteditable style="display: inline-block;"></div><hr>
+ <!--
+ FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1654364
+ <table contenteditable style="display: inline-table;"><tr><td></table><hr>
+ -->
+ <div contenteditable style="display: inline-flex;"></div><hr>
+ <div contenteditable style="display: inline-grid;"></div>
+</div>
+
+<script>
+function selectHostByClicks(host, number) {
+ for (let i = 1; i <= number; i++) {
+ synthesizeMouseAtCenter(host, { clickCount: i });
+ }
+}
+
+function nonCollapsedDeletionTest(host, clickCount) {
+ host.textContent = "contenteditable";
+ selectHostByClicks(host, clickCount);
+ const selection = getSelection();
+
+ is(selection.isCollapsed, false, "Noncollapsed selection should occur");
+
+ synthesizeKey("KEY_Backspace");
+ is(
+ host.textContent,
+ "",
+ `Backspace should delete the content of the editing host (<div contenteditable style="${
+ host.getAttribute("style")
+ }">)`);
+}
+
+function collapsedSelectionTest(host, clickCount) {
+ host.textContent = "";
+ selectHostByClicks(host, clickCount);
+ const selection = getSelection();
+
+ is(selection.isCollapsed, true, "Collapsed selection should occur");
+ is(selection.anchorNode, host, "Caret should be in host");
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(() => {
+ const hosts = document.querySelectorAll("[contenteditable]");
+ for (const host of hosts) {
+ nonCollapsedDeletionTest(host, 2);
+ nonCollapsedDeletionTest(host, 3);
+ collapsedSelectionTest(host, 3);
+ }
+ SimpleTest.finish();
+});
+</script>
diff --git a/layout/generic/test/test_bug1644511.html b/layout/generic/test/test_bug1644511.html
new file mode 100644
index 0000000000..e9e82406d2
--- /dev/null
+++ b/layout/generic/test/test_bug1644511.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for Bug 1644511</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ [contenteditable] {
+ padding: .5em 40%;
+ }
+</style>
+
+<div id="host" contenteditable="" dir="rtl">مرحبا عالم!<br>Hello World</div>
+
+<button onclick="subtestLTR()"></button>
+
+<script>
+const caretMovementStyleFlag = "bidi.edit.caret_movement_style";
+
+/**
+ * Can't use synthesizeKey("KEY_Arrow*") as it triggers
+ * nsFrameSelection::PhysicalMove() instead of CharacterMove() and thus
+ * suppresses the flag. See also Bug 1644489.
+ */
+function moveCaret(aRight, aSelect) {
+ const select = aSelect ? "selectChar" : "char";
+ const dir = aRight ? "Next" : "Previous";
+ SpecialPowers.doCommand(window, `cmd_${select}${dir}`);
+}
+
+/**
+ * Safer to directly select text node when the paragraph ends with LTR text as
+ * the left edge is the end of the paragraph and then also the start of the LTR
+ * text.
+ */
+function putCaretInLastLine(div) {
+ const { lastChild } = div;
+ getSelection().collapse(lastChild, 1); // Pass 1 because of bug 1645877
+}
+
+// LTR behavior should be always same regardless of the flag
+function subtestLTR() {
+ putCaretInLastLine(host);
+ moveCaret(true, true);
+ is(getSelection().anchorOffset, 1, "Shift+ArrowRight should select from left");
+ is(getSelection().focusOffset, 2, "Shift+ArrowRight should select to right");
+ moveCaret(true, false);
+ is(getSelection().anchorOffset, 2, "Collapsing by ArrowRight should put the caret to the right side");
+
+ putCaretInLastLine(host);
+ moveCaret(true, true);
+ moveCaret(false, false);
+ is(getSelection().anchorOffset, 1, "Collapsing by ArrowLeft should put the caret to the left side");
+}
+
+async function testLogicalMovement() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[caretMovementStyleFlag, 0]]
+ });
+ getSelection().collapse(host);
+ moveCaret(true, true);
+ is(getSelection().anchorOffset, 0, "Shift+ArrowRight should select from right");
+ is(getSelection().focusOffset, 1, "Shift+ArrowRight should select to left");
+ moveCaret(true, false);
+ is(getSelection().anchorOffset, 1, "Collapsing by ArrowRight should put the caret to the left side");
+
+ getSelection().collapse(host);
+ moveCaret(true, true);
+ moveCaret(false, false);
+ is(getSelection().anchorOffset, 0, "Collapsing by ArrowLeft should put the caret to the right side");
+
+ subtestLTR();
+}
+
+async function testVisualMovement() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[caretMovementStyleFlag, 1]]
+ });
+ getSelection().collapse(host);
+ moveCaret(false, true);
+ is(getSelection().anchorOffset, 0, "Shift+ArrowLeft should select from right");
+ is(getSelection().focusOffset, 1, "Shift+ArrowLeft should select to left");
+ moveCaret(false, false);
+ is(getSelection().anchorOffset, 1, "Collapsing by ArrowLeft should put the caret to the left side");
+
+ getSelection().collapse(host);
+ moveCaret(false, true);
+ moveCaret(true, false);
+ is(getSelection().anchorOffset, 0, "Collapsing by ArrowRight should put the caret to the right side");
+
+ subtestLTR();
+}
+
+async function testHybridMovement() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[caretMovementStyleFlag, 2]]
+ });
+ getSelection().collapse(host);
+ moveCaret(true, true);
+ is(getSelection().anchorOffset, 0, "Shift+ArrowRight should select from right");
+ is(getSelection().focusOffset, 1, "Shift+ArrowRight should select to left");
+ moveCaret(false, false);
+ is(getSelection().anchorOffset, 1, "Collapsing by ArrowLeft should put the caret to the left side");
+
+ getSelection().collapse(host);
+ moveCaret(true, true);
+ moveCaret(true, false);
+ is(getSelection().anchorOffset, 0, "Collapsing by ArrowRight should put the caret to the right side");
+
+ subtestLTR();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.promiseFocus().then(async () => {
+ await testLogicalMovement();
+ await testVisualMovement();
+ await testHybridMovement();
+ SimpleTest.finish();
+});
+</script>
diff --git a/layout/generic/test/test_bug1655135.html b/layout/generic/test/test_bug1655135.html
new file mode 100644
index 0000000000..186983c0af
--- /dev/null
+++ b/layout/generic/test/test_bug1655135.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1655135
+-->
+<head>
+ <title>Test for Bug 1655135</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1655135">Mozilla Bug 1655135</a>
+<p id="display"></p>
+<div id="content">
+<textarea style="white-space: pre-line">foo
+bar</textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set": [["layout.word_select.eat_space_to_next_word", true],
+ ["browser.triple_click_selects_paragraph", false]]}, startTest);
+});
+function startTest() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = textarea.selectionEnd = 0;
+
+ // Simulate a double click on foo
+ synthesizeMouse(textarea, 5, 5, {clickCount: 2});
+
+ ok(true, "Testing word selection");
+ is(textarea.selectionStart, 0, "The start of the selection should be at the beginning of the text");
+ is(textarea.selectionEnd, 3, "The end of the selection should not include a newline character");
+
+ textarea.selectionStart = textarea.selectionEnd = 0;
+
+ // Simulate a triple click on foo
+ synthesizeMouse(textarea, 5, 5, {clickCount: 3});
+
+ ok(true, "Testing line selection");
+ is(textarea.selectionStart, 0, "The start of the selection should be at the beginning of the text");
+ is(textarea.selectionEnd, 3, "The end of the selection should not include a newline character");
+
+ textarea.selectionStart = textarea.selectionEnd = 0;
+ textarea.value = "Very very long value which would eventually overflow the visible section of the textarea";
+
+ // Simulate a quadruple click on Very
+ synthesizeMouse(textarea, 5, 5, {clickCount: 4});
+
+ ok(true, "Testing paragraph selection");
+ is(textarea.selectionStart, 0, "The start of the selection should be at the beginning of the text");
+ is(textarea.selectionEnd, textarea.value.length, "The end of the selection should be the end of the paragraph");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug1756831.html b/layout/generic/test/test_bug1756831.html
new file mode 100644
index 0000000000..c95a79b84e
--- /dev/null
+++ b/layout/generic/test/test_bug1756831.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1756831
+-->
+<head>
+ <title>Test for Bug 1756831</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+
+<style>
+.scroller {
+ height: 300px;
+ width: 300px;
+ overflow: hidden;
+ border: 1px solid grey;
+}
+
+.long-content {
+ height: 300vh;
+}
+
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.1);
+ z-index: 10;
+}
+
+</style>
+<div id="sc" class="scroller">
+
+ <div class="long-content">
+ <div style="width: 30px; height: 30px; background: blue;"></div>
+ <div class="modal"></div>
+ </div>
+
+</div>
+
+
+<script>
+SimpleTest.requestFlakyTimeout("need to wait for overlay scrollbars to fade out");
+SimpleTest.waitForExplicitFinish();
+// Test that mousing over content descendants that are not scrollable does not
+// scroll the overlay scrollbars.
+
+/* We are testing overlay scrollbars here, specifically those that display when
+ the mouse is moved over scrollable content. The first two prefs are for
+ that. We set the prefs that determine the duration of the scrollbar fade out
+ so they are uniform for us. It would be nice if we could set a very short
+ duration so we didn't have to wait, or a very long duration so as to prevent
+ the possibility of the scrollbars disappearing before we take a screenshot
+ to compare against, however neither of those are workable. If the duration
+ is too short we risk the scrollbars disappearing before we can screenshot
+ them. If the duration is too long the test takes a long time. We can't
+ change the duration prefs part way through the test because the prefs are
+ only read when the a scroll frame is created or when a scroll frame is
+ reflowed and we've switched to/from overlay scrollbars. Both of these would
+ cause the scrollbars to be shown again which would interrupt the test.
+*/
+
+add_task(async function() {
+ await SimpleTest.promiseFocus(window);
+ await SpecialPowers.pushPrefEnv({"set": [["ui.useOverlayScrollbars", 1],
+ ["ui.scrollbarDisplayOnMouseMove", 1],
+ ["ui.scrollbarFadeDuration", 500],
+ ["ui.scrollbarFadeBeginDelay", 500]]});
+ let utils = SpecialPowers.DOMWindowUtils;
+
+ let sc = document.getElementById("sc");
+ let boundingClientRect = sc.getBoundingClientRect();
+
+ // The div is initially overflow hidden, so it doesn't have scrollbars,
+ // capture that so we can compare against it later.
+ let noscrollbars = document.createElement("canvas");
+ noscrollbars.setAttribute("width", boundingClientRect.width);
+ noscrollbars.setAttribute("height", boundingClientRect.height);
+ let ctx = noscrollbars.getContext("2d");
+ SpecialPowers.wrap(ctx).drawWindow(window, boundingClientRect.x, boundingClientRect.y,
+ boundingClientRect.width,
+ boundingClientRect.height,
+ "rgb(255,255,255)");
+
+ // dump("noscrollbars " + noscrollbars.toDataURL() + "\n");
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ // make div scrollable
+ sc.style.overflow = "auto";
+
+ // and make sure it's reconstructed so the prefs get applied
+ document.documentElement.style.display = "table";
+ document.documentElement.getBoundingClientRect();
+ document.documentElement.style.display = "";
+ document.documentElement.getBoundingClientRect();
+
+
+ const maxRetries = 5;
+ let retries = 0;
+ let canvasesSame = false;
+ while (!canvasesSame && retries < maxRetries) {
+ // wait for the scrollbars to fade away, 500+500 from prefs, then a bit more.
+ await new Promise(resolve => setTimeout(resolve, 1500));
+
+ let canvas = document.createElement("canvas");
+ canvas.setAttribute("width", boundingClientRect.width);
+ canvas.setAttribute("height", boundingClientRect.height);
+ // take screen shot
+ ctx = canvas.getContext("2d");
+ SpecialPowers.wrap(ctx).drawWindow(window, boundingClientRect.x, boundingClientRect.y,
+ boundingClientRect.width,
+ boundingClientRect.height,
+ "rgb(255,255,255)");
+ canvasesSame = (utils.compareCanvases(noscrollbars, canvas, {}) == 0);
+ retries++;
+ // dump("differences " + utils.compareCanvases(noscrollbars, canvas, {}));
+ // dump("canvas " + canvas.toDataURL() + "\n");
+ }
+
+ ok(canvasesSame, "scrollbars disappeared: canvasesSame " + canvasesSame + " retries " + retries)
+
+ // send mouse move that should not show scrollbar
+ await synthesizeMouseAtCenter(document.documentElement, { type: "mousemove" });
+ await new Promise(r => requestAnimationFrame(r));
+
+ let canvas = document.createElement("canvas");
+ canvas.setAttribute("width", boundingClientRect.width);
+ canvas.setAttribute("height", boundingClientRect.height);
+ ctx = canvas.getContext("2d");
+ SpecialPowers.wrap(ctx).drawWindow(window, boundingClientRect.x, boundingClientRect.y,
+ boundingClientRect.width,
+ boundingClientRect.height,
+ "rgb(255,255,255)");
+
+ let differences = utils.compareCanvases(noscrollbars, canvas, {});
+ // dump("canvas " + canvas.toDataURL() + "\n");
+
+ ok(differences == 0, "differences " + differences);
+});
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug1803209.html b/layout/generic/test/test_bug1803209.html
new file mode 100644
index 0000000000..6e7ed220da
--- /dev/null
+++ b/layout/generic/test/test_bug1803209.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+
+<style>
+.box {
+ background: linear-gradient(90deg, rgba(256,0,0,1), rgba(256,0,0,1));
+ background-attachment: fixed;
+ width: 25%;
+ height: 450px;
+}
+</style>
+
+<div style="height: 1000vh;"></div>
+<div class="box"></div>
+
+
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+add_task(async function() {
+ let utils = SpecialPowers.DOMWindowUtils;
+
+ let sc = document.getElementById("sc");
+ let boundingClientRect = document.documentElement.getBoundingClientRect();
+
+ let canvas = document.createElement("canvas");
+ canvas.setAttribute("width", boundingClientRect.width);
+ canvas.setAttribute("height", boundingClientRect.height);
+ let ctx = canvas.getContext("2d");
+
+ let rect = new window.DOMRect(boundingClientRect.x, boundingClientRect.y,
+ boundingClientRect.width,
+ boundingClientRect.height);
+
+ let image = await SpecialPowers.snapshotContext(window, rect, "rgb(255,255,255)");
+
+ SpecialPowers.wrap(ctx).drawImage(image, 0, 0);
+
+ var data = ctx.getImageData(0, 0, boundingClientRect.width, boundingClientRect.height).data;
+ var foundRed = false;
+ for (var i = 0; i < data.length; i+=4) {
+ if (data[i] == 255 && data[i+1] == 0 && data[i+2] == 0 && data[i+3] == 255) {
+ foundRed = true;
+ break;
+ }
+ }
+ ok(foundRed, "found some red pixels");
+});
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug240933.html b/layout/generic/test/test_bug240933.html
new file mode 100644
index 0000000000..e412c0a6e5
--- /dev/null
+++ b/layout/generic/test/test_bug240933.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=240933
+-->
+
+<head>
+ <title>Test for Bug 240933</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=240933">
+ Mozilla Bug 240933
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 240933 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.waitForFocus(function() {
+ var t = document.getElementById("t");
+ synthesizeMouse(t, t.clientWidth / 2, 5, {}, window);
+ is(t.selectionStart, 3, "The selection should be set before the newline");
+ is(t.selectionEnd, 3, "The selection should be set before the newline");
+
+ t = document.getElementById("ta");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+ var val = t.value;
+ synthesizeKey("KEY_Enter");
+ is(t.value, val + "\n", "Pressing enter right after focusing the textarea should work");
+
+ t = document.getElementById("tb");
+ t.focus();
+ synthesizeKey("KEY_Enter");
+ is(t.value, "\n", "Pressing enter for the first time should work");
+ synthesizeKey("KEY_Enter");
+ is(t.value, "\n\n", "Pressing enter for the second time should work");
+ synthesizeKey("KEY_Backspace");
+ is(t.value, "\n", "Pressing backspace for the first time should work");
+ synthesizeKey("KEY_Backspace");
+ is(t.value, "", "Pressing backspace for the second time should work");
+ SimpleTest.finish();
+ });
+
+ </script>
+ </pre>
+
+ <textarea id="t" rows="10" cols="10">abc
+</textarea>
+ <textarea id="ta" rows="10" cols="10">
+test
+
+</textarea>
+ <textarea id="tb" rows="10" cols="10"></textarea>
+
+</body>
+</html>
diff --git a/layout/generic/test/test_bug263683.html b/layout/generic/test/test_bug263683.html
new file mode 100644
index 0000000000..d3223f04ae
--- /dev/null
+++ b/layout/generic/test/test_bug263683.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=263683
+-->
+
+<head>
+ <title>Test for Bug 263683</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body onload="onLoad();" onunload="onUnload();">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=263683">
+ Mozilla Bug 263683
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 263683 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var userSetBG = false;
+ var userValueBG = null;
+ var prefNameBG = "ui.textHighlightBackground";
+ var userSetFG = false;
+ var userValueFG = null;
+ var prefNameFG = "ui.textHighlightForeground";
+
+ function onLoad() {
+ SpecialPowers.pushPrefEnv({'set': [[prefNameBG, "#EF0FFF"], [prefNameFG, "#FFFFFF"]]}, startTest);
+ }
+
+ function startTest() {
+ var textToSelect = document.getElementById("selecttext");
+
+ // Take a snapshot now. This will be used to check that removing the
+ // ranges removes the highlighting correctly
+ var noHighlight = snapshotWindow(window);
+
+ var controller = SpecialPowers.wrap(window).
+ docShell.
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsISelectionDisplay).
+ QueryInterface(SpecialPowers.Ci.nsISelectionController);
+
+ // Get selection
+ var findSelection = controller.getSelection(controller.SELECTION_FIND);
+
+ // Lastly add range
+ var range = document.createRange();
+ range.selectNodeContents(textToSelect);
+ findSelection.addRange(range);
+
+ // Take a snapshot of the highlighting
+ var highlighted = snapshotWindow(window);
+
+ // Clear the highlighting, and take another snapshot
+ findSelection.removeAllRanges();
+ var removedHighlight = snapshotWindow(window);
+
+ // Manually "highlight" the text so we can check the rendering
+ textToSelect.style.backgroundColor="#EF0FFF";
+ textToSelect.style.color="#FFFFFF";
+ var manualHighlight = snapshotWindow(window);
+
+ // Test 1: Did the highlighting render correctly?
+ var res = compareSnapshots(highlighted, manualHighlight, true);
+ ok(res[0], "SELECTION_FIND highlighting renders correctly");
+
+ // Test 2: Does removing the ranges from the SELECTION_FIND selection
+ // work as expected?
+ res = compareSnapshots(removedHighlight, noHighlight, true);
+ ok(res[0], "Removing ranges from FIND_SELECTION works correctly");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+ </pre>
+
+ <p><span id="selecttext">Text to be selected</span></p>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug288789.html b/layout/generic/test/test_bug288789.html
new file mode 100644
index 0000000000..1d52bed70d
--- /dev/null
+++ b/layout/generic/test/test_bug288789.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=288789
+-->
+<head>
+ <title>Test for Bug 288789</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=288789">Mozilla Bug 288789</a>
+<p id="display"></p>
+<div id="content">
+<textarea id="ta" dir="rtl">
+
+&#x05d0;a&#x05d1;
+
+</textarea>
+<textarea id="tb">
+
+abc
+
+</textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 288789 **/
+
+SimpleTest.waitForExplicitFinish();
+
+// This seems to be necessary because the selection is not set up properly otherwise
+setTimeout(test, 0);
+
+function test() {
+ var textarea = $("ta");
+
+ function collapse(offset) {
+ textarea.selectionStart = offset;
+ textarea.selectionEnd = offset;
+ }
+
+ function testRight(offset) {
+ synthesizeKey("KEY_ArrowRight");
+ is(textarea.selectionStart, offset, "Right movement broken");
+ }
+
+ function testLeft(offset) {
+ synthesizeKey("KEY_ArrowLeft");
+ is(textarea.selectionStart, offset, "Left movement broken");
+ }
+
+ textarea.focus();
+ collapse(0);
+ ok(true, "Testing forward movement in RTL mode");
+ for (var i = 0; i < textarea.textContent.length; ++i) {
+ if (i == 0) {
+ testRight(i);
+ }
+ if (textarea.textContent[i] == 'a') {
+ testLeft(i);
+ } else {
+ testLeft(i + 1);
+ }
+ if (i == textarea.textContent.length - 1) {
+ testLeft(i + 1);
+ }
+ }
+ ok(true, "Testing backward movement in RTL mode");
+ for (var i = textarea.textContent.length; i > 0; --i) {
+ if (i == textarea.textContent.length) {
+ testLeft(i);
+ }
+ if (i > 0 && textarea.textContent[i - 1] == 'a') {
+ testRight(i);
+ } else {
+ testRight(i - 1);
+ }
+ if (i == 1) {
+ testRight(i - 1);
+ }
+ }
+
+ textarea = $("tb");
+ textarea.focus();
+ collapse(0);
+ ok(true, "Testing forward movement in LTR mode");
+ for (var i = 0; i < textarea.textContent.length; ++i) {
+ if (i == 0) {
+ testLeft(i);
+ }
+ testRight(i + 1);
+ if (i == textarea.textContent.length - 1) {
+ testRight(i + 1);
+ }
+ }
+ ok(true, "Testing backward movement in LTR mode");
+ for (var i = textarea.textContent.length; i > 0; --i) {
+ if (i == textarea.textContent.length) {
+ testRight(i);
+ }
+ testLeft(i - 1);
+ if (i == 1) {
+ testLeft(i - 1);
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug290397.html b/layout/generic/test/test_bug290397.html
new file mode 100644
index 0000000000..b907b53fa5
--- /dev/null
+++ b/layout/generic/test/test_bug290397.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=290397
+-->
+<head>
+ <title>Test for Bug 290397</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=290397">Mozilla Bug 290397</a>
+<p id="display">
+<map name=a>
+ <area coords=0,0,20,20 href=#pass>
+ <area shape=default href=#fail>
+</map>
+<img src=file_Dolske.png usemap=#a id=image>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 290397 **/
+SimpleTest.waitForExplicitFinish();
+onhashchange = function() {
+ is(location.hash, "#pass", "Got the wrong area");
+ SimpleTest.finish();
+};
+function runTest() {
+ var image = document.getElementById("image");
+ SimpleTest.waitForFocus(() => synthesizeMouse(image, 10, 10, {}));
+}
+addLoadEvent(runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug323656.html b/layout/generic/test/test_bug323656.html
new file mode 100644
index 0000000000..726a4c2dee
--- /dev/null
+++ b/layout/generic/test/test_bug323656.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=323656
+-->
+<head>
+ <title>Test for Bug 323656</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ /**
+ * The idea is that "color" inherits by default while "border-color" does
+ * not. So if the former is red and the latter is green on a parent, and
+ * the child's border-color is set to "inherit", it'll be green only if
+ * the child is inheriting from the parent. If not, it'll either be
+ * whatever the border-color is on what it's inheriting from, which will
+ * be red if what it's inheriting from has the default (currentColor)
+ *border-color).
+ */
+
+ /* 't' for "test" */
+ #display, #display *
+ { color: red; border: 0px hidden red; background: transparent }
+ #display .t { border-color: green }
+ #display .t > :first-child
+ { border-color: inherit; border-style: solid; border-width: 10px }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=323656">Mozilla Bug 323656</a>
+<p id="display">
+ <select size="1" class="t">
+ <option id="testOption"></option>
+ </select>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 323656 **/
+var s = document.defaultView.getComputedStyle($("testOption"));
+is(s.borderRightColor, "rgb(0, 128, 0)", "Inheritance broken");
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug344830.html b/layout/generic/test/test_bug344830.html
new file mode 100644
index 0000000000..f071ba85b6
--- /dev/null
+++ b/layout/generic/test/test_bug344830.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=344830
+-->
+<head>
+ <title>Test for Bug 344830</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=344830">Mozilla Bug 344830</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+ <embed name="svg1"
+ width="200" height="200"
+ type="image/svg+xml"
+ src="bug344830_testembed.svg">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 344830 **/
+function getSVG() {
+ var embed = document.embeds.svg1;
+ var svgDocument = embed.getSVGDocument();
+ var element = svgDocument.getElementById("g1");
+ ok(embed, "document.embeds[] works with SVG");
+ ok(svgDocument, "document.embeds[] works with SVG and embed.getSVGDocument()");
+ ok(element, "document.embeds[] works with SVG and svgDocument.getElementById()");
+ SimpleTest.finish();
+}
+addLoadEvent(getSVG);
+SimpleTest.waitForExplicitFinish()
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug348681.html b/layout/generic/test/test_bug348681.html
new file mode 100644
index 0000000000..71edd2ffd1
--- /dev/null
+++ b/layout/generic/test/test_bug348681.html
@@ -0,0 +1,490 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=348681
+-->
+
+<head>
+ <title>Test for Bug 348681</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body onload="loaded()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=348681">Mozilla Bug 348681</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 348681 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function loaded() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.testing.selection.GetRangesForInterval", true]]}, doTest);
+ }
+
+ var rangeChecker = {
+ ranges: [],
+
+ reset: function() {
+ this.ranges = [];
+ },
+
+ check: function(testID, selection) {
+ is(selection.rangeCount, this.ranges.length,
+ "Test "+testID+": correct number of ranges in selection");
+ var rangesMatch = true;
+ var foundMsg = "Found ";
+ var expectedMsg = "Expected ";
+ var maxIndex = Math.max(selection.rangeCount, this.ranges.length);
+ for (var x = 0; x < maxIndex; x++) {
+ var expect = null;
+ if (x < this.ranges.length)
+ expect = this.ranges[x];
+
+ var found = null;
+ if (x < selection.rangeCount)
+ found = selection.getRangeAt(x);
+
+ if (found) {
+ foundMsg +="["+found.startOffset+","+found.endOffset+"] ";
+ }
+ if (expect) {
+ expectedMsg +="["+expect.start+","+expect.end+"] ";
+ }
+
+ if (found && expect) {
+ if (found.startContainer != expect.node ||
+ found.endContainer != expect.node ||
+ found.startOffset != expect.start ||
+ found.endOffset != expect.end)
+ rangesMatch = false;
+ } else {
+ rangesMatch = false;
+ }
+ }
+ var okMsg = "Test "+testID+": correct ranges in selection.";
+ okMsg = okMsg + foundMsg + expectedMsg;
+ ok(rangesMatch, okMsg);
+ },
+
+ addExpected: function(node, start, end) {
+ var expected = {};
+ expected.node = node;
+ expected.start = start;
+ expected.end = end;
+ this.ranges[this.ranges.length] = expected;
+ this.ranges.sort(function(a,b) {return a.start - b.start;});
+ }
+ }
+
+ var intervalChecker = {
+ ranges: [],
+
+ reset: function() {
+ this.ranges = [];
+ },
+
+ check: function(testID, testArr) {
+ is(testArr.length, this.ranges.length,
+ "Test "+testID+": correct number of ranges for interval");
+ var rangesMatch = true;
+ var foundMsg = "Found ";
+ var expectedMsg = "Expected ";
+ var maxIndex = Math.max(testArr.length, this.ranges.length);
+ for (var x = 0; x < maxIndex; x++) {
+ var expect = null;
+ if (x < this.ranges.length)
+ expect = this.ranges[x];
+
+ var found = null;
+ if (x < testArr.length) {
+ // testArr may contain the results coming from
+ // Selection.GetRangesForInterval(), which are
+ // wrappers for the underlying objects, therefore we may
+ // need to unwrap them before comparing them with the
+ // expected objects.
+ found = SpecialPowers.unwrap(testArr[x]);
+ }
+
+ if (found) {
+ foundMsg +="["+found.startOffset+","+found.endOffset+"] ";
+ }
+ if (expect) {
+ expectedMsg +="["+expect.start+","+expect.end+"] ";
+ }
+
+ if (found && expect) {
+ if (found.startContainer != expect.node ||
+ found.endContainer != expect.node ||
+ found.startOffset != expect.start ||
+ found.endOffset != expect.end)
+ rangesMatch = false;
+ } else {
+ rangesMatch = false;
+ }
+ }
+ var okMsg = "Test "+testID+": correct ranges for interval.";
+ okMsg = okMsg + foundMsg + expectedMsg;
+ ok(rangesMatch, okMsg);
+ },
+
+ addExpected: function(node, start, end) {
+ var expected = {};
+ expected.node = node;
+ expected.start = start;
+ expected.end = end;
+ this.ranges[this.ranges.length] = expected;
+ this.ranges.sort(function(a,b) {return a.start - b.start;});
+ }
+ }
+
+ function doTest() {
+ var testNode = document.getElementById("testparagraph").firstChild;
+
+ var selection = window.getSelection();
+ selection.removeAllRanges();
+ ok(selection.rangeCount == 0, "Test 0 - No selections so far");
+
+ // Test 1. Add a single range, to ensure we've not broken anything.
+ var range1 = document.createRange();
+ range1.setStart(testNode, 0);
+ range1.setEnd(testNode, 5);
+ selection.addRange(range1);
+ rangeChecker.addExpected(testNode, 0, 5);
+ rangeChecker.check(1, selection);
+
+ // Test 2. Add a non-overlapping range, to ensure it gets added too.
+ var range2 = document.createRange();
+ range2.setStart(testNode, 8);
+ range2.setEnd(testNode, 10);
+ selection.addRange(range2);
+ rangeChecker.addExpected(testNode, 8, 10);
+ rangeChecker.check(2, selection);
+
+ // Test 3. Add the same range again. This should silently succeed.
+ selection.addRange(range2);
+ rangeChecker.check(3, selection);
+
+ // Test 4. Add a range that is left-adjacent to an existing range.
+ var range3 = document.createRange();
+ range3.setStart(testNode, 6);
+ range3.setEnd(testNode, 8);
+ selection.addRange(range3);
+ rangeChecker.addExpected(testNode, 6, 8);
+ rangeChecker.check(4, selection);
+
+ // Test 5. Add a range that is right-adjacent to an existing range.
+ selection.removeRange(range2);
+ selection.addRange(range2);
+ rangeChecker.check(5, selection);
+
+ // Test 6. Add a range, add a second range that overlaps it.
+ rangeChecker.reset();
+ selection.removeAllRanges();
+ selection.addRange(range2);
+ var range4 = document.createRange();
+ range4.setStart(testNode, 9);
+ range4.setEnd(testNode, 11);
+ selection.addRange(range4);
+ rangeChecker.addExpected(testNode, 8, 9);
+ rangeChecker.addExpected(testNode, 9, 11);
+ rangeChecker.check(6, selection);
+
+ // Test 7. Add a range, and this time a left-hand overlap.
+ rangeChecker.reset();
+ selection.removeAllRanges();
+ var range5 = document.createRange();
+ range5.setStart(testNode, 5);
+ range5.setEnd(testNode, 7);
+ selection.addRange(range3);
+ selection.addRange(range5);
+ rangeChecker.addExpected(testNode, 5, 7);
+ rangeChecker.addExpected(testNode, 7, 8);
+ rangeChecker.check(7, selection);
+
+ // Test 8. Add a range, and add a second range wholly contained
+ // within it.
+ rangeChecker.reset();
+ selection.removeAllRanges();
+ var range6 = document.createRange();
+ range6.setStart(testNode, 0);
+ range6.setEnd(testNode, 10);
+ selection.addRange(range6);
+ selection.addRange(range5);
+ rangeChecker.addExpected(testNode, 0, 5);
+ rangeChecker.addExpected(testNode, 5, 7);
+ rangeChecker.addExpected(testNode, 7, 10);
+ rangeChecker.check(8, selection);
+
+ // Test 9. Add a range that will wholly contain some existing ranges.
+ rangeChecker.reset();
+ selection.addRange(range6);
+ rangeChecker.addExpected(testNode, 0, 10);
+ rangeChecker.check(9, selection);
+
+ // Test 10. This time with the last range being a partial overlap.
+ selection.removeAllRanges();
+ selection.addRange(range1);
+ selection.addRange(range3);
+ selection.addRange(range4);
+ selection.addRange(range6);
+ rangeChecker.addExpected(testNode, 10, 11);
+ rangeChecker.check(10, selection);
+
+ // Test 11. Check we can add a collapsed range without problem.
+ selection.removeAllRanges();
+ rangeChecker.reset();
+ range1.collapse(true);
+ selection.addRange(range1);
+ rangeChecker.addExpected(testNode, 0, 0);
+ rangeChecker.check(11, selection);
+
+ // Test 12. Check we can add a collapsed range twice without a problem.
+ // Part 1 - No other ranges present.
+ selection.addRange(range1);
+ rangeChecker.check(12, selection);
+
+ // Test 13. Check we can add a collapsed range twice without problem.
+ // Part 2 - Collapsed range is before all existing ranges.
+ selection.removeAllRanges();
+ rangeChecker.reset();
+ selection.addRange(range2);
+ selection.addRange(range1);
+ selection.addRange(range1);
+ rangeChecker.addExpected(testNode, 0, 0);
+ rangeChecker.addExpected(testNode, 8, 10);
+ rangeChecker.check(13, selection);
+
+ // Test 14. Check we can add a collapsed range twice without problem.
+ // Part 3 - Collapsed range is after all existing ranges.
+ selection.removeAllRanges();
+ rangeChecker.reset();
+ selection.addRange(range3);
+ range4.collapse(false);
+ selection.addRange(range3);
+ selection.addRange(range4);
+ rangeChecker.addExpected(testNode, 6, 8);
+ rangeChecker.addExpected(testNode, 11, 11);
+ rangeChecker.check(14, selection);
+
+ // Test 15. Check that when adding a range where after overlap
+ // adjustment an exisiting range would collapse that the collapsed range
+ // is removed - part 1 (LHS).
+ selection.removeAllRanges();
+ rangeChecker.reset();
+ selection.addRange(range2);
+ var range7 = document.createRange();
+ range7.setStart(testNode, 6);
+ range7.setEnd(testNode, 10);
+ selection.addRange(range7);
+ rangeChecker.addExpected(testNode, 6, 10);
+ rangeChecker.check(15, selection);
+
+ // Test 16. Check that when adding a range where after overlap
+ // adjustment an exisiting range would collapse that the collapsed range
+ // is removed - part 2 (RHS).
+ selection.removeAllRanges();
+ selection.addRange(range3);
+ selection.addRange(range7);
+ rangeChecker.check(16, selection);
+
+ // Test 17. Check GetRangesForInterval returns correct results.
+ // Part 1 - Test interval matches a range, adjacency not allowed.
+ selection.removeAllRanges();
+ selection.addRange(range2);
+ selection.addRange(range3);
+ var range8 = document.createRange();
+ range8.setStart(testNode, 10);
+ range8.setEnd(testNode, 12);
+ selection.addRange(range8);
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 8, 10);
+ var privSel = SpecialPowers.wrap(selection);
+ var results = privSel.GetRangesForInterval(testNode, 8, testNode, 10,
+ false);
+ intervalChecker.check(17, results);
+
+ // Test 18. Check GetRangesForInterval returns correct results.
+ // Part 2 - Test interval matches a range, adjacent ranges allowed.
+ intervalChecker.addExpected(testNode, 6, 8);
+ intervalChecker.addExpected(testNode, 10, 12);
+ results = privSel.GetRangesForInterval(testNode, 8, testNode, 10,
+ true);
+ intervalChecker.check(18, results);
+
+ // Test 19. Check GetRangesForInterval returns correct results.
+ // Part 3 - Test interval not selected.
+ intervalChecker.reset();
+ results = privSel.GetRangesForInterval(testNode, 14, testNode, 16,
+ true);
+ intervalChecker.check(19, results);
+
+ // Test 20. Check GetRangesForInterval returns correct results.
+ // Part 4 - Test interval is not equal to, and entirely contained in
+ // an existing range.
+ selection.removeAllRanges();
+ selection.addRange(range6);
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 0, 10);
+ results = privSel.GetRangesForInterval(testNode, 5, testNode, 7,
+ true);
+ intervalChecker.check(20, results);
+
+ // Test 21. Check GetRangesForInterval returns correct results.
+ // Part 5 - Test interval is not equal to, and entirely contains an
+ // existing range.
+ selection.removeAllRanges();
+ selection.addRange(range3);
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 6, 8);
+ results = privSel.GetRangesForInterval(testNode, 5, testNode, 9,
+ true);
+ intervalChecker.check(21, results);
+
+ // Test 22. Check GetRangesForInterval returns correct results.
+ // Part 6 - Test interval is end-adjacent to range at position 0 in
+ // the range array, and adjacencies permitted.
+ selection.removeAllRanges();
+ selection.addRange(range2);
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 8, 10);
+ results = privSel.GetRangesForInterval(testNode, 6, testNode, 8,
+ true);
+ intervalChecker.check(22, results);
+
+ // Test 23. Check GetRangesForInterval returns correct results.
+ // Part 7 - Test interval is end-adjacent to range at position 0 in
+ // the range array, and adjacencies not permitted.
+ intervalChecker.reset();
+ results = privSel.GetRangesForInterval(testNode, 6, testNode, 8,
+ false);
+ intervalChecker.check(23, results);
+
+ // Test 24. Check GetRangesForInterval returns correct results.
+ // Part 8 - Test interval is start-adjacent to last range in array,
+ // and adjacencies permitted.
+ intervalChecker.addExpected(testNode, 8, 10);
+ results = privSel.GetRangesForInterval(testNode, 10, testNode, 12,
+ true);
+ intervalChecker.check(24, results);
+
+ // Test 25. Check GetRangesForInterval returns correct results.
+ // Part 9 - Test interval is start-adjacent to last range in array,
+ // and adjacencies not permitted.
+ intervalChecker.reset();
+ results = privSel.GetRangesForInterval(testNode, 10, testNode, 12,
+ false);
+ intervalChecker.check(25, results);
+
+ // Test 26. Check GetRangesForInterval returns correct results.
+ // Part 10 - Test interval is equal to a collapsed range at position 0
+ // in the range array, and adjacencies permitted.
+ selection.removeAllRanges();
+ selection.addRange(range4);
+ intervalChecker.addExpected(testNode, 11, 11);
+ results = privSel.GetRangesForInterval(testNode, 11, testNode, 11,
+ true);
+ intervalChecker.check(26, results);
+
+ // Test 27. Check GetRangesForInterval returns correct results.
+ // Part 11 - Test interval is equal to a collapsed range at position 0
+ // in the range array, and adjacencies not permitted.
+ results = privSel.GetRangesForInterval(testNode, 11, testNode, 11,
+ false);
+ intervalChecker.check(27, results);
+
+ // Test 28. Check GetRangesForInterval returns correct results.
+ // Part 12 - Test interval is equal to a collapsed range at end of the
+ // range array, and adjacencies permitted.
+ selection.removeAllRanges();
+ selection.addRange(range2);
+ selection.addRange(range4);
+ results = privSel.GetRangesForInterval(testNode, 11, testNode, 11,
+ true);
+ intervalChecker.check(28, results);
+
+ // Test 29. Check GetRangesForInterval returns correct results.
+ // Part 13 - Test interval is equal to a collapsed range at end of the
+ // range array, and adjacencies not permitted.
+ results = privSel.GetRangesForInterval(testNode, 11, testNode, 11,
+ false);
+ intervalChecker.check(29, results);
+
+ // Test 30. Check GetRangesForInterval returns correct results.
+ // Part 14 - Test interval is a collapsed range contained in an
+ // existing range.
+ selection.removeAllRanges();
+ selection.addRange(range3);
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 6, 8);
+ results = privSel.GetRangesForInterval(testNode, 7, testNode, 7,
+ true);
+ intervalChecker.check(30, results);
+
+ // Test 31. Check GetRangesForInterval returns correct results.
+ // Part 15 - Test interval equals a collapsed range which is not stored
+ // at either endpoint of the selection's range array,
+ // adjacencies not allowed.
+ selection.removeAllRanges();
+ range3.collapse(false);
+ selection.addRange(range5);
+ selection.addRange(range3);
+ selection.addRange(range8);
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 8, 8);
+ results = privSel.GetRangesForInterval(testNode, 8, testNode, 8,
+ false);
+ intervalChecker.check(31, results);
+
+ // Test 32. Check GetRangesForInterval returns correct results.
+ // Part 16 - Test interval equals a collapsed range which is not stored
+ // at either endpoint of the selection's range array,
+ // adjacencies allowed.
+ results = privSel.GetRangesForInterval(testNode, 8, testNode, 8,
+ true);
+ intervalChecker.check(32, results);
+
+ // Test 33. Check GetRangesForInterval returns correct results.
+ // Part 17 - Test interval contains a collapsed range which is not
+ // stored at either endpoint of the selection's range array.
+ results = privSel.GetRangesForInterval(testNode, 7, testNode, 9,
+ false);
+ intervalChecker.check(33, results);
+
+ // Test 34. Check GetRangesForInterval returns correct results.
+ // Part 18 - Test interval left-ovelaps an existing range.
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 5, 7);
+ results = privSel.GetRangesForInterval(testNode, 2, testNode, 6,
+ true);
+ intervalChecker.check(34, results);
+
+ // Test 35. Check GetRangesForInterval returns correct results.
+ // Part 19 - Test interval right-ovelaps an existing range.
+ selection.removeAllRanges();
+ selection.addRange(range8);
+ intervalChecker.reset();
+ intervalChecker.addExpected(testNode, 10, 12);
+ results = privSel.GetRangesForInterval(testNode, 11, testNode, 13,
+ true);
+ intervalChecker.check(35, results);
+
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+
+ <p id="testparagraph">
+This will be the first child of the p node above. The intention is that it is a suitably long text node to which we can add multiple ranges in order to test the various aspects of the bug.
+</p>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug382429.html b/layout/generic/test/test_bug382429.html
new file mode 100644
index 0000000000..bf8f242e8e
--- /dev/null
+++ b/layout/generic/test/test_bug382429.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=382429
+-->
+<head>
+ <title>Test for Bug 382429</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=382429">Mozilla Bug 382429</a>
+<p id="display">&#x05e2; 3/4</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function do_Replace()
+{
+/** Test for Bug 382429 **/
+ var t = $("display").firstChild;
+
+ t.data = t.data.replace(/3\/4/, "3 \u05d3 4");
+ ok(true, "We should get here without crash or hang");
+ is(t.data, "\u05e2 3 \u05d3 4", "replace succeeded");
+ SimpleTest.finish();
+}
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(do_Replace, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug384527.html b/layout/generic/test/test_bug384527.html
new file mode 100644
index 0000000000..21e136a3dc
--- /dev/null
+++ b/layout/generic/test/test_bug384527.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=384527
+-->
+<head style="display: inline-table;">
+ <title>Test for Bug 384527</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style style="display: block; direction: rtl;">
+ style::first-letter {float: right;}
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384527">Mozilla Bug 384527</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function do_Test()
+{
+/** Test for Bug 384527 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(do_Test, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug385751.html b/layout/generic/test/test_bug385751.html
new file mode 100644
index 0000000000..a2947723f7
--- /dev/null
+++ b/layout/generic/test/test_bug385751.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=385751
+-->
+<head>
+ <title>Test for Bug 385751</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>#content:first-letter {float: right; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=385751">Mozilla Bug 385751</a>
+<div id="content"><span style="direction: rtl; unicode-bidi: embed;">*::first
+m</span></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function do_Test()
+{
+/** Test for Bug 385751 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(do_Test, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug389630.html b/layout/generic/test/test_bug389630.html
new file mode 100644
index 0000000000..e6a8fabf40
--- /dev/null
+++ b/layout/generic/test/test_bug389630.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=389630
+-->
+<head>
+ <title>Test for Bug 389630</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>.fl:first-letter { }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=389630">Mozilla Bug 389630</a>
+<div id="content" style="direction: rtl;"><table><tr><td width="1"><div id="div" class="fl"><font><span>x y</span></font></div></td></tr></table></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function boom()
+{
+/** Test for Bug 389630 **/
+ document.getElementById("div").className = "";
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(boom, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug391747.html b/layout/generic/test/test_bug391747.html
new file mode 100644
index 0000000000..ff1c4521d2
--- /dev/null
+++ b/layout/generic/test/test_bug391747.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391747
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Test for bug 391747</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391747">Mozilla Bug 391747</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+<iframe id="iframe_391747" srcdoc="<table><tr><td style='width:500px;height:500px;border:1px solid blue'>x</td>"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function ctrlclick_391747(doc,x,y){
+ var wu = SpecialPowers.getDOMWindowUtils(doc.defaultView);
+ wu.sendMouseEvent('mousedown', x, y, 0, 1, 2);
+ wu.sendMouseEvent('mouseup', x, y, 0, 1, 2);
+}
+
+function select_391747(doc){
+ var range = doc.createRange();
+ range.setStart(doc, 0);
+ range.setEnd(doc, 0);
+ doc.defaultView.getSelection().addRange(range);
+}
+
+function boom_391747() {
+ var target = document.getElementById('iframe_391747')
+ select_391747(target.contentDocument)
+ ctrlclick_391747(target.contentDocument, 100, 100);
+ ok(true, "pass");
+ SimpleTest.finish();
+}
+
+addLoadEvent(boom_391747);
+SimpleTest.waitForExplicitFinish()
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug392746.html b/layout/generic/test/test_bug392746.html
new file mode 100644
index 0000000000..31b35ed484
--- /dev/null
+++ b/layout/generic/test/test_bug392746.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=392746
+-->
+<head>
+ <title>Test for Bug 392746</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=392746">Mozilla Bug 392746</a>
+<div id="content">
+text text text text text <span id="d">ddd text text </span>text text text <br>
+text <span id="c">ccc text</span> text text <span id="e">eee text</span> text text text <span id="b">bbb text</span><br>
+ text text text text text text <span id="a">aaa text</span> text text text <br>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var wu = SpecialPowers.getDOMWindowUtils(window);
+
+function ctrlselect(aX,aY, aX2, aY2) {
+ var modifyers = (navigator.platform.includes("Mac")) ? 8 : 2;
+ wu.sendMouseEvent('mousedown', aX, aY, 0, 1, modifyers);
+ wu.sendMouseEvent('mousemove', aX2, aY2, 0, 0, modifyers);
+ wu.sendMouseEvent('mouseup', aX2, aY2, 0, 1, modifyers);
+}
+
+ function test() {
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var a=document.getElementById('a').getBoundingClientRect();
+ ctrlselect(a.left+1, a.top+1, a.right-1, a.top+1);
+ var b=document.getElementById('b').getBoundingClientRect();
+ ctrlselect(b.left+1, b.top+1, b.right-1, b.top+1);
+ var c=document.getElementById('c').getBoundingClientRect();
+ ctrlselect(c.left+1, c.top+1, c.right-1, c.top+1);
+ var d=document.getElementById('d').getBoundingClientRect();
+ ctrlselect(d.left+1, d.top+1, d.right-1, d.top+1);
+ var e=document.getElementById('e').getBoundingClientRect();
+ ctrlselect(e.right-1, e.top+1, e.left+1, e.top+1);
+
+ ok(sel.getRangeAt(0).toString() == 'ddd text text ', 'First selection range should be "ddd text text "');
+ ok(sel.getRangeAt(1).toString() == 'ccc text', 'First selection range should be "ccc text"');
+ ok(sel.getRangeAt(2).toString() == 'eee text', 'First selection range should be "eee text"');
+ ok(sel.getRangeAt(3).toString() == 'bbb text', 'First selection range should be "bbb text"');
+ ok(sel.getRangeAt(4).toString() == 'aaa text', 'First selection range should be "aaa text"');
+
+ ok(sel.focusNode == sel.anchorNode, 'focusNode and anchorNode should be the same');
+ ok(sel.focusNode.parentNode == document.getElementById('e'), 'focusNode.parentNode should be the same as the node with id=e');
+ ok(sel.focusOffset == 0, 'focusOffset should be 0');
+ ok(sel.anchorOffset == 8, 'anchorOffset should be 8');
+
+ wu.sendMouseEvent('mousedown', 0, 0, 0, 1, 0);
+ wu.sendMouseEvent('mousemove', 0, 0, 0, 0, 0);
+ wu.sendMouseEvent('mouseup', 0, 0, 0, 1, 0);
+
+ SimpleTest.finish();
+}
+
+ window.onload=function() {
+ SimpleTest.waitForExplicitFinish();
+ setTimeout(test, 0);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug392923.html b/layout/generic/test/test_bug392923.html
new file mode 100644
index 0000000000..db684ec511
--- /dev/null
+++ b/layout/generic/test/test_bug392923.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=392923
+-->
+<head>
+ <title>Test for Bug 392923</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>p { column-count: 2; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=392923">Mozilla Bug 392923</a>
+<div id="content">
+<p>&#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA; Lorem ipsum dolor sit
+amet, consectetuer adipiscing elit. Maecenas volutpat. Duis purus lectus,
+hendrerit vitae, blandit ac, tristique quis, neque. Cras a nisl. Cum sociis
+natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In
+consectetuer lorem sit amet quam facilisis mollis. Etiam dolor. Donec
+elementum leo in ligula. Duis lacus diam, sodales vitae, rutrum a,
+feugiat non, tortor. Maecenas cursus lobortis diam. Mauris in pede eu
+purus ultricies vehicula. &#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA;</p>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function test()
+{
+/** Test for Bug 392923 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(test, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug394173.html b/layout/generic/test/test_bug394173.html
new file mode 100644
index 0000000000..9f43ee9f1d
--- /dev/null
+++ b/layout/generic/test/test_bug394173.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=394173
+-->
+<head>
+ <title>Test for Bug 394173</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>.fl:first-letter { }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=394173">Mozilla Bug 394173</a>
+<div id="content">
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%0A%3Chead%3E%0A%3Cstyle%3E%0Adiv%3A%3Afirst-letter%20%7B%20%7D%0Aspan%3A%3Aafter%20%7B%20content%3A%22before%20text%22%3B%20%7D%0A%3C/style%3E%0A%3C/head%3E%0A%3Cbody%3E%0A%3Cdiv%20style%3D%22position%3A%20fixed%3B%20direction%3A%20rtl%3B%22%3E%0A%20%20%3Cspan%20style%3D%22%20direction%3A%20ltr%3B%20unicode-bidi%3A%20bidi-override%3B%20font-size%3A%2070px%3B%20%22%3E%0A%20%20%20%20%3Cspan%20style%3D%22display%3A%20table%3B%20position%3A%20fixed%3B%22%3E%3C/span%3E%0A%20%20%3C/span%3E%0A%3C/div%3E%0A%3Cscript%3E%0Afunction%20doe%28i%29%7B%0Adocument.documentElement.setAttribute%28%27style%27%2C%20%27%27%29%3B%0A%7D%0AsetTimeout%28doe%2C100%29%3B%0A%3C/script%3E%0A%3C/body%3E%0A%3C/html%3E" style="width:200px;height: 200px;"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function boom()
+{
+/** Test for Bug 394173 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(boom, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug394239.html b/layout/generic/test/test_bug394239.html
new file mode 100644
index 0000000000..dd0732e024
--- /dev/null
+++ b/layout/generic/test/test_bug394239.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=394239
+-->
+<head>
+ <title>Test for Bug 394239</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #a::after { content:"anonymous text"; }
+ </style>
+</head>
+<body>
+<div id="content">
+ <object id="a" style="position: relative;">
+ <div style="position: absolute;">
+ <input>
+ </div>&#1593;
+ <div style="position: absolute;">
+ <span style="position: fixed;">t</span>
+ </div>
+ </object>
+</div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=394239">Mozilla Bug 394239</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 394239 **/
+function test()
+{
+ $("a").style.direction="rtl";
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(test, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug402380.html b/layout/generic/test/test_bug402380.html
new file mode 100644
index 0000000000..5dba2f9935
--- /dev/null
+++ b/layout/generic/test/test_bug402380.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=402380
+-->
+<head>
+ <title>Test for Bug 402380</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ div::first-letter { color: green; }
+ span:before { content: open-quote "This "; }
+ span:after { content: close-quote; }
+ </style>
+</head>
+<body style="font-family: monospace; width: 7ch; border: 1px solid orange;"
+ onload="document.getElementById('div').style.direction = 'rtl';">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=402380">Mozilla Bug 402380</a>
+<div id="div"><span>is text</span></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function test()
+{
+/** Test for Bug 402380 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(test, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug404872.html b/layout/generic/test/test_bug404872.html
new file mode 100644
index 0000000000..700276ad5c
--- /dev/null
+++ b/layout/generic/test/test_bug404872.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=404872
+-->
+<head>
+ <title>Test for Bug 404872</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=404872">Mozilla Bug 404872</a>
+<p id="display">
+ <select multiple id="a">
+ <option name="value1">value1</option>
+ <option name="value2">value2</option>
+ <option name="value3">value3</option>
+ </select>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/* Focus, press key down twice, and make sure we successfully selected the second item. */
+$('a').focus();
+synthesizeKey("KEY_ArrowDown");
+synthesizeKey("KEY_ArrowDown");
+is($("a").value, "value2", "value2 should be selected!");
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug405178.html b/layout/generic/test/test_bug405178.html
new file mode 100644
index 0000000000..c177f01ee8
--- /dev/null
+++ b/layout/generic/test/test_bug405178.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=405178
+-->
+<head>
+ <title>Test for Bug 405178</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=405178">Mozilla Bug 405178</a>
+<p id="display"><div id="div" style="column-width: 10px;">&#23377;&#1741; </div></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 405178 **/
+
+var div, textNode;
+
+function boom()
+{
+ div = document.getElementById("div");
+ textNode = div.firstChild;
+
+ div.removeChild(textNode);
+ setTimeout(boom2, 200);
+}
+
+function boom2()
+{
+ textNode.data = "";
+ div.appendChild(document.createTextNode("X"))
+ var foo = document.body.offsetHeight;
+
+ ok(true, "Test is successful if we get here without crashing");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(boom);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/generic/test/test_bug416168.html b/layout/generic/test/test_bug416168.html
new file mode 100644
index 0000000000..90273c57c7
--- /dev/null
+++ b/layout/generic/test/test_bug416168.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416168
+-->
+<head>
+ <title>Test for Bug 416168</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=416168">Mozilla Bug 416168</a>
+<p id="display">
+
+<div id="a" style="overflow: hidden; outline: 1px solid black; height: 0px;">
+ <div style="margin-top: 50px; width:20px; height:20px;"></div>
+</div>
+
+<div id="b" style="overflow: hidden; outline: 1px solid black; width:0px; height: 100px;">
+</div>
+
+<div id="c" style="overflow: hidden; outline: 1px solid black; width:100px; height: 0px;">
+</div>
+
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var a = document.getElementById("a");
+is(a.scrollHeight, 70, "margin-top included");
+
+var b = document.getElementById("b");
+is(b.scrollHeight, 100, "zero width");
+
+var c = document.getElementById("c");
+is(c.scrollWidth, 100, "zero height");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug421436.html b/layout/generic/test/test_bug421436.html
new file mode 100644
index 0000000000..909c452497
--- /dev/null
+++ b/layout/generic/test/test_bug421436.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421436
+-->
+<head>
+ <title>Test for Bug 421436</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=421436">Mozilla Bug 421436</a>
+<p id="display"></p>
+<div id="content">
+<span id="outer"><span id="inner">Hello</span><br></span>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 421436 **/
+var inner = document.getElementById("inner");
+var outer = document.getElementById("outer");
+
+is(inner.getBoundingClientRect().left, outer.getBoundingClientRect().left, "left edges equal");
+todo_is(inner.getBoundingClientRect().right, outer.getBoundingClientRect().right, "right edges equal");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug421839-1.html b/layout/generic/test/test_bug421839-1.html
new file mode 100644
index 0000000000..d746ad198c
--- /dev/null
+++ b/layout/generic/test/test_bug421839-1.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421839
+-->
+<head>
+ <title>Test for Bug 421839</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=421839">Mozilla Bug 421839</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+Moving with you mouse from above to below and back a few times across the iframe, shouldn't crash Mozilla<br/>
+
+<iframe id="iframe" src="data:text/html;charset=utf-8,text%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%0A%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3Etext%3Cbr%3E"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 421839 **/
+
+var counter = 0;
+
+SimpleTest.waitForExplicitFinish();
+
+var doc = document;
+if (document.getElementById('iframe'))
+ doc = document.getElementById('iframe').contentDocument;
+
+function toggleIframe(){
+ var x=document.getElementById('iframe');
+ x.style.display = x.style.display == 'none' ? x.style.display = '' : x.style.display = 'none';
+ setTimeout(toggleIframe,100);
+
+ if (++counter == 4)
+ setTimeout(finish, 200);
+
+}
+setTimeout(toggleIframe,100);
+
+function ctrlclick(i){
+ var wu = SpecialPowers.getDOMWindowUtils(doc.defaultView);
+ var wu2 = SpecialPowers.getDOMWindowUtils(top);
+
+ try
+ {
+ wu.sendMouseEvent('mousedown', 2*i, 2*i, 0, 1, 0);
+ wu2.sendMouseEvent('mousemove', 500*i, 500*i, 0, 0, 0);
+ //wu.sendMouseEvent('mouseup', 2*i, 2*i, 0, 1, 2);
+ }
+ catch(e)
+ {
+ }
+
+ i+=1;
+ if (i>50)
+ i =0;
+
+ setTimeout(ctrlclick,20,i);
+
+ if (++counter == 4)
+ setTimeout(finish, 200);
+
+}
+
+setTimeout(ctrlclick,20,0);
+
+function finish()
+{
+ ok(true, "This is a mochikit version of a crash test. To complete is to pass.");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug421839-2.html b/layout/generic/test/test_bug421839-2.html
new file mode 100644
index 0000000000..7833835baa
--- /dev/null
+++ b/layout/generic/test/test_bug421839-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421839
+-->
+<head>
+ <title>Test for Bug 421839</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=421839">Mozilla Bug 421839</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 421839 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+setTimeout(finish, 5000);
+
+function finish()
+{
+ ok(true, "This is a mochikit version of a crash test. To complete is to pass.");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug424627.html b/layout/generic/test/test_bug424627.html
new file mode 100644
index 0000000000..e043bda32e
--- /dev/null
+++ b/layout/generic/test/test_bug424627.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=424627
+-->
+<head>
+ <title>Test for Bug 424627</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=424627">Mozilla Bug 424627</a>
+<p id="display"></p>
+<div id="content">
+<table><tr><td><textarea cols="60" rows="4" style="border: 20px solid blue;">text
+more text
+even more text
+enough text</textarea></td></tr></table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 424627 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var t = document.querySelector("textarea")
+ t.focus();
+ var originalValue = t.value;
+ synthesizeMouse(t, 10, 30, {accelKey: true});
+ sendString("x");
+ t = document.querySelector("textarea")
+ ok(t, "The text area should not be removed from the document");
+ isnot(t.value, originalValue, "Its value should be modified");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug438840.html b/layout/generic/test/test_bug438840.html
new file mode 100644
index 0000000000..0b5c1bef2e
--- /dev/null
+++ b/layout/generic/test/test_bug438840.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Character Movement (including nsTextFrame::PeekOffsetCharacter)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div>content before the editor</div>
+<div contentEditable="true" id="editor" style="width:200px;height:150px;overflow-y:auto;overflow-x:hidden;"><p>paragraph1</p>
+<p>paragraph2</p>
+<p>paragraph3</p>
+<p>paragraph4</p>
+<p>paragraph5</p>
+<p>paragraph6</p>
+</div>
+<div>content after the editor</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function test() {
+ var sel = window.getSelection();
+ var editor = document.getElementById("editor");
+
+ var keymodifier={};
+ //in windows/linux, pageup/pagedown will trigger movement of caret
+ //while in Mac, pageup/pagedown will just scroll. We need to press
+ //alt-pageup/pagedown in Mac to actually move caret
+ if(navigator.platform.includes("Mac")){
+ keymodifier.altKey=true;
+ }
+
+ sel.collapse(editor.firstChild.firstChild, 1);
+ synthesizeKey("VK_PAGE_UP", keymodifier);
+ is(sel.anchorNode, editor.firstChild.firstChild, 'after pageup caret should still be in the first paragraph');
+
+ synthesizeKey("VK_PAGE_DOWN", keymodifier);
+ is(sel.anchorNode.parentNode.parentNode, editor, 'pagedown should not move caret outside the editor');
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(test);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug448860.html b/layout/generic/test/test_bug448860.html
new file mode 100644
index 0000000000..b7a986c8bc
--- /dev/null
+++ b/layout/generic/test/test_bug448860.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=448860
+-->
+<head>
+ <title>Test for Bug 448860</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+:focus {outline:2px solid red}
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448860">Mozilla Bug 448860</a>
+<p id="display">
+
+<img src='data:image/gif,GIF89a%93%01D%01%F7%00%00%F9%F9%F9%FA%FA%F5%EB%E9%CE%FC%FC%FC%E9%E7%CC%EE%EE%EE%FE%FE%FEhhg%A0%A0%A0HHH%ED%EB%D0333%F6%F6%F6%F0%EE%D2%C3%C3%C1%F1%EF%D3%DB%D9%C0%DB%DC%DChhh%9D%9D%9B%D5%D3%BB%C9%C9%C9%F1%F1%F1%E0%E0%E0%CA%C8%B2%ED%EB%D1%E3%E1%C7%DF%DE%C4%8F%8E%82%B9%B9%B8%F5%F4%E8%E8%E8%E7%D0%CE%B7%B3%B2%9F%E6%E6%E6%BF%BD%A9%B9%B8%A4%F3%F0%D4%D7%D7%D7%F4%F4%F4%DF%DF%DD%E4%E4%E4%84%84%83%F0%F0%EF%F7%F5%D7%90%90%8Duup%EE%EE%ED%EA%EA%E9%E2%E3%E2%9C%9D%9B%EB%E9%D2%D3%D3%D2yyt%F0%EE%DD%93%92%85%E5%E4%C9%EB%EC%EB%F5%F2%D6%85%85z%93%93%91%C4%C3%AD%ED%ED%ED%E5%E3%C9%88%88%83%7F%7Ev%AA%AA%A7%F5%F5%F5%C5%C4%C2%CF%CF%CF%8C%8B%80%AD%AD%AC%DD%DD%DC%F9%F7%D9%8C%8C%8A%B8%B8%B7%B1%B1%B1%80%80%7E%85%85%7E%A8%A6%96%AD%AC%9B%87%87%86%F7%F4%D7%C6%C6%C5%97%97%94%5B%5B%5B%EF%EF%E1%AF%AE%9C%9F%9E%8F%AC%AB%99%92%93%92%95%94%87%9C%9B%8C%80%81%81%F7%F7%EE%A7%A6%95%FD%FC%F6%9E%9D%8E%A3%A2%92%FC%FB%DD%98%98%98%EA%EB%EA%98%98%8B%8E%8F%8E%95%95%88%ED%ED%E8%D6%D6%D6ddc%81%81yooi%A1%A0%90%9B%9A%8C%5B%5BZ%AA%AA%A8%A4%A3%93xyv%A7%A7%A7%7E%7Ev%D0%D0%CF%C8%C8%C7%AC%AD%AC%B1%B1%B0%A6%A5%A4%97%97%95%9A%9B%96%F0%EF%EF%C7%C7%C7%F3%F3%F3%C4%C2%AD%BF%BF%BF%D9%D9%D9%A2%A3%A2%AA%A8%96%D3%D3%D3%E9%E9%E9%E3%E3%E1%F8%F8%ED%A7%A7%A6%98%9A%99%A2%A2%99%A7%A6%99%A3%A5%A4%E2%E2%E3%F0%F0%EB%A0%A0%96ooj%C9%C9%C3%CA%CD%CE%AA%AD%A0%A0%9F%96%F4%F4%EF%EA%E9%E4%E3%E2%D2y%80%80%E8%E6%CB%FF%FF%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%21%F9%04%00%00%00%00%00%2C%00%00%00%00%93%01D%01%00%08%FF%007%09%1CH%B0%A0%C1%83%08%13%2A%5C%C8%B0%A1%C3%87%10%23J%9CH%B1%A2%C5%8B%183j%DCx%B1%00%00%8E%20C%8A%1CI%B2%A4%C9%93%28S%A6%F4%A8%B2%A5%CB%970c%CA%9CI%D3%21%CB%9A8s%EA%DC%C9%B3%27%CC%9B%3E%83%0A%1DJ%B4hO%A0F%93%2A%5D%CA%B4iD%A4N%A3J%9DJu%27%D4%AAX%B3j%DD%AA%F1%2A%D7%AF%60%C3%8A%F5%2A%B6%AC%D9%B3K%C9%A2%5D%CB%B6-M%B5n%E3%CA%9D%1B%12.%DD%BBx%F36%B4%AB%B7%AF%DF%BC%7C%FF%0A%1E%BC60%E1%C3%88%B7%1AN%CC%B8q%D3%C5%8E%23K%0E%0Ay%B2%E5%CB3%2Bc%DE%CC%F9%A4%E6%CE%A0Cw%FD%28%BA%B4%E9%92%9FO%AB%5E%7D05%EB%D7%AB%5D%C3%9E-Z6%ED%DB%9Bm%E3%DE-Y7%EF%DF%89%7D%03%1F.X8%F1%E3%80I%23_%9E%5B9%F3%E7%BD%9DC%9F%1E%5C%3A%F5%EB%C5%ADc%DF%9E%9C%BB%F7%BE%C6%BF%8B%FFw%1A%7E%BC%F9%A4%E5%CF%AB%17%9A%7E%BD%7B%AB%DA%DF%CB%A7%DA%7E%BE%FD%9F%F1%EF%EBG%9F%7F%BF%7F%F6%FD%FD%27%20%7C%03%168T%7D%06%26h%11%82%0A6%F8T%80%0EFh%12%83%12V%88%10%85%16f8%10%86%1Af%C8a%87%15%7E%08b%84%22%8E%D8%60%89%26%26%88b%8A%05%AE%C8%A2%80.%BE%E8_%8C2%EAGc%8D%F6%DD%88%A3%7C%3A%EE%E8%5E%8F%3E%AA%07d%90%E6%0DI%A4xF%1E%E9%5D%92Jn%C7d%93%D7%3D%09%E5tRN%F9%5C%95V.%87e%96%C7m%C9%E5p%5E%7E%F9%5B%98b%EEFf%99%B7%9D%89%26U%00%0C%00%E0%9A%AF%01%F0B%0E1%0C%12%C1%9Bp%9EvB%11G%B4%B0%83%1B%244%E1%A6Oj%E6Y%D4%0A%08p%00B%03I4%20%C0%17z%0C%CAS%A1%86%0A%05%C0%11%84%8C%A1%00%01%9Ah%22%00%04F%A4%60%C0Q%10V%DAX%04-%94%20%40%A7%AC%96%40%02%10%92%EA%FFD%A9%A9%3DU%80E%12%AC%B2%CA%A9%19%0E%C4%8A%D3%AC%B4%EE%E4%C0%17%3A%E4%CAj%03%3D%28%01%C0%A8%B2%96%1A%ECa%0E%10R%AC%B1%9A%10%D0%00%16%1D%F8%FA%96%B3%CF%0E%E6%C0%15%25P%DB%A9%ABd%2C%DBl%B7%989%10B%B8%E2%2A%00%01%0F%29h%2B%13%B0%E8%D2%D4%01%09%EC%8A%3B%06%1AEp%EB%99%BF%F5%EA%D5%C1%08%0D%88%DBi%1278%60%EE%AF%00%07%8C%97%1E%3D%14l%B0%0E_0%C1%00%B35%D1%EB0L%40%80%A0%80%C1%9A4%40%82%10%16%C8%8B%DF%C6%8E%01%80%06%04%AB%1A%AC%00%05-%C4%9B%93%C6%28%ABDC%08%D5%82%ACI%12%40D%60%F2K4%D7%8C%12%02%18%B4%0C%B2%14%3B%98%D0%F0HA%0Bm%92%0A%2C%EB%ACI%09%1C%D0p1%C3N%1F%F6%C2%0D%1Bp%AA%B3%02Wtp%02%C6%99-%9D%F5YJ%80%20u%A7%04%40%40%C5%0B%3F%B7%D4%F4%D9%20%1D%C1E%CEk%2B%E0%02%0Cq%AB%FF47%DD%1A%D1%81%85%00%5E%AF-%C0%0DJc%0Dx%5E%7Cp%B0%E9%DA%B9b%40%06%03%8A%2FNW%07%1C%E0P8%E4%9AhP%C3%09%7D%A3%F4%B7%E5%13%BD%B0%83%00Fs%AE%09%0E%5C%F4%9A%B1%D9%A4%B3yD%0F%1F%AB%AE%2B%05M%5C%5Dv%ECqY%D0F%03%9B%ABN%C0%06%40%5C%10%FA%84%B0%F3%1EU%117%E4k%3B%DB%18%B4%90%7CF%A3%2B%CF%10%03tP%90%FA%F3%9F%EE%F0%02%D9%40Oo%BDR%29%D40%ED%F3%B9%0A0%82%B2%BB%8Fo%96%01%1DH%8B%BE%B1%02l%60%84%09%E0%CB-%BE%FBD%7D%10%04%0E%DB%9B%9F%02%5E%B5%3F%8AT%8F%7F%05%11A%13%D65%3Fj%09%00%077%C8VL%0E%E8%BE%08%E8%81%0C%3C%A0%C2%0Dz%80%B7%06%E6%EAe%2A%88A%FEDW%40%04%D6%C4%04T%80%00%04%28%40%01%96%05%D0%83%04%18%A0%12%2Cp2%13%FA%E4%03wr%C8%14%E4%A0%03%D4%A1%CE%83%20C%9D%19%FF%84p%3C%90P%B0%5E%0E%E0%01%1F%F4%20%82%86t%80%81%40%CC%DB%06%94%80%82%11%A2%A6%846%D4%08%03%A8P%B4%F55q%21%83%88X%14%21%27%85%2B%0C%A2%88%1B9b%B7V%00%89%0D4%40%0A%18%00%C2%07%142%00%3EP%A0vc%D4Y%FDZ%10%034R%0F%8BY%C4%C8%0A%16%B1%81U%25%21%0B-%A0%1CB%06Y%C8%3C%AEm_%15%F0%23F%D4%F8%AC%1C%10rU%D6%92%83%10%14i%90%220%82%00%C1s%24%B5%C6%D0%82%0A%E8%8E%84%81%D4%C9%07%18%A1%81%96%09%A0%01f%40%40%04V%60%90%23%90%40b%A2%04Y%12vP%84S%FE%2B%959a%C0%CA%8C%F6J%12p%80%07%17%20%C8%00Z%80%01%3C%E6R%5C%0D%08%01%25%3AP%04%1A%E4%10y%C0%C4%89%018%A0%BD%5CYk%0C%20%E0%E3%09%18%00%80%0A%C8%A1k%CF%04%19%01p%D0%83%10%7C%C1%0DFP%186%B39%13%18%E8%C1%0D%E8%A4V%12%B0%00%FF%04%1E%F0%80%0C.%00%C1%0B%D3%A9%AB%06%94%40%07I%20%00%17%E8%00%B0%14p%8B%92%5Cj%D3A%180%81%16%84%80%02%3A%2B%81%14%A4%A0%03G%11%D4v%02H%02%1A%22%E9%90%01%1C%21%08%AE%DB%0B%20%E9Y%90%17%1C%C0%05J%88%E9%0D8%D0%02%04%A8%E0%A2%1E%FD%28A%05%A0%01%25%AC%C0%8A%04%E9%00%10F%10%84%3B%2Cl%21%10%B5R%130%B0%3A%0Dl%60%03%10%00%01%06%3C%E6L%9D%A63%09M0A%DC%8A%D0%04B%40%C0%0Dt%10%01%E8T%CA%D2%91%9C%A0%06%1A%D0%15%28%05%A0%80%81ZU%94%EAS%82%08%0F%02%83%26%60%01%03%B0%A4%C3%05%C4z%BC%A4B%A9%05%18%7D%AB%60%0DV%82%2B4A%04%A7%8C%00%10%D0%00%02%0D%E8%C0%08G%40A%0C%5EpT%85%F8UI%270%82%5B%07%FB%D6%06%80%20%08T%D0%EA%14%9A%A0%A8B6%60%07L%88%C0d%2Bk%D9%95%B2%14%00%2A%C0%40%F0%AC%A5%03%29%FF%8Ca%0CI%A8%2Ag%1D%29%80%12l%40%0E%1C%B8%02%0E%D2%8A%BA%20t%40%B5%94%05%AAA.%1B%24%03D%60%07%B8d%9B%06%B8%C0%81%1D%B8%00%08%1C%28%DAn%09J%80%1F%B2%0A%0758n%0CV%C0Z%A4%BA6%9B%00%98%40%23u%B5%8160%A1%02Sp%C0%14%3A%10%84%CDn%D7%918po%04R%40%5E%E5%B6%E6%BC%C0%3CA%0Bp%A5%2B%1C%5Ca%02%11%B8%40%0CR%20%02%11%D0%01%8A%F7%25%28%0EH%20%CB%14%94%CC%BF%FF-%2BG%2C%C0%83%12%14%AE%7B%26%98%EC%09%C6%C9%00%14%A05%94%11%1E%E3%F0nP%84%F1JrC%00N%E5%A5%08%C6%2A%01%E0%EE%02%94%1D%80%01v%CC%80%23%5C%21%BA%DE%04%25%8AS%AC%B3%B6%ED%00%05%C95%60%8CS%19%81%E6%B1J%01%3D%88%99%2F%0D%60%02%0E%9C%CFSl%FD%98%0A%21%90V%22sn%C5%26%18%5BE%98%1B%24%18%28%E1%8E%9D%12%19%19%3E%60%9D%0B%28a%FF%0C%AF%EC%E8%06%A4%DAN%23%D4%20%085%E8%A6%97%A5F%00%0D%B8%A1%97%0BZr%20%070%85%20LK%01%20P%01e%0D%F2%81%16%E8%00%02%23%B8%02%23%CC%10S%15%D0%C1%04%11%88%C0%14%DA%E0%A9%3D%EBQ%03%90%B0%1A%86%13Bf%1F%19%00%00%83%80%A2%02F%E0%82d%16%04%00z0B%0Bx%80%00%3AL%21%D3%11%90%AC%08%3E%F0%81%23%DC%80%C0%9E%16%17%A7%18%D1%01_%3EH%C3%20%214%B1t%05%82%1Dt%A0%20%06%88%01%13%8A%90i%05%EF%1A%06%2F%B0%009%01%90%83%16%E0%2B%D8%84%0D%C1%11%C4%ACddsd%00%0E%C8%C2%95%87%87%06%04%90%0D%00%2B%D8u%0E%B2%BD%ED61k%00%5C%85%80n%C1-2%04%8C%B5%DC%E6%CEH%0E%EE%E0m%E7y%8A%00f%60%02%C6%06%00%80q%B6I%C7%0795%138%00%3Cp%1BKd%24%7B1%8C%03%8E%11%06%20%80%0B%1B%E8%60%AB0%C0%83S%1A%00%E2%0B%FF%19%00%87%B3%00l%8B%EF%EC%0BBX%81%C6%05R%EA%1A%0D%80%09%83%1B%A8%B5np%07%8D%0F%E0%02%2AX%94%CB5%F1%B2%1D8%00nc%16%B4%09%EF%C0%B5%7D%B7%AA%07%7C0vCN%DD%81%1BtZ%941%3Ch%12%C6%D0C%D5%E9%20%0B%13%88A%C9%92%CE%F1%8A%7C%A0c%06%A7%96%B5%E2%E9s%0B%28%01%C2%40%7C%A5%14J%40%81%2Cp%01%0DF%F8%C2%D4%86L%BF%0D%EC%60%93%A3%BE%90%D2%DD7%00%25%7C%DBX%DDU%C0%E3B%86%BB%0F%04%BE%20%3Fo%02%05%80%CC%3D%29%40%00%0BAP%01%0F%98%E0%80%0Et%80%0C%40%C0k%C1b%D8V%14%F7%D9%0CGx%FCr%07%3F%BE%29%60%A1%95%C6b%27%168%90%05%D8%87%EC%C0%27%98%C8%A9%EF%09%CA%F9%3D%90%10mP%81%03%2AP%01%13%A0%00%05%0A%96%B6%12%CC%B0%28%01H%15%02%21g%5B%A7%E2%8C0%07%A8%9E%205O%11%03%800%F9%5C%B9%CA%05%13p%40%FF%11%12EtO%29%00%084%F0%F9%0Bj%40%3B%DBq%0A%07r8%80%1E%AA%99%E0%18%7C%20%07%2B%B0%80%FEs%10%01%3E%28%21%0B%23%D0%06F%80%06r%A0o%9D%02%02F%10%7Cw%16%7F%F83%11%D9g%22%15%20%5C%5E%03J%25%80%06%0E%90k%17%10%01%83%60%04%E5%E7Yos%7D%021%00%26%B0%03%1B%E0tj%27%00%18%D0%06%97%96k%29%80%7F%0E7%000%C8p%16%F0%01%26%40%07M%D0%04%99V%03%23%A0%01%3D%10%04m%D0%01%26%60%024P%04%1D%E0%02%7C%F0%02%0E%C8z%CA%C3%04%23%A0%2A%A8%A3%018%40%08%3C%60%02%22%90m3%88%00%A73wcp%3F%1Ag%00%D8s7%F6U-%02%10%02.P%01%09%F6%01%2F%40N%3A%06%3E%27%C7%00%2B%F0%011%90i%22Pu5%A0%04%D6t%860%00%874%60%022%27%11%0F8%22%A8B%02%2CD%01_%E0%02%97fa%0F%07%000%D0%015%E0%06_%FF%C0%05A%A04%20%B8%09%03%90%03%0B%D4%7B%EA%D4%00YP%03E%80%02%22%B0%02%0C%80r%09%D1%86%2B0o%0Cp%02%1F%80%02%2C%08%03%DA%06%00%A7%B8%02%2F%F0o%10%F1%87%20%F2s%40p%03F%D0%04%A9%85%5C%0Bsj%2Fp%07%9D%C7%04%1D%10%03RWR%11%10%04%025d1%D4%03.%40%03%28%00%03%A0%F3x%27go%03p%02y%18%8B%F6%B6%09%3B%C6p%12ule%17%11%03%C0%000%90%81%B9f%7F%FDE%10%A7%16o%17%B0%8E0P%8CS%07%004%10%04z%E6%40%18%E0%02%C6%D7%8E3%27%10%D3%98%8D%06%B1cI%F8%8D%12%11%8E%2F%C0k9%A0m%A28%10mh%01%F9%17%8A%16%11%8E%9B%165%F4C%01.%D0%89%F8%88%27%00%09%110%08%00%1Ay%90%02q%04%AEvr1%88%11%D5%C8%04.%00%91%D2W%03%0E%F0%8C%0Ci%91%17%A9%11M%D0%03%1C%E0%00%22%A1rzP%03%EBEtr%FF%40%06%11%F0%01%2B%C9%92-y%3D%CF%98%03%D2%C1%00%07%00%02m%13%04%3F%15%12%03%B0%02z%C0%01%B0%D76m%B0_cG%14%B4X%8B%0E%B0%03%90HD%03Qx%23%B0%2A%CE%07%2B%21%91%8ELp%03%9AC%00%28%85cJHv%3F%D9%10%DB%A7%03%25%D0%00%5CP%01%A3%02%00%08%F0%05%A9%83%05G%90%8F%A3%08ot%60%06%0A%90%05%3D%C3%93%93%18%3Ek%D9%10%16%B0%03%E1%D2g5p1%23%B8x%D5%02%01%3B%80%00%17%A8%97%FD%08o%2A%20%075%A0ZSY%14U%D9%21%11%C0%05%B8%D4%03G%C0%00%2A%D0%5D%3E%A4%00o%09%01%24%40%09%90%40%05%20qj%11P%05%09%B6h%FCQ%98%29%27%04%CD4%7D%20%40%060p%00%21%D0%03%180U%23%80%05%24%20w%20P%03%E5e%11%5E%18b0%90%9C%94%91%96%B1S%01%40%20%05%BA%12%02d%B0%02%7BB%07z0%08%13%20%04S%00X%0D%A0%01%F1%FFD%5E%C9%E6%86%EE%F8%9C%B6%89%10%16Pu%06%E8%29%14%904%B18%8E%B8%96i-%00%01%0A%85%00%28%20%94J%D9%8DJ%D1%99%0DB%03%E2w%07T%80%06%AF%F4d%24%A0%04%17%40%5E%AF%08%8B%2F%80%02-%20%05%81%22%95%94%89%10%83%D9%3Ek%C9%00S0%08%7C%B0%08_p%05_%D0%03I%40L%1A%80-%22%80%2A%13%00%3A1H%03V%D7%04w%90%A0%13%AA%15%FE9%20E%C0%01%23%B0%28IpP%40%B6%2Ax%09%03%C3%22n%83b%02.%80%01%90%92%9F%CE%29%171%2A%20%15%10%02%9A%C2w%21S%8F%83%D01%20%00%04%CB%22%04A%00%01%C9b%02%3C%F9%A20%0A%9D%28s%2Fi%D7.%1AP%A3mU%88%16%A5%01%1A%10A.%96%1D%859%00DCy%06%83%89%9D%A3B8%00%A1-%10%01%CDY%A1aq%A4%FEq%29%98%40y%5E3%03%9C%02%A8j%D7%5D%B0%C4%04%829%18z%BA%1F%2F%00%03%01%FF%10%00%1E%60%036%00%A8%1E%E0%A8%93%1A%00%5E%D0%A8%1E%20.%25%80%094%20sx%3A%16%5Cj%28%15%B0%09%29%90%02%05%A1%7F%2Fp%04i%D0%A8%8Dz%A9%AB%1A%00%28%B0%AA4%B0%AA%99%9A%2B3%00%06s%D2%26Gp%04%081%AA%A5%1A%17%89j%1E%0B%B0%09%08%80%00%04a%01%090%08%1D%B0%00%13%D0%AC%E1%17%00%0EP%05i%40%03%2A%E0%02%CB%DA%01%28%10%003%90%2B6%00%06%83p%00%E0%9A%00%09%00%AE%07%C0%03%041%AC%C5%0A%AC%A1%9A%27%E8j%AC%3C%40C%9B%60%02%3CP%05-%E0%AC%CF%1A%00%2Ap%00-p%00%E2%0A%AEB%10%006%E0%01%5E%E0%05%1Ep%ABE%20_w%A0%02%2A%40%7C%C5w%AE%C4%EA%AE%F0%8A%16%C1%EA%1D%08%B0%00%16K%AC5p%00U%60%02%C4%3A%01T%90%00%16k%B1%07%60%A9%8DJ%034%C0%AF%E3%DA%02%98%CA%AA%8E%1A%00%1F%40%06%E3%DA%AFUp%00%02Q%B1%17%FF%8B%00%19%BB%B1l1%B1%E2%81%AE%09%60%AC%02a%01U%E0%00%F5%EA%00%C27%B2%D0%DA%9D%0E%90%00%CD%9A%AF-%9B%06%E2%BA%AA%5E%F0%B2%07%80%00%E0jS4%EB%B0%08%F0%B3n%C1%B3%DF%D1%AE%03%11o%260%01%21%1B%B2%23k%B4%07%B0%B4M%3B%B2%93J%03%16K%03%8E%FA%02d%B0%B0%0A%5B%01V%5B%10%60%DB%B5%EB%BA%26G0%AC%13%C0%03%22p%04%0E%00%03%96%D0%02E%D8%01%CD%DA%01H%0B%ADi%CB%B4%13%E0%B4%02%1B%00B%F0%AF%5E%60%03%01%60%B5%20%BB%00%E3j%AE%03%D1%B7%0F%BB%09%40%B8%B3%7B%2B%26%5B%7B%00%C3J%06d%10%00d%C0%07%8D%9A%00%1D%90%00U%00%BB%E2%0A%B7%92%5B%05%27K%AE%2A%AB%AD%2C%3B%03%01%00%06%220%01%09P%035%F0%B3%F0Z%BA%60%9B%AE%851%BA_%D2%01%A3%EA%B7%070%01U%F0%AFB%90%00%28%B0%00%0E%E0%00%D0%DB%02%AB%0A%BC.P%05%F6Z%AF%00%FF%3B%03%90%2A%BE%92%3B%AE1P%AC%29%A0%02%09%A0%02%A0%DB%BC%C4Z%B5U%C0%AB%C9kn4%BB%02%F5z%04%60%00%064%F0%AF%AC%FAR%AF%EA%8C%01%40%03%F6%FA%AC%DB%CA%2A%BC%8B%02%0E%F0%01%26%25%BF%290%AA%03A%B3%16P%AC%F2%3B%BF%1C%87%8A%22%10%03%D9%CA%AA%95%8A%A9%97J%B2%2C%DB%AA%C13%A9%60%A0U%9F%9A%A7%CA%0B%27%DC%96%02%26%10%04%E2%5B%C0%01%FB%A8%9D2%03%DB%2A%BE%90%3A%BE%28%26%BE%1C%C07%84%E1%B5%E3Q%8D9%00%03%40%C0%A4%0Dd%04%17%C0I%7F%C1%C3%E21%8D%0C%C0%03%26%E8AY%E0%00F%EC%17Hl%1E%DB%E7%A6%40%84%03%21%206%25%FC%15S%3C%1E0%00%5D%CFd%60%0E%40nG%7C%C2y%02%00%7C%20P%B9%D4gO%BC%99Rl%C6k2%00%1D%E0%06V%0CC%1A%40%08S%E0%C6%E0%01%C7eb%00%0E%C0%01%84%F3L%7D%F6%05%15%20%8Bo%5Cv%0EP%039%FF%B5%C6%1A%40%C8d%7C%C8%E6f%00E%B0%03%00%B4S%A0%12%01%8F%BC%C7%E6%86nF%D0%5D%1F%05B%29p%9Et%D1%C5%D4Ah6%19%86%1E%A4%00%18%C0%07%88%B5%C3%7Cl%25%26%00%04%ED%F9%C9P%C7f%AE%ACa%03%20%04pGP%223%08%B6%8C%A8%AF%0C%25%0E%60%06%DE%A5S%22s%04%CDy%CB%2C%D5hx%25XI%E0%06G%A0%9F%C0%FCZd%10%02%D4%F9V%D6%82%06%1D%D0%87%D3LO%1D08B%9CG%2F3%85z%AC%C9%D9%94%027%80f%82%A5%03%210%01%7C%A5%CC%16b%01K%A0%10%40%BB%09j%F0%AB%0Aa%01%1Ck%02%11K%AA%C4%17%B4%1CK%11%2F%B0T_JP%3C%C3%04w%0A%CF%FEQ%01e%1B%B2%120%10K%00%07x%20%01%14-%01x%20%10%0D%BC%00%15%40C%12%80%00%0F%AD%10%15%40%B3%07%E0%C05%2B%AE%2A%60S%3F%5B%CF%0F%F1%01%3C%40%02M%9CK%C82%85H%A7%D0%FB%FFQ%01%1F-%01%A3j%D3%03%21%01ZP%D1%15%AD%06%9B0%D1%0B%20%01sP%B6%0B%11%D2%9B0%D2%1CV%B36U%AC%EA%8B%BClI%03BU%9C%E1%ECHI%60%04%1D%90%02%86%5C%C6%03R%01%09%40%D1%09%00%07%12%20%D6%02%C1%D0KP%ACh%8D%074%A4%06%0C%5D%01Z%80%00Z%60%B7%09a%BA%0B%20%D2%3C%10%BC4%84%00w%CD%03%1A%3B%AE%070%C1%09q%02%15%A0%07%3B%F0%058P%C7W%F5%05%08%60%02B%B9%C5%60A%CAIQ%01p%40%7Cp%80%00v%FB%D1p%90%00n%ED%D3%17%BD%09%3D%3D%D4sP%ACQ%F0%D1%9B%E0%D5sP%10H%CD%AF%CFV%B3%EA%5B%01%2AP%05%0A%8B%00%AB%9D%10%26%D0%027%80%01%3A%D0u%9CU%02%3D%00%04E%A0%D5Z%FA%D8%C1%CC%19%5E%5D%AC%09%10%05%080%DA%02%B1%048%DD%D3%15%DD%D9x%80%07%1A%FD%D6K%B0%00%9DM%AC%9A%8B%DA%22M%D2%0F%FB%DA%0B%FF%60%B5%9A%1B%D0%08%81%2A.0yU-J%C8%82%92HV%A4%DD%81%A4%29%7D%DC%C9M%DA8%8D%07hm%D9L%3D%D4%99-%01%DF%ED%C0%11%CC%DD%1C%B6%DD%C4%9A%D7%F9z%00%0A%9B%D2%0A%01%00M%00%02%3D%B0%03%F6%B9%5B%0D0%02%28i%A7%A2%DC%DE%06%82%D3%06%81%D3c%0D%D62%A0%91%5D%00%B2jP%AC%12%9D%00%A6z%10%2AP%D7%00%3E%D2%18%AB%02%26%80%D2P%7D%10%86G%00%BB4%02%BBU%02F%00%04%15%80%02Y%CA%18%90%CD%14%16%5E%10%18%DE%05%7B%B0%07k%B0%07%3Ep%01%09%B0%04%09%B0%062%00%D7%C5%0A%D4%07%81%00%0D%2C%D2%27-%E2%0F%9B%02N%9D%D2%DCM%10L%E0%06%81%7Cy%5D%A6S%9C%A2%00F%40%06%CE%08%8D%C1%0D%AA%0A%A2%05NN%108%BD%06u%A0%082%20%012%90%BFq%00%06%8F%90%07g%B0%E4-%7E%10%F2%1A%AF%C5%EA%C0%E8%5B%DF%7F%7E%104%B0%03%98%D8%00A%FF%00%E6%0F%04U%A8e%02%17%90%03%3DY%1D%1D%02%00%29%90%BF%1F%CC%B2%88P%06%8E%CD%10%16P%92%9B%E3%02%D1%F7L%ABRw%D6uk%ABu%E6e%B1%E3U1%00D%60%05%1E%60%05V%80%08%1E%C0%02%0F%60%05%F5%A3%01y%E0%08%EC%BD%11J%90%9B%D2%E7N%E7%AD%3A%F5C%02%07%40%07%99%E6%89%E7%18%19%AAN%15CP%07%2C%E0%29%0F%80%01%21%40%02%23%00%08%21%10%05l%80%07%17%D0%07%A8%FE%E4Y%C0.%BD%C5%29%10%D0%06%2F%FD%3Cm%C3%01%2APm%22%90%03%D18%19%13%FB%07%1A%F9%15%17%10%06%19%C0SP%C0%06Z%D0%08%08%20%03%89%20%08%82%80%04%220%04%2F%CApn%02%03%98%16%01B%C0%04%08%40%A0%9CR%3Fnp%05%EED%08%A8%AC%3AgZ%A78%0E%8A%FC%19%1D%7E%F1%07K%10%08%08%90%08q%40%04%87%90%02e0%04%9B%1E%13%03%B0%04%23%F0%00%10%B0%05Z%60%07H%B0%8E%EB%FF%E8%08%29%60%08%E5%1C%900%C0%045%B0%03%1C%60%06%21%00%F1t%165%F5%F3%27W%00%80%D7%8C_%24%90%EE1%00%E9%1C%A9%F1%7D%91%07%5B%40%02%2AD%01%21%10%06%1C%10%052%A0%06%C3%FD%10%16%A0%08%02%90%01%80%20%011%FF%01%3E%D0%07%16%60%F6C%C0%8F%15Ah%07%D0%03%3A%F0%7E8%10%F7%8A%D7V%9E%02%01%1C%E0%06n%D9%00%E5%0ER%10%90%99%ABu%F2%EA%AA%17%22%B0%04l%40%EB%19p%F8%0F%F0%00%2C%C0%02%1A%C0%01%89%00%F8-%F1%01Q%C0%02m%E3%04%81%E0%03%02%1F%83M%3F%11%27%D0%01m%80%03%BA5%5B%3DP%03%09%B5S%18P%03D%0A%F9z%2B%17%22%90%07%20%DF%08q%10%06b%B0%F2%06%13%ED%8A%B0%EBBQ%01a%40%F9%04%10%02y%20%F0%21%01%03%83%80%06R%10%86%9F%92%05%B7%C4%5D%8D%DC%01%88%A5%FA%AB%CF%16%F0%C3%06%B3%0F%08%18%00%08%8D%95%F8%3F%600%19%00%02e%FF%B8%F5%0B%21%02Q%C02%2C%CF%06H%60%F2%20%81B%EAV%D5%CE%C7%05p%DA%40%0A%20%05%5B%27%05%1E%F5%29F%10%01%D2%0C%1Aa%D2%01M%90%E5%13%01%10%26Td%D9%A0A%C0%83%0C%09%05%10%D0%D4%D0%E1CM%194%3Cq%11%25%10%80M%195n%E4%D8%D1%E3G%90%21%3F%0A%E2%80%01%21%08%272R%FC%19%20%D2%E5%C6%13%1D%80%60H%02%D1%E6C%05%18%B8%E8%B8%D9%F3%26%81%06%14n%18Ac%86%84%06%05%0A%40%18%89a%A1%E5K%A8Q%A5N%A5Z%F5c%01%8CV%B5N5%C1C%C3%0D%04%06%40%9E%10%91%E2C%8E%15%16N0%00%00%A0H%0D3%10p%2C%F4Y%97%80%00%01%1BHp%E8%22h%EB%DF%A8K%A2Px%A0%89%C5%93%BE%1F%00%88%05%BC%89%C6%84%1B%1AJ%D4%B5%A9%A0%C7%97%9A%94%7D%DE%0D%D1DH%87%0EL%10%98%D1Tb%04%15%11%0C%1A%AFf%DDZ%2AV%D7%B1%85%5C%91%A2a%07%FF%1D%C6%1B%2B%88%A6%C2%83G%0B%E0-%A8%28%09%E2%06%82%26%02%0C5%2F%D7%84%81%AF%A3%D8%AB%FFDy%B3%A1%B0%A6%07%24%B4%1C%EA%F34%E3%9F%3E%7F%DAzw%19%81%09%87%1EI%040oH%20%09%86%1A%3D%1A%B0%87h%BA%C9%9D%08%17b%A4%B8%C0%E3%CA%98%10%26%80%21%AB%E8%0A4%100%D8%0E%FC%8B%06%2A%E6%12%40%03%2CZ%28%E2%85%15L%E8%E0%08%0E%B2%A0%A0%841%3AL%22%09%29th%60%3D%FA%98ch%03%0C%DE8%03%09%05%A9%22%22%8A%A3%AE%D3%E4A%0E.%CAM%10%19%A2%E0%20%8A%3D%F6%90%21%10A%04A%E2%02%B3%60X%21%82%29%A6%08%CD%3F%02x%A2o%A1%10v%E0%02%03%05Jl%A8%81%11%82%B8%03%85%0F%5EP%CB%82%29%80%18c%91%0Er%20%B0E4%D3%BC%EAL%AD%C8k%8D%87%11%E6%9BQ%03%0C%18%81%04%92E%E4%C0%A0%01%29%14P%CEJ%40%1D%BAk%83%11%DE%D8%C3%0F5C%12%FFa%0E1%20%C0%0B%A2%07P%12%84%B1%0B8%80%80%85%07%10%CA%00%82%11B%08%01%0A%28%9Ex%82%91E%18%A1%84%12BH%C0A%BD%12%F1%CA%E2%86%116%90%B3%C4%12z%08%C2%01%14%608a%00%5E%07%C8%81%0F%0CZ0a%057%135%B6%C5%04%B7%3A%81%86%0Fr%5BM%0F7%1Emo%BE%0D6P%40%8A%06%FE%0Ct%5B%87%F0BQ%0C%04%F2%28%C4%822%0A%29D%C8%0B%0Ciq%08-%9C%C0%E0%07%BAlz%00%83%28%FC%B8%40%0D-F%60%E1%A6%1F%FA%D5%E0_%0D6%D0D%87%10G%5CN%5B%01%14%40%C3%08%08%0C%B6R%00%1C88%82K%06%C8%1B%80%89%1A%A8Ha%D7c%3BF3Y%AD%8E%B8%A1%0D%13%ACb%C0H%F2%5E%E0%81%82l%21%BA%2B%5Enc%86%E8%A0%E6%A0%A8%04%81F%E6p%E2%890%B6%E0%60%12%1F%8A%FDk%00%00%0C%D9%83%8Dw%91%AB%EB%01%0A%C4%08%E3%0D%C2%ECJ%EEe%12%97%CB%FF%29CM%AAL%8A%83%1D6%DCV%87%10x0%E1%03%8E72%20%87%0E%EE%F8%80%01g%3Dv%DB5%90%AB2%80%0A%0A%40h%22%05%AAL%A0%83%07%2A%3AX%5B%AC%23z%20%E1%28me6%7C%B3%0CX%C8%8B%0010%60%E1q%02%C2%98c%09%1F%00%10z%AA%21%FA%28%A4%0B%27%28%60%A8%F0%9E%12%7F%3C%83%C3m%CA%0B%8B%10%AE%A8%E1%0B%01%20%A8%C1%D2%2A%03M%F8%86%0E%9A%12z%00%0BP%7E%7B%F7%D8%E2%CE%FB%06%08%A4%B8%82%8F%132%02%40%0F24%1A%A0%88%29%22%D0%E8%03%26vH%8F%80%2C%85%05%80%0A%12Z%A8%02%84YK%F7%FE%27%A6%9F%00%81D%01X%C8%00%90%3A%A2%A8%E0%F2%97%2C%40b%0F%17%C4%D8%80%F4%EFen%80%84%2B%1C%DD%20%84%1Bn%20%C1Zn5%00%04%3C%88%80%99%DA%96%11%03%00%80-%EC%E3%5D%03%A7%E2%BB%A8%0C%E0%025%00A%95%A4%10%84%F5mb%02%238%8D%10%16A%FF%85E%5C%81%0AL%B8C%07x%C0%05%01L%26aR%80%00%17j%D0%86%03T%40%0FX%98K%FDl%F8%90%07%7Cj%03%DA%22%40%06%0A%13%02%278%21%0F%1F%98%0A%12%14%E1%04%08%20%04t7%A4%95%1Bz%10%3B%1C%40%40.U%0BT%09H0%88%8D%B1%CF%00%038%A0%03%BD%F8%406I%05%00%99%A8A%16%8Es%97%1A%AC%E0%04%96%12%00%080%D0%83%F1%25A%03%24%40%03%166%A0%03%2A%CE%28%28d%C8%CF%05%9A%F0%C4%252Qf%0F%08%C3%08%7E%10%C8%A9%01%22%0Cu%20%02%035b%87%3A%98%24%8F%82%04T%09n%F0DA%25Gf%25%C8B%07%06%D4%C5%2F%86%12Aa%94%CA%00N%E0%80%1A8%AA%8D5%A8%02%06%92%93%B0%A40D%00%0D%00%D1%24%1D%92%04%20%98%00%06%16%88%C1%F6t%E0%27J%96%EE%01%1C%A0%80-m%A2%81%07%9C%A1%0C%A0%D4H%058%F0%03%19%D5%05%2F%07%C9T5%8D%F9%3DK%FFb%F2%7BR%B8%81%03%5E%E0HQ%86%13%8C%5B%C1%DD%07%94P%CC%86l%40.%81%D4%8C%0000%81%18%EC%0A%004%00B%0D0%A0%03%3C%9A%28%98%3EaA%E7%AE%89C%12%28%A2%0C%0C%14%01%1B%E6%A7%99%C4A%A0n%21%08C%18%A0%20%86%10h%20%03%EC%E4%D64%09%A0%003%8C%AF%7E%25%E0%C2%14%88%25N%90%8E%92%9C%0A%24%03%04%94%A3%C9m%B9%93%0E%8A%D9%22%03%22p%07%1E%04%21%0B8%C0V%20%05%40%81%11%1Cg%9F6a%08%1B5C%80%07%D4A%0De%20%A5F%CE%A0Q%CAd%60%04N%08%83%18%A0%10%02%12%40%F5%09bx%02%09%E0%F5%BD%06%28%B4%98%3D%C0%82%A3%EA%A7%03%0ET%C0%29%21%25%ABU%208%95%154a%87%87%13%40%16%98%60%A6%8C%98%12%06%26p%00%02%94%00%2B%05%D8%B2%01%1CPA%1BF%A0%81%9D%3E%84%05a%20%1CB%9F%10%899%D8%A1%A8%9B%B8%C0%1B4%40Q%E4d%60%FF%0E%23%A0%40e%E7WM%02%40a%0BO%40%A7%CC.%AA%14.%18%21%08%18%F0%D4Z%BF%07%D6%22%8C%B5%AC%AB%8D%CAY%A3%22%82%03%60%C0%7B%02%B8%C1%B0%BCC4%0B%E4%20%05%BB%A1%82%116%90G%02%E0%C0%05%11%88%C0%04%5C%80%85%C0%12%80%05%18%88%84I%0FF%01%85%BA%80%7D2%00%01d%B1%C3%01%ABZ%F4%21%40%D5%C0%1B%C2%00%82V%25%25%29%0Dh%C0d00%82%1D%5C%A1J%1C%08%82i%B1%C9%01%1A%A8%96%B5%F3%0D%89k_b%80%26%DC%A5%27%0A%28A%09%AC%BB%5D%0E%A0%40%BE%9B%D8%22%00N%F0%82%0F%C4%80%0E%BE%E5%21%17%98%C0%25%11%5C%A0%03U%C0A0%05%90%81%27%60%F7%07%F4%E9%21%07%F6%40%A0%3F8%C2%0E%7E%90%04%07%1EK%19w%1A%14%C5%2C%08%01%07%DC%BB4%29P%00%03%18%00A%0FRw%85%10p%C1%C5%F3%D9%AB%0B%9C%8B%1C%05%90%97%BC%FD%EDo%F7%98%23%85%1A%98%E0%04%CC%FF%A4%2F%7D%ED%EB%92%01%04%21v6%11%60%16B0e%E6%18%21%02K%EE%C8%16%07%C0%80%DC1%E1%06%27m%00%06T%10%01%B21%60%05%1F%D8%A0%20%CB%07%81-%BCa%04%0F%F8%EFC%04P%87%10l6%0E%7BxB%25%A6%DA%B8%E5%FC%40%02%14%60%0E%0B%E6%00X%CD%3C%40%0CF%90%83%A7B%20%87%2B%C8%C1%0C%84%80%40%09%C87%82%2A%E8T%00%3D%B8%C1%0Evp%034p%E1%06n%E0%02%09%26%C3%9E%9B%B6%00%05lkr%AB%3B%F2d%91%00%80%07Fn%88%06%DC%00%04%26%CC%F0%9F6%81%80%0B%88%08%92%02%A7%D5%95%04%F0%82%17.p%02%DBy9%07%96%08%40%B3%9B%ED%01%5BZ%C1%03%D3%A6%F6%B4%AD%20%00%0F8%5B%DB%01%F0%40%188%00%85%83%02%AA%0E%9A%80%80%1B1%B0%81%1F%3C%CE%7C%17%9E%26%5E%10%C6%01%40%D4%99%00.%D80e%08%B0%97%11%80%A0%B2%95-%AF%88%F2%A8%00%F4%8A%D6%08%84%80n%FFe%DD%D8%03%C1%7Da%CC%A8%FE%02%13R%C3dW%97%15%D6%21a%40%0BhM%00%0D%B8%A0%08%110A%1B%B0%BC%1C%0Ad%8C%06%2B%006%00%2C%E4%85lw%C0%01%0A%E4b%5CS%00%060l%1B%11%7F%C2%0B%22%B6-%F3%9B7%7B%08%228%04%20%A4e%A5%1F8Aq%09%C9%C0%A3.%FC%00%0D%00%C2%8D%FA%A6%80%C0%1C%02%02%0E%D4%B99%5B%F8o%E7%F2%DAn%14%E7U%138%A80%D5%60%99%D5%1A%10%9A9%02%D8%81%09%E0%1Aq%B4O%1C%24%0C%00%16%15%19B%02%15p%09%06%13%F0%2A%7D%A2H%08%0E%A0%81%07t%98B%97%01%90%02g%F3%80%0C%15k%E6%02%0C%7Fx%C3%13%21%00%A4%E3%EE%0F%C0P%08%09D%5E%F2%91%B7C%00%88%80x%C4%07%22%00V%90%BAM%7E%D0TLU3S%1B%00%C4%A78%C0%06%27p%60%0Bux%03%06%10M%01z%B3%07%89%D6%FD%01%07%E8W%3Fwb%A1%CE%1Bh%C3%05%06%FF%8C%F6%26%AB%FD%23%03%E8%C0%08%A6%8C%97%11%B4%81%06%1B%03%C0%1D%D4%0B%28%1C%A8%93%02%B6j%F9F%7Cu%00%EC%27%20%01%D8%3F%40K%2Ap%80%3BL%80%0FT%D8%03%02%D6%90%87%00%B0%00%11%888%84%24%E2%E0%83%40%C0%81%08p%E8B%FD%E7%AFx%22H%80%082%E0%BF%0C%E2%B0%06%3F%00%83h%02%14%0A%E0%00%0Eh%2A%AA%0A%95P%D9%02%28%D0%847%00%81%D0%A3%80-p%810%A0%80%A0%83%82%5Dk%08%94%18%C0%9E%D8%80q%D3%81%12%F88%C3%B1%8D%0C%DC%3A98%82%14%605%E0s5%E1%FB%88%22p%83S%BB%A9%1D%90%10%14x%01%8C0%01385%2By%25%0D%40%83%0Bp%13%03%B0%80%05p%00%07P%01%15%20%C2%05%10%81M%F8%BE%F0%C3%3E%FF%93%80%40H%01%3Bx%04C%10%81%3FH%01%1FX%825%B0%00%09%E8%BF%00%D0%3F%CB%D3%3F%19%88%3C%FF%5B%83%40%40%84L%89%19%19%C3%00%40%18%81%FF%C1%81%82%9C%C2%949%1A%01%F2%29%3A%0C%F0%99%278%24%14%DB%80%CE%A9%B3%11X%03%23%C0%02%0C%28A%F6%C0%B8%20%A0%B5%9B%C8%B8%08%18%90%15%8C%B8%16%F4%88%08%D8%01M%18%01%23p%01%25%98%02%1A%C8%15V%BB%004p%92%40%C189%28%820%02%80%05%80%814%A0%02%2A%F8%00%18X%80%1C%18%80%EFK%01%04pB%3F%90%00%3F%F8%03%0BH%01%ED%AB%1C%00%20%8250%84%04X%02E%90%813%00%C3%FC%BB%801%94%80%3D%D0%C2%25%28%03%04p%9C%8A%BA0%A2%CB%94%A2C%8E%1F%18%01%28%B8%BD%F6%D0%84%7E%A1%A8%0B%A3%80%27%D8%82%7Da%8Ej%B41.%10An%21%00%08%90D%C8%C2%01%12%A0%82%0B%F8%A8Fl%B5G%EC%88%E6S%02%04%A8%00%130%01%14%88%81%1C%20%BCM%88%013%F0D%40y%10%2C%28%02%15%D4%08%03X%00%3E%E0%03%EC%9B%80%09X%00%CB%B9%83%03H%81%09%C8%BE%FC%5B%1F%FF%00%B0%03%C3%2B%04%5E%99%C5.H%00%1F%D0%825X%003%7C%04-%9C%04%91%5C%83%04%08%04%28%DCEw%09G%1Bj%1D%0E%E0%40%84z%00%08%80%82%A7%81%99%9FB%BA%1B%18D%EF%21%80%0D%10%C4%7FB%C7%E1z%B8x%94%C7%C5%82%0A%03%60%80%0F%B8%00%FD%10%81%1C8%01%CB%D1%08%11%B8%81%CC%10%94%19%F8%13%02%98%01%97%99%25Z%DA%80%1DX%B5%03Z%00%87l%C8%87%8C%C8%03P%92%26h%825%C0%03%09%A8%00%2F%C3%03%3C%60%8C%0A%80%038%28%84%8C%94%01-%0C%04%0B%60I%3B%20%82%FA%93%80%25%D0%3F%00%F0%01%3F%88%02%08%BC%A1%9B%DA%02%9A%94%26%0Ax%031%10%03%12x%00Bl%0F%0D%08%01%23%D0A%EF%D1%89Y%29%1CPt%C7o%3AJ%A4%04%0CSZ%015Z%20%8Ep%8B1x%88%19%F0%82%00%F0%02%1B%98%B6f%F3%82n%D1%0B7X%98%1D%40%00%A3%EC%B2%01%40%00%04%E8%15%030%80%FF%298%00%25%E0%3E%09%88%825%A8%00%02%0B%CE%DC%A8%00%09%20%B0%5E%01%00%C2%CC%BFa%94%3C-%A0E%DC%B2%83%CE%81%C9%9F%C4%800h%CC%D0%C1%00%3C%A3C%F0%F4%89%0C%A0%80%1D%F8-%DCCO%9E%C0%0B%1C%D0Jw%0A%82%08%D8M%D1%9C%AFy%E4M%5E%E9%22%7D%84%811%F8%93ls%00mK%03%1A%08%00%1B%98%91%0D%40%032%98%82%22%B8%83%3B%18%9B%A4%F4M%8E%E8%80%ED3N%09H%00%22%F8%88%0A%C0%BC%C3%F3%83%01%F0%83%04%98%BC%C8K%00%E5%DC%A2%3F%A8%80%28H%A2%9F%B0%89%DB%CB%00%2B%F0%21%CA%BC%B6n%F1F%40%C0%C6v%02%01%A4a%81%CEk%88%D6%81%82%2B%10%C8%D2i%A3%1B%20%2F%0D%E8%81%2B%A0%92%19%D94%E5%7BGp%AA%CFP%BA%CF%A98%00%01%899%0F%D0%84%19h%B6%05%08%00%078%00%2C%D5%D2%29-%8D%2C%00%02%14p%CA%18%F8%80%DF%E3%88%238%02%E59%25%15%98%02%FF%22t%00%22%20%82%3A%98%84.r%84D%28%83%0F0%842%28%83K%90%01%3B%B0%005P%9F%0A%08%D4%40%8D%025P%9E%21%20%02%27%D8%00%C5%21%11%02%C86h%B3%02ik6D%D0%04%2B%C89nk%08%A5%9A%C9%87%19%01%0E%E0N%F6P%2A6%00%26%DC%83%80%2F%F8%82%11%C8%024%F0%B4%11%D1%80%118%80%0E%40%01%03bR%27K%CA%BF%D8%AD%0A%A8%022%E8%80%29H%83%00%A8R%2B%DDR%60%856G%95%89%B4X%8B%EA%13%095%EB%00%13%C890%28%03%118V%8D%E8%03C%08%00%98%AB%D6%98%23%92%21%80%B2%3FX%02w%01%81%1F%28%3A%9B%F3%81%9B%AB%BCl%0B%00%19%C8%C2%25%08%80%F7%DB%3C%01%F8%81%87%F2%D4%E5x%00%40x%83%F2%EC%89%BB%20%81-%90%02n%99%01%1B%B0%01%AE%BC%09%10%C0%029%F8%02Y%C9%02%3D%E1%80%260%014%F3GYe-%27%7D%89%23%C0%BE%05%A8%82%03%18B%FF%02%0D%00%87%5C%00W%AD%82%2A%C8RnsMg%03%81%20%A0%81%E0%04%89%01%88%01%11%40%B0%22%40%80%1A%08%01M%88M%7F%9D%81%19%60Q%2B%98%D7D%C8%83%0A%10%01%008%84D%80%02%0F%88%26%01%B8%B6%84%90%045%808%DC%C9%83%3Dh%A8X%B1%BC%04%40%02u%1D%C3%04%80%03qE%02%098%03%AB%FD%C8%FA%E36%8C%C3I%83%E0%B0%0C%60%28%F0%B4%29%08%08%01%400Gl%FB%D8iC9n%E3W%D9t%B6i%EBW%9C%202%05%28%40%E2l%1E.%29%9B%86uXZm%8C%05%98%020%B0X%1B%08%80%16x%C8%16P%81%03p%01%C3%E56%2CM%00%17%E06%0A0%024%FD%08%00%E8%80%3A%BA%013%08%81%861G%1D%BD%A9N%E9%B3FHZ%892O%E7%F0%01%A3%B5%00%11%40%82%3C%D0%91C%08%80.%90%80%2F%EC%02%22p%DA%00%B0%B9%8F%ECB%09%88%03%C6%15%D7%84%8031%D0I%14%FB%81%FF%C8%84%D7l%AC%9B%3C%CA%8B%10%88%13%9E%02YK%DD6%21%10%82m%2BP%88%C8%AA%20%E0%A3%08%40%81%14%E8%C7%25%CD%5B%2FzX%A8%98%82%04%B0%84%BF%05%D0f%3B%00%07X%80%0Ep%81%03%10%82%C4%CD%B6%87L%80f%5B%8F%B0%EA%22%06%A0%9B1%00A%87%B1%8B%84%90%C3%E3P%1C%CA%60%01pq%A4%04%22%97%0F8%84%0B%40%82Cx%84%CA%93%80.%E8%3F%C5%B3%83%DAm%5D%17%D0%3E%19%D8%3C%EC%80%009%A3Q%BB%10%80%A9%C2%60L%C5%00%170%A8%3C%FA%DA%27%D0%CC%86%A8R%07%D8%BE%04%A8H%8AM%00%00%15%02%EE%D3%3E%EEk%01%02%85%08%05%80%00%0E%D0%83%FC%E8%12%A9%848%EDm%D2%BD%FD%8B6h%02%2Bu%80Wm%B6%0E%D0R%8F%F5%D8lK%83%09%00%D0%29%1D%033X%B9.%12%02S%FBDv%3B%98%0Cp%02%22%80%B8%02%03%00%5B%DCE-%40%80%D7%95%81%05%90%01%FC%3B%03%FEs%81%FF2%BE%DD%09%B6%82%868%0C1%A8W%88%C8%00%10%10%9FIj%D7-%20%AC%C9%84%14Q%F1Q%2A%5D%DC%03H%E1%03Xa%00E%8165%C26%1DP%E8%BD%92%0D0%02%1CN%81%17%A8%98%EC%ED%E1%ED%FDa%AD%18%84%05%28%82%21V%81%09h6%17%90%E1%24%E6%D2r%7DM%BC%D8%AB%0A%C0%DB%8D%A0%02%F9%D8%A9%F3%8C%82%14%E0%E1%B8%EA%C8.X%83GX%00%A7%BD%D2f%DB%BF%FEc%DC.8%838%A0%60%EC%80%82%3D%A6%0F%0C%0B%01%0E%7C%80%A9%DA%80%AA%1A%C0%EC%80%82%F7%84%08%13Fa%15%0Ed%F1%9D%00%14%DE%3E%8A%7DM%9C%20%003%98%80%08H%81%15X%0CJ%1E%CD%02%21%83%05p%5E%2BU%01%16%0E%00%21H%00%14%00V%25%A6R%7Fe%88%1A%26%20T6%1E%BA%D1%5C%1Bz%00%28%F8%B0%A90%00%5E%5C%02%F7%0D%00%5C%B6R8%D0%3E%17H%E8%09%0E%00%12A%E69%7B%12o%C4%00%1A%3D%CF%FF%D6%0B%95%AB%B2%B3%0D%10%03%E4eM%60U%01%1A%10%82N%26%DFf%AB%C8%8A%84%D2%F5%ED%96%06%90%03%02%8A%81p%86%E5q%F6%E1%02%11%01%95%7B6vvH%23Fg%01%0D%00%1A%90%E1Ev%88%B9%E5%83%0B8H%8D%80%01F%E8%DA%9D%EA%A1-X%82IN%20-H%00%FE%FBUg%5B%80%83%FE%C5%0F%FDK%0AV%AE%27%A0C%FAP%B4%10%40%29%87x%80-%08%01%16%08k%9Ad%01%0E%08%01%1F5%E1%F4%0Dd%8Au%E1j%3EBD%AE%C8Km%08%1D%20%01%20%A0%01%96%16%E7%97%0E%3EK%96%0A%1A%08%82%0F%98M%99%5D%DErU%DE%9En%88%B9%9D%80%14%D0%EB%8DX%81E0%EA%9D%C2%8B-h%84%3Fh%9F%3Eh%EA%08F%E31%7Cj%82n%DD%0F%95%E0%C6%F5%80%89%92%40%06%AC%BBD%933fN%11%0A%F8%14%D3%C6%A1Gkm%B5%06%E9%04%E0d%B7n6%17N%00%C3%DB%3E%9E%BE%12%08%00%82%FF%29%B8%00%1Bt%E9%BD%FE%22%EEU%1E%5E%29%8260%83b%9A%E7%12%EEW%7F%15%80%7E%E5%CA%AD%7CY%B8%AD%0C%10%E0%83%C5%F6%08%1A%28%EA%CA%3C%1C%B3%5E%82%21%00%A5%01%10%01A%20%82D%C8%C2%0B%08%00%3B%E8%3F%FEK%D7%2F%7CSa%F4%813X%80t%F5%00%08%10%03%1E9%CCaN%B40p%1Ci%F4%A1%1F%A8%03%12%60%016x%E8%9F%20%004%D0%A6%12%DER%1A%E8%80%05H%80%0E%A0%01%91%1Ei%C6%8D%E0%09%E0U%02%BD%28%23x0%18%10%EA%E1%D6%DB%BF8%81%0B8%02%25%28%23%0Ax%2C%86%F8W%2AU%F1%F6%60qi%92%020e%93%8B%01%82%1E%F0%EA%9D%EA%A7%3E-%165X%02%3C%D0%026%B8Fg%B3%B9%E5%B5T0%C0%00%27%88%02tI%04%03%D7%0C%B3%3E%5EO%21%01%40%F0N1%60U%CE%BA%26%160%02%E3%E3%E8%2C%B5%E6%E6M%E1%8Ee%E7%EDC%01%87D%01uV%01n%93%82%FF%B0%19%1B%86%FDp%FB%ECk%8F%C8%81%16%08%82%1EP%1A%A59%5B%0F%88%CDgs%88ruT%C5%85%B6%F6%08%B2%24%40%C7%1A%88%006%A1%DCb%FA%2F%05x%00u%1B%9D%1C%D5Q%0A%D0%02A%F8%BD%0A%D8%021%00%81%C4%A1%B3%83X%0F%9A%85T%2BxQ%0FhQH%CD%80lC%04%10p%81%25%18%92%0F%90o%89f%8E%0C%20%81%27%08%01%9E%F1%AEoK7%0E%B0%F4%BAhu%82%F8%93%2A%DDi%5Ci%B64P%01%17%E0UC%B6mt%1Ev%1B%10%80%FB%B0%1D6%27%E7%AA%18%00%17x%A2I%CA%B6wn6%14xg%7F%5D%E2i%DF6%DA%04%B8Nk%03%15%20%03%07%60%A9%8D%D8%01%0Dh%99%AC%DB%0B%D4%D3%11%CE%C2%94%EE%9E%11%10%40%80%0B%08o%8E%A8%83K%99%C6%8A%CA%B8%D8%C5%C2%CCa%83%CE%0A4%CF%11%BDL%01%AA-%E8%F7%9F%A0-%A4j%08%E5%E5%B6QV%DC%D7%D4%B6%C2%96%02B%C0%E1%FFX%5D%F6Y%D5%0A%13%20%04%7B%0D%804H%00%19%C6%D8Nv6%26n%814%D0X%8DM%80%29%08%80V%05%F7%22Xy%13%B8%80%89%CF%88%1Bx%B1%FD%22%81%1A%40%00%3F%F0%83%40%0D%84FP%3D%12%F8%B9%E5P%AA8%10w%8DP%83%3Cl%F4%9B%C0%B08%40%022%5D%8C%3C%60C%9D%3A%18%DFm%88a%AA%AEn%29%3Am%D1%81%B0%CB%23%E7%FE%D7%D8%BC%F3%15%DF%CA%E8%5E%F1%84%A9%81%22%10%81%7B%A6x%89ss%8E%40%00%F0%BA%09_%15%F3%B2%84%D2%09xgk%9E%00%15%20yjN%03%3D%A0%01%7D%7CJ%18%88d7i%82%13%93%9A%1A%C0G%A7%14%81%0F%E09%3B%C8%03-%B0%14T%23%00%09%00%1A7%E9%C3%C3%F9%DA%3C%B0%C2%A7h%84%0DX%03%24%92%99%AF%8D%A8%0C%80%02%03%AC%83%27%00%AALa%811%D0%93%0A3%9C%06%20%84A%88%80%D0D%FB%8A%B7%0A%20%F0%89%C0%05i%82.ik%06%D0%FF%8DO%00%1A%20K%B2%84%F0%18%B8%00%14HY%0B%60%00%B6%98J3%95%83%A8%C7%89%20%E8%80%F9%F4%01%0B%18%02%EA%FF%03%1F%18%60Ex%A6%9D%04%01%C1%BC%80%EE%D8%88Bx%26%A3%FF%89%1F%98%83%1F%D4%888%80%02%04%D8%83%3A%80%02DK%29z%C7%006%80%80%7E%01%A2%20z%03%098%806%B8%82%F7%07%08M%02%07%12%2ChP%60%09%23%15D00%B0%E9%21%C4%88%12%27R%ACh%F1%22%C6%8C%1A7r%EC%E81b%01%00%1F%1F%0E%B8%21%E0%A0%A6%19%01hTi%B1%20%C0%84%09%01%0E8%08%10%C0%C5%84%031%17L%20S%01%40%8C%1C%0C%06%0C0%60%B4%E2%0A%20%844%14%24%A0I%83%06%10m%98D%10q%02%40Q%A3D%01X%28s%86%04A%02%02%C6%FE%28%BB%01%8A%8B3%15%90%88%18%E2%10%E2%1C%0A%28%E7%D2%15%98%21%C4%99%01%10%0B%C1%29t%E1%02%829L%EB%D2%CD%F0%06%8A%84%0DN%09%FFh%80%40%81%C2%16%27%5C%5C%F4%C0A%F8%B2%A6%06FL%BC%D0%3B%F23%E8%D0%A2G%93%86%18%F23%80%1D%27Qz%B1%99f%81%03%15%2A%1CT%A9%29%A4J%82%03%3C%17PI%E0%00%C0%8A%86%1A%07%AC8%40%C2%A9%40%02%1B0p%09%02%A4H%84%18%16%3CK40%00%80%08%17%AB%05hxL%81%C4%16%17s%96%14R%C3vz%C4%25PVc%C6%9C%81C%1E%CF%03%16%1A%F2%F1%A1%11%94%1F%ED%0F%3E%80CB%FF%40be%E0%04%04%04%800%D8%5Cc%21W%97%02F%A0p%C2%5B%A5I8%21%85%15vt%DAH9p%A1%C0%5C%1E%C0%D4%C1%02%07%24%90%9Bo%2B%A9%A0S%88%21%FAv%5D%84%17%0D%10%83%03-%84%C0%DE%06%1C%A8%D0A%04%11%A0%20%C2%0A%00%B4X%1D%00Nh%20%16%08o%B8%10%85%16%8D%10%21%08%12H%5C%20%82%0FC%14%05%D1%00ph%C0%DE%7Eu%11%90A%1DH84%80%0F%86X%00%FF%C0%10%3E%B0%91%E5A%10%609%90%00uhB%80%02%0B%1A%24%96c%10%5C%26%40%10%28%08g%21%9F%7D%FA%29%21%86%1E%0DP%C4%15%1C%1E%A4R%00%09tPSL%2B%A5%01%D3%01%91%A6%28%E2%11G%0D%D7A%1B8%24%B1%9A%06%5CL%A0c%0C%22%BCp%C2%94..1%82%00%20%CC%81%00%93N%5Ep%88%08eX0%04V%12%0D%90%07%07%1B%A0%89%19%04Q%88tk%20%814I%04%1Bk%A2i%EC%40N%10%26%00%0E%18%EC%A0B%0D%20%10F%80%1B%1D0%F0%27%B6%D9j%ABQ%A0%1D%01%D0%01%08%C8jb%03L%09%D8%04%93L%E7%B6%90%D3%04T%A4%00%DB%045t4%C0%05%3Cd1%C2%06%0D%E0%40B%0D%D1%E5%60%C1P%3FVd%80%1AohR%87%B0O%96%E1%C3%AC%00%60%25%F0C%00%E4%B1%C5%03%BB%DA%F5%00%0B%0FdP%D0%0Fg%FCQT%19%81%28%B2F%02%91%8C%A0q%06%E2%D2%E5%D4%06%1B%FC%20%FF%A7%26%3Fp0%96%02%C8%0A%40%81%1B5L%11A%11.%D8Y%17%05J%EC%B9-%D1Ec%DB-G%0C%20%40%C1j8%E0%20%96S%2A%D1%D0A%00%1E%0A%21D%006%90%9B%C6%14-%A4%F1%01%00-Dj%82%A0%270%D1%C2%0D%20Pp%40t%2F%D4%DA%91%0F%1C%84%B0%07%5B%7D%D0J%14%C4%12%01%D0G%17%2F%1F%CB%02%04Pp%00%05%04%19%2F%B6%C1%19%3E%FCJ%E6%10CT%10%18%1Bo%60%C0B%CAVX%B1%A6S%02%5C%3E%D6%06%80%B0%D1E%1D%1CT%3C%90%06%1C%B0%A0%09%04%0AH%D1%40A%20%00%D1%82%8E%29%7C%60%C2%01%86%CE%85C%0D%E8%19%BD%3B%EF%13%22m%11%00%2F%40D%83%11c%BC%B9%01%215%04%E1%86b%9A%B4f%13%A2%D0k%E2a%000%7C%00%C3%C3%23%0D%C0%40%0E1tP%83%1BJ%A0%90%83%8F%1F%F90G%97%29L%97%F7%C0C%E0%81%81%CA%28e%40%C1%1CZ%C4%91G%1EQ%84q%FF%F2%03%0F%F4j%88H%0C%E0%B0%3F%F4%A1%0C%22%40%82%1A%0A%B1%84%0A%5C%02%0C%E7%F2%80%07%AC%90%01%0FQ%2F%00V%10%C4%12%B4%D0%2A%5C%8DN9%91%03%02%158%C0%05%0C%94%E0%24%04%80%40%10%1C%10%81%0B%C0%E0%04%0CX%C1%04%C25%17%C6%18%21%05%22%E9%1D%0Es8%92%DFU%04%06%08p%40%0C%2A%A0%04%0C%D4l%03f%80%5D%04%8Ep%03%1D%08d%0668%89%13m0%03%81p%28%04%1DH%C1%05t%A7%3D%AE%E4%E0%025%A0%03C%D8w%91%3F%2C%F0%02%8A%1B%C9%10%12%C1%B4%F6%88e%04%E3%29%84%AB%2A%10%07%0E%BC%21%04%23%A8%C4%19%00%F8%10%EB8l%08%16%F0%81%E2%00%F0%87%0F%5C%22%0Eq8%D7%B9%7C%60%938%88%C0%10%29%A0%D5%10%1A%29%828%BC%E1t%02%80%C0%1A%CEp%07%138%80%09J%E0%C2X4%E0%86AT%A5Gz%B1%00%1D%E0%17%96%06%94%A0%04%0D%60%16%21%7E%A3%C3Y%D2%12%FF%23%3C%A4%88%08%9A%80%05%1E%18%01%04%3ApJ%16%80%A0%23%11%D0%00%08%C6%A3%0B%87%E4%A0%84%15%BAM%8C%19%E1%E3%0Ab%90%02%B7%7DF%80%0B%2B%9F%F9%CE%C0%02%98%CDP%13%24p%C2Z%1C%B1%B0%3E%D8%C7%11yP%C4%1Ed%80%842%DC%D0V%D7%19%00%1E%24%20%01%92%25%40%9E%128C%00%7C%90%808%10a%01ZH%A7%0C%880%00Z%01%A0%0C%7B%08A%CA%28%20%01%BF%88%00%06%22%88%00%0F%DCp%B3%20Te%7D%C1%13%82%0B%98%C8%26%05%8C%00%0B_%20%C1%06%A4%80%81%09%0C%AD%96%26%AD%E5-%27r%01%2A%80a%0CI%E0%90%00B%D0%2F%14t%20%08M%B8A%12%E8%C2%1De%AE%90%7C%CE%E4%C8%F6%00F%9D%8F%0C%F4m%E6%D3%C26%2FC%80%07%60%80%0Dv%B8%C0%07%C6%B4%15%3F%96%01%8BH8D%1F%86J%91%14T%C0%0F%7E%E8%C2x%88%40%04%3B%D8%C4%0E%8A%80C%14d%A0V%19%F8%C1%28%06%FF%B8%80%13%D8%00%82%94%8D%40%0B%17%10%0A%00%5E%08%032%90%80%02-%B8%C0%0A%06p%82%180%21%08W%D0%A8%40%04%B0%01%9CL%C1%01%08%E0%25%07%3A%A0%C5%93R6%87%29%95H%0Cx%20%85%D54%A0%07A0%C1%05%1C%60%04%05%40%85%9B%04i%C0%08%3E%7B%81fN%88%28%A5%1A%89u%7E%0A%BCD%8C%E0t%2B%FB%01%068P%07%22%5C%A0%0CR%8A%10%1F%FF%C80l%5E%04%01%F5%1C%D1%1A%E0%20%010%80A%06%8A%D0B%02%16%20%DD%05H%60%8FH%88%04%05%CAr3%27%14%82%21Y%D9%5E%04%82%40%01%1E%7C%80%01%26%98%40%10B%F0J%82%5C%D2%08%1D0%01%0A%FE25%26HG%AB%95%BDo%D1.%1B%91%13%F0%81%04%25%D4%84%0E%18A%87%F1%11%AA%04%CB%DA%00%1A%3ApW%E2%E2%F7%23%04c%C3RQB%80%1F%00b%0E%8A%A8%00%1C%0D%21%25%8AX%E7%9D%B2E%80%04%40%2C%01%19ta%0Dex%C4%FF%21%E0%10%883%B0%15%9C%D5%DD%C4%10%10p2%A7d%00%03u0%E3P%0D%00%83%16%7C%E1%080p%00%10z0%06%DB%25%27%A6%3C%F0%D7%0AV%F0%02%87%B6%D0%BE%0D%7E%B2%9F%F4%0B%11%03%98%A0%09%21%D8%80%00J%10%02%04%94%F7%0E%210p%5Dt%40%02%F1%C1%A0%A4P%F6%D6%C4%7E0%BA%00%29v%0B%5DP%83%20%5E%E5%5B%27%7F%06%01Q%A8%40%14%9C%E0%07%19H%C0%07%EBT%83%0C%A6%3B%DD%EA%A6%60%0B%B6%15%0B%14%10%D0%96%1F%0D%A0%03%2A%A0%81%038%C0%CA%83%28%00%04%9F%8D%81%29%B7%E2B%3A%9F%B9%D3%80j%27F%04%28%02%25%DC%00%02%3A%18%01%15%3A3%05%12%B0%8E.%0D%80%80%12%1C%60%15N%7B%DA%22%03%F8%03%028%00%81%94%25%F6f%1C%60%15%5B%7C%F0%07%A3%92%26%9E%D1%5D%40%3D%B50%801%9Da%09%E3A%00%02%96P%5D%00%2C%E1Jv%81%C0%19%D4%C0%CE%89%18%80%01%C4%E4%00%0E%88%FF%CC%26%0D%98%01%01%AB%25n%87e%5B%EBu_%08%D4%18%D9%1E%0C%F4%60%861%40%80%BC%03%A0C%0FZ-%96%9A%B1%B7%01f%98%00%0A%A8%C9%EEy%F5%21%11l%C0%97%00R%06%08%17%C4%C1Is%7E%AD%84%8C%3B%879%24%00%01Y%25J%02%96%90%008l%7CDj%F0Am%13%FB%00%28D%C1%11Y%A5%08q%26%10%82%9C%1E%04%B5M0Ay%D5%3D%F0%99%7BD%CA%11%D9%5E%11%CC%D02%0E%14%81%01%13%C0%00%EBn%86%01%0Ch%A0%D5%0A%28A%16Z%40%03%EF%D2%7C%23%02%EC%83%1D%A2%B0%85%11P%00%0As%F0%03%12R%D0%07%06K%08%0F%F5L%01%B4S%10%85%04D%C1%00%5E%7FW%05%1E%07%07-%18%C2%10%0F%10%C0%960%E0%82%3D%D8%E1%03n%E1p%11ffB%02%F0%9D%EF%95%DE%01%10%03%DB%F4%C1%97%C6%E6%111%80%05%06%C1%01H%08%B3%03X%18%8B%26%C6p%03%25%20%00%08%148%BA%00vY%04%FFL%D3%9A%F0S%E6%8A%08%88p%06%274%C2%3C%22%18%93%CC%3D%C2%D5%87%E0%01%0F%0FY%BD%1A%5C%0F%11%09%CCaV%92%F8%81%06H0%F7%0A%A8%01%C7%16%11%02%096%D5%00%E5%40%00%02%BA%02%81%0B%10%10%01%EC%A5%DE%F3%CE%07%89%BB%87%C3%80%0F%E4%A8%03%A5%E6%90%02%20%D0%82%0AD%40%09W%18%03%08v0%01%1A%2C%B8%F94%B7N%99%B0x%01q%FE%F6%A4%06%28D%14%BA%10%07%3B%F4%3E%05%8A%CB%9B%08%F8%F04%10%60%80%047p%8E%0A4%81%12%BC%D7%AC%3D%9F%01%D6%5C%F4%3D%13p%5C%00%13%DC%C0%06p%08%014%C0%06P%C1%F2E%40%0D%EC%00%10P%C5%5D%0D%C5%01f%C4u%94%C9pu%1E%EF%0C%D4%07%5C%80%93%40%09%AD%B0%CF%05%B4%40G%B5%80%0A%0C%02t%E4%88%8E%EC%C8du%A0%0DV%84%E1U%C7%07L%C0%0EP%40c%F4%80%1C%98%81%0B%F0A%0C%B8P%27q%DF4%11%DB%0DV%84kA%FF%9CI%A1%1F%20%D9%8D%12r%5B%8C%08A%8E%C4W%0A%88%C0%F5%E4%C0%A8p%DD%12%7E%E1%26%E4%60D%00%00%1DT%C1%0E%F0%00%10%A8%80%1Et%00%0D%D0%80%09%60%CF%F6%7C%40%0CD%95%17%82%E1%C0%5DG%F6%3C%13%03%C0%C0%05%CC%E1%BF0%00%038%0CV8%A1%1D.%A1%18B%84%05%2C%DF%0B%C4%00%0A%E8%C8_%84%0Az%04U%C0%14%22%25%DA%CA%09%BC%00%C0%E4a%25n%E2%21R%C9%09%E4%C0%07%5C%CF%0B%AC%80%05%B8P%F9%A4%DB%26%A6%E2%26t%18%21%AA%A2%1Dv%22I%E4U%20%3A%0C%DE%98%9F%2B%DE%22.%1A%20%2C%AE%A2%A5%E4%A2%2F%FE%A2%2B%EE%220%0E%231V%A20%16%232%26%E3%01%1E%A326%A33%0E%1C3%3E%A34N%23%7EE%235%5E%236ZV%02f%237vceY%A37%86%A38%FA%CE6%8E%A39%9E%E3%D1%94%23%3A%AE%23%3B%16%9E%3A%B6%23%3C%C6c%BB%C9%23%3D%D6%23%02%DA%7F%23%3E%E6%23%0E%BE%A3%3E%F6%23%3B%82%A3%3F%06d3%02%A4%40%16%241%12%A4A%26d.%22%A4B6d%2A2%A4CF%E4%2B%F2%A3DV%E40B%A4Ef%E4%F3a%A4Fvd%D3q%A4G%86%E4%BA%81%A4H%96%24%94%91%A4I%A6%E47R%A4J%B6%A4%E7%A1%A4K%C6%24%0E%C1%A4L%D6d%7E%B1%A4M%E6%E4I%E2%A4N%F6%E4J%FA%24P%B2%1BM%06%25Q%8E%C6P%16%25R%EE%10%00%04%04%00%3B' alt="全国" width="403" height="324" border="0" usemap="#Map9" id="innermapBox">
+
+
+<map name="Map9">
+ <area shape="poly" coords="228,236,250,213,243,204,275,155,278,120,311,102,335,74,357,80,377,119,382,143,346,148,333,163,316,153,304,173,300,207,260,252,225,235" href="javascript:void(0);" onfocus="mapChange(1);" alt="北海é“・æ±åŒ—エリア" onclick="mapChange(1);">
+ <area shape="poly" coords="212,264,211,243,225,239,257,254,243,265,242,278,229,289,209,278,211,264" href="javascript:void(0);" onfocus="mapChange(2);" alt="é–¢æ±ã‚¨ãƒªã‚¢" onclick="mapChange(2);">
+ <area shape="poly" coords="207,261,205,240,222,236,244,213,237,207,179,215,154,235,175,239,189,232,189,244,185,259" href="javascript:void(0);" onfocus="mapChange(3);" alt="甲信越・北陸エリア" onclick="mapChange(3);">
+ <area shape="poly" coords="163,241,180,242,186,238,182,252,183,261,202,264,205,267,205,284,164,274,161,241" href="javascript:void(0);" onfocus="mapChange(4);" alt="æ±æµ·ã‚¨ãƒªã‚¢" onclick="mapChange(4);">
+ <area shape="poly" coords="129,228,145,231,152,241,157,241,160,275,151,276,134,282,123,277,119,266,127,260,126,247,119,244,126,227" href="javascript:void(0);" onfocus="mapChange(5);" alt="近畿エリア" onclick="mapChange(5);">
+ <area shape="poly" coords="56,225,101,214,123,227,114,244,118,259,114,270,86,266,76,272" href="javascript:void(0);" onfocus="mapChange(6);" onclick="mapChange(6);">
+ <area shape="poly" coords="54,231,65,268,24,292,9,282,27,255,17,241,23,230,53,231" href="javascript:void(0);" onfocus="mapChange(7);" alt="ä¹å·žãƒ»æ²–縄エリア" onclick="mapChange(7);">
+</map>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 448860 **/
+
+function mapChange(mapNo){
+ $('innermapBox').style.display = 'none';
+}
+
+function focusarea() {
+ synthesizeKey("KEY_Tab", {repeat: 5});
+ ok(true, "we did not crash");
+ SimpleTest.finish();
+}
+function delayed_focusarea() {
+ setTimeout(focusarea,100);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(delayed_focusarea);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug448987.html b/layout/generic/test/test_bug448987.html
new file mode 100644
index 0000000000..0af51dc6c9
--- /dev/null
+++ b/layout/generic/test/test_bug448987.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=448987
+-->
+<head>
+ <title>Test for Bug 448987</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ iframe {
+ width: 500px;
+ height: 300px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448987">Mozilla Bug 448987</a>
+<p id="display"></p>
+<div id="content">
+<iframe id="f1"></iframe>
+<iframe id="f2"></iframe>
+<iframe id="f3"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 448987 **/
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.setIntPref("accessibility.tabfocus", 7);
+
+var f1 = document.getElementById("f1");
+var f2 = document.getElementById("f2");
+var f3 = document.getElementById("f3");
+
+var snapshotf1;
+
+SimpleTest.waitForFocus(function() {
+ f1.src = "file_bug448987.html";
+});
+
+function firstIframeLoaded() {
+ snapshotf1 = snapshotWindow(f1.contentWindow);
+ f2.src="file_bug448987_ref.html";
+}
+
+function secondIframeLoaded() {
+ ok(compareSnapshots(snapshotf1,
+ snapshotWindow(f2.contentWindow), true)[0],
+ "<area shape=default> should render focus outline");
+ f3.src="file_bug448987_notref.html";
+}
+
+function thirdIframeLoaded() {
+ ok(compareSnapshots(snapshotf1,
+ snapshotWindow(f3.contentWindow), false)[0],
+ "file_bug448987.html should render focus outline, file_bug448987_notref.html should not");
+ finish();
+}
+
+function finish()
+{
+ SpecialPowers.clearUserPref("accessibility.tabfocus");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug449653.html b/layout/generic/test/test_bug449653.html
new file mode 100644
index 0000000000..b3c9f8399b
--- /dev/null
+++ b/layout/generic/test/test_bug449653.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449653
+-->
+<head>
+ <title>Test for Bug 449653</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ iframe {
+ width: 500px;
+ height: 300px;
+ }
+ </style>
+</head>
+<body onload="doTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=449653">Mozilla Bug 449653</a>
+<p id="display"></p>
+<div id="content">
+<iframe src="file_bug449653_1.html" id="f1"></iframe>
+<iframe id="f2" src="file_bug449653_1_ref.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 449653 **/
+SimpleTest.waitForExplicitFinish();
+
+var f1 = document.getElementById("f1");
+var f2 = document.getElementById("f2");
+
+function doTest() {
+ ok(compareSnapshots(snapshotWindow(f1.contentWindow),
+ snapshotWindow(f2.contentWindow), true)[0],
+ "No red should be shown");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug460532.html b/layout/generic/test/test_bug460532.html
new file mode 100644
index 0000000000..3d275481ed
--- /dev/null
+++ b/layout/generic/test/test_bug460532.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=460532
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Test for bug 460532</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=460532">Mozilla Bug 460532</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+lorem ipsum.
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function select_460532(){
+ var content = document.getElementById('content');
+ var node = content.firstChild;
+
+ var range = document.createRange();
+ range.setStart(node, 0);
+ range.setEnd(node, 5);
+ window.getSelection().addRange(range);
+
+ range = document.createRange();
+ range.setStart(node, 0);
+ range.setEnd(node, 6);
+ window.getSelection().addRange(range);
+}
+
+function click_460532(){
+ var wu = SpecialPowers.getDOMWindowUtils(window);
+ var content = document.getElementById('content');
+ var rect = content.getBoundingClientRect();
+ wu.sendMouseEvent('mousedown', rect.left+5, rect.top+5, 0, 1, 0);
+ wu.sendMouseEvent('mouseup', rect.left+5, rect.top+5, 0, 1, 0);
+ ok(true, "pass");
+ SimpleTest.finish();
+}
+
+function boom_460532() {
+ select_460532();
+ setTimeout(click_460532, 100);
+}
+
+addLoadEvent(boom_460532);
+SimpleTest.waitForExplicitFinish()
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug468167.html b/layout/generic/test/test_bug468167.html
new file mode 100644
index 0000000000..d710e1e38c
--- /dev/null
+++ b/layout/generic/test/test_bug468167.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=468167
+-->
+<head>
+ <title>Test for Bug 468167</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=468167">Mozilla Bug 468167</a>
+<p id="display">
+<textarea id="ta" rows="10" cols="80"></textarea>
+</p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 468167 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests()
+{
+ var ta = document.getElementById("ta");
+ ta.focus();
+ is(ta.scrollTop, 0, "initially scrolled to top");
+ var s = "";
+ for (var i = 0; i < 40; ++i) {
+ // three characters per line
+ if (i < 10)
+ s += "0";
+ s += i + "\n";
+ }
+ ta.value = s;
+ is(ta.scrollTop, 0, "scrolled to top after adding content");
+ ta.scrollTop = 9999; // scroll down as far as we can
+ isnot(ta.scrollTop, 0, "scrolled down after scrolling down");
+
+ ta.setSelectionRange(0, 99); // 33 lines out of 40
+
+ // Send a backspace key event to delete the selection
+ synthesizeKey("KEY_Backspace");
+
+ is(ta.scrollTop, 0, "scrolled to top after deleting selection");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug469613.xhtml b/layout/generic/test/test_bug469613.xhtml
new file mode 100644
index 0000000000..94ab163afa
--- /dev/null
+++ b/layout/generic/test/test_bug469613.xhtml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469613
+-->
+<window title="Mozilla Bug 469613"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469613">Mozilla Bug 469613</a>
+
+ <p id="display"></p>
+<div id="content" style="display: none">
+</div>
+</body>
+
+<vbox style="height: 100px; overflow: auto;" id="scrollbox">
+ <hbox style="min-height: 200px;"/>
+</vbox>
+
+<script class="testbody" type="application/javascript"><![CDATA[
+
+/** Test for Bug 469613 **/
+
+function doTest() {
+ let scrollbox = document.getElementById("scrollbox");
+ scrollbox.scrollTop = 0;
+
+ // Make sure that the "scroll focus" is inside the scrollbox by moving the
+ // mouse in the scrollbox.
+ synthesizeMouse(scrollbox, 6, 6, { type: "mousemove" });
+ synthesizeMouse(scrollbox, 8, 8, { type: "mousemove" });
+
+ // Now scroll 10px down.
+ synthesizeWheel(scrollbox, 10, 10, { deltaY: 10.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL });
+
+ // Send a 0-delta scroll.
+ synthesizeWheel(scrollbox, 10, 10, { deltaY: 0.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL });
+
+ setTimeout(function() {
+ // Check if the 10px were scrolled.
+ todo(false, "Starting a 0-delta scroll shouldn't cancel a pending async scroll is disabled, see bug 752786");
+ //is(scrollbox.scrollTop, 10, "Starting a 0-delta scroll shouldn't cancel a pending async scroll.");
+
+ // Second test
+ scrollbox.scrollTop = 20;
+
+ // Start an async scroll to 30.
+ synthesizeWheel(scrollbox, 10, 10, { deltaY: 10.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL });
+
+ // Start a sync scroll to 30.
+ scrollbox.scrollTop = 30;
+
+ is(scrollbox.scrollTop, 30, "Setting scrollTop should have immediate effect, even if there was a pending async scroll to the same position.");
+
+
+ // Third test
+ scrollbox.scrollTop = 40;
+
+ // Start an async scroll to 50.
+ synthesizeWheel(scrollbox, 10, 10, { deltaY: 10.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL });
+
+ // Cancel the async scroll.
+ scrollbox.scrollTop = 40;
+
+ // Send a 0-delta scroll.
+ synthesizeWheel(scrollbox, 10, 10, { deltaY: 0.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL });
+
+ setTimeout(function() {
+ is(scrollbox.scrollTop, 40, "Canceling an async scroll should reset the point of reference for relative scrolls (mDestinationX/Y).");
+
+ SimpleTest.finish();
+ }, 0);
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(() => setTimeout(doTest, 0));
+
+]]></script>
+
+</window>
diff --git a/layout/generic/test/test_bug469774.xhtml b/layout/generic/test/test_bug469774.xhtml
new file mode 100644
index 0000000000..f890e8c373
--- /dev/null
+++ b/layout/generic/test/test_bug469774.xhtml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469774
+-->
+<window title="Mozilla Bug 469774"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+<vbox height="50"/>
+
+<menupopup id="popup">
+ <input xmlns="http://www.w3.org/1999/xhtml" id="textbox"/>
+</menupopup>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469774">Mozilla Bug 469774</a>
+
+ <p id="display"></p>
+<div id="content" style="display: none">
+</div>
+</body>
+
+<script class="testbody" type="application/javascript"><![CDATA[
+
+/** Test for Bug 469774 **/
+
+// Test whether menu popups are blocked from being painted in their parent window.
+
+// Like snapshotWindow, but with DRAWWINDOW_DRAW_CARET
+function snapShot() {
+ var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.setAttribute("width", 200);
+ canvas.setAttribute("height", 50);
+ var ctx = canvas.getContext("2d");
+ ctx.drawWindow(window, 0, 0, 200, 50, "transparent", ctx.DRAWWINDOW_DRAW_CARET);
+ return canvas;
+}
+
+function doTest() {
+ window.removeEventListener("focus", doTest);
+
+ var before = snapShot();
+
+ var popup = document.getElementById("popup");
+ popup.openPopup(null, "after_start", 0, 0, false, false);
+
+ popup.addEventListener("popupshown", function() {
+ var textbox = document.getElementById("textbox");
+ textbox.focus(); // show caret
+
+ var after = snapShot();
+
+ var equal, str1, str2;
+ [equal, str1, str2] = compareSnapshots(after, before, true);
+ ok(equal, "Showing a popup shouldn't affect drawing in its parent window" +
+ "got " + str1 + ", expected " + str2);
+
+ popup.hidePopup();
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("focus", doTest);
+
+]]></script>
+
+</window>
diff --git a/layout/generic/test/test_bug470212.html b/layout/generic/test/test_bug470212.html
new file mode 100644
index 0000000000..f14c2a77e0
--- /dev/null
+++ b/layout/generic/test/test_bug470212.html
@@ -0,0 +1,58 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=470212
+-->
+<head>
+ <title>Test for Bug 470212 - crash [@ nsContentUtils::ComparePoints]</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=470212">Mozilla Bug 470212</a>
+<div style="width: 200px;">
+<ca>
+<canvas style="border: 1px solid black;" id="dragSource"></canvas>
+</ca>
+</div>
+
+<pre id="test">
+<script>
+function doShiftDrag(){
+ setTimeout(function() {
+ var wu = SpecialPowers.DOMWindowUtils;
+ var canvas = document.getElementById("dragSource");
+ var canvasRect = canvas.getBoundingClientRect();
+
+ // Drag canvas element starts with a mouse down event, combine with shift
+ // key, follows by two mouse move events.
+
+ window.addEventListener("dragstart", e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }, { once: true });
+
+ // Press on left-top corner of the canvas element.
+ wu.sendMouseEvent('mousedown', canvasRect.left, canvasRect.top, 0, 1, 4);
+ // Move to the center of this cavas element.
+ wu.sendMouseEvent('mousemove', canvasRect.left + (canvasRect.width / 2),
+ canvasRect.top + (canvasRect.height / 2), 0, 0, 4);
+ // move out of cavas's region.
+ wu.sendMouseEvent('mousemove', canvasRect.left + (canvasRect.width / 2),
+ canvasRect.bottom + 10, 0, 0, 4);
+
+ is(window.getSelection().rangeCount, 0, "rangeCount should be 0");
+
+ wu.sendMouseEvent('mouseup', canvasRect.left + (canvasRect.width / 2),
+ canvasRect.bottom + 10, 0, 0, 4);
+
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(doShiftDrag);
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug488417.html b/layout/generic/test/test_bug488417.html
new file mode 100644
index 0000000000..f9ce2ffbe1
--- /dev/null
+++ b/layout/generic/test/test_bug488417.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=488417
+-->
+<head>
+ <title>Test for Bug 488417</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=488417">Mozilla Bug 488417</a>
+<div id="display">
+ <table border="1">
+ <tr>
+ <td id="a">A1</td>
+ <td id="b">B1</td>
+ </tr>
+ <tr>
+ <td>A2</td>
+ <td>B2</td>
+ </tr>
+ </table>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function clickIt(node) {
+ synthesizeMouse(node, node.getBoundingClientRect().width/2,
+ node.getBoundingClientRect().height/2,
+ { accelKey: 1 });
+}
+
+/** Test for Bug 488417 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Do the test async so we can unsuppress painting
+ SimpleTest.executeSoon(function() {
+ clickIt($("a"));
+ clickIt($("b"));
+ clickIt($("a"));
+ ok(1, "Got here");
+ // Clean up
+ window.getSelection().removeAllRanges();
+ SimpleTest.finish();
+ });
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug496275.html b/layout/generic/test/test_bug496275.html
new file mode 100644
index 0000000000..0cd0b04e9f
--- /dev/null
+++ b/layout/generic/test/test_bug496275.html
@@ -0,0 +1,288 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=496275
+-->
+
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Test for Bug 496275</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body onload="run()" style="font-family: monospace">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=496275">
+ Mozilla Bug 496275
+</a>
+<p id="display"></p>
+<div id="c" contenteditable="true">
+ <p id="p1">The first paragraph. Another sentence. Even more text.</p>
+ <p id="p2">Paragraph no. 2.</p>
+ <p id="p3">This paragraph<br>
+is broken up<br>
+into four<br>
+lines</p>
+</div>
+
+<div id="ltr" contenteditable="true">
+ <p id="l1">×לש Hello</p>
+ <p id="l2">Goodbye</p>
+</div>
+
+<div id="rtl" contenteditable="true" dir="rtl">
+ <p id="r1">×”×ª×¨×’×•× ×”×חרון שפיתחנו ×”×•× ×¢×‘×•×¨ Firefox 3.5.6.</p>
+ <p id="r2">קר×ו ×ת הערות ההפצה (×נגלית) להור×ות ורשימת בעיות ידועות.</p>
+</div>
+
+<!-- Special characters: لا is actually two characters, while تَ should be
+ treated as one character. -->
+<div id="special" contenteditable="true">
+ <p id="s1">a لا b تَ c</p>
+</div>
+
+<div>
+ <p>Anchor offset: <span id="anchor-offset"></span></p>
+ <p>Focus offset: <span id="focus-offset"></span></p>
+</div>
+
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+SimpleTest.requestFlakyTimeout("untriaged");
+
+function isOrIsParent(actual, expected, msg) {
+ // actual should be expected or actual's parent node should be expected.
+ msg += " Expected " + actual + " or " + actual.parentNode +
+ " to be " + expected + ".";
+
+ ok(actual == expected || actual.parentNode == expected, msg);
+}
+
+function isAt(anchorNode, anchorOffset, focusNode, focusOffset, msg) {
+ var sel = window.getSelection();
+
+ isOrIsParent(sel.anchorNode, $(anchorNode), msg + ": Wrong anchor node.");
+ is(sel.anchorOffset, anchorOffset, msg + ": Wrong anchor offset.");
+ isOrIsParent(sel.focusNode, $(focusNode), msg + ": Wrong focus node.");
+ is(sel.focusOffset, $(focusOffset), msg + ": Wrong focus offset.");
+}
+
+function run() {
+ var sel = window.getSelection();
+
+ // If nothing is focused, selection.modify() should silently fail.
+ sel.removeAllRanges();
+ sel.modify("move", "forward", "character");
+
+ // Now focus our first div and put the cursor at the beginning of p1.
+ $("c").focus();
+ sel.collapse($("p1"), 0);
+
+ // We're intentionally inconsistent with the capitalization of "move",
+ // "extend", etc. below to test the case-insensitivity of modify().
+
+ // If we move backward, selection.modify() shouldn't do anything, since we're
+ // already at the beginning of the frame.
+ isAt("p1", 0, "p1", 0, "test 0a");
+ sel.modify("Move", "Backward", "Character");
+ isAt("p1", 0, "p1", 0, "test 0b");
+
+ // After this move, the cursor should be at the second character of p1
+ sel.modify("Move", "Forward", "Character");
+ isAt("p1", 1, "p1", 1, "test 1");
+
+ // Select the first character in p1
+ sel.collapse($("p1"), 0);
+ sel.modify("Extend", "Forward", "Character");
+ isAt("p1", 0, "p1", 1, "test 2");
+ sel.modify("Move", "Forward", "Character");
+ isAt("p1", 2, "p1", 2, "test 2a");
+
+ // Select all of p1, then move the selection forward one character a few
+ // times.
+ sel.collapse($("p1"), 0);
+ sel.extend($("p1"), 1);
+ sel.modify("move", "forward", "character");
+ isAt("p2", 0, "p2", 0, "test 3a");
+ sel.modify("move", "forward", "character");
+ isAt("p2", 1, "p2", 1, "test 3b");
+
+ // Put the cursor at the beginning of p3, then extend forward one line.
+ // Now go back twice and forward once. Focus should now be at the end of p3.
+ sel.collapse($("p3"), 0);
+ sel.modify("Extend", "Forward", "Line");
+ sel.modify("extend", "backward", "character");
+ sel.modify("extend", "backward", "character");
+ sel.modify("extend", "forward", "character");
+ isAt("p3", 0, "p3", 14, "test 4");
+
+ // Put the cursor at the beginning of p3, then go forward a few words
+ sel.collapse($("p3"), 0);
+ sel.modify("Move", "Forward", "Word");
+ isAt("p3", 4, "p3", 4, "test 4a");
+ sel.modify("move", "forward", "word");
+ // Go back and forward so the indexes are correct.
+ sel.modify("move", "backward", "character");
+ sel.modify("move", "forward", "character");
+ isAt("p3", 14, "p3", 14, "test 4b");
+
+ // Test the lineboundary granularity
+ sel.collapse($("p3"), 0);
+ sel.modify("Move", "Forward", "Lineboundary");
+ // Go back and forward so the indexes are correct.
+ sel.modify("move", "Backward", "character");
+ sel.modify("move", "forward", "character");
+ isAt("p3", 14, "p3", 14, "test 5");
+
+ //
+ // Test RTL text within a dir=LTR div.
+ //
+ $("ltr").focus();
+ sel.collapse($("l1"), 0);
+ SpecialPowers.wrap(sel).caretBidiLevel = 1;
+ isAt("l1", 0, "l1", 0, "test 6a");
+ sel.modify("Move", "Left", "Character");
+ isAt("l1", 1, "l1", 1, "test 6b");
+ sel.modify("Extend", "Backward", "Character");
+ isAt("l1", 1, "l1", 0, "test 6c");
+ sel.modify("extend", "forward", "character");
+ isAt("l1", 1, "l1", 1, "test 6d");
+ sel.modify("Extend", "Right", "Character");
+ isAt("l1", 1, "l1", 0, "test 6e");
+
+ sel.collapse($("l1"), 0);
+ SpecialPowers.wrap(sel).caretBidiLevel = 1;
+ sel.modify("move", "left", "character");
+ sel.modify("extend", "right", "Word");
+ isAt("l1", 1, "l1", 3, "test 7a");
+ sel.modify("move", "left", "word");
+ isAt("l1", 3, "l1", 3, "test 7b");
+ sel.modify("move", "forward", "word");
+ isAt("l1", 9, "l1", 9, "test 7c");
+ sel.modify("extend", "backward", "word");
+ isAt("l1", 9, "l1", 4, "test 7d");
+ sel.modify("move", "left", "word");
+ isAt("l1", 3, "l1", 3, "test 7e");
+
+ sel.collapse($("l1"), 0);
+ SpecialPowers.wrap(sel).caretBidiLevel = 1;
+ sel.modify("extend", "left", "lineboundary");
+ isAt("l1", 0, "l1", 0, "test 8a");
+ sel.modify("move", "forward", "lineboundary");
+ isAt("l1", 9, "l1", 9, "test 8b");
+ sel.modify("extend", "backward", "lineboundary");
+ isAt("l1", 9, "l1", 0, "test 8c");
+ sel.modify("move", "left", "lineboundary");
+ isAt("l1", 0, "l1", 0, "test 8d");
+ sel.modify("extend", "forward", "lineboundary");
+ isAt("l1", 0, "l1", 9, "test 8e");
+
+ // Put the cursor at the left edge of the first line so that when we go up
+ // and down, where we end up doesn't depend on how the characters line up.
+ sel.collapse($("l1"), 0);
+ SpecialPowers.wrap(sel).caretBidiLevel = 1;
+ sel.modify("move", "left", "lineboundary");
+ isAt("l1", 0, "l1", 0, "test 9a");
+ sel.modify("move", "forward", "Line");
+ // Offset becomes non-zero because the line boundary was in the middle of the
+ // line. See bug 1667129
+ isAt("l2", 3, "l2", 3, "test 9b");
+ sel.modify("extend", "backward", "Line");
+ // Going back to the beginning of the previous line
+ isAt("l2", 3, "l1", 0, "test 9c");
+
+ // Same test as above, now with absolute directions.
+ sel.collapse($("l1"), 0);
+ SpecialPowers.wrap(sel).caretBidiLevel = 1;
+ sel.modify("move", "left", "lineboundary");
+ isAt("l1", 0, "l1", 0, "test 10a");
+ sel.modify("move", "right", "line");
+ // Also bug 1667129
+ isAt("l2", 3, "l2", 3, "test 10b");
+ sel.modify("extend", "left", "line");
+ isAt("l2", 3, "l1", 0, "test 10c");
+
+ //
+ // Test RTL text within a dir=RTL div.
+ //
+ $("rtl").focus();
+ sel.collapse($("r1"), 0);
+ sel.modify("move", "forward", "character");
+ isAt("r1", 1, "r1", 1, "test 11a");
+ sel.modify("extend", "backward", "character");
+ isAt("r1", 1, "r1", 0, "test 11b");
+ sel.modify("move", "forward", "word");
+ isAt("r1", 6, "r1", 6, "test 11c");
+ sel.modify("extend", "backward", "word");
+ isAt("r1", 6, "r1", 0, "test 11d");
+ sel.modify("extend", "forward", "lineboundary");
+ isAt("r1", 6, "r1", 45, "test 11e");
+
+ // Same as above, but with absolute directions.
+ sel.collapse($("r1"), 0);
+ sel.modify("move", "left", "character");
+ isAt("r1", 1, "r1", 1, "test 12a");
+ sel.modify("extend", "right", "character");
+ isAt("r1", 1, "r1", 0, "test 12b");
+ sel.modify("move", "left", "word");
+ isAt("r1", 6, "r1", 6, "test 12c");
+ sel.modify("extend", "right", "word");
+ isAt("r1", 6, "r1", 0, "test 12d");
+
+ // Test that move left/right is correct within the RTL div.
+ sel.collapse($("r1"), 0);
+ sel.modify("extend", "left", "lineboundary");
+ isAt("r1", 0, "r1", 45, "test 13a");
+ sel.modify("move", "right", "lineboundary");
+ isAt("r1", 0, "r1", 0, "test 13b");
+
+ // Test that up/down at the first/last line correctly wraps to the
+ // beginning/end of the line.
+ sel.collapse($("r1"), 0);
+ sel.modify("move", "forward", "word");
+ isAt("r1", 6, "r1", 6, "test 14a");
+ // Even in RTL text, "left" still means up.
+ sel.modify("extend", "left", "Line");
+ isAt("r1", 6, "r1", 0, "test 14b");
+ sel.modify("move", "right", "line");
+ isAt("r2", 0, "r2", 0, "test 14c");
+ sel.modify("extend", "forward", "line");
+ isAt("r2", 0, "r2", 57, "test 14d");
+ sel.modify("move", "backward", "line");
+ isAt("r1", 45, "r1", 45, "test 14e");
+
+ // Test some special characters.
+ $("special").focus();
+ sel.collapse($("s1"), 0);
+ sel.modify("move", "forward", "character");
+ sel.modify("move", "forward", "character");
+ sel.modify("move", "forward", "character");
+ isAt("s1", 3, "s1", 3, "test 15a");
+ sel.modify("move", "forward", "character");
+ isAt("s1", 4, "s1", 4, "test 15b");
+ sel.modify("move", "backward", "word");
+ isAt("s1", 2, "s1", 2, "test 15c");
+ sel.modify("move", "forward", "word");
+ sel.modify("move", "forward", "word");
+ sel.modify("move", "forward", "word");
+ isAt("s1", 9, "s1", 9, "test 15d");
+
+ SimpleTest.finish();
+}
+
+function update_debug_info() {
+ var sel = window.getSelection();
+ document.getElementById("anchor-offset").innerHTML = sel.anchorOffset;
+ document.getElementById("focus-offset").innerHTML = sel.focusOffset;
+ setTimeout(update_debug_info, 100);
+}
+
+setTimeout(update_debug_info, 100);
+
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug503813.html b/layout/generic/test/test_bug503813.html
new file mode 100644
index 0000000000..ef34c9e284
--- /dev/null
+++ b/layout/generic/test/test_bug503813.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=503813
+-->
+<head>
+ <title>Test for Bug 503813</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #a:after { content:"anonymous text"; display:block; height:10px; }
+ #a:hover:after { content:"hover content"; display:block; height:20px; }
+ #b:after { content:"anonymous text"; display:block; height:10px; }
+ #b:hover:after { content:url(file_Dolske.png); display:block; height:20px; }
+ </style>
+</head>
+<body>
+<div id="content" style="width:300px;">
+ <div id="a" style="margin:10px;"></div>
+ <div id="b" style="margin:10px;"></div>
+</div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=503813">Mozilla Bug 503813</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 503813 **/
+function test()
+{
+ synthesizeMouse(document.getElementById("a"), 5, 5, { type: "mousemove" });
+ is(document.getElementById("a").getBoundingClientRect().height, 20,
+ "Expected hover style");
+ synthesizeMouse(document.getElementById("b"), 5, 5, { type: "mousemove" });
+ is(document.getElementById("b").getBoundingClientRect().height, 20,
+ "Expected hover style");
+ SimpleTest.finish();
+}
+// Run 'test' when we've exited paint suppression and done layout
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug507902.html b/layout/generic/test/test_bug507902.html
new file mode 100644
index 0000000000..2394ad8567
--- /dev/null
+++ b/layout/generic/test/test_bug507902.html
@@ -0,0 +1,382 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=507902
+-->
+<head>
+ <title>Test for Bug 507902</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=507902">Mozilla Bug 507902</a>
+
+<iframe id="testFrameElem"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+//
+// Mochitest to test nsImageFrame icons
+//
+// The 'loading' icon should be displayed up until we have enough image
+// data to determine the frame size.
+//
+// The 'broken' icon should be displayed when the URL is invalid (either
+// a bad server or a file that fails to be sniffed to an appropriate
+// mimetype).
+//
+
+// Boilerplate
+gWindowUtils = SpecialPowers.getDOMWindowUtils(window);
+
+// URL + paths
+//
+// We have a separate copy of the icons in the test directory to
+// avoid any firefox caching mechanisms that might affect the
+// behavior of the load.
+var us = window.location.href;
+var baseURL = us.substring(0, us.lastIndexOf('/') + 1);
+var loadIconFilename = "file_LoadingImageReference.png";
+var imageFilename = "file_Dolske.png";
+var brokenIconFilename = "file_BrokenImageReference.png";
+var serverFilename = "file_IconTestServer.sjs";
+var serverContinueFlag = "?continue=true";
+var bogusFilename = "oneuponatimewhendolskewasyoung.png";
+
+// Our test image element, inside a div, inside an iframe
+var testFrameElem = document.getElementById("testFrameElem");
+var innerDoc = testFrameElem.contentWindow.document;
+var divContainer = innerDoc.createElement("div");
+divContainer.style.cssFloat = "left";
+innerDoc.body.appendChild(divContainer);
+var testImageElem = new Image();
+divContainer.appendChild(testImageElem);
+var pingImage = new Image();
+
+// Set up the canvases
+var canvases = {};
+var canvasNames = [ "brokenIconTest", "brokenIconReference",
+ "loadingIconTest", "loadingIconReference",
+ "loadedTest", "loadedReference" ];
+var windowElem = document.documentElement;
+for (let i in canvasNames) {
+ var can = document.createElement("canvas");
+ can.setAttribute("width", windowElem.getAttribute("width"));
+ can.setAttribute("height", windowElem.getAttribute("height"));
+ canvases[canvasNames[i]] = can;
+
+ // When the image frame has no idea how to size itself, it sizes itself
+ // to dimensions capable of displaying the alt feedback icons. However, if
+ // the image has been loaded before, something (I don't know what) seems to
+ // remember the size of the last successful image for that URL. So when we
+ // create a new image frame for that URL, it uses that size until it hears
+ // something different. This happens through a refresh (not sure if this is
+ // desired behavior). This means that if you refresh the test, the "loading"
+ // icon for the test image will appear with a border that stretches further
+ // right and down, because that URL previously displayed an image with larger
+ // dimensions. This causes the verify stage to fail. To allow for
+ // successful test refreshes (only useful for people, not automated tests),
+ // we add a clipping region so that we see the left and top borders, along
+ // with the image, but not the bottom and right borders.
+
+ if ((i > 1) && (i < 4)) {
+ let ctx = can.getContext("2d");
+ ctx.beginPath();
+ ctx.rect(0,0, 30, 30);
+ ctx.clip();
+ }
+
+}
+
+// Stage 1 - Load the reference image for the broken icon
+function loadBrokenIconReference() {
+
+ // Debugging - Let's see if setting onload after src is a problem
+ testImageElem.onload = function(event) { dump("test_bug507902.html DEBUG - uh oh, placeholder onload 1 called\n");};
+
+ // Debug - Figure out if we're getting an onerror instead of onload
+ testImageElem.onerror = function(event) {dump("test_bug507902.html DEBUG - Got onerror for testImageElem!\n");};
+
+ testImageElem.src = baseURL + brokenIconFilename;
+ stageTransition();
+}
+
+// Stage 2 - Draw the reference image for the broken icon to a canvas
+function drawBrokenIconReference() {
+
+ enableBorderAndPad();
+ drawWindowToCanvas("brokenIconReference");
+ disableBorderAndPad();
+
+ stageTransition();
+}
+
+// Stage 3 - Load the reference image for the loading icon
+function loadLoadingIconReference() {
+
+ // Debugging - Let's see if setting onload after src is a problem
+ testImageElem.onload = function(event) { dump("test_bug507902.html DEBUG - uh oh, placeholder onload 3 called\n");};
+
+ testImageElem.src = baseURL + loadIconFilename;
+ stageTransition();
+}
+
+// Stage 4 - Draw the reference image for the loading icon to a canvas
+function drawLoadingIconReference() {
+
+ enableBorderAndPad();
+ drawWindowToCanvas("loadingIconReference");
+ disableBorderAndPad();
+
+ stageTransition();
+}
+
+// Stage 5 - Try to load a broken image
+function loadBrokenImage() {
+ resetImage();
+ testImageElem.src = baseURL + bogusFilename;
+ stageTransition();
+}
+
+// Stage 6 - Draw the screen to a canvas. This should hopefully
+// be the broken icon.
+function drawBrokenIcon() {
+ drawWindowToCanvas("brokenIconTest");
+ stageTransition();
+}
+
+// Stage 7 - Load the reference image for the test image
+function loadImageReference() {
+ resetImage();
+
+ // Debugging - Let's see if setting onload after src is a problem
+ testImageElem.onload = function(event) { dump("test_bug507902.html DEBUG - uh oh, placeholder onload 7 called\n");};
+
+ testImageElem.src = baseURL + imageFilename;
+ stageTransition();
+}
+
+// Stage 8 - Draw the reference image for the test image to a canvas
+function drawImageReference() {
+ drawWindowToCanvas("loadedReference");
+ stageTransition();
+}
+
+// Stage 9 - Start a load of the test image from the delay-generating server
+function startServerLoad() {
+
+ // Reset the image
+ resetImage();
+
+ // Debugging info so we can figure out the hang
+ dump("test_bug507902.html DEBUG - starting server load\n");
+
+ // Load the image
+ testImageElem.src = baseURL + serverFilename;
+ stageTransition();
+}
+
+// Stage 10 - Draw the screen to a canvas. This should hopefully be the loading
+// icon.
+function drawLoadingIcon() {
+
+ // Debugging info so we can figure out the hang
+ dump("test_bug507902.html DEBUG - drawing loading icon\n");
+
+ drawWindowToCanvas("loadingIconTest");
+ stageTransition();
+}
+
+// Stage 11 - Tell the server to continue.
+function signalServerContinue() {
+
+ // Debugging info so we can figure out the hang
+ dump("test_bug507902.html DEBUG - signaling server to continue\n");
+
+ pingImage.src = baseURL + serverFilename + serverContinueFlag;
+ stageTransition();
+}
+
+// Stage 12 - Draw the screen to a canvas. This should hopefully be the loaded
+// test image.
+function drawLoadedImage() {
+ drawWindowToCanvas("loadedTest");
+ stageTransition();
+}
+
+
+// Stage 13 - Verify That the appropriate canvases match
+function verifyCanvases() {
+
+ // Verify the broken icon
+ ok(canvasesAreEqual("brokenIconTest", "brokenIconReference"),
+ "Window drawn on broken load should match broken icon reference");
+
+ // Verify the loading icon
+ ok(canvasesAreEqual("loadingIconTest", "loadingIconReference"),
+ "Window drawn mid-load should match loading icon reference");
+
+ // Verify the loaded image
+ ok(canvasesAreEqual("loadedTest", "loadedReference"),
+ "Window drawn post-load should match reference image");
+
+ stageTransition();
+}
+
+// We have a bunch of different things that need to happen in order
+// with different transitions. We make a "stage table" here where
+// each entry contains the stage function ('fn') and a transition
+// ('trans'), which can be one of the following:
+// "instant" - Just calls the next stage directly
+// "onload" - Sets the next stage as an onload event for the image element
+// "onerror" - Sets the next stage as an onerror event for the image element
+// integer - Sets the next stage to be called after the given timeout duration
+// "finish" - Finish the test
+var testStages = [
+ { "fn" : loadBrokenIconReference, "trans" : "onload"},
+ { "fn" : drawBrokenIconReference, "trans" : "instant"},
+ { "fn" : loadLoadingIconReference, "trans" : "onload" },
+ { "fn" : drawLoadingIconReference, "trans" : "instant" },
+ { "fn" : loadBrokenImage, "trans" : "onerror" },
+ { "fn" : drawBrokenIcon, "trans" : "instant" },
+ { "fn" : loadImageReference, "trans" : "onload" },
+ { "fn" : drawImageReference, "trans" : "instant" },
+ // XXXbholley - We use a timeout here because resetting the
+ // image doesn't seem to be quite synchronous. If we make
+ // this transition "instant", then the drawImage call draws
+ // an empty (0,0,0,0) rect to the canvas and we're left with
+ // whatever was there before. I don't know of any good event
+ // mechanism to figure out when the image frame is bootstrapped
+ // enough to display the loading image, so I did trial-and-error
+ // with timeouts. 50ms seems to be enough time for things to work
+ // reliably, so *= 6 for good measure.
+ { "fn" : startServerLoad, "trans" : 300 },
+ { "fn" : drawLoadingIcon, "trans" : "instant" },
+ { "fn" : signalServerContinue, "trans" : "onload" },
+ { "fn" : drawLoadedImage, "trans" : "instant" },
+ { "fn" : verifyCanvases, "trans" : "finish" } ];
+var currentStage = 0;
+
+// Transition function called at the end of each stage
+function stageTransition() {
+
+ // Debugging info so we can figure out the hang
+ dump("test_bug507902.html DEBUG - Current Stage: " + currentStage + "\n");
+
+ // What's our transition?
+ var trans = testStages[currentStage++].trans;
+
+ // If the transition is finish, stop now before we access out of bounds
+ if (trans == "finish") {
+ makeCanvasesVisible(); // Useful for debugging
+ SimpleTest.finish();
+ return;
+ }
+
+ // Otherwise, get the next function
+ var nextfn = testStages[currentStage].fn;
+
+ // Switch based on transition
+ switch (trans) {
+
+ // Continue right away
+ case "instant":
+ nextfn();
+ break;
+
+ // Continue after we get an onload event on the test image
+ case "onload":
+ testImageElem.onload = function(event) {testImageElem.onload = undefined; nextfn();};
+ break;
+
+ // Continue after we get an onerror event on the test image
+ case "onerror":
+ testImageElem.onerror = function(event) {testImageElem.onerror = undefined; nextfn();};
+ break;
+
+ // Timeout
+ default:
+ setTimeout(nextfn, trans);
+ break
+ }
+}
+
+// Lots if asynchronous behavior here
+SimpleTest.waitForExplicitFinish();
+
+// Catch somebody's eye
+dump("This test is failing intermittently, see bug 510001 - If you see orange here, please paste the following debugging output on the bug!\n");
+
+// Kick off the test by invoking the first stage. The stages call each other
+testStages[0].fn();
+
+
+// We need to get rid of the old image element and make a new one. If we
+// don't, the "current/pending" machinery will display the old image until
+// the new one is loaded, so we won't see the loading icon.
+function resetImage() {
+ divContainer.removeChild(testImageElem);
+ testImageElem = null;
+ testImageElem = new Image();
+ divContainer.appendChild(testImageElem);
+}
+
+//
+// Makes the canvases visible. Called before the tests finish. This is useful for
+// debugging.
+//
+function makeCanvasesVisible() {
+ for (let i = 0; i < canvasNames.length - 1; i += 2) {
+ var title = document.createElement("h3");
+ title.innerHTML = canvasNames[i] + ", " + canvasNames[i+1] + ":";
+ document.body.appendChild(title);
+ var myDiv = document.createElement("div");
+ myDiv.appendChild(canvases[canvasNames[i]]);
+ myDiv.appendChild(canvases[canvasNames[i+1]]);
+ document.body.appendChild(myDiv);
+ }
+}
+
+//
+// Enables and disables bordering/padding to mimic the look of alt feedback icons
+//
+function enableBorderAndPad() {
+ divContainer.style.border = "1px";
+ divContainer.style.borderStyle = "inset";
+ testImageElem.style.padding = "3px";
+}
+
+function disableBorderAndPad() {
+ testImageElem.style.padding = 0;
+ divContainer.style.border = "0px";
+ divContainer.style.borderStyle = "";
+}
+
+//
+// Helper canvas methods. This is mostly copped directly from the reftest framework
+//
+
+function drawWindowToCanvas(canvasName) {
+ var win = testFrameElem.contentWindow;
+ let ctx = canvases[canvasName].getContext("2d");
+ // drawWindow always draws one canvas pixel for each CSS pixel in the source
+ // window, so scale the drawing to show the zoom (making each canvas pixel be one
+ // device pixel instead)
+ ctx.drawWindow(win, win.scrollX, win.scrollY,
+ Math.ceil(canvases[canvasName].width),
+ Math.ceil(canvases[canvasName].height),
+ "rgb(255,255,255)");
+}
+
+function canvasesAreEqual(canvas1Name, canvas2Name) {
+ var c1 = canvases[canvas1Name];
+ var c2 = canvases[canvas2Name];
+ var differences = gWindowUtils.compareCanvases(c1, c2, {});
+ return (differences == 0);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug508115.xhtml b/layout/generic/test/test_bug508115.xhtml
new file mode 100644
index 0000000000..01ac39b355
--- /dev/null
+++ b/layout/generic/test/test_bug508115.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=508115
+-->
+<window title="Mozilla Bug 508115"
+ onload="setTimeout(doTest, 0)"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=508115">Mozilla Bug 508115</a>
+
+ <p id="display"></p>
+<div id="content" style="display: none">
+</div>
+</body>
+<script class="testbody" type="application/javascript"><![CDATA[
+function doTest() {
+ document.getElementById("panel").style.display = '';
+ document.getElementById("deck").selectedIndex = 1;
+ document.getElementById("anchor").open = true;
+ document.getElementById("container").style.width = "0";
+ document.getElementById("anchor2").open = true;
+}
+var count = 0;
+function shown(id) {
+ ok(true, "Shown popup " + id);
+ ++count;
+ if (count >= 2) {
+ SimpleTest.finish();
+ }
+}
+SimpleTest.waitForExplicitFinish();
+]]></script>
+<deck id="deck" style="margin:50px;">
+ <vbox></vbox>
+ <vbox id="panel" style="display:none">
+ <vbox>
+ <menulist id="anchor">
+ <menupopup id="popup" onpopupshown="shown('anchor')">
+ <menuitem label="One"/>
+ <menuitem label="Two"/>
+ <menuitem label="Three"/>
+ </menupopup>
+ </menulist>
+ </vbox>
+ </vbox>
+</deck>
+<description>
+<html:div id="container">
+ <html:span id="span" style="transform: translate(0,0)">Hello Kitty
+ <menulist id="anchor2" style="display:-moz-inline-box;">
+ <menupopup id="popup" onpopupshown="shown('anchor2')">
+ <menuitem label="One"/>
+ <menuitem label="Two"/>
+ <menuitem label="Three"/>
+ </menupopup>
+ </menulist>
+ </html:span>
+</html:div>
+</description>
+</window>
diff --git a/layout/generic/test/test_bug514732-2.xhtml b/layout/generic/test/test_bug514732-2.xhtml
new file mode 100644
index 0000000000..57d0449433
--- /dev/null
+++ b/layout/generic/test/test_bug514732-2.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514732
+-->
+<window title="Mozilla Bug 514732"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src= "chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=514732">
+ Mozilla Bug 514732</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 514732 **/
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("file_bug514732_window.xhtml", "bug514732",
+ "chrome,width=600,height=600,scrollbars,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/layout/generic/test/test_bug522632.html b/layout/generic/test/test_bug522632.html
new file mode 100644
index 0000000000..8a2614045c
--- /dev/null
+++ b/layout/generic/test/test_bug522632.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=522632
+-->
+<head>
+ <title>Test for Bug 522632</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=522632">Mozilla Bug 522632</a>
+<p id="display"></p>
+<div id="content">
+ <table border="1">
+ <tr>
+ <td style="height: 200px; padding: 0; margin: 0; border: none">
+ <div style="height: 0; min-height: 50%" id="foo"></div>
+ </td>
+ <tr>
+ </table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 522632 **/
+is(document.defaultView.getComputedStyle($("foo")).height, "100px",
+ "Unexpected computed height");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug524925.html b/layout/generic/test/test_bug524925.html
new file mode 100644
index 0000000000..d80ee559b2
--- /dev/null
+++ b/layout/generic/test/test_bug524925.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=524925
+-->
+
+<head>
+ <title>Test for Bug 524925</title>
+ <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+
+<div id="container" style="width: 100px; height: 100px; overflow: auto;">
+ <div id="container2" style="width: 100px; height: 100px; overflow: visible;">
+ <div id="block" style="width: 50px; height: 50px; background-color: #000; transform:translatey(0px);"></div>
+ </div>
+</div>
+
+<pre id="test">
+ <script type="application/javascript">
+ // Move 'block' 100 pixels downwards.
+ var block = document.getElementById("block");
+ block.style.transform = "translatey(100px)";
+
+ // Check the result is correct and finish the test
+ var container = document.getElementById("container");
+ is(container.scrollHeight, 150, "Overflow areas should update after dynamic transform changes");
+ </script>
+</pre>
+
+</body>
+</html>
diff --git a/layout/generic/test/test_bug579767.html b/layout/generic/test/test_bug579767.html
new file mode 100644
index 0000000000..94b9bf75d4
--- /dev/null
+++ b/layout/generic/test/test_bug579767.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=579767
+-->
+<head>
+ <title>Test for Bug 579767</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ iframe {
+ width: 1006px;
+ height: 306px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=579767">Mozilla Bug 579767</a>
+<p id="display"></p>
+<div id="content">
+<iframe src="file_bug579767_1.html" id="f1"></iframe>
+<iframe src="file_bug579767_2.html" id="f2"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 579767 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var f1 = document.getElementById("f1");
+ var f2 = document.getElementById("f2");
+ var t1 = f1.contentDocument.documentElement;
+ var t2 = f2.contentDocument.documentElement;
+
+ setTimeout(function() {
+ // drag the vertical handle 10px to the right
+ synthesizeMouse(t1, 100, 6, {type: "mousedown"}, f1.contentWindow);
+ synthesizeMouse(t1, 101, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 102, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 103, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 104, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 105, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 106, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 107, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 108, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 109, 6, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 200, 6, {type: "mouseup" }, f1.contentWindow);
+
+ setTimeout(function() {
+ // drag the horizontal handle 10px to down and 5px to right
+ synthesizeMouse(t1, 2, 92, {type: "mousedown"}, f1.contentWindow);
+ synthesizeMouse(t1, 3, 93, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 4, 94, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 5, 95, {type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 7, 102,{type: "mousemove"}, f1.contentWindow);
+ synthesizeMouse(t1, 7, 102,{type: "mouseup" }, f1.contentWindow);
+
+ setTimeout(function() {
+ // now compare the two windows
+ ok(compareSnapshots(snapshotWindow(f1.contentWindow),
+ snapshotWindow(f2.contentWindow), true)[0],
+ "The borders should be painted correctly after resizing");
+ is(t1.querySelectorAll("frameset")[0].getAttribute("cols"), "11%,89%",
+ "The cols attribute should be correctly updated");
+ is(t1.querySelectorAll("frameset")[1].getAttribute("rows"), "100,200",
+ "The rows attribute should be correctly updated");
+ SimpleTest.finish();
+ }, 0);
+ }, 0);
+ }, 0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug589621.html b/layout/generic/test/test_bug589621.html
new file mode 100644
index 0000000000..abae12df6d
--- /dev/null
+++ b/layout/generic/test/test_bug589621.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=589621
+-->
+<head>
+ <title>Test for Bug 589621</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589621">Mozilla Bug 589621</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 589621 **/
+var sel = getSelection();
+var range = document.createRange();
+sel.addRange(range);
+function t(n) {
+ try {
+ sel.getRangeAt(n);
+ ok(false, "Should not be here");
+ } catch(e) {
+ ok(e instanceof DOMException, "Should be a DOMException");
+ is(e.name, "IndexSizeError", "Should be an IndexSizeError");
+ is(e.code, DOMException.INDEX_SIZE_ERR, "Should be an INDEX_SIZE_ERR");
+ }
+}
+t(-1);
+t(1);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug589623.html b/layout/generic/test/test_bug589623.html
new file mode 100644
index 0000000000..4a1c0e0929
--- /dev/null
+++ b/layout/generic/test/test_bug589623.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=589623
+-->
+<head>
+ <title>Test for Bug 589623</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589623">Mozilla Bug 589623</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 589623 **/
+var sel = getSelection();
+sel.removeAllRanges();
+function t(m) {
+ try {
+ sel[m]();
+ ok(false, "Should not be here");
+ } catch(e) {
+ is(e.name, "InvalidStateError", "Should be an InvalidStateError");
+ ok(e instanceof DOMException, "Should be a DOMException");
+ is(e.code, DOMException.INVALID_STATE_ERR, "Should be an INVALID_STATE_ERR");
+ }
+}
+t("collapseToStart");
+t("collapseToEnd");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug597333.html b/layout/generic/test/test_bug597333.html
new file mode 100644
index 0000000000..c416f114cc
--- /dev/null
+++ b/layout/generic/test/test_bug597333.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=597333
+-->
+<head>
+ <title>Test for Bug 597333</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug?id=597333">Mozilla Bug 597333</a>
+
+<textarea id="a">&#x202E;</textarea>
+
+<pre id="test">
+<script>
+
+function runTests() {
+ var t = document.getElementById("a");
+ t.focus();
+ setTimeout(function() {
+ synthesizeKey('KEY_ArrowRight');
+ ok(true, "The browser did not crash!");
+
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/layout/generic/test/test_bug632379.xhtml b/layout/generic/test/test_bug632379.xhtml
new file mode 100644
index 0000000000..4d25b60fd3
--- /dev/null
+++ b/layout/generic/test/test_bug632379.xhtml
@@ -0,0 +1,223 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=632379
+-->
+<window title="Mozilla Bug 632379"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+ menuitem { height: 19px; box-sizing: border-box }
+ ]]></style>
+
+<toolbox flex="1">
+ <menubar>
+ <menu label="MENU" accesskey="m" id="mainMenu">
+ <menupopup maxheight="100" onpopupshown="openSubmenu(this, event)">
+ <menu label="menu1" accesskey="1" id="menu1">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu2" accesskey="2" id="menu2">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu3" accesskey="3" id="menu3">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu4" accesskey="4" id="menu4">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu5" accesskey="5" id="menu5">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu6" accesskey="6" id="menu6">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu7" accesskey="7" id="menu7">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu8" accesskey="8" id="menu8">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ <menu label="menu9" accesskey="9" id="menu9">
+ <menupopup onpopupshown="snapshot(this)">
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+</toolbox>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=632379">Mozilla Bug 632379</a>
+
+ <p id="display"></p>
+<div id="content" style="display: none">
+</div>
+</body>
+
+
+<script class="testbody" type="application/javascript"><![CDATA[
+
+var gFinished = false;
+
+/** Test for Bug 632379 **/
+// Tests whether scrolling a menu affects the position at which popups appear
+var pos = new Array(2);
+var count=0;
+
+function snapshot(elem)
+{
+ info(`snapshot(${elem.parentNode.id}) called`);
+ pos[count] = elem.getBoundingClientRect().top;
+ info(elem.querySelector("menuitem").getBoundingClientRect().height);
+ ++count;
+ if (count <= 1) {
+ // close the submenu and open the bottom submenu
+ synthesizeKey("KEY_ArrowLeft");
+ sendString("9");
+ } else {
+ if (!navigator.platform.includes("Mac")) {
+ is(pos[1], pos[0], `Popup ${elem.parentNode.id} should open in the same place when the menu is scrolled`);
+ } else {
+ todo(false, "This test fails on Mac since it was ported to chrome: Bug 668716.");
+ }
+ info("Test finished");
+ gFinished = true;
+ SimpleTest.finish();
+ }
+}
+
+function doTest() {
+ info("doTest() called");
+ // open the top-level menu
+ $("mainMenu").open = true;
+}
+
+function openSubmenu(mainMenu, e)
+{
+ if (e.originalTarget != mainMenu) {
+ return;
+ }
+ info("openSubmenu() called");
+ // open a submenu in the middle
+ sendString("5");
+}
+
+SimpleTest.waitForExplicitFinish();
+info("Wait for focus");
+SimpleTest.waitForFocus(doTest);
+
+]]></script>
+</window>
+
diff --git a/layout/generic/test/test_bug633762.html b/layout/generic/test/test_bug633762.html
new file mode 100644
index 0000000000..19a6184b14
--- /dev/null
+++ b/layout/generic/test/test_bug633762.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633762
+-->
+<head>
+ <title>Test for Bug 633762</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug?id=633762">Mozilla Bug 633762</a>
+
+<iframe id="i" src="bug633762_iframe.html#a"></iframe>
+
+<pre id="test">
+<script>
+
+var doc;
+
+async function runTests() {
+ var i = document.getElementById("i");
+ doc = i.contentDocument;
+ var win = i.contentWindow;
+ // set display none on b
+ doc.getElementById("b").style.display = "none";
+ // flush layout
+ doc.documentElement.offsetLeft;
+ // focus on the iframe
+ win.focus();
+
+ // clear out any potential scroll events so we can listen for the one we want without false positives
+ await waitToClearOutAnyPotentialScrolls(window);
+
+ step2();
+}
+
+function step2() {
+ var i = document.getElementById("i");
+ doc = i.contentDocument;
+ var win = i.contentWindow;
+ // record scrolltop
+ scrollTopBefore = doc.body.scrollTop;
+ // send up arrow key event
+ sendKey("UP");
+
+ win.addEventListener("scroll", finish, {once: true, capture: true});
+}
+
+function finish() {
+ // assert that scroll top is now less than before
+ ok(scrollTopBefore > doc.body.scrollTop, "pressing up arrow should scroll up");
+ SimpleTest.finish();
+}
+
+var smoothScrollPref = "general.smoothScroll";
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, runTests);
+});
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/layout/generic/test/test_bug666225.html b/layout/generic/test/test_bug666225.html
new file mode 100644
index 0000000000..717418ce7a
--- /dev/null
+++ b/layout/generic/test/test_bug666225.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666225
+-->
+<head>
+ <title>Test for Bug 666225</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666225">Mozilla Bug 666225</a>
+<p id="display"><input id="i" onmousedown="this.type='file';"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 666225
+ Causes ASSERTION: Unexpected document: 'capturingContent->GetUncomposedDoc() == GetDocument()' (bug 560764) **/
+
+function click()
+{
+ $("i").addEventListener("mouseup", test);
+ synthesizeMouseAtCenter($("i"), { type: "mousedown" });
+ synthesizeMouseAtCenter($("i"), { type: "mouseup" });
+}
+
+function test()
+{
+ ok(true, "should not crash");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(click);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug719503.html b/layout/generic/test/test_bug719503.html
new file mode 100644
index 0000000000..94d0c5d377
--- /dev/null
+++ b/layout/generic/test/test_bug719503.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=719503
+-->
+<title>Test for Bug 719503</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=719503">Mozilla Bug 719503</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script>
+try {
+ getSelection().collapse(document.head, 0);
+ getSelection().deleteFromDocument();
+} catch(e) {
+ ok(false, "Exception thrown");
+}
+ok(true, "Passed, no exception");
+</script>
diff --git a/layout/generic/test/test_bug719515.html b/layout/generic/test/test_bug719515.html
new file mode 100644
index 0000000000..932e227144
--- /dev/null
+++ b/layout/generic/test/test_bug719515.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=719515
+-->
+<title>Test for Bug 719515</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=719515">Mozilla Bug 719515</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script>
+getSelection().collapse(document.body, 0);
+var range = getSelection().getRangeAt(0);
+getSelection().extend(document.body, 1);
+isnot(range, getSelection().getRangeAt(0),
+ "extend() must replace existing ranges, not mutate them");
+range = getSelection().getRangeAt(0);
+getSelection().extend(document.body, 1);
+isnot(range, getSelection().getRangeAt(0),
+ "extend() must replace existing ranges, not mutate them (even for no-op extend()s");
+</script>
diff --git a/layout/generic/test/test_bug719518.html b/layout/generic/test/test_bug719518.html
new file mode 100644
index 0000000000..e686944d5e
--- /dev/null
+++ b/layout/generic/test/test_bug719518.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=719518
+-->
+<title>Test for Bug 719518</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=719518">Mozilla Bug 719518</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script>
+var thrown = false;
+try {
+ getSelection().extend(document.body, 0);
+} catch(e) {
+ thrown = true;
+ is(e.name, "InvalidStateError",
+ "Need to throw InvalidStateError for extend() with no ranges");
+ ok(e instanceof DOMException,
+ "Need to throw DOMException for extend() with no ranges");
+ is(e.code, DOMException.INVALID_STATE_ERR,
+ "Need to throw INVALID_STATE_ERR for extend() with no ranges");
+}
+ok(thrown, "Need to throw exception for extend() with no ranges");
+</script>
diff --git a/layout/generic/test/test_bug719523.html b/layout/generic/test/test_bug719523.html
new file mode 100644
index 0000000000..ff6da72102
--- /dev/null
+++ b/layout/generic/test/test_bug719523.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=719523
+-->
+<title>Test for Bug 719523</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=719523">Mozilla Bug 719523</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script>
+getSelection().selectAllChildren(document);
+is(document.childNodes.length, 2, "Sanity check: number of document children");
+is(getSelection().focusNode, document,
+ "Sanity check: document must be selected");
+is(getSelection().focusOffset, 2,
+ "selectAllChildren() must select all the document's children");
+</script>
diff --git a/layout/generic/test/test_bug735641.html b/layout/generic/test/test_bug735641.html
new file mode 100644
index 0000000000..9be71704cf
--- /dev/null
+++ b/layout/generic/test/test_bug735641.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=735641
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 735641</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=735641">Mozilla Bug 735641</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 735641 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var w;
+
+function runTest() {
+ try {
+ var s = w.document.getSelection();
+ s.selectAllChildren(w.document.body);
+ synthesizeMouse(w.document.body,1,1,{},w);
+ ok(s.isCollapsed,"mouse click on <body> did reset the selection in image document");
+ } finally {
+ w.close();
+ }
+ SimpleTest.finish();
+}
+
+w=window.open("file_Dolske.png","_blank");
+w.addEventListener("load", function() {setTimeout(runTest,0)});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug748961.html b/layout/generic/test/test_bug748961.html
new file mode 100644
index 0000000000..6e39b5587d
--- /dev/null
+++ b/layout/generic/test/test_bug748961.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=748961
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 748961</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=748961">Mozilla Bug 748961</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 748961 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var f = document.createElement('iframe');
+document.body.appendChild(f);
+
+src = "<meta charset='utf-8'><body><p id='sel'>Selection</p><scr" + "ipt>"
+src += "try {"
+src += "var range = document.createRange();"
+src += "range.selectNode(document.getElementById('sel'));"
+src += "var sel = window.getSelection();"
+src += "sel.addRange(range);"
+src += "parent.is(sel.toString(), 'Selection', 'Selection.toString()');"
+src += "document.body.offsetHeight;"
+src += "parent.is(sel.toString(), 'Selection', 'Selection.toString() after explicit flush');"
+src += "} finally { parent.ok(true,'silence no test run warning'); parent.SimpleTest.finish(); }"
+src += "</scr"+"ipt>";
+
+f.srcdoc = src;
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug756984.html b/layout/generic/test/test_bug756984.html
new file mode 100644
index 0000000000..a478074db7
--- /dev/null
+++ b/layout/generic/test/test_bug756984.html
@@ -0,0 +1,138 @@
+<!--
+ Important: needs to be in quirks mode for the test to work.
+ If not in quirks mode, the down and up arrow don't position as expected.
+-->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=756984
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 756984</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=756984">Mozilla Bug 756984</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="div1">123<br>45678<br></div>
+<div id="div2"><font face="Arial">123</font><br><i>45678</i><br></div>
+<div id="div3"><font face="Courier"><i><strong>123</strong></i></font><br><i>45678</i><br></div>
+<div id="div4"><br>45678<br></div>
+<div id="div5" contenteditable=true spellcheck=false>1234567890<br>abc<br>defghijklmno<br>end</div>
+<div id="div6" contenteditable=true spellcheck=false><font face="Arial">1234567890</font><br><i>abc</i><br><font color="red">defghijklmno</font><br>end</div>
+<div id="div7" contenteditable=true spellcheck=false><font face="Courier"><i><strong>1234567890</strong></i></font><br><i>abc</i><br><font color="red"><b>defghijklmno</b></font><br>end</div>
+
+<pre id="test">
+
+ <script type="application/javascript">
+
+ /** Test for Bug 756984 **/
+ /*
+ * We test that clicking beyond the end of a line terminated with <br> selects the preceding text, if any.
+ * In a contenteditable div, we also test that getting to the end of a line via the "end" key or by using
+ * the left-arrow key from the beginning of the following line selects the text preceding the <br>.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.waitForFocus(function() {
+
+ var sel = window.getSelection();
+ var i, theDiv, selRange;
+
+ for (i = 1; i <= 3; i++) {
+ // click beyond the first line (100px to the left and 2px down), expect text
+ theDiv = document.getElementById("div" + i.toString());
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {});
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be in text node");
+ is(selRange.endOffset, 3, "offset should be 3");
+ }
+
+ // click beyond the first line (100px to the left and 2px down), expect DIV.
+ // This is the previous behaviour which hasn't changed since the line is empty.
+ // If the processing were wrong, the selection would end up in some other non-empty line.
+ theDiv = document.getElementById("div4");
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {});
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "DIV", "selection should be in DIV");
+ is(selRange.endOffset, 0, "offset should be 0");
+
+ // Now we do a more complex test, this time with an editable div.
+ // We have four lines:
+ // 1234567890<br>
+ // abc<br>
+ // defghijklmno<br> <!-- Note: 12 characters long. -->
+ // end
+
+ for (i = 5; i <= 7; i++) {
+ // We do these steps:
+ // 1) Click behind the first line, add "X".
+ theDiv = document.getElementById("div" + i.toString());
+ theDiv.focus();
+ var originalHTML = theDiv.innerHTML;
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be in text node (1)");
+ is(selRange.endOffset, 10, "offset should be 10");
+ sendString("X");
+
+ // 2) Down arrow to the end of the second line, add "Y".
+ synthesizeKey("KEY_ArrowDown");
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be in text node (2)");
+ is(selRange.endOffset, 3, "offset should be 3");
+ sendString("Y");
+
+ // 3) Right arrow and end key to the end of the third line, add "Z".
+ synthesizeKey("KEY_ArrowRight");
+ if (navigator.platform.indexOf("Win") == 0) {
+ synthesizeKey("KEY_End");
+ } else {
+ // End key doesn't work as expected on Mac and Linux.
+ sel.modify("move", "right", "lineboundary");
+ }
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be in text node (3)");
+ is(selRange.endOffset, 12, "offset should be 12");
+ sendString("Z");
+
+ // 4) Up arrow to the end of the second line, add "T".
+ synthesizeKey("KEY_ArrowUp");
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be in text node (4)");
+ is(selRange.endOffset, 4, "offset should be 4");
+ sendString("T");
+
+ // 5) Left arrow six times to the end of the first line, add "A".
+ synthesizeKey("KEY_ArrowLeft", {repeat: 6});
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be in text node (5)");
+ is(selRange.endOffset, 11, "offset should be 11");
+ sendString("A");
+
+ // Check the resulting HTML. First prepare what we expect.
+ originalHTML = originalHTML.replace(/1234567890/, "1234567890XA");
+ originalHTML = originalHTML.replace(/abc/, "abcYT");
+ originalHTML = originalHTML.replace(/defghijklmno/, "defghijklmnoZ");
+ var newHTML = theDiv.innerHTML;
+ is(newHTML, originalHTML, "unexpected HTML");
+ }
+ SimpleTest.finish();
+ });
+ </script>
+
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug784410.html b/layout/generic/test/test_bug784410.html
new file mode 100644
index 0000000000..29918b1cc1
--- /dev/null
+++ b/layout/generic/test/test_bug784410.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 784410</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="outer" style="overflow:auto; height:200px; border:2px dotted black; transform: translateY(1px)" onscroll="doneScroll()">
+ <div id="d" style="overflow:auto; height:102px;" onscroll="doneScroll()">
+ <div id="inner" style="height:100.1px; border:1px solid black; background:yellow;">Hello</div>
+ </div>
+ <div style="height:500px;"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var sel = window.getSelection();
+var outer = document.getElementById("outer");
+var d = document.getElementById("d");
+var inner = document.getElementById("inner");
+var smoothScrollPref = "general.smoothScroll";
+
+function innerScrollOffset() {
+ return inner.getBoundingClientRect().top - d.getBoundingClientRect().top;
+}
+var innerStartScrollOffset = innerScrollOffset();
+
+var step = 0;
+var wheelAndPaintDone = false;
+async function doneScroll() {
+ ++step;
+ switch (step) {
+ case 1:
+ is(innerScrollOffset(), innerStartScrollOffset, "Inner element should not have scrolled down");
+ ok(outer.scrollTop > 0, "Outer element should have scrolled down");
+
+ outer.scrollTop = 0;
+ break;
+ case 2:
+ // wait until APZ is ready to handle the wheel event, then send it
+ await promiseApzFlushedRepaints();
+ // Wait for paints to flush, so APZ is notified of the new scroll offset.
+ sendWheelAndPaint(inner, 4, 4,
+ { deltaMode: WheelEvent.DOM_DELTA_LINE, deltaY: 1 },
+ function() {
+ wheelAndPaintDone = true;
+ doneTest();
+ });
+ break;
+ case 3:
+ is(innerScrollOffset(), innerStartScrollOffset, "Inner element should not have scrolled down");
+ ok(outer.scrollTop > 0, "Outer element should have scrolled down");
+
+ doneTest();
+ break;
+ }
+}
+
+function doneTest() {
+ // wait until wheelAndPaintDone has invoked its callback before we end the
+ // test, otherwise we might get calls to advanceTimeAndRefresh after we
+ // restore normal refresh, and that will screw up the next test.
+ if (wheelAndPaintDone && step == 3) {
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ SimpleTest.finish();
+ }
+}
+
+function test() {
+ sel.collapse(inner.firstChild, 2);
+ synthesizeKey("KEY_PageDown");
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false],
+ ["test.events.async.enabled", true]]}, test);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug785324.html b/layout/generic/test/test_bug785324.html
new file mode 100644
index 0000000000..07c3006021
--- /dev/null
+++ b/layout/generic/test/test_bug785324.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785324
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 785324</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785324">Mozilla Bug 785324</a>
+<div id="test785324" style="width:100px;height:100px;border:1px solid black;" onclick="this.innerHTML = 'TEST';"></div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 785324 **/
+
+function test()
+{
+ var div1 = document.getElementById('test785324');
+ synthesizeMouse(div1, 25, 25, {});
+ var sel = window.getSelection();
+ is(sel.rangeCount,1,"have Selection");
+ var r = sel.getRangeAt(0);
+ ok(r.startContainer == div1,"startContainer");
+ ok(r.endContainer == div1,"endContainer");
+ ok(r.startOffset == 0,"startOffset");
+ ok(r.endOffset == 0,"endOffset");
+ SimpleTest.finish();
+}
+
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug791616.html b/layout/generic/test/test_bug791616.html
new file mode 100644
index 0000000000..744a5f76b7
--- /dev/null
+++ b/layout/generic/test/test_bug791616.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 791616</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+#t {
+ overflow: auto;
+ position: absolute;
+ left: 200px;
+ top: 100px;
+ font: 14px/1.3em "Consolas","Bitstream Vera Sans Mono","Courier New",Courier,monospace;
+}
+ </style>
+</head>
+<body>
+<p id="display"></p>
+<div id="t" contenteditable>
+ <div>66666666666666</div>
+ <div id="target">777777777777777777777777777777777777777</div>
+</div>
+</div>
+<pre id="test">
+<script class="testbody">
+var t = document.getElementById("t");
+var target = document.getElementById("target");
+var sel = window.getSelection();
+var smoothScrollPref = "general.smoothScroll";
+
+SimpleTest.waitForExplicitFinish();
+t.scrollTop = 0;
+var targetY = target.getBoundingClientRect().top;
+
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, runTest);
+});
+function runTest() {
+ is(target.getBoundingClientRect().top, targetY, "Target should not have scrolled due to waitForFocus");
+ t.focus();
+ is(target.getBoundingClientRect().top, targetY, "Target should not have scrolled due to focus change");
+
+ // Move the caret to scroll it into view
+ sel.collapse(target.firstChild, 2);
+ synthesizeKey("KEY_ArrowLeft");
+
+ // Delay until next repaint in case stuff is asynchronous. Also
+ // take a trip through the event loop.
+ setTimeout(function() {
+ window.requestAnimationFrame(function() {
+ is(sel.anchorNode, target.firstChild, "Should have selected 'target' text node");
+ is(sel.anchorOffset, 1, "Selection should have moved left one character");
+ // We should not have needed to scroll the caret into view
+ is(target.getBoundingClientRect().top, targetY, "Target should not have scrolled");
+ SimpleTest.finish();
+ });
+ // Make sure repainting actually happens.
+ target.style.background = "yellow";
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug831780.html b/layout/generic/test/test_bug831780.html
new file mode 100644
index 0000000000..d07dbb9621
--- /dev/null
+++ b/layout/generic/test/test_bug831780.html
@@ -0,0 +1,32 @@
+<!-- Important: needs to be in quirks mode for the test to be meaningful -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=831780
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 831780</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=831780">Mozilla Bug 831780</a>
+<p id="display" style="width: 1px; height: 1px; overflow: hidden">
+ <!-- No src so it'll be a broken image, but that only happens in quirks
+ mode. In standards mode the test would end up timing-dependent -->
+ <img style="width: 1px; height: 1px">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 831780 **/
+ is($("display").scrollWidth, 1, "scrollWidth should be 1");
+ is($("display").scrollHeight, 1, "scrollHeight should be 1");
+
+ </script>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug841361.html b/layout/generic/test/test_bug841361.html
new file mode 100644
index 0000000000..9d41840d75
--- /dev/null
+++ b/layout/generic/test/test_bug841361.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=841361
+-->
+<head>
+ <title>Test for Bug 841361</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841361">Mozilla Bug 841361</a>
+<p id="display">
+
+<div style="width:500px; height:0;" id="a"></div>
+
+<div style="width:0; height:500px;" id="b"></div>
+
+<div style="width:500px;" id="c">
+ <div style="width:50px; height:50px; float:left; background:yellow"></div>
+ <div style="width:200px; height:50px; float:left; background:green"></div>
+</div>
+
+<div style="width:500px; height:0; overflow:hidden" id="d"></div>
+
+<div style="width:0; height:500px; overflow:hidden" id="e"></div>
+
+<div style="width:500px; overflow:hidden" id="f">
+ <div style="width:50px; height:50px; float:left; background:yellow"></div>
+ <div style="width:200px; height:50px; float:left; background:green"></div>
+</div>
+
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function doTest(id, w, h) {
+ var e = document.getElementById(id);
+ is(e.scrollWidth, w, "scrollWidth for element '" + id + "'");
+ is(e.scrollHeight, h, "scrollHeight for element '" + id + "'");
+}
+
+doTest("a", 500, 0);
+doTest("b", 0, 500);
+doTest("c", 500, 50);
+doTest("d", 500, 0);
+doTest("e", 0, 500);
+doTest("f", 500, 50);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug904810.html b/layout/generic/test/test_bug904810.html
new file mode 100644
index 0000000000..93190bc8bb
--- /dev/null
+++ b/layout/generic/test/test_bug904810.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=904810
+-->
+<head>
+ <title>Test for Bug 904810</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=904810">Mozilla Bug 904810</a>
+<p id="display">
+<textarea dir="ltr" id="textarea904810">
+first line
+second line
+third line
+</textarea></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 904810 **/
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function test() {
+ function shiftLeft(i) {
+ for ( ; i > 0; --i)
+ synthesizeKey("KEY_ArrowLeft", {shiftKey:true});
+ }
+
+ function shiftRight(i) {
+ for ( ; i > 0; --i)
+ synthesizeKey("KEY_ArrowRight", {shiftKey:true});
+ }
+
+ const isWindows = /WINNT/.test(SpecialPowers.OS);
+ var textarea = document.getElementById('textarea904810');
+ textarea.focus();
+
+ // Testing KEY_ArrowDown with forward selection
+ textarea.selectionStart = 1;
+ textarea.selectionEnd = 1;
+ shiftRight(7);
+ synthesizeKey("KEY_ArrowDown");
+ is(textarea.selectionStart, 19, "caret moved to next line below selection end");
+ is(textarea.selectionEnd, 19, "caret moved to next line below selection end");
+
+ // Testing KEY_ArrowDown with backward selection
+ textarea.selectionStart = 8;
+ textarea.selectionEnd = 8;
+ shiftLeft(7);
+ synthesizeKey("KEY_ArrowDown");
+ is(textarea.selectionStart, 12, "caret moved to next line below selection start");
+ is(textarea.selectionEnd, 12, "caret moved to next line below selection start");
+
+ // Testing KEY_ArrowUp with forward selection
+ textarea.selectionStart = 12;
+ textarea.selectionEnd = 12;
+ shiftRight(7);
+ synthesizeKey("KEY_ArrowUp");
+ var result = textarea.selectionEnd
+ is(textarea.selectionStart, 8, "caret moved to previous line above selection end");
+ is(textarea.selectionEnd, 8, "caret moved to previous line above selection end");
+
+ // Testing KEY_ArrowUp with backward selection
+ textarea.selectionStart = 19;
+ textarea.selectionEnd = 19;
+ shiftLeft(7);
+ synthesizeKey("KEY_ArrowUp");
+ var result = textarea.selectionEnd
+ is(textarea.selectionStart, 1, "caret moved to previous line above selection start");
+ is(textarea.selectionEnd, 1, "caret moved to previous line above selection start");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_bug938772.html b/layout/generic/test/test_bug938772.html
new file mode 100644
index 0000000000..7500517656
--- /dev/null
+++ b/layout/generic/test/test_bug938772.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=938772
+-->
+<title>Test for Bug 938772</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938772">Mozilla Bug 938772</a>
+<p id="display"></p>
+<iframe id="i"></iframe>
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+
+i.onload = function() {
+ ok(!i.contentDocument.getElementById("v").failed,
+ "Check whether an error was reported");
+ SimpleTest.finish();
+};
+i.srcdoc = "<base href='http://basetag/basetag'><video id='v' onerror='v.failed=true'></video>";
+</script>
+
diff --git a/layout/generic/test/test_bug970363.html b/layout/generic/test/test_bug970363.html
new file mode 100644
index 0000000000..7739a85ed2
--- /dev/null
+++ b/layout/generic/test/test_bug970363.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=970363
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 970363</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=970363">Mozilla Bug 970363</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<div contenteditable="true" id='editablediv'>
+ <div id='t'>S<span contenteditable="false">readonly</span>E</div>
+</div>
+<pre id="test">
+
+ <script type="application/javascript">
+
+ /** Test for Bug 970363 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.waitForFocus(function() {
+ var t = document.getElementById("t");
+ var editablediv = document.getElementById("editablediv");
+ var sel = window.getSelection();
+ editablediv.focus();
+ sel.collapse(t, 0);
+
+ // Move past the 'S' char.
+ synthesizeKey("KEY_ArrowRight");
+ // Move across the span.
+ synthesizeKey("KEY_ArrowRight");
+ // Insert an 'I' to show when IP currently is.
+ sendString("I");
+
+ is(t.textContent, "SreadonlyIE", "the 'I' char should be inserted between 'readonly' and 'E'");
+ SimpleTest.finish();
+ });
+
+ </script>
+
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_crash_on_mouse_move.html b/layout/generic/test/test_crash_on_mouse_move.html
new file mode 100644
index 0000000000..64f6ab957f
--- /dev/null
+++ b/layout/generic/test/test_crash_on_mouse_move.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>
+ This is a crash test to make sure handling mouse move events on an input
+ element doesn't crash even if the input type has been changed during the
+ events
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<input style="width:1000px;height:300px"
+ onmousemove="this.setAttribute('type','text')"
+ id="formPassword" type="password" disabled/>
+<pre id="test"></pre>
+</body>
+<script>
+SimpleTest.waitForExplicitFinish();
+window.onload = () => {
+ const position = formPassword.getBoundingClientRect();
+ for (let i = 0; i < 100; i++) {
+ synthesizeMouseAtPoint(position.x + i, position.y + i,
+ { type: "mousemove" });
+
+ }
+ ok(true, "mouse movement on a input element whose type is going to be " +
+ "changed by the mouse movement doesn't crash");
+ SimpleTest.finish();
+}
+</script>
+</html>
diff --git a/layout/generic/test/test_dynamic_reflow_root_disallowal.html b/layout/generic/test/test_dynamic_reflow_root_disallowal.html
new file mode 100644
index 0000000000..8b1c313cde
--- /dev/null
+++ b/layout/generic/test/test_dynamic_reflow_root_disallowal.html
@@ -0,0 +1,747 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1508420
+-->
+<head>
+ <meta charset="utf-8">
+ <title>
+ Test for Bug 1508420: Cases where a frame isn't allowed to be a dynamic
+ reflow root
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="main()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1508420">Mozilla Bug 1508420</a>
+<p id="display">
+ <!-- Here's the iframe that we'll do all of our testing/snapshotting in: -->
+ <iframe srcdoc="<!DOCTYPE html><body></body>"></iframe>
+</p>
+<script type="application/javascript">
+ /** Test for Bug 1508420 **/
+ /**
+ * This test exercises various cases where we exclude a frame from being
+ * flagged as a dynamic reflow root. (We prevent this because we know that
+ * there are cases where we'd produce incorrect layout if we initiated reflow
+ * from the frame in question.)
+ *
+ * Roughly, the idea in each subtest here is to do the following:
+ * 1) Set up a scenario with some condition that we think should prevent a
+ * particular frame from being flagged as a dynamic reflow root.
+ * 2) Make a dynamic tweak that we expect would result in broken layout, if
+ * we had allowed the frame in question to be a dynamic reflow root.
+ * Take a snapshot.
+ * 3) Force a full reconstruct + reflow of the document's frames (by
+ * toggling "display:none" on the root element). Take another snapshot.
+ * 4) Assert that snapshots look the same -- i.e. that our incremental
+ * reflow didn't produce the wrong layout.
+ *
+ * Ideally, every condition in ReflowInput::InitDynamicReflowRoot()
+ * should have a corresponding subtest here (and the subtest should fail if
+ * we remove the condition from InitDynamicReflowRoot).
+ */
+
+ // Styles that are sufficient to make a typical element into a reflow root.
+ // We apply these styles to "reflow root candidates" throughout this test
+ // (and then add other styles that should make the candidate ineligible,
+ // typically).
+ const gReflowRootCandidateStyles =
+ "display: flow-root; will-change: transform; width: 10px; height: 10px;";
+
+ // Some convenience globals for the document inside the iframe:
+ // (initialized in 'main' after the iframe gets a chance to load)
+ // --------------------------------------------------------------
+ let gFWindow;
+ let gFDoc;
+ let gFBody;
+
+ // Some utility functions used in each test function:
+ // --------------------------------------------------
+ function createStyledDiv(divStyleStr, divInnerText) {
+ let div = gFDoc.createElement("div");
+ div.style.cssText = divStyleStr;
+ if (typeof divInnerText !== "undefined") {
+ div.innerText = divInnerText;
+ }
+ return div;
+ }
+
+ // This function takes an initial snapshot, then a second snapshot after
+ // invoking the given tweakFunc, and finally a third after forcing the frame
+ // tree to be reconstructed from scratch. Then it compares the snapshots to
+ // validate that the tweak did produce a visible change, & that the
+ // after-tweak rendering looks the same in the last two snapshots.
+ function tweakAndCompareSnapshots(tweakFunc, descPrefix) {
+ let snapPreTweak = snapshotWindow(gFWindow, false);
+ let descPreTweak = descPrefix + "-initial-rendering";
+
+ // Now we invoke the tweak (changing the size of some content inside the
+ // reflow root candidate). If this influences the size of the candidate
+ // itself, and we fail to do any reflow outside of the candidate because
+ // we made it a reflow root, then we expect to end up with a broken layout
+ // due to a parent or sibling not having been resized/repositioned.
+ // We'll discover that when comparing snapIncReflow against snapFullReflow
+ // below.
+ tweakFunc();
+
+ let snapIncReflow = snapshotWindow(gFWindow, false);
+ let descIncReflow = descPrefix + "-after-tweak-inc-reflow";
+
+ // Now we trigger a "full" reflow (not incremental), by forcing
+ // frame reconstruction all the way from the body element. This should
+ // force us to reflow from the actual document root, even if we have
+ // promoted any frames to be dynamic reflow roots.
+ gFBody.style.display = "none";
+ gFBody.offsetTop; // flush layout
+ gFBody.style.display = "";
+ let snapFullReflow = snapshotWindow(gFWindow, false);
+ let descFullReflow = descPrefix + "-after-tweak-full-reflow";
+
+ assertSnapshots(snapIncReflow, snapPreTweak, false, null,
+ descIncReflow, descPreTweak);
+ assertSnapshots(snapIncReflow, snapFullReflow, true, null,
+ descIncReflow, descFullReflow);
+ }
+
+ // Test functions (called from "main"), with a subtest array in most cases:
+ // ------------------------------------------------------------------------
+
+ // Subtests for intrinsic size keywords (and equivalent, e.g. percentages) as
+ // values for width/height/{min,max}-{width,height} on reflow root candidates:
+ let intrinsicSizeSubtests = [
+ { desc: "width-auto",
+ candStyle: "width:auto",
+ },
+ { desc: "width-pct",
+ candStyle: "width:80%",
+ },
+ { desc: "width-calc-pct",
+ candStyle: "width:calc(10px + 80%)",
+ },
+ { desc: "width-min-content",
+ candStyle: "width:-moz-min-content; width:min-content;",
+ },
+ { desc: "width-max-content",
+ candStyle: "width:-moz-max-content; width:max-content;",
+ },
+ { desc: "min-width-min-content",
+ candStyle: "min-width:-moz-min-content; min-width:min-content;",
+ },
+ { desc: "min-width-max-content",
+ candStyle: "min-width:-moz-max-content; min-width:max-content;",
+ },
+ { desc: "max-width-min-content",
+ // Note: hardcoded 'width' here must be larger than what 'inner'
+ // gets resized to, so that max-width gets a chance to clamp.
+ candStyle: "width: 50px; \
+ max-width:-moz-min-content; max-width:min-content;",
+ },
+ { desc: "max-width-max-content",
+ candStyle: "width: 50px; \
+ max-width:-moz-max-content; max-width:max-content;",
+ },
+ { desc: "height-auto",
+ candStyle: "height:auto",
+ },
+ { desc: "height-pct",
+ candStyle: "height:80%",
+ },
+ { desc: "height-calc-pct",
+ candStyle: "height:calc(10px + 80%)",
+ },
+ { desc: "height-min-content",
+ candStyle: "height:-moz-min-content; height:min-content;",
+ },
+ { desc: "height-max-content",
+ candStyle: "height:-moz-max-content; height:max-content;",
+ },
+ { desc: "min-height-min-content",
+ candStyle: "min-height:-moz-min-content; min-height:min-content;",
+ },
+ { desc: "min-height-max-content",
+ candStyle: "min-height:-moz-max-content; min-height:max-content;",
+ },
+ { desc: "max-height-min-content",
+ // Note: hardcoded 'height' here must be larger than what 'inner'
+ // gets resized to, so that max-height gets a chance to clamp.
+ candStyle: "height: 50px; \
+ max-height:-moz-min-content; max-height:min-content;",
+ },
+ { desc: "max-height-max-content",
+ candStyle: "height: 50px; \
+ max-height:-moz-max-content; max-height:max-content;",
+ },
+ ];
+
+ // Intrinsic heights (e.g. 'height:auto') should prevent
+ // an element from being a reflow root.
+ function runIntrinsicSizeSubtest(subtest) {
+ // Run each testcase in horizontal & vertical writing mode:
+ for (let wmVal of ["horizontal-tb", "vertical-lr"]) {
+ gFBody.style.writingMode = wmVal;
+
+ // Short version of WM, for use in logging for snapshot comparison below:
+ let wmDesc = (wmVal == "horizontal-tb" ? "-horizWM" : "-vertWM");
+
+ // This outer div is intrinsically sized, and it needs to be reflowed
+ // when the size of its child (the reflow root candidate) changes.
+ let outer = createStyledDiv("border: 2px solid teal; \
+ inline-size: -moz-max-content; \
+ inline-size: max-content");
+ // The reflow root candidate:
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ subtest.candStyle);
+
+ // Something whose size we can adjust, inside the reflow root candidate:
+ let inner = createStyledDiv("height:20px; width:20px; \
+ border: 1px solid purple");
+
+ cand.appendChild(inner);
+ outer.appendChild(cand);
+ gFBody.appendChild(outer);
+
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc + wmDesc);
+
+ // clean up
+ outer.remove();
+ gFBody.style.writingMode = "";
+ }
+ }
+
+ let flexItemSubtests = [
+ { desc: "flex-basis-content",
+ candStyle: "flex-basis:content;",
+ },
+ { desc: "flex-basis-min-content",
+ candStyle: "flex-basis:-moz-min-content;flex-basis:min-content;",
+ },
+ { desc: "flex-basis-auto-width-auto",
+ candStyle: "flex-basis:auto;width:auto;",
+ },
+ // For percent flex-basis, we're concerned with cases where the percent
+ // triggers content-based sizing during the flex container's intrinsic
+ // sizing step. So we need to get the container to be intrinsically sized;
+ // hence the use of the (optional) "isContainerIntrinsicallySized" flag.
+// FIXME(bug 1548078): the following two tests fail to produce a rendering difference:
+// { desc: "flex-basis-pct",
+// candStyle: "flex-basis:80%;",
+// isContainerIntrinsicallySized: true,
+// },
+// { desc: "flex-basis-calc-pct",
+// candStyle: "flex-basis:calc(10px + 80%);",
+// isContainerIntrinsicallySized: true,
+// },
+ { desc: "flex-basis-from-pct-isize",
+ candStyle: "inline-size:80%",
+ isContainerIntrinsicallySized: true,
+ },
+ { desc: "flex-basis-from-calc-pct-isize",
+ candStyle: "inline-size:calc(10px + 80%);",
+ isContainerIntrinsicallySized: true,
+ },
+ // Testing the magic "min-main-size:auto" keyword
+ // and other intrinsic min/max sizes
+ { desc: "flex-min-inline-size-auto",
+ candStyle: "flex:0 5px; inline-size:auto; min-inline-size:auto",
+ },
+ { desc: "flex-min-inline-size-min-content",
+ candStyle: "flex:0 5px; inline-size:auto; min-inline-size:min-content",
+ },
+ { desc: "flex-min-block-size-auto",
+ candStyle: "flex:0 5px; block-size:auto; min-block-size:auto",
+ isContainerColumnOriented: true,
+ },
+ { desc: "flex-min-block-size-auto",
+ candStyle: "flex:0 5px; block-size:auto; min-block-size:min-content",
+ isContainerColumnOriented: true,
+ },
+ ];
+
+ // Content-dependent flex-basis values should prevent a flex item
+ // from being a reflow root.
+ function runFlexItemSubtest(subtest) {
+ // We create a flex container with two flex items:
+ // - a simple flex item that just absorbs all extra space
+ // - the reflow root candidate
+ let containerSizeVal = subtest.isContainerIntrinsicallySized ?
+ "max-content" : "100px";
+ let containerSizeDecl =
+ "inline-size: " + containerSizeVal + "; " +
+ "block-size: " + containerSizeVal + ";";
+ let containerFlexDirectionDecl = "flex-direction: " +
+ (subtest.isContainerColumnOriented ? "column" : "row") + ";"
+
+ let flexContainer = createStyledDiv("display: flex; \
+ border: 2px solid teal; " +
+ containerSizeDecl +
+ containerFlexDirectionDecl);
+
+ let simpleItem = createStyledDiv("border: 1px solid gray; \
+ background: yellow; \
+ min-inline-size: 10px; \
+ flex: 1");
+
+ // The reflow root candidate
+ // (Note that we use min-width:0/min-height:0 by default, but subtests
+ // might override that with other values in 'candStyle'.)
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ " min-width: 0; min-height: 0; " +
+ subtest.candStyle);
+
+ // Something whose size we can adjust, inside the reflow root candidate:
+ let inner = createStyledDiv("height:20px; width:20px");
+
+ cand.appendChild(inner);
+ flexContainer.appendChild(simpleItem);
+ flexContainer.appendChild(cand);
+ gFBody.appendChild(flexContainer);
+
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc);
+
+ flexContainer.remove(); // clean up
+ }
+
+ let gridItemSubtests = [
+ { desc: "grid-pct-inline-isize",
+ candStyle: "inline-size:80%",
+ isContainerIntrinsicallySized: true,
+ },
+ { desc: "grid-calc-pct-inline-isize",
+ candStyle: "inline-size:calc(10px + 80%);",
+ isContainerIntrinsicallySized: true,
+ },
+ { desc: "grid-min-inline-size-min-content",
+ candStyle: "min-inline-size:min-content",
+ },
+ ];
+
+ // 'auto' and intrinsic size keywords on some properties should prevent
+ // a grid item from becoming a reflow root.
+ function runGridItemSubtest(subtest) {
+ // We create a 4x4 grid container with two grid items:
+ // - a simple grid item that just absorbs all extra space
+ // - the reflow root candidate
+ let containerSizeVal = subtest.isContainerIntrinsicallySized ?
+ "max-content" : "100px";
+ let containerSizeDecl =
+ "inline-size: " + containerSizeVal + "; " +
+ "block-size: " + containerSizeVal + ";";
+ let containerGridDirectionDecl = "grid-auto-flow: " +
+ (subtest.isContainerColumnOriented ? "column" : "row") + ";"
+ let gridContainer = createStyledDiv("display: grid; \
+ grid: 1fr auto / 1fr auto; \
+ border: 2px solid teal; " +
+ containerSizeDecl +
+ containerGridDirectionDecl);
+
+ let simpleItem = createStyledDiv("border: 1px solid gray; \
+ background: yellow;");
+
+ // The reflow root candidate
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ "background: blue; " +
+ "grid-area:2/2; " +
+ "min-width: 10px; min-height: 10px; " +
+ subtest.candStyle);
+ // Something whose size we can adjust, inside the reflow root candidate:
+ let inner = createStyledDiv("height:20px; width:20px;");
+
+ cand.appendChild(inner);
+ gridContainer.appendChild(simpleItem);
+ gridContainer.appendChild(cand);
+ gFBody.appendChild(gridContainer);
+
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc);
+
+ gridContainer.remove(); // clean up
+ }
+
+ let gridContainerSubtests = [
+ { desc: "grid-column-start",
+ candStyle: "grid-column-start:2",
+ },
+ { desc: "grid-column-end",
+ candStyle: "grid-column-end:3",
+ },
+ { desc: "grid-row-start",
+ candStyle: "grid-row-start:2",
+ },
+ { desc: "grid-row-end",
+ candStyle: "grid-row-end:3",
+ },
+ ];
+
+ // Test that changes to grid item properties that affect grid container
+ // layout causes a grid container reflow when the item is a reflow root.
+ function runGridContainerSubtest(subtest) {
+ // We create a 4x4 grid container with one grid item:
+ // - a reflow root grid item that we'll tweak from
+ // the list above. By default it's placed at 1,1
+ // but after the tweak it should be placed elsewhere
+ let gridContainer = createStyledDiv("display: grid; \
+ width: 100px; \
+ height: 100px; \
+ grid: 1fr 10px / 1fr 10px; \
+ border: 2px solid teal");
+ // The reflow root candidate
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ "background: blue; " +
+ " min-width: 10px; min-height: 10px; ");
+
+ gridContainer.appendChild(cand);
+ gFBody.appendChild(gridContainer);
+
+ let tweakFunc = function() {
+ cand.style.cssText += "; " + subtest.candStyle;
+ };
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc);
+
+ gridContainer.remove(); // clean up
+ }
+
+ let gridSubgridSubtests = [
+ { desc: "subgrid",
+ candStyle: "grid: subgrid / subgrid",
+ },
+ { desc: "subgrid-rows",
+ candStyle: "grid: subgrid / 20px",
+ },
+ { desc: "subgrid-columns",
+ candStyle: "grid: 20px / subgrid",
+ },
+ ];
+
+ // Test that a subgrid is not a reflow root.
+ function runGridSubgridSubtest(subtest) {
+ // We create a 4x4 grid container a with one grid item:
+ // - a reflow root display:grid that we'll style as a subgrid from
+ // the list above. We place an item inside it that we'll tweak
+ // the size of, which should affect the outer grid track sizes.
+ let gridContainer = createStyledDiv("display: grid; \
+ width: 100px; \
+ height: 100px; \
+ grid: 1fr auto / 1fr auto; \
+ border: 2px solid teal");
+ // The reflow root candidate
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ "display: grid;" +
+ "grid-area: 2/2;" +
+ "background: blue;" +
+ "min-width: 10px; min-height: 10px;" +
+ subtest.candStyle);
+
+ // Something whose size we can adjust, inside the subgrid:
+ let inner = createStyledDiv("height:20px; width:20px;");
+
+ cand.appendChild(inner);
+ gridContainer.appendChild(cand);
+ gFBody.appendChild(gridContainer);
+
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc);
+
+ gridContainer.remove(); // clean up
+ }
+
+ let tableSubtests = [
+ { desc: "table",
+ /* Testing the default "display:table" styling that runTableTest uses: */
+ candStyle: "",
+ },
+ { desc: "inline-table",
+ candStyle: "display:inline-table;",
+ },
+ { desc: "table-caption",
+ candStyle: "display:table-caption;",
+ },
+ { desc: "table-cell",
+ candStyle: "display:table-cell;",
+ },
+ { desc: "table-column",
+ candStyle: "display:table-column;",
+ isColumn: true,
+ },
+ { desc: "table-column-group",
+ candStyle: "display:table-column-group;",
+ isColumn: true,
+ },
+ { desc: "table-row",
+ candStyle: "display:table-row;",
+ },
+ { desc: "table-row-group",
+ candStyle: "display:table-row-group;",
+ },
+ ];
+
+ function runTableSubtest(subtest) {
+ let outer = createStyledDiv("");
+ let shrinkWrapIB = createStyledDiv("display: inline-block; \
+ border: 2px solid teal");
+ let cand = createStyledDiv("display: table; \
+ width: 1px; height: 1px; \
+ will-change: transform; \
+ border: 1px solid purple;" +
+ subtest.candStyle);
+ let inner = createStyledDiv("display: block; \
+ width: 10px; height: 10px; \
+ background: pink;");
+ if (subtest.isColumn) {
+ // The candidate is a table-column / table-column-group, so
+ // the inner content that we tweak shouldn't be inside of it.
+ // Create an explicit table, separately, and put the candidate
+ // (the column/column-group) and the tweakable inner element
+ // both inside of that explicit table.
+ let table = createStyledDiv("display: table");
+ table.appendChild(inner);
+ table.appendChild(cand);
+ shrinkWrapIB.appendChild(table);
+ } else {
+ // The candidate is a table or some other table part
+ // that can hold content. Just put the tweakable inner
+ // element directly inside of it, and let anonymous table parts
+ // be generated as-needed.
+ cand.appendChild(inner);
+ shrinkWrapIB.appendChild(cand);
+ }
+
+ outer.appendChild(gFDoc.createTextNode("a"));
+ outer.appendChild(shrinkWrapIB);
+ gFBody.appendChild(outer);
+
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc);
+
+ outer.remove(); // clean up
+ }
+
+ let inlineSubtests = [
+ { desc: "inline",
+ candStyle: "display:inline",
+ },
+ ];
+ function runInlineSubtest(subtest) {
+ let outer = createStyledDiv("");
+ let shrinkWrapIB = createStyledDiv("display: inline-block; \
+ border: 2px solid teal");
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ subtest.candStyle);
+ let inner = createStyledDiv("display: inline-block; \
+ width: 20px; height: 20px; \
+ background: pink;");
+
+ cand.appendChild(inner);
+ shrinkWrapIB.appendChild(cand);
+ outer.appendChild(gFDoc.createTextNode("a"));
+ outer.appendChild(shrinkWrapIB);
+ gFBody.appendChild(outer);
+
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc);
+
+ outer.remove(); // clean up
+ }
+
+ let rubySubtests = [
+ { desc: "ruby",
+ candStyle: "display:ruby",
+ },
+ { desc: "ruby-base",
+ candStyle: "display:ruby-base",
+ },
+ { desc: "ruby-base-container",
+ candStyle: "display:ruby-base-container",
+ },
+ { desc: "ruby-text",
+ candStyle: "display:ruby-text",
+ },
+ { desc: "ruby-text-container",
+ candStyle: "display:ruby-text-container",
+ },
+ ];
+
+ function runRubySubtest(subtest) {
+ let outer = createStyledDiv("");
+ let shrinkWrapIB = createStyledDiv("display: inline-block; \
+ border: 2px solid teal");
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ subtest.candStyle);
+ let inner = createStyledDiv("display: inline-block; \
+ width: 20px; height: 20px; \
+ background: pink;");
+
+ cand.appendChild(inner);
+ shrinkWrapIB.appendChild(cand);
+ outer.appendChild(gFDoc.createTextNode("a"));
+ outer.appendChild(shrinkWrapIB);
+ gFBody.appendChild(outer);
+
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, subtest.desc);
+
+ outer.remove(); // clean up
+ }
+
+ function runFixedPosTest() {
+ // We reset the 'will-change' value on the candidate (overriding
+ // 'will-change:transform'), so that it won't be a fixed-pos CB. We also
+ // give the candidate some margins to shift it away from the origin, to
+ // make it visually clearer that its child's fixed-pos offsets are being
+ // resolved against the viewport rather than against the candidate div.
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ "will-change: initial; \
+ margin: 20px 0 0 30px; \
+ border: 2px solid black;");
+
+ let inner = createStyledDiv("height: 20px; width: 20px; \
+ background: pink;");
+ let fixedPos = createStyledDiv("position: fixed; \
+ width: 10px; height: 10px; \
+ background: gray;");
+
+ cand.appendChild(inner);
+ cand.appendChild(fixedPos);
+ gFBody.appendChild(cand);
+
+ // For our tweak, we'll adjust the size of "inner". This change impacts
+ // the position of the "fixedPos" placeholder (specifically, its static
+ // position), so this will require an incremental reflow that is rooted at
+ // the viewport (the containing block of "fixedPos") in order to produce
+ // the correct final layout. This is why "cand" isn't allowed to be a
+ // reflow root.
+ let tweakFunc = function() {
+ inner.style.width = inner.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, "fixed-pos");
+
+ cand.remove(); // clean up
+ }
+
+ function runMarginCollapseTest() {
+ let outer = createStyledDiv("background: lime");
+
+ // We use 'display:block' on the candidate (overriding 'display:flow-root')
+ // so that it won't be a block formatting context. (See usage/definition of
+ // NS_BLOCK_BFC_STATE_BITS in our c++ layout code.)
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ "display: block; \
+ background: purple;");
+ // We'll add border to this div in the "tweak" function, which will break
+ // the stack of margin collapsed divs.
+ let divWithEventualBorder = createStyledDiv("");
+ let divWithMargin = createStyledDiv("margin-top: 30px; \
+ width: 10px; height: 10px; \
+ background: pink;");
+
+ divWithEventualBorder.appendChild(divWithMargin);
+ cand.appendChild(divWithEventualBorder);
+ outer.appendChild(cand);
+ gFBody.appendChild(outer);
+
+ // For our tweak, we'll add a border around "divWithEventualBorder", which
+ // prevents the margin (on "divWithMargin") from collapsing all the way up
+ // to the outermost div wrapper (which it does, before the tweak).
+ // So: this tweak effectively moves the y-position towards 0, for all
+ // div wrappers outside the new border. This includes "outer", the parent
+ // of our reflow root candidate. So: if we mistakenly allow "cand" to be a
+ // reflow root, then we probably would neglect to adjust the position of
+ // "outer" when reacting to this tweak (and we'd catch that & report a
+ // test failure in our screenshot comparisons below).
+ let tweakFunc = function() {
+ divWithEventualBorder.style.border = "2px solid black";
+ };
+ tweakAndCompareSnapshots(tweakFunc, "margin-uncollapse");
+
+ outer.remove(); // clean up
+ }
+
+ function runFloatTest() {
+ let outer = createStyledDiv("");
+
+ // We use 'display:block' on the candidate (overriding 'display:flow-root')
+ // so that it won't be a block formatting context. (See usage/definition of
+ // NS_BLOCK_BFC_STATE_BITS in our c++ layout code.)
+ // This allows floats inside the candidate to affect the position of
+ // inline-level content outside of it.
+ let cand = createStyledDiv(gReflowRootCandidateStyles +
+ "display: block; \
+ border: 2px solid black;");
+ let floatChild = createStyledDiv("float: left; \
+ width: 60px; height: 60px; \
+ background: pink;");
+ let inlineBlock = createStyledDiv("display: inline-block; \
+ width: 80px; height: 80px; \
+ background: teal");
+ cand.appendChild(floatChild);
+ outer.appendChild(cand);
+ outer.appendChild(inlineBlock);
+ gFBody.appendChild(outer);
+
+ let tweakFunc = function() {
+ floatChild.style.width = floatChild.style.height = "40px";
+ };
+ tweakAndCompareSnapshots(tweakFunc, "float");
+
+ outer.remove(); // clean up
+ }
+
+ function main() {
+ SimpleTest.waitForExplicitFinish();
+
+ // Initialize our convenience aliases:
+ gFWindow = frames[0].window;
+ gFDoc = frames[0].document;
+ gFBody = frames[0].document.body;
+
+ for (let subtest of intrinsicSizeSubtests) {
+ runIntrinsicSizeSubtest(subtest);
+ }
+ for (let subtest of flexItemSubtests) {
+ runFlexItemSubtest(subtest);
+ }
+ for (let subtest of gridContainerSubtests) {
+ runGridContainerSubtest(subtest);
+ }
+ for (let subtest of gridSubgridSubtests) {
+ runGridSubgridSubtest(subtest);
+ }
+ for (let subtest of gridItemSubtests) {
+ runGridItemSubtest(subtest);
+ }
+ for (let subtest of tableSubtests) {
+ runTableSubtest(subtest);
+ }
+ for (let subtest of inlineSubtests) {
+ runInlineSubtest(subtest);
+ }
+ for (let subtest of rubySubtests) {
+ runRubySubtest(subtest);
+ }
+ runFixedPosTest();
+ runMarginCollapseTest();
+ runFloatTest();
+
+ SimpleTest.finish();
+ }
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_flex_interrupt.html b/layout/generic/test/test_flex_interrupt.html
new file mode 100644
index 0000000000..dfe4208f93
--- /dev/null
+++ b/layout/generic/test/test_flex_interrupt.html
@@ -0,0 +1,107 @@
+<!doctype html>
+<title>Test for bug 1579929</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ .container {
+ display: flex;
+ flex-direction: column;
+ overflow-y: scroll;
+ }
+ #outer {
+ width: 500px;
+ height: 300px;
+ }
+</style>
+<div class="container" id="outer">
+ <div class="item" id="firstItem" style="width: 50px">
+ <!--popuplated by script-->
+ </div>
+ <div class="container">
+ <div class="container">
+ <div class="container">
+ <div class="container">
+ <div class="container">
+ <div class="item">Item</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// Globals/constants:
+
+const gUtils = SpecialPowers.DOMWindowUtils;
+
+// This needs to match/exceed nsPresContext::sInterruptChecksToSkip in order
+// for us to actually be able to trigger the mHasPendingInterrupt=true codepath
+// in this test. Each of these "dummy" blocks will trigger a call to
+// nsPresContext::CheckForInterrupt, and after 200, we'll actually trigger an
+// interrupt.
+const gInterruptCheckThreshold = 200;
+
+// Expected to match the inline style="width:..." for #firstItem (we slowly
+// increment it to trigger reflows):
+let gFirstItemWidthPX = 50;
+
+function main() {
+ const outer = document.getElementById("outer");
+ const firstItem = document.getElementById("firstItem");
+
+ // Insert a bunch of elements to be sure we actually get interrupted.
+ // (See description of gInterruptCheckThreshold above)
+ for (let i = 0; i < gInterruptCheckThreshold; i++) {
+ let dummyBlock = document.createElement("div");
+ dummyBlock.innerText = "dummy";
+ firstItem.appendChild(dummyBlock);
+ }
+
+ // Flush any pending relayout:
+ outer.offsetHeight;
+
+ // Take control of the refresh driver
+ gUtils.advanceTimeAndRefresh(0);
+
+ // Force reflow and store the "cost" (in num-frames-reflowed)
+ const costOfNormalReflow = forceReflow();
+
+ // Sanity-check: do that again and remeasure cost, to be sure it's stable:
+ const costOfNormalReflow2 = forceReflow();
+ is(costOfNormalReflow, costOfNormalReflow2,
+ "Our forceReflow function is expected to reliably trigger " +
+ "the same set of frames to be reflowed. If this fails, there's a " +
+ "bug in the test, or non-determinism in layout code.");
+
+ // Now, we force the next reflow to get interrupted, and then measure the
+ // cost under those conditions:
+ gUtils.forceReflowInterrupt();
+ const costOfInterruptedReflow = forceReflow();
+
+ // Hopefully the interrupted one was less expensive...
+ ok(costOfInterruptedReflow <= costOfNormalReflow,
+ "num frames reflowed in interrupted reflow (" +
+ costOfInterruptedReflow +
+ ") shouldn't exceed the num frames reflowed in normal reflow (" +
+ costOfNormalReflow + ")");
+
+ gUtils.restoreNormalRefresh();
+ SimpleTest.finish();
+}
+
+// This function dirties firstItem's width, forces a reflow, and
+// returns the number of frames that were reflowed as a result.
+function forceReflow() {
+ gFirstItemWidthPX++;
+ firstItem.style.width = gFirstItemWidthPX + "px";
+
+ const origFramesReflowed = gUtils.framesReflowed;
+ gUtils.advanceTimeAndRefresh(0);
+ return gUtils.framesReflowed - origFramesReflowed;
+}
+
+main();
+</script>
diff --git a/layout/generic/test/test_frame_visibility_in_iframe.html b/layout/generic/test/test_frame_visibility_in_iframe.html
new file mode 100644
index 0000000000..393b129552
--- /dev/null
+++ b/layout/generic/test/test_frame_visibility_in_iframe.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script>
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ // Set layout.visibility.min-recompute-interval-ms to 0ms so that
+ // ScheduleApproximateFrameVisibilityUpdateNow gets called on every frame
+ // we flush layout.
+ set: [ ["layout.visibility.min-recompute-interval-ms", 0] ],
+ },
+ () =>
+ {
+ window.open("frame_visibility_in_iframe.html", "_blank");
+ }
+);
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_grid_track_sizing_algo_001.html b/layout/generic/test/test_grid_track_sizing_algo_001.html
new file mode 100644
index 0000000000..c454cb0df3
--- /dev/null
+++ b/layout/generic/test/test_grid_track_sizing_algo_001.html
@@ -0,0 +1,1641 @@
+<!DOCTYPE HTML>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>CSS Grid Test: intrinsic track sizing with spanning items</title>
+ <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1425599">
+ <link rel="help" href="https://drafts.csswg.org/css-grid/#algo-content">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css">
+body,html { color:black; background:white; font-family:monospace; font-size:16px; padding:0; margin:0; }
+* { vertical-align: top; line-height: 1px; }
+
+.grid {
+ display: inline-grid;
+ grid-template-rows: 1px;
+ grid-auto-rows: 10px;
+ border: 1px solid;
+ place-content: start;
+ place-items: start;
+}
+
+x {
+ grid-row: 1;
+ height: 1px;
+ background: grey;
+}
+
+x:nth-child(1) { background: lime; width: 3px; grid-column: 1/span 2; }
+x:nth-child(2) { background: silver; width: 6px; grid-column: 2/span 4; }
+x:nth-child(3) { background: blue; width: 12px; grid-column: 2/span 2; }
+ </style>
+</head>
+<body>
+
+<script>
+let min1 = [
+ "0", "4px"
+];
+let min2 = [
+ "auto", "min-content", "max-content"
+];
+let max1 = [
+ "8px"
+];
+let max2 = [
+ "auto", "min-content", "max-content"
+];
+
+var track = [];
+min1.forEach((min) => { max2.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+min2.forEach((min) => { max2.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+min2.forEach((min) => { max1.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+min2.forEach((min) => { max2.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+track.push("fit-content(8px)");
+
+var cols = [];
+track.forEach((c1) => {
+ cols.push(c1 + " " + c1 + " " + c1 + " " + c1 + " " + c1);
+ track.forEach((c2) => {
+ if (c1 != c2) {
+ cols.push(c1 + " " + c2 + " " + c2 + " " + c2 + " " + c2);
+ cols.push(c2 + " " + c1 + " " + c1 + " " + c2 + " " + c2);
+ }
+})});
+
+document.body.style.display = 'none';
+cols.forEach((col) => {
+ let grid = document.createElement('div');
+ grid.className = "grid";
+ grid.style.gridTemplateColumns = col;
+ grid.appendChild(document.createElement('x'))
+ grid.appendChild(document.createElement('x'))
+ grid.appendChild(document.createElement('x'))
+ document.body.appendChild(grid);
+});
+document.body.style.display = '';
+</script>
+
+<script>
+var actual = [];
+[...document.querySelectorAll('.grid')].forEach(function(e) {
+ let cs = window.getComputedStyle(e);
+ actual.push([cs.gridTemplateColumns, cs.width]);
+});
+
+function dumpResult() {
+ var s = "";
+ actual.forEach(v => {
+ s += '["' + v[0] + '","' + v[1] + '"],\n';
+ });
+ let pre = document.createElement("pre");
+ pre.innerHTML = s;
+ document.body.appendChild(pre);
+}
+
+let expected = [
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 8px 8px 8px 8px","32px"],
+["8px 6px 6px 8px 8px","36px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 3px 3px","21px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 4px 4px","24px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 6px 6px 8px 8px","36px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["4px 6px 6px 0px 0px","16px"],
+["0px 6px 6px 0px 0px","12px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 0px 0px","20px"],
+["0px 8px 8px 0px 0px","16px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 4px 4px","28px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["8px 6px 6px 0px 0px","20px"],
+["1.5px 8px 8px 0px 0px","17.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["3px 6px 6px 0px 0px","15px"],
+["0px 6px 6px 0px 0px","12px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["0px 6px 6px 4px 4px","20px"],
+["4px 6px 6px 4px 4px","24px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 8px 8px 8px 8px","33.5px"],
+["8px 6px 6px 8px 8px","36px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+["1.5px 6px 6px 0px 0px","13.5px"],
+];
+
+function test() {
+ ok(expected.length > 0, "sanity check");
+ for (i = 0; i < expected.length; ++i) {
+ var msg = "";
+ if (actual[i][0] != expected[i][0]) {
+ msg = "'grid-template-columns' for grid index " + i + ":\n" + [...document.querySelectorAll('.grid')][i].outerHTML;
+ is(actual[i][0], expected[i][0], msg);
+ }
+ if (actual[i][1] != expected[i][1]) {
+ msg = "'width' for grid index " + i + ":\n" + [...document.querySelectorAll('.grid')][i].outerHTML;
+ is(actual[i][1], expected[i][1], msg);
+ }
+ if (msg != "") {
+ i = expected.length; // stop after first failed grid by default
+ }
+ }
+ SimpleTest.finish();
+}
+
+</script>
+
+<script>
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</script>
+
+</body>
+</html>
diff --git a/layout/generic/test/test_grid_track_sizing_algo_002.html b/layout/generic/test/test_grid_track_sizing_algo_002.html
new file mode 100644
index 0000000000..499fd61e24
--- /dev/null
+++ b/layout/generic/test/test_grid_track_sizing_algo_002.html
@@ -0,0 +1,1641 @@
+<!DOCTYPE HTML>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+ <meta charset="utf-8">
+ <title>CSS Grid Test: intrinsic track sizing with spanning items</title>
+ <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1425599">
+ <link rel="help" href="https://drafts.csswg.org/css-grid/#algo-content">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css">
+body,html { color:black; background:white; font-family:monospace; font-size:16px; padding:0; margin:0; }
+* { vertical-align: top; line-height: 1px; }
+
+.grid {
+ display: inline-grid;
+ grid-template-rows: 1px;
+ grid-auto-rows: 10px;
+ border: 1px solid;
+ place-content: start;
+ place-items: start;
+}
+
+x {
+ grid-row: 1;
+ height: 1px;
+ background: grey;
+}
+
+x:nth-child(1) { background: lime; width: 3px; grid-column: 1/span 2; }
+x:nth-child(2) { background: silver; width: 6px; grid-column: 2/span 3; }
+x:nth-child(3) { background: blue; width: 12px; grid-column: 1/span 2; }
+ </style>
+</head>
+<body>
+
+<script>
+let min1 = [
+ "0", "4px"
+];
+let min2 = [
+ "auto", "min-content", "max-content"
+];
+let max1 = [
+ "8px"
+];
+let max2 = [
+ "auto", "min-content", "max-content"
+];
+
+var track = [];
+min1.forEach((min) => { max2.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+min2.forEach((min) => { max2.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+min2.forEach((min) => { max1.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+min2.forEach((min) => { max2.forEach((max) => {
+ track.push("minmax(" + min + "," + max + ")");
+})});
+track.push("fit-content(8px)");
+
+var cols = [];
+track.forEach((c1) => {
+ cols.push(c1 + " " + c1 + " " + c1 + " " + c1 + " " + c1);
+ track.forEach((c2) => {
+ if (c1 != c2) {
+ cols.push(c1 + " " + c2 + " " + c2 + " " + c2 + " " + c2);
+ cols.push(c2 + " " + c1 + " " + c1 + " " + c2 + " " + c2);
+ }
+})});
+
+document.body.style.display = 'none';
+cols.forEach((col) => {
+ let grid = document.createElement('div');
+ grid.className = "grid";
+ grid.style.gridTemplateColumns = col;
+ grid.appendChild(document.createElement('x'))
+ grid.appendChild(document.createElement('x'))
+ grid.appendChild(document.createElement('x'))
+ document.body.appendChild(grid);
+});
+document.body.style.display = '';
+</script>
+
+<script>
+var actual = [];
+[...document.querySelectorAll('.grid')].forEach(function(e) {
+ let cs = window.getComputedStyle(e);
+ actual.push([cs.gridTemplateColumns, cs.width]);
+});
+
+function dumpResult() {
+ var s = "";
+ actual.forEach(v => {
+ s += '["' + v[0] + '","' + v[1] + '"],\n';
+ });
+ let pre = document.createElement("pre");
+ pre.innerHTML = s;
+ document.body.appendChild(pre);
+}
+
+let expected = [
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["4px 8px 4px 4px 4px","24px"],
+["8px 4px 0px 4px 4px","20px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 8px 8px 8px","36px"],
+["12px 0px 0px 8px 8px","28px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 0px 6px 0px","18px"],
+["6px 6px 4px 4px 4px","24px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["6px 6px 4px 4px 4px","24px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["6px 6px 4px 4px 4px","24px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["8px 4px 1px 1px 0px","14px"],
+["4px 8px 4px 0px 0px","16px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["6px 6px 4px 4px 4px","24px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 8px 8px 8px","36px"],
+["8px 4px 4px 8px 8px","32px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["4px 8px 0px 0px 0px","12px"],
+["8px 4px 4px 0px 0px","16px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["8px 8px 8px 8px 8px","40px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 8px 8px 8px 8px","40px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 8px 8px 8px 8px","40px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 8px 0px 0px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 8px 4px 4px","28px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 8px 8px 8px 8px","40px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["8px 6px 0px 0px 0px","14px"],
+["6px 8px 8px 0px 0px","22px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["12px 0px 3px 3px 0px","18px"],
+["0px 12px 0px 0px 0px","12px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["8px 4px 4px 4px 4px","24px"],
+["4px 8px 0px 4px 4px","20px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 8px 8px 8px 8px","38px"],
+["8px 6px 0px 8px 8px","30px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+["6px 6px 0px 0px 0px","12px"],
+];
+
+function test() {
+ ok(expected.length > 0, "sanity check");
+ for (i = 0; i < expected.length; ++i) {
+ var msg = "";
+ if (actual[i][0] != expected[i][0]) {
+ msg = "'grid-template-columns' for grid index " + i + ":\n" + [...document.querySelectorAll('.grid')][i].outerHTML;
+ is(actual[i][0], expected[i][0], msg);
+ }
+ if (actual[i][1] != expected[i][1]) {
+ msg = "'width' for grid index " + i + ":\n" + [...document.querySelectorAll('.grid')][i].outerHTML;
+ is(actual[i][1], expected[i][1], msg);
+ }
+ if (msg != "") {
+ i = expected.length; // stop after first failed grid by default
+ }
+ }
+ SimpleTest.finish();
+}
+
+</script>
+
+<script>
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</script>
+
+</body>
+</html>
diff --git a/layout/generic/test/test_image_selection.html b/layout/generic/test/test_image_selection.html
new file mode 100644
index 0000000000..5b1a6a2408
--- /dev/null
+++ b/layout/generic/test/test_image_selection.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=599368
+-->
+<head>
+ <title>Test for Bug 599368</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=599368">Mozilla Bug 599368</a>
+<iframe id="display" src="about:blank"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 599368 **/
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", step1);
+
+var gImage;
+var gIframe;
+var gBlueNotSelected;
+var gBlueSelected;
+var gFuchsiaSelected;
+
+function step1()
+{
+ gIframe = document.getElementById("display");
+ doc = gIframe.contentDocument;
+
+ gImage = doc.createElement('img');
+ var src = String(window.location).split("/");
+ src.pop();
+ src.push("blue-32x32.png");
+ src = src.join("/");
+ gImage.src = src;
+ gImage.addEventListener("load", step2);
+ doc.body.appendChild(gImage);
+
+ doc.designMode = "on";
+}
+
+function step2() {
+ gImage.removeEventListener("load", step2);
+
+ gBlueNotSelected = snapshotWindow(gIframe.contentWindow, false);
+
+ synthesizeMouse(gImage, 5, 5, {}, gIframe.contentWindow);
+ setTimeout(step3, 0);
+}
+
+function step3() {
+ gBlueSelected = snapshotWindow(gIframe.contentWindow, false);
+
+ var src = String(window.location).split("/");
+ src.pop();
+ src.push("fuchsia-32x32.png");
+ src = src.join("/");
+ gImage.addEventListener("load", step4);
+ gImage.src = src;
+}
+
+function step4() {
+ gImage.removeEventListener("load", step4);
+
+ gFuchsiaSelected = snapshotWindow(gIframe.contentWindow, false);
+
+ // FYI: test_image_selection_in_contenteditable.html tests the detail.
+ assertSnapshots(gBlueNotSelected, gBlueSelected, false, null,
+ "blue image without selection",
+ "blue image which is selected or added resizers");
+ assertSnapshots(gBlueSelected, gFuchsiaSelected, false, null,
+ "blue image which is selected",
+ "fuchsia image which is selected");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_image_selection_2.html b/layout/generic/test/test_image_selection_2.html
new file mode 100644
index 0000000000..cd4ba578c4
--- /dev/null
+++ b/layout/generic/test/test_image_selection_2.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=758179
+-->
+<head>
+ <title>Test for Bug 758179</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+#t1 { width:80px; height:60px; background:yellow; }
+#t1.chosen #i1 { visibility:hidden; }
+ </style>
+</head>
+<body>
+<div id="t1">
+ <img id="i1" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyAQMAAACQ%2B%2Bz9AAAAA1BMVEUA%2FwA0XsCoAAAAD0lEQVQoFWNgGAWjYGgCAAK8AAEb3eOQAAAAAElFTkSuQmCC">
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+var selection = window.getSelection();
+var t1 = document.getElementById("t1");
+
+function doTest() {
+ t1.addEventListener("mousedown", function() {
+ t1.className = "chosen";
+ });
+ t1.addEventListener("mouseup", function() {
+ ok(selection.isCollapsed, "checking that selection is collapsed");
+ });
+ synthesizeMouse(t1, 10, 10, {});
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(doTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_image_selection_3.html b/layout/generic/test/test_image_selection_3.html
new file mode 100644
index 0000000000..32a49b54d0
--- /dev/null
+++ b/layout/generic/test/test_image_selection_3.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<title>Test for bug 1754459</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style>
+ /* This avoids the draggable image check that our code does to avoid handling the mousedown. */
+ img { pointer-events: none }
+</style>
+<div id="block">
+ Some text <img width="100" height="100" id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyAQMAAACQ%2B%2Bz9AAAAA1BMVEUA%2FwA0XsCoAAAAD0lEQVQoFWNgGAWjYGgCAAK8AAEb3eOQAAAAAElFTkSuQmCC"> and more.
+</div>
+<script>
+const image = document.getElementById("image");
+const block = document.getElementById("block");
+const selection = window.getSelection();
+
+function clickOnImage(x, y) {
+ synthesizeMouse(image, x, y, {});
+}
+
+add_task(async function test_click_pointer_events_none() {
+ await SimpleTest.promiseFocus(window);
+ clickOnImage(10, 10);
+ ok(selection.isCollapsed, "Should be collapsed");
+ is(selection.focusNode, block, "Should be at block");
+ is(selection.focusOffset, 1, "Should be at start of image");
+
+ clickOnImage(60, 10);
+ ok(selection.isCollapsed, "Should be collapsed");
+ is(selection.focusNode, block, "Should be at block");
+ is(selection.focusOffset, 2, "Should be at end of image");
+});
+
+add_task(async function test_click_pointer_events_none_vertical() {
+ block.style.writingMode = "vertical-lr";
+ block.getBoundingClientRect();
+ clickOnImage(10, 10);
+ ok(selection.isCollapsed, "Should be collapsed");
+ is(selection.focusNode, block, "Should be at start of image");
+ is(selection.focusOffset, 1, "Should be at start of image");
+
+ clickOnImage(10, 60);
+ ok(selection.isCollapsed, "Should be collapsed");
+ is(selection.focusNode, block, "Should be at block");
+ is(selection.focusOffset, 2, "Should be at end of image");
+});
+</script>
diff --git a/layout/generic/test/test_image_selection_in_contenteditable.html b/layout/generic/test/test_image_selection_in_contenteditable.html
new file mode 100644
index 0000000000..8371b1b163
--- /dev/null
+++ b/layout/generic/test/test_image_selection_in_contenteditable.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1551185
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1551185</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #editor:focus {
+ outline: none;
+ }
+ </style>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1551185">Mozilla Bug 1551185</a>
+ <p id="display"></p>
+ <div id="editor"><img id="img1" src="blue-32x32.png"><img id="img2" src="blue-32x32.png"></div>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(() => {
+ let selection = window.getSelection();
+ let selectionController =
+ SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsISelectionDisplay)
+ .QueryInterface(SpecialPowers.Ci.nsISelectionController);
+ let editor = document.getElementById("editor");
+ let img1 = document.getElementById("img1");
+ let img2 = document.getElementById("img2");
+ // First, take snapshot without selection.
+ selection.removeAllRanges();
+ const kSnapshotWithoutSelection = snapshotWindow(window);
+ // Next, take snapshot of selected both images.
+ selection.setBaseAndExtent(editor, 0, editor, 2);
+ const kSnapshotBothImagesSelected = snapshotWindow(window);
+ // Next, take snapshot of selected the last image.
+ selection.setBaseAndExtent(editor, 1, editor, 2);
+ const kSnapshotLastImageSelected = snapshotWindow(window);
+
+ editor.contentEditable = true;
+ editor.focus();
+
+ document.execCommand("enableObjectResizing", false, false);
+ selection.collapse(editor, 2);
+ selectionController.characterMove(false, true);
+ // When object resizing is disabled, <img> elements should be rendered
+ // as selected if they are in selection ranges.
+ assertSnapshots(snapshotWindow(window), kSnapshotLastImageSelected, true, null,
+ "Selects only the last image when object resizing is disabled", "");
+ selectionController.characterMove(false, true);
+ assertSnapshots(snapshotWindow(window), kSnapshotBothImagesSelected, true, null,
+ "Selects both the images when object resizing is disabled", "");
+
+ document.execCommand("enableObjectResizing", false, true);
+ selection.collapse(editor, 2);
+ // When an <img> element is selected, it should become target of resizers.
+ // At this time, the image shouldn't be rendered as selected but there should
+ // be resizers. I.e., shouldn't match with kSnapshotWithoutSelection nor
+ // kSnapshotLastImageSelected.
+ selectionController.characterMove(false, true);
+ assertSnapshots(snapshotWindow(window), kSnapshotLastImageSelected, false, null,
+ "Selects only the last image when object resizing is enabled",
+ "(compared with the last image selected snapshot)");
+ assertSnapshots(snapshotWindow(window), kSnapshotWithoutSelection, false, null,
+ "Selects only the last image when object resizing is enabled",
+ "(compared without no selection)");
+ // If not only one <img> element is selected, selected <img> elements should
+ // be rendered as selected normally.
+ selectionController.characterMove(false, true);
+ assertSnapshots(snapshotWindow(window), kSnapshotBothImagesSelected, true, null,
+ "Selects both the images when object resizing is enabled");
+
+ SimpleTest.finish();
+ });
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/layout/generic/test/test_intrinsic_size_on_loading.html b/layout/generic/test/test_intrinsic_size_on_loading.html
new file mode 100644
index 0000000000..85032f064d
--- /dev/null
+++ b/layout/generic/test/test_intrinsic_size_on_loading.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1205027
+-->
+<head>
+ <title>Test for images intrinsic size while load is pending</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ body { margin: 0; }
+ img { display: block; }
+ </style>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ var initialOffsetTop;
+ var finalOffsetTop;
+
+ function report() {
+ finalOffsetTop = marker.offsetTop;
+ is(initialOffsetTop, 0, "initial offsetTop: " + initialOffsetTop);
+ is(finalOffsetTop, 8, "final offsetTop: " + finalOffsetTop);
+ ok(initialOffsetTop < finalOffsetTop, "offsetTop should get larger.");
+ SimpleTest.finish();
+ }
+
+ function fail() {
+ ok(false, "image loading should not fail.");
+ }
+ </script>
+</head>
+<body onload="report();">
+ <!-- Bunch of tiny images: -->
+ <img src="file_SlowImage.sjs" onerror="fail();">
+ <img src="file_SlowImage.sjs" onerror="fail();">
+ <img src="file_SlowImage.sjs" onerror="fail();">
+ <img src="file_SlowImage.sjs" onerror="fail();">
+ <img src="file_SlowImage.sjs" onerror="fail();">
+ <img src="file_SlowImage.sjs" onerror="fail();">
+ <img src="file_SlowImage.sjs" onerror="fail();">
+ <img src="file_SlowImage.sjs" onerror="fail();">
+
+ <div id="marker">Marker div</div>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1205027">Mozilla Bug 1205027</a>
+ <script>
+ initialOffsetTop = marker.offsetTop;
+ // prompt the sjs "server" to proceed to serve data to our <img> elements
+ var img = new Image();
+ img.onerror = fail;
+ img.src = "file_SlowImage.sjs?continue";
+ </script>
+</body>
+</html>
diff --git a/layout/generic/test/test_key_enter_open_second_summary.html b/layout/generic/test/test_key_enter_open_second_summary.html
new file mode 100644
index 0000000000..6c05ac41d4
--- /dev/null
+++ b/layout/generic/test/test_key_enter_open_second_summary.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function runTest() {
+ // Dispatch 'return' key on second summary should not collapse details.
+ const summary = document.getElementById("summary");
+ summary.focus();
+ synthesizeKey("KEY_Enter");
+
+ const details = document.querySelector("details");
+ ok(details.open, "Dispatch 'return' key on second summary should not collapse details.");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+ <details open>
+ <summary>Summary</summary>
+ <summary id="summary">Second Summary</summary>
+ <p>This is the details.</p>
+ </details>
+</body>
+</html>
diff --git a/layout/generic/test/test_key_enter_prevent_default.html b/layout/generic/test/test_key_enter_prevent_default.html
new file mode 100644
index 0000000000..2d5793fe8f
--- /dev/null
+++ b/layout/generic/test/test_key_enter_prevent_default.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function runTest() {
+ const summary = document.querySelector("summary");
+ summary.addEventListener('click', function(e) {
+ // Prevent the details from toggling by key events.
+ e.preventDefault();
+ });
+ // Dispatch 'return' key to the summary element.
+ summary.focus();
+ synthesizeKey("KEY_Enter");
+
+ const details = document.querySelector("details");
+ ok(!details.open, "Prevent default on summary should not open details.");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+ <details>
+ <summary>Summary</summary>
+ <p>This is the details.</p>
+ </details>
+</body>
+</html>
diff --git a/layout/generic/test/test_key_enter_single_summary.html b/layout/generic/test/test_key_enter_single_summary.html
new file mode 100644
index 0000000000..a9022596a8
--- /dev/null
+++ b/layout/generic/test/test_key_enter_single_summary.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function runTest() {
+ // Dispatch 'return' key to the summary element.
+ const summary = document.querySelector("summary");
+ summary.focus();
+ synthesizeKey("KEY_Enter");
+
+ const details = document.querySelector("details");
+ ok(details.open, "Dispatch return key on summary should open details.");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+ <details>
+ <summary>Summary</summary>
+ <p>This is the details.</p>
+ </details>
+</body>
+</html>
diff --git a/layout/generic/test/test_key_space_single_summary.html b/layout/generic/test/test_key_space_single_summary.html
new file mode 100644
index 0000000000..b393888ab6
--- /dev/null
+++ b/layout/generic/test/test_key_space_single_summary.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function runTest() {
+ const summary = document.querySelector("summary");
+ summary.focus();
+ synthesizeKey(" ");
+
+ const details = document.querySelector("details");
+ ok(details.open, "Dispatch space key on summary should open details.");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+ <details>
+ <summary>Summary</summary>
+ <p>This is the details.</p>
+ </details>
+</body>
+</html>
diff --git a/layout/generic/test/test_movement_by_characters.html b/layout/generic/test/test_movement_by_characters.html
new file mode 100644
index 0000000000..061dd6c57f
--- /dev/null
+++ b/layout/generic/test/test_movement_by_characters.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Character Movement (including nsTextFrame::PeekOffsetCharacter)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: block">
+<div contentEditable id="editor"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+if (navigator.userAgent.includes("Windows NT 6.2")) {
+ todo(false, "Too many intermittent failures on Windows 8 (bug 886781)");
+ SimpleTest.finish();
+} else {
+ setTimeout(focusing, 0);
+}
+
+function focusing() {
+ document.getElementById("editor").focus();
+ // This seems to be necessary because the selection is not set up properly otherwise
+ setTimeout(test, 0);
+}
+
+function test() {
+ var sel = window.getSelection();
+ var editor = document.getElementById("editor");
+
+ function testRight(node, offset) {
+ synthesizeKey("KEY_ArrowRight");
+ is(sel.anchorNode, node, "Right movement broken in " + editor.innerHTML);
+ is(sel.anchorOffset, offset, "Right movement broken in " + editor.innerHTML);
+ }
+
+ function testLeft(node, offset) {
+ synthesizeKey("KEY_ArrowLeft");
+ is(sel.anchorNode, node, "Left movement broken in " + editor.innerHTML);
+ is(sel.anchorOffset, offset, "Left movement broken in " + editor.innerHTML);
+ }
+
+ editor.innerHTML = "H K";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 1);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 3);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 1);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "<b>H</b> K";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 1);
+ testRight(editor.firstChild.nextSibling, 1);
+ testRight(editor.firstChild.nextSibling, 2);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.nextSibling, 0);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "H <br>K";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 1);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild.nextSibling.nextSibling, 0);
+ testRight(editor.firstChild.nextSibling.nextSibling, 1);
+ testLeft(editor.firstChild.nextSibling.nextSibling, 0);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 1);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "<pre>aa\nbb</pre>";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 1);
+ // at the end of the first line, before the \n
+ testRight(editor.firstChild.firstChild, 2);
+ testRight(editor.firstChild.firstChild, 3);
+ testRight(editor.firstChild.firstChild, 4);
+ testLeft(editor.firstChild.firstChild, 3);
+ // at the end of the first line, before the \n
+ testLeft(editor.firstChild.firstChild, 2);
+ testLeft(editor.firstChild.firstChild, 1);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_movement_by_words.html b/layout/generic/test/test_movement_by_words.html
new file mode 100644
index 0000000000..a6639f39ca
--- /dev/null
+++ b/layout/generic/test/test_movement_by_words.html
@@ -0,0 +1,781 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Word Movement (including nsTextFrame::PeekOffsetWord)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: block">
+<div contentEditable id="editor"></div>
+</div>
+<p id="catch">Catch-all
+<pre id="test"><script class="testbody" type="text/javascript">
+
+/** Tests for bugs 384147, 981281, 1066756, 1820290 **/
+
+const kIsMac = navigator.platform.includes("Mac");
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(async () => {
+ const editor = document.getElementById("editor");
+ const waitForFocusEditor = new Promise(resolve =>
+ editor.addEventListener("focus", resolve, {once: true})
+ );
+ editor.focus();
+ // This seems to be necessary because the selection is not set up properly otherwise
+ await waitForFocusEditor;
+
+ await test1();
+ await test2();
+ await test3();
+ await test4();
+
+ SimpleTest.finish();
+});
+
+var eatSpace;
+var stopAtPunctuation;
+var sel = window.getSelection();
+var editor = document.getElementById("editor");
+
+function setPrefs(eat_space, stop_at_punctuation) {
+ eatSpace = eat_space;
+ stopAtPunctuation = stop_at_punctuation;
+ return SpecialPowers.pushPrefEnv({
+ "set": [["layout.word_select.eat_space_to_next_word", eat_space],
+ ["layout.word_select.stop_at_punctuation", stop_at_punctuation],
+ ["intl.icu4x.segmenter.enabled", true]]
+ });
+}
+
+function currentStateDescription() {
+ return `eatSpace=${eatSpace}, stopAtPunctuation=${stopAtPunctuation}, testing="${editor.outerHTML}"`;
+}
+
+function getNodeDescription(aNode) {
+ if (aNode === undefined) {
+ return "undefined";
+ }
+ if (aNode === null) {
+ return "null";
+ }
+ switch (aNode.nodeType) {
+ case Node.TEXT_NODE:
+ return `"${
+ aNode.data.replace(/\n/g, "\\n")
+ }" in ${getNodeDescription(aNode.parentNode)}`;
+ case Node.ELEMENT_NODE:
+ return `<${aNode.tagName.toLowerCase()}${
+ aNode.getAttribute("id") !== null
+ ? ` id="${aNode.getAttribute("id")}"`
+ : ""
+ }${
+ aNode.getAttribute("dir") !== null
+ ? ` dir="${aNode.getAttribute("dir")}"`
+ : ""
+ }>`;
+ }
+ return aNode.nodeName;
+}
+
+function testRight(node, offset, knownFailure) {
+ if (!kIsMac) {
+ synthesizeKey("KEY_ArrowRight", { ctrlKey: true });
+ } else {
+ // macOS does not have default shortcut key to move caret per word.
+ SpecialPowers.doCommand(window, "cmd_wordNext");
+ }
+ (knownFailure ? todo_is : is)(
+ `{${getNodeDescription(sel.anchorNode)} - ${sel.anchorOffset}}`,
+ `{${getNodeDescription(node)} - ${offset}}`,
+ `testRight(${currentStateDescription()})`
+ );
+ if (knownFailure) {
+ getSelection().collapse(node, offset);
+ }
+}
+
+function testLeft(node, offset, knownFailure) {
+ if (!kIsMac) {
+ synthesizeKey("KEY_ArrowLeft", { ctrlKey: true });
+ } else {
+ // macOS does not have default shortcut key to move caret per word.
+ SpecialPowers.doCommand(window, "cmd_wordPrevious");
+ }
+ (knownFailure ? todo_is : is)(
+ `{${getNodeDescription(sel.anchorNode)} - ${sel.anchorOffset}}`,
+ `{${getNodeDescription(node)} - ${offset}}`,
+ `testLeft(${currentStateDescription()})`
+ );
+ if (knownFailure) {
+ getSelection().collapse(node, offset);
+ }
+}
+
+const afterEditorNode = document.getElementById("catch").firstChild;
+
+const ChineseChars = "&#x6F22;&#x5B57;";
+const HiraganaChars = "&#x3072;&#x3089;&#x304C;&#x306A;";
+const KatakanaChars = "&#x30AB;&#x30BF;&#x30AB;&#x30CA;";
+const JapaneseFullStop = "&#x3002;";
+const JapaneseComma = "&#x3001;";
+const ModifierColon = "&#xA789;";
+const Symbol = "&#x26C5;"; // "sun behind cloud" (weather symbol)
+const Emoji = "&#x1f400;"; // rat emoji
+const DeseretChars = "&#x10440;&#x10441;";
+const ChineseCharsPlane2 = "&#x2000B;&#x2003F;";
+const ArabicOne = "\u0648\u0627\u062D\u062F";
+const ArabicTwo = "\u0627\u062B\u0646\u064A\u0646";
+const ArabicThree = "\u062B\u0644\u0627\u062B\u0629";
+
+async function test1() {
+ await setPrefs(false, true);
+
+ editor.innerHTML = "Hello Kitty";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 11);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello</b> Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.nextSibling, 0);
+ testRight(editor.firstChild.nextSibling, 6);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello </b>Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 5);
+ testRight(editor.firstChild.nextSibling, 5);
+ testLeft(editor.firstChild.firstChild, 6);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Log out</b> roc";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 3);
+ testRight(editor.firstChild.nextSibling, 0);
+ testRight(editor.firstChild.nextSibling, 5);
+ // In the next test, we expect to be at the end of the
+ // space that is not collapsed away
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 4);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "http://www.mozilla.org";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 11);
+ testRight(editor.firstChild, 19);
+ testLeft(editor.firstChild, 11);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "Set .rc to <b>'</b>quiz'";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 10);
+ testRight(editor.firstChild.nextSibling.nextSibling, 5);
+ testLeft(editor.firstChild.nextSibling.firstChild, 1);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "layout.word_select.stop_at_punctuation";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 19);
+ testRight(editor.firstChild, 38);
+ testLeft(editor.firstChild, 19);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + HiraganaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + KatakanaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + HiraganaChars + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 12);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = HiraganaChars + JapaneseComma + HiraganaChars + JapaneseFullStop + HiraganaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 10);
+ testRight(editor.firstChild, 13);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 13);
+ testLeft(editor.firstChild, 10);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 3);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + JapaneseComma + KatakanaChars + JapaneseFullStop + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 10);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 10);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + JapaneseComma + ChineseChars + JapaneseFullStop + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 3);
+ testLeft(editor.firstChild, 0);
+
+ // test for bug 1066756
+ editor.innerHTML = "hello" + ModifierColon + " wo" + ModifierColon + "rld";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 13);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+
+ // test word selection in Deseret (alphabet in Plane 1)
+ editor.innerHTML = DeseretChars + " " + DeseretChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 9);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ // Latin words separated by a pictographic symbol
+ editor.innerHTML = "hello" + Symbol + "world";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 11);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 0);
+
+ // Latin words separated by an emoji symbol
+ editor.innerHTML = "hello" + Emoji + "world";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 12);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ // Emoji and Chinese
+ editor.innerHTML = Emoji + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 4);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ // test that word selection stops at Latin/Chinese boundary
+ editor.innerHTML = "hello" + ChineseChars + "world";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 12);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ // similar, but with Deseret (alphabet in Plane 1) in place of Latin
+ editor.innerHTML = DeseretChars + ChineseChars + DeseretChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 10);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ // with Plane 2 Chinese characters
+ editor.innerHTML = "hello" + ChineseCharsPlane2 + "world";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 9);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 9);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ // Plane 1 Deseret and Plane 2 Chinese characters
+ editor.innerHTML = DeseretChars + ChineseCharsPlane2 + DeseretChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 12);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ // Out-of-flow frames around line edges should be treated as independent lines.
+ for (const floatValue of ["left", "right"]) {
+ editor.innerHTML = `<b>One</b> <i>Two</i><span style=float:${floatValue}>Three</span>`;
+ sel.collapse(editor.querySelector("i").firstChild, 0);
+ testRight(editor.querySelector("i").firstChild, "Two".length);
+ testRight(editor.querySelector("span").firstChild, "Three".length);
+ testLeft(editor.querySelector("span").firstChild, 0);
+ testLeft(editor.querySelector("i").previousSibling, " ".length); // FIXME: Should stop at start of "Two"
+ editor.innerHTML = `<span style=float:${floatValue}>One</span><b>Two</b> <i>Three</i>`;
+ sel.collapse(editor.querySelector("b").firstChild, "Two".length);
+ testLeft(editor.querySelector("b").firstChild, 0);
+ testLeft(editor.querySelector("span").firstChild, 0);
+ testRight(editor.querySelector("span").firstChild, "One".length);
+ testRight(editor.querySelector("b").nextSibling, 0); // FIXME: Should stop at end of "Two"
+ }
+
+ // And also visually connected words which are split by an out-of-flow content
+ // shouldn't be treated as a word.
+ editor.innerHTML = 'One<span style=float:right>Two</span>Three';
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, "One".length);
+ testRight(editor.querySelector("span").firstChild, "Two".length);
+ testRight(editor.lastChild, "Three".length);
+ testLeft(editor.lastChild, 0);
+ testLeft(editor.querySelector("span").firstChild, 0);
+ testLeft(editor.firstChild, 0);
+
+ // Simple RTL cases of above.
+ editor.setAttribute("dir", "rtl");
+ for (const floatValue of ["left", "right"]) {
+ editor.innerHTML = `<b>${ArabicOne}</b> <i>${ArabicTwo}</i><span style=float:${floatValue}>${ArabicThree}</span>`;
+ sel.collapse(editor.querySelector("i").firstChild, 0);
+ testLeft(editor.querySelector("i").firstChild, ArabicTwo.length);
+ testLeft(editor.querySelector("span").firstChild, ArabicThree.length);
+ testRight(editor.querySelector("span").firstChild, 0);
+ testRight(editor.querySelector("i").previousSibling, " ".length); // FIXME: Should stop at start of ArabicTwo.
+ editor.innerHTML = `<span style=float:${floatValue}>${ArabicOne}</span><b>${ArabicTwo}</b> <i>${ArabicThree}</i>`;
+ sel.collapse(editor.querySelector("b").firstChild, ArabicTwo.length);
+ testRight(editor.querySelector("b").firstChild, 0);
+ testRight(editor.querySelector("span").firstChild, 0);
+ testLeft(editor.querySelector("span").firstChild, ArabicOne.length);
+ testLeft(editor.querySelector("b").nextSibling, 0); // FIXME: Should stop at end of ArabicTwo.
+ }
+
+ // And also the bidi cases which LRT frames are at line edge if ignoring the
+ // out-of-flow things.
+ // XXX Although these tests are behave as expected if I test them manually.
+ // I don't know the reason why the behavior changes in this test file.
+ for (const floatValue of ["left", "right"]) {
+ editor.innerHTML = `<div><b>${ArabicOne}</b> <i dir="ltr">Two</i><span style=float:${floatValue}>${ArabicThree}</span></div>`;
+ sel.collapse(editor.querySelector("i").firstChild, "Two".length);
+ testLeft(editor.querySelector("i").firstChild, 0, true);
+ testLeft(editor.querySelector("span").firstChild, 0, true);
+ testLeft(editor.querySelector("span").firstChild, ArabicThree.length);
+ testRight(editor.querySelector("span").firstChild, 0);
+ testRight(editor.querySelector("i").previousSibling, " ".length); // FIXME: Should stop at end of "Two"
+ editor.innerHTML = `<div><span style=float:${floatValue}>${ArabicOne}</span><b dir="ltr">Two</b> <i>${ArabicThree}</i></div>`;
+ sel.collapse(editor.querySelector("b").firstChild, 0);
+ testRight(editor.querySelector("b").firstChild, "Two".length, true);
+ testRight(editor.querySelector("span").firstChild, ArabicOne.length, true);
+ testRight(editor.querySelector("span").firstChild, 0);
+ testLeft(editor.querySelector("span").firstChild, ArabicOne.length);
+ testLeft(editor.querySelector("b").nextSibling, 0); // FIXME: Should stop at start of "Two".
+ }
+
+ editor.removeAttribute("dir");
+}
+
+async function test2() {
+ // test basic word movement with eat_space_next_to_word true.
+ await setPrefs(true, true);
+
+ editor.innerHTML = "Hello Kitty";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 11);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello</b> Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.nextSibling, 1);
+ testRight(editor.firstChild.nextSibling, 6);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello </b>Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.nextSibling, 0);
+ testRight(editor.firstChild.nextSibling, 5);
+ testLeft(editor.firstChild.firstChild, 6);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Log out</b> roc";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 4);
+ testRight(editor.firstChild.nextSibling, 2);
+ testRight(editor.firstChild.nextSibling, 5);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 4);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "http://www.mozilla.org";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 11);
+ testRight(editor.firstChild, 19);
+ testLeft(editor.firstChild, 11);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "Set .rc to <b>'</b>quiz'";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild.nextSibling.firstChild, 0);
+ testRight(editor.firstChild.nextSibling.nextSibling, 5);
+ testLeft(editor.firstChild.nextSibling.firstChild, 1);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "layout.word_select.stop_at_punctuation";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 19);
+ testRight(editor.firstChild, 38);
+ testLeft(editor.firstChild, 19);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + HiraganaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + KatakanaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + HiraganaChars + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 12);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = HiraganaChars + JapaneseComma + HiraganaChars + JapaneseFullStop + HiraganaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 10);
+ testRight(editor.firstChild, 13);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 13);
+ testLeft(editor.firstChild, 10);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 3);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + JapaneseComma + KatakanaChars + JapaneseFullStop + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 10);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 10);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + JapaneseComma + ChineseChars + JapaneseFullStop + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 3);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "hello" + ModifierColon + " wo" + ModifierColon + "rld";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 13);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+}
+
+async function test3() {
+ // Test basic word movement with stop_at_punctuation false (bug 981281).
+ await setPrefs(false, false);
+
+ editor.innerHTML = "Hello Kitty";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 11);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello</b> Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.nextSibling, 0);
+ testRight(editor.firstChild.nextSibling, 6);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello </b>Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 5);
+ testRight(editor.firstChild.nextSibling, 5);
+ testLeft(editor.firstChild.firstChild, 6);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Log out</b> roc";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 3);
+ testRight(editor.firstChild.nextSibling, 0);
+ testRight(editor.firstChild.nextSibling, 5);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 4);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "http://www.mozilla.org";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 22);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "Set .rc to <b>'</b>quiz'";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 10);
+ testRight(editor.firstChild.nextSibling.nextSibling, 5);
+ testLeft(editor.firstChild, 11);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "layout.word_select.stop_at_punctuation";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 38);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + HiraganaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + KatakanaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + HiraganaChars + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 12);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = HiraganaChars + JapaneseComma + HiraganaChars + JapaneseFullStop + HiraganaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 13);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 13);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 3);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + JapaneseComma + KatakanaChars + JapaneseFullStop + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + JapaneseComma + ChineseChars + JapaneseFullStop + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "hello" + ModifierColon + " wo" + ModifierColon + "rld";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 13);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+}
+
+async function test4() {
+ // And again with eat_space_next_to_word true.
+ await setPrefs(true, false);
+
+ editor.innerHTML = "Hello Kitty";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 11);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello</b> Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.nextSibling, 1);
+ testRight(editor.firstChild.nextSibling, 6);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Hello </b>Kitty";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.nextSibling, 0);
+ testRight(editor.firstChild.nextSibling, 5);
+ testLeft(editor.firstChild.firstChild, 6);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "<b>Log out</b> roc";
+ sel.collapse(editor.firstChild.firstChild, 0);
+ testRight(editor.firstChild.firstChild, 4);
+ testRight(editor.firstChild.nextSibling, 2);
+ testRight(editor.firstChild.nextSibling, 5);
+ testLeft(editor.firstChild.nextSibling, 1);
+ testLeft(editor.firstChild.firstChild, 4);
+ testLeft(editor.firstChild.firstChild, 0);
+
+ editor.innerHTML = "http://www.mozilla.org";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 22);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "Set .rc to <b>'</b>quiz'";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild.nextSibling.firstChild, 0);
+ testRight(editor.firstChild.nextSibling.nextSibling, 5);
+ testLeft(editor.firstChild, 11);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "layout.word_select.stop_at_punctuation";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 38);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + HiraganaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 5);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 5);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + KatakanaChars + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 2);
+ testRight(editor.firstChild, 6);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 6);
+ testLeft(editor.firstChild, 2);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + HiraganaChars + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 4);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 12);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 4);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = HiraganaChars + JapaneseComma + HiraganaChars + JapaneseFullStop + HiraganaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 3);
+ testRight(editor.firstChild, 8);
+ testRight(editor.firstChild, 13);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 13);
+ testLeft(editor.firstChild, 8);
+ testLeft(editor.firstChild, 3);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = KatakanaChars + JapaneseComma + KatakanaChars + JapaneseFullStop + KatakanaChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 14);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = ChineseChars + JapaneseComma + ChineseChars + JapaneseFullStop + ChineseChars;
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 8);
+ testLeft(editor.firstChild, 0);
+
+ editor.innerHTML = "hello" + ModifierColon + " wo" + ModifierColon + "rld";
+ sel.collapse(editor.firstChild, 0);
+ testRight(editor.firstChild, 7);
+ testRight(editor.firstChild, 13);
+ testLeft(editor.firstChild, 7);
+ testLeft(editor.firstChild, 0);
+}
+
+
+</script></pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_overflow_event.html b/layout/generic/test/test_overflow_event.html
new file mode 100644
index 0000000000..152b625f30
--- /dev/null
+++ b/layout/generic/test/test_overflow_event.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for overflow events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ window.addEventListener("load", function(event) {
+ if (event.target == document) {
+ test_bug981637();
+ }
+ });
+
+ /** Test for Bug 981637: correct overflow event firing in updateOverflow **/
+
+ var overflow_fired = false;
+ function overflow_listener(event) {
+ overflow_fired = true;
+ }
+
+ function test_bug981637() {
+ var outerDiv = document.getElementById("bug981637");
+ var innerDiv = outerDiv.firstChild;
+ innerDiv.offsetWidth; // flush layout
+ is(overflow_fired, false, "correct setup");
+ outerDiv.addEventListener("overflow", overflow_listener);
+ innerDiv.style.transform = "scale(1.2)";
+ innerDiv.offsetWidth; // flush layout
+ // run finish step after the overflow event fires (off the event loop)
+ setTimeout(test_bug981637_step2, 0);
+ }
+
+ function test_bug981637_step2() {
+ is(overflow_fired, true, "overflow event should have fired after updating transform");
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=981637">Mozilla Bug 981637</a>
+<div id="bug981637" style="overflow: hidden; width: 100px; height: 100px;"><div style="transform: scale(0.8); width: 100px; height: 100px"></div></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_overlay_scrollbar_position.html b/layout/generic/test/test_overlay_scrollbar_position.html
new file mode 100644
index 0000000000..b70a0e7aa4
--- /dev/null
+++ b/layout/generic/test/test_overlay_scrollbar_position.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for overlay scrollbar positions</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ div {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<div style="overflow-x: scroll" data-expected-bottom></div>
+<div style="overflow-y: scroll" data-expected-right></div>
+<div style="overflow-y: scroll" dir="rtl" data-expected-left></div>
+<script>
+add_task(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.useOverlayScrollbars", 1]],
+ });
+ for (let div of document.querySelectorAll("div")) {
+ let rect = div.getBoundingClientRect();
+ let kids = SpecialPowers.InspectorUtils.getChildrenForNode(
+ div,
+ /* anonymous = */ true,
+ false
+ );
+ is(kids.length, 3, "Should create both horizontal and vertical scrollbars, and a scrollcorner");
+ for (let attr of ["top", "left", "bottom", "right"]) {
+ if (!div.hasAttribute(`data-expected-${attr}`)) {
+ continue;
+ }
+ let vertical = attr == "left" || attr == "right";
+ let scrollbar = kids[vertical ? 1 : 0];
+ is(scrollbar.tagName, "scrollbar", "Should find scrollbar");
+ is(scrollbar.getAttribute("orient"), vertical ? "vertical" : "horizontal", "Should be the right scrollbar");
+ let scrollbarRect = scrollbar.getBoundingClientRect();
+ let diff = scrollbarRect[attr] - rect[attr];
+ is(diff, 0, `Scrollbar should be at ${attr}: ${scrollbarRect[attr]} vs. ${rect[attr]}`);
+ }
+ }
+});
+</script>
diff --git a/layout/generic/test/test_page_scroll_with_fixed_pos.html b/layout/generic/test/test_page_scroll_with_fixed_pos.html
new file mode 100644
index 0000000000..32ae984a10
--- /dev/null
+++ b/layout/generic/test/test_page_scroll_with_fixed_pos.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, user-scalable=no" />
+ <title>Scrolling by pages with fixed-pos headers and footers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<script class="testbody">
+SimpleTest.waitForExplicitFinish();
+window.open("./page_scroll_with_fixed_pos_window.html", "", "width=600,height=600");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_reframe_for_lazy_load_image.html b/layout/generic/test/test_reframe_for_lazy_load_image.html
new file mode 100644
index 0000000000..0ac3c63d33
--- /dev/null
+++ b/layout/generic/test/test_reframe_for_lazy_load_image.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<div id='log'></div>
+<script>
+'use strict';
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ // Open a new document with enabling lazy load since we need to create an
+ // image element with loading="lazy" and alt="something" in the first place.
+ // That's because if the element has `alt` but doesn't have `lazy` we will
+ // generate inline frames due to the `alt`. Also if we do add the `lazy` and
+ // the `alt` dynamically, it ends up generating reconstruct change hint for
+ // the `alt` change.
+ {
+ set: [
+ ["dom.image-lazy-loading.root-margin.top", 0],
+ ["dom.image-lazy-loading.root-margin.bottom", 0],
+ ["dom.image-lazy-loading.root-margin.left", 0],
+ ["dom.image-lazy-loading.root-margin.right", 0],
+ ]
+ },
+ () => { window.open('file_reframe_for_lazy_load_image.html'); }
+);
+</script>
+</html>
diff --git a/layout/generic/test/test_scroll_animation_restore.html b/layout/generic/test/test_scroll_animation_restore.html
new file mode 100644
index 0000000000..cf2ffbac65
--- /dev/null
+++ b/layout/generic/test/test_scroll_animation_restore.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1247074
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1247074</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .outer {
+ direction: ltr;
+ height: 400px;
+ width: 415px;
+ overflow: hidden;
+ position: relative;
+ }
+ .inner {
+ height: 100%;
+ outline: none;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ position: relative;
+ scroll-behavior: smooth;
+ }
+ .outer.contentBefore::before {
+ top: 0;
+ content: '';
+ display: block;
+ height: 2px;
+ position: absolute;
+ width: 100%;
+ z-index: 99;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247074">Mozilla Bug 1247074</a>
+<p id="display"></p>
+<div class="outer">
+ <div class="inner">
+ <ol>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ </ol>
+ </div>
+</div>
+<script>
+SimpleTest.waitForExplicitFinish();
+window.onload = function() {
+ var elm = document.getElementsByClassName('inner')[0];
+
+ // Take control of the refresh driver
+ var utils = SpecialPowers.DOMWindowUtils;
+ utils.advanceTimeAndRefresh(0);
+
+ // Start a smooth scroll and advance a couple of frames so we're in the
+ // middle of the scroll animation
+ elm.scrollTop = 500;
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+
+ // Trigger a frame reconstruction
+ elm.parentNode.classList.add('contentBefore');
+
+ // Reach a stable state and verify the scroll position is 500
+ utils.restoreNormalRefresh();
+ waitForAllPaintsFlushed(function() {
+ SimpleTest.is(elm.scrollTop, 500, "Scroll position ended up at 500");
+ SimpleTest.finish();
+ });
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_scroll_behavior.html b/layout/generic/test/test_scroll_behavior.html
new file mode 100644
index 0000000000..0a9fefc559
--- /dev/null
+++ b/layout/generic/test/test_scroll_behavior.html
@@ -0,0 +1,262 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for CSSOM-View Smooth-Scroll DOM API Methods and MSD Animation</title>
+ <style>
+ #scroll_behavior_test_body {
+ width: 100000px;
+ height: 100000px;
+ }
+ .scroll_to_target {
+ position: absolute;
+ left: 20000px;
+ top: 10000px;
+ width: 200px;
+ height: 200px;
+ background-color: rgb(0, 0, 255);
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function clamp(val, minVal, maxVal) {
+ return Math.max(minVal, Math.min(maxVal, val));
+ }
+
+ window.addEventListener("load", async function(event) {
+ if (event.target != document) {
+ return;
+ }
+ await testScrollBehaviorInterruption();
+ await testScrollBehaviorFramerate();
+ window.scrollTo(0,0);
+ SimpleTest.finish();
+ });
+
+ async function testScrollBehaviorInterruption() {
+ // Take control of refresh driver
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+ await promiseApzFlushedRepaints();
+
+ window.scrollTo(10, 9);
+ ok(window.scrollX == 10 && window.scrollY == 9,
+ "instant scroll-behavior must be synchronous when setting initial position");
+
+ window.scrollTo(15, 16);
+ ok(window.scrollX == 15 && window.scrollY == 16,
+ "instant scroll-behavior must be synchronous when setting new position");
+
+ window.scrollTo({left: 100, top: 200, behavior: 'smooth'});
+ ok(window.scrollX == 15 && window.scrollY == 16,
+ "smooth scroll-behavior must be asynchronous");
+
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100);
+ await promiseApzFlushedRepaints();
+
+ ok(window.scrollX != 15 && window.scrollY != 16
+ && window.scrollX != 100 && window.scrollY != 200,
+ "smooth scroll-behavior must be triggered by window.scrollTo");
+
+ window.scrollTo(50, 52);
+ ok(window.scrollX == 50 && window.scrollY == 52,
+ "instant scroll-behavior must interrupt smooth scroll-behavior animation");
+
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100);
+ await promiseApzFlushedRepaints();
+
+ ok(window.scrollX == 50 && window.scrollY == 52,
+ "smooth scroll-behavior animation must stop after being interrupted");
+
+ // Release control of refresh driver
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ await promiseApzFlushedRepaints();
+ }
+
+ async function testScrollBehaviorFramerate() {
+ /**
+ * CSSOM-View scroll-behavior smooth scroll animations must produce the
+ * same results indendently of frame-rate:
+ *
+ * - Reference samples of scroll position for each frame are captured from
+ * a smooth scroll at 120fps for variations in X-Distance, Y-Distance.
+ * - Test samples are captured from an animation with the same parameters
+ * at varying framerates.
+ * - Variance in position at each sampled interval is compared to the
+ * 120fps reference. To pass the test, the position of each test
+ * sample must match the reference position with a tolerance of one test
+ * sample frame's range of motion. This range of motion is calculated
+ * by the position delta of the reference samples one test frame duration
+ * before and after.
+ * - The duration of the reference sample animation and the test sample
+ * animation must match within 1 frame to pass the test.
+ * - The simulation driving the animation must converge and stop on the
+ * destination position for the test to pass.
+ */
+
+ // Use 120hz for reference samples
+ var referenceFrameRate = 120;
+
+ var frameRates = [ 13, 60 ];
+ var deltas = [ {x: 0, y: 0},
+ {x: 1, y: 100},
+ {x: -100, y: 50000} ];
+
+ for (var deltaIndex = 0; deltaIndex < deltas.length; deltaIndex++) {
+ var deltaX = deltas[deltaIndex].x;
+ var deltaY = deltas[deltaIndex].y;
+
+ // startX and startY must be at least as big as the greatest negative
+ // number in the deltas array in order to prevent the animation from
+ // being interrupted by scroll range boundaries.
+ var startX = 1000;
+ var startY = 1000;
+ var endX = startX + deltaX;
+ var endY = startY + deltaY;
+ var referenceTimeStep = Math.floor(1000 / referenceFrameRate);
+
+ let refSamples = await sampleAnimation(startX, startY, endX, endY,
+ referenceTimeStep);
+
+ var referenceDuration = refSamples.length * referenceTimeStep; // ms
+
+ for (var frameRateIndex = 0; frameRateIndex < frameRates.length; frameRateIndex++) {
+ var frameRate = frameRates[frameRateIndex];
+
+ var testTimeStep = Math.floor(1000 / frameRate);
+
+ let testSamples = await sampleAnimation(startX, startY, endX, endY,
+ testTimeStep);
+ var testDuration = testSamples.length * testTimeStep; // ms
+
+ // Variance in duration of animation must be accurate to within one
+ // frame interval
+ var durationVariance = Math.max(0, Math.abs(testDuration - referenceDuration) - testTimeStep);
+ is(durationVariance, 0, 'Smooth scroll animation duration must not '
+ + 'be framerate dependent at deltaX: ' + deltaX + ', deltaY: '
+ + deltaY + ', frameRate: ' + frameRate + 'fps');
+
+ var maxVariance = 0;
+ testSamples.forEach(function(sample, sampleIndex) {
+
+ var testToRef = refSamples.length / testSamples.length;
+ var refIndexThisFrame = clamp(Math.floor(sampleIndex * testToRef),
+ 0, refSamples.length - 1);
+ var refIndexPrevFrame = clamp(Math.floor((sampleIndex - 1) * testToRef),
+ 0, refSamples.length - 1);
+ var refIndexNextFrame = clamp(Math.floor((sampleIndex + 1) * testToRef),
+ 0, refSamples.length - 1);
+
+ var refSampleThisFrame = refSamples[refIndexThisFrame];
+ var refSamplePrevFrame = refSamples[refIndexPrevFrame];
+ var refSampleNextFrame = refSamples[refIndexNextFrame];
+
+ var refXMin = Math.min(refSamplePrevFrame[0],
+ refSampleThisFrame[0],
+ refSampleNextFrame[0]);
+
+ var refYMin = Math.min(refSamplePrevFrame[1],
+ refSampleThisFrame[1],
+ refSampleNextFrame[1]);
+
+ var refXMax = Math.max(refSamplePrevFrame[0],
+ refSampleThisFrame[0],
+ refSampleNextFrame[0]);
+
+ var refYMax = Math.max(refSamplePrevFrame[1],
+ refSampleThisFrame[1],
+ refSampleNextFrame[1]);
+
+ // Varience is expected to be at most 1 pixel beyond the range,
+ // due to integer rounding of pixel position.
+ var positionTolerance = 1; // 1 pixel
+
+ maxVariance = Math.max(maxVariance,
+ refXMin - sample[0] - positionTolerance,
+ sample[0] - refXMax - positionTolerance,
+ refYMin - sample[1] - positionTolerance,
+ sample[1] - refYMax - positionTolerance);
+ });
+
+ is(maxVariance, 0, 'Smooth scroll animated position must not be '
+ + 'framerate dependent at deltaX: ' + deltaX + ', deltaY: '
+ + deltaY + ', frameRate: ' + frameRate + 'fps');
+ }
+
+ await promiseApzFlushedRepaints();
+ }
+ }
+
+ async function sampleAnimation(startX, startY, endX, endY, timeStep) {
+ // The animation must be stopped at the destination position for
+ // minStoppedFrames consecutive frames to detect that the animation has
+ // completed.
+ var minStoppedFrames = 15; // 15 frames
+
+ // In case the simulation fails to converge, the test will time out after
+ // processing maxTime milliseconds of animation.
+ var maxTime = 10000; // 10 seconds
+
+ var positionSamples = [];
+
+ var frameCountAtDestination = 0;
+
+ // Take control of refresh driver so we can synthesize
+ // various frame rates
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+ await promiseApzFlushedRepaints();
+
+ window.scrollTo(startX, startY);
+ window.scrollTo({left: endX, top: endY, behavior: 'smooth'});
+
+ var currentTime = 0; // ms
+
+ while (currentTime < maxTime && frameCountAtDestination < 15) {
+ positionSamples.push([window.scrollX, window.scrollY]);
+
+ currentTime += timeStep;
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(timeStep);
+ await promiseApzFlushedRepaints();
+ if (window.scrollX == endX && window.scrollY == endY) {
+ frameCountAtDestination++;
+ } else {
+ frameCountAtDestination = 0;
+ }
+ }
+
+ isnot(frameCountAtDestination, 0,
+ 'Smooth scrolls must always end at their destination '
+ + 'unless they are interrupted, at deltaX: '
+ + (endX - startX) + ', deltaY: ' + (endY - startY));
+
+ window.scrollTo(0, 0);
+
+ // Release control of refresh driver
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+ await promiseApzFlushedRepaints();
+
+ // We must not include the duplicated frames at the animation
+ // destination as the tests are dependant on the total duration of
+ // the animation to be accurate.
+ positionSamples.splice(1 - minStoppedFrames,
+ minStoppedFrames - 1);
+
+ return positionSamples;
+ }
+
+ </script>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<div id="scroll_behavior_test_body">
+ <div id="scroll_to_target" class="scroll_to_target"></div>
+</body>
+</html>
diff --git a/layout/generic/test/test_scroll_on_display_contents.html b/layout/generic/test/test_scroll_on_display_contents.html
new file mode 100644
index 0000000000..83954a3dce
--- /dev/null
+++ b/layout/generic/test/test_scroll_on_display_contents.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1790253
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1790253</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790253">Mozilla Bug 1790253</a>
+ <p id="display"></p>
+ <style>
+ .container {
+ height: 200px;
+ width: 200px;
+ overflow: scroll;
+ background-color: gray;
+ }
+ </style>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ document.addEventListener("DOMContentLoaded", async () => {
+ await testShadowRoot();
+ await testShadowRootInDisplayContent();
+ await testNestedShadowRoot();
+ await testDisplayContent();
+ await testNestedDisplayContent();
+
+ SimpleTest.finish();
+ });
+
+ async function testShadowRoot() {
+ // Structure:
+ // <div class="container">
+ // #ShadowRoot - Listener
+ // <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);">
+
+ const container = document.createElement("div");
+ container.classList.add("container");
+ container.attachShadow({ mode: "open" });
+ container.shadowRoot.innerHTML = `
+ <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);"></div>
+ `;
+
+ container.shadowRoot.addEventListener("wheel", e => { e.preventDefault(); });
+
+ document.body.append(container);
+ await doTest(container);
+ container.remove();
+ }
+
+ async function testShadowRootInDisplayContent() {
+ // Structure:
+ // <div class="container">
+ // <div style="display: contents">
+ // #ShadowRoot - Listener
+ // <div style="display: contents">
+ // <div style="display: contents">
+ // <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);">
+
+ const container = document.createElement("div");
+ container.classList.add("container");
+ container.innerHTML = `
+ <div style="display: contents"></div>
+ `;
+ const displayContent = container.querySelector("div");
+ displayContent.attachShadow({ mode: "open" });
+ displayContent.shadowRoot.innerHTML = `
+ <div style="display: contents">
+ <div style="display: contents">
+ <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);"></div>
+ </div>
+ </div>
+ `;
+ displayContent.shadowRoot.addEventListener("wheel", e => { e.preventDefault(); });
+
+ document.body.append(container);
+ await doTest(container);
+ container.remove();
+ }
+
+ async function testNestedShadowRoot() {
+ // Structure:
+ // <div class="container">
+ // <div style="display: contents">
+ // #ShadowRoot - Listener
+ // <div style="display: contents">
+ // #ShadowRoot
+ // <div style="display: contents">
+ // <div style="display: contents">
+ // <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);">
+
+ const container = document.createElement("div");
+ container.classList.add("container");
+ container.innerHTML = `
+ <div style="display: contents"></div>
+ `;
+
+ const firstDisplayContent = container.querySelector("div");
+ firstDisplayContent.attachShadow({ mode: "open" });
+ firstDisplayContent.shadowRoot.innerHTML = `
+ <div style="display: contents"></div>
+ `;
+ firstDisplayContent.shadowRoot.addEventListener("wheel", e => { e.preventDefault(); });
+
+ const secondDisplayContent = firstDisplayContent.shadowRoot.querySelector("div");
+ secondDisplayContent.attachShadow({ mode: "open" });
+ firstDisplayContent.shadowRoot.innerHTML = `
+ <div style="display: contents">
+ <div style="display: contents">
+ <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);"></div>
+ </div>
+ </div>
+ `;
+
+ document.body.append(container);
+ await doTest(container);
+ container.remove();
+ }
+
+ async function testDisplayContent() {
+ // Structure:
+ // <div class="container">
+ // <div style="display: contents"> - Listener
+ // <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);">
+
+ const container = document.createElement("div");
+ container.classList.add("container");
+ container.innerHTML = `
+ <div style="display: contents">
+ <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);"></div>
+ </div>
+ `;
+
+ const displayContent = container.querySelector("div");
+ displayContent.addEventListener("wheel", e => { e.preventDefault(); });
+
+ document.body.append(container);
+ await doTest(container);
+ container.remove();
+ }
+
+ async function testNestedDisplayContent() {
+ // Structure:
+ // <div class="container">
+ // <div style="display: contents"> - Listener
+ // <div style="display: contents">
+ // <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);">
+
+ const container = document.createElement("div");
+ container.classList.add("container");
+ container.innerHTML = `
+ <div style="display: contents">
+ <div style="display: contents">
+ <div style="height: 300px; background: linear-gradient(#e66465, #9198e5);"></div>
+ </div>
+ </div>
+ `;
+
+ const displayContent = container.querySelector("div");
+ displayContent.addEventListener("wheel", e => { e.preventDefault(); });
+
+ document.body.append(container);
+ await doTest(container);
+ container.remove();
+ }
+
+ async function doTest(target) {
+ await promiseAllPaintsDone();
+
+ const previousScroll = target.scrollTop;
+
+ await promiseMoveMouseAndScrollWheelOver(target, 1, 1, false);
+ await promiseApzFlushedRepaints();
+
+ is(previousScroll, target.scrollTop, "The target should not be scrolled");
+ }
+ </script>
+</body>
+</html>
diff --git a/layout/generic/test/test_scroll_position_iframe.html b/layout/generic/test/test_scroll_position_iframe.html
new file mode 100644
index 0000000000..d77a1b6266
--- /dev/null
+++ b/layout/generic/test/test_scroll_position_iframe.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1305579
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1305579</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1305579">Mozilla Bug 1305579</a>
+<p id="display"></p>
+<style>
+.off {
+ display:none;
+}
+</style>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function runtest() {
+ var iframe = document.getElementById("iframe");
+ iframe.contentDocument.scrollingElement.scrollTop = 50;
+ iframe.classList.toggle("off");
+ waitForAllPaintsFlushed(function() {
+ iframe.classList.toggle("off");
+ is(iframe.contentDocument.scrollingElement.scrollTop, 50, "scroll position restored");
+ SimpleTest.finish();
+ });
+}
+</script>
+<iframe onload="runtest()" id="iframe" class="" srcdoc="<p>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br>bla<br></p>"></iframe><br>
+</body>
+</html>
diff --git a/layout/generic/test/test_scroll_position_restore.html b/layout/generic/test/test_scroll_position_restore.html
new file mode 100644
index 0000000000..0b3aca0b48
--- /dev/null
+++ b/layout/generic/test/test_scroll_position_restore.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1269539
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1269539</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1269539">Mozilla Bug 1269539</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var loadCount = 0;
+var childWin = window.open('file_scroll_position_restore.html', '_blank');
+
+function handleLoad() {
+ if (loadCount == 0) {
+ loadCount++;
+ childWin.scrollTo(0, childWin.scrollMaxY);
+ childWin.waitForAllPaintsFlushed(function() {
+ childWin.location.reload();
+ });
+ } else {
+ childWin.waitForAllPaintsFlushed(function() {
+ // Verify that the scroll position was retained.
+ // NOTE: Window.scrollMaY is a long value, so we need to round
+ // Window.scrollY which is double.
+ // NOTE: Window.scrollMaxY is non-standard, so difference < 1.0 would not
+ // be a big problem.
+ is(Math.round(childWin.scrollY), childWin.scrollMaxY);
+ childWin.close();
+ SimpleTest.finish();
+ });
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_scroll_position_restore_after_stop.html b/layout/generic/test/test_scroll_position_restore_after_stop.html
new file mode 100644
index 0000000000..50c3771bdc
--- /dev/null
+++ b/layout/generic/test/test_scroll_position_restore_after_stop.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1312697
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1312697</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1312697">Mozilla Bug 1312697</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var loadCount = 0;
+var childWin = window.open('file_SlowPage.sjs', '_blank');
+var targetPos = 0;
+
+// Called by the page in the child window when it's halfway loaded.
+function partiallyLoaded() {
+ if (loadCount == 1) {
+ // Halfway through the first reload, stop loading.
+ childWin.stop();
+
+ // Force a reflow in the stopped state. This triggers the buggy behaviour.
+ var newNode = childWin.document.createElement('p');
+ newNode.innerHTML = "Added text.";
+ childWin.document.body.insertBefore(newNode, childWin.document.body.childNodes[0]);
+
+ childWin.waitForAllPaintsFlushed(function() {
+ // Since we're only partially loaded, we should not have been able to
+ // reach the target scroll position.
+ ok(childWin.scrollY < targetPos, "Expected page to not be fully loaded");
+
+ // Now re-load again. Continue reading in fullyLoaded(), the
+ // 'loadCount == 2' case.
+ loadCount++;
+ childWin.location.reload();
+ });
+ }
+}
+
+// Called by the page in the child window when it's fully loaded.
+function fullyLoaded() {
+ if (loadCount == 0) {
+ // Scroll to a target position near the end of the page (past the
+ // half-way point.)
+ targetPos = childWin.scrollMaxY - 100;
+ childWin.scrollTo(0, targetPos);
+ childWin.waitForAllPaintsFlushed(function() {
+ ok(childWin.scrollY == targetPos, "Expected page to have scrolled");
+
+ // Reload the page.
+ loadCount++;
+ childWin.location.reload();
+
+ // Next, we'll get into partiallyLoaded(). Read on there.
+ });
+ } else if (loadCount == 2) {
+ // After the second reload is complete, check that the initial target
+ // position was remembered and scrolled to.
+ childWin.waitForAllPaintsFlushed(function() {
+ ok(childWin.scrollY == targetPos, "Expected page to have scrolled to target position");
+ childWin.close();
+ SimpleTest.finish();
+ });
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/generic/test/test_scroll_position_restore_no_bfcache.html b/layout/generic/test/test_scroll_position_restore_no_bfcache.html
new file mode 100644
index 0000000000..abb390a327
--- /dev/null
+++ b/layout/generic/test/test_scroll_position_restore_no_bfcache.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for Bug 1269539</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let loadCount = 0;
+let childWin;
+let targetScrollPosition;
+function handlePageShow(persisted) {
+ ok(typeof persisted == "boolean", "Should get the persisted state from the pageshow event");
+ ok(!persisted, "Shouldn't have gotten into the bfcache");
+ is(childWin.getComputedStyle(childWin.document.body).color, "rgb(0, 255, 0)", "Sheet should have been applied");
+ let scroller = childWin.document.querySelector("#scroll");
+ if (loadCount == 0) {
+ loadCount++;
+ targetScrollPosition = childWin.innerHeight;
+ isnot(targetScrollPosition, 0, "Should scroll somewhere");
+ scroller.scrollTo(0, childWin.innerHeight);
+ childWin.requestAnimationFrame(() => childWin.requestAnimationFrame(() => {
+ childWin.location.reload();
+ }));
+ } else {
+ childWin.requestAnimationFrame(() => childWin.requestAnimationFrame(() => {
+ // Verify that the scroll position was retained
+ is(scroller.scrollTop, targetScrollPosition, "Should have restored the scroll position");
+ childWin.close();
+ SimpleTest.finish();
+ }));
+ }
+}
+
+childWin = window.open('file_scroll_position_restore_no_bfcache.html', '_blank');
+</script>
diff --git a/layout/generic/test/test_scrollframe_abspos_interrupt.html b/layout/generic/test/test_scrollframe_abspos_interrupt.html
new file mode 100644
index 0000000000..fe25e991f9
--- /dev/null
+++ b/layout/generic/test/test_scrollframe_abspos_interrupt.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<title>Test for bug 1526609</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ body {
+ margin: 0;
+ }
+ .scroller {
+ overflow-y: auto;
+ position: relative;
+ width: 500px;
+ height: 300px;
+ }
+ .kid {
+ position: absolute;
+ width: 100%;
+ background: linear-gradient(to bottom, red, green);
+ line-height: 100px;
+ }
+</style>
+<div class="scroller" id="scroller">
+ <div class="kid"></div>
+</div>
+<script>
+{
+ let text = " foo bar ";
+
+ for (let i = 0; i < 16; ++i)
+ text = text + text;
+ document.querySelector(".kid").innerText = text;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+const scroller = document.querySelector("#scroller");
+
+is(scroller.scrollTop, 0, "Initial scroll position");
+ok(scroller.scrollTopMax > 0, "Should be able to scroll down");
+
+scroller.scrollTop = scroller.scrollTopMax;
+is(scroller.scrollTop, scroller.scrollTopMax, "Should've scrolled");
+
+const origWidth = scroller.offsetWidth;
+const utils = SpecialPowers.DOMWindowUtils;
+
+// Take control of the refresh driver
+utils.advanceTimeAndRefresh(0);
+
+// Force the next reflow to get interrupted
+utils.forceReflowInterrupt();
+scroller.style.width = "300px";
+utils.advanceTimeAndRefresh(0);
+
+isnot(scroller.scrollTop, 0, "Shouldn't have lost scroll position");
+isnot(scroller.offsetWidth, origWidth, "Should've had to reflow");
+
+utils.restoreNormalRefresh();
+SimpleTest.finish();
+</script>
diff --git a/layout/generic/test/test_selection_changes_with_middle_mouse_button.html b/layout/generic/test/test_selection_changes_with_middle_mouse_button.html
new file mode 100644
index 0000000000..5499b6a825
--- /dev/null
+++ b/layout/generic/test/test_selection_changes_with_middle_mouse_button.html
@@ -0,0 +1,335 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for selection changes with middle mouse button</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ span, td {
+ white-space: nowrap;
+ }
+ </style>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none;">
+
+</div>
+
+<textarea id="textarea" cols="1" rows="1">copied to clipboard</textarea>
+
+<div id="container">
+<span id="span1">first span.</span>
+<span id="span2">second span.</span><br>
+<a id="link" href="#top">link.</a>
+<table>
+<tr><td id="td1">first td.</td></tr>
+<tr><td id="td2">second td.</td></tr>
+</table>
+</div>
+
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const kIsMac = navigator.platform.includes("Mac");
+const selection = getSelection();
+
+async function doTests(aEnableMiddlePaste, aEditable, aDescription) {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["middlemouse.paste", aEnableMiddlePaste],
+ ["middlemouse.contentLoadURL", false],
+ ["general.autoScroll", false],
+ ["general.autoscroll.prevent_to_collapse_selection_by_middle_mouse_down", false],
+ ]});
+
+ if (aEditable) {
+ document.getElementById("container").setAttribute("contenteditable", "true");
+ } else {
+ document.getElementById("container").removeAttribute("contenteditable");
+ }
+
+ let pasteEvents = [];
+ function pasteEventHandler(event) {
+ pasteEvents.push(event);
+ event.preventDefault();
+ }
+ document.getElementById("container").addEventListener("paste", pasteEventHandler, true);
+
+ // We need to behave as same as Chromium as far as possible.
+ // - middle mouse button down should be collapse selection at the point.
+ // - middle mouse button down can expand only with mouse button down with Shift key.
+ // - middle mouse button shouldn't select table cells.
+
+ function doTest(aMouseDown, aMouseUp,
+ aExpectedSelectionAnchor, aExpectedSelectionFocus, aExpectedPastEventTarget,
+ aAdditionalDescription) {
+ pasteEvents = [];
+ synthesizeMouseAtCenter(aMouseDown.target,
+ {
+ button: 1,
+ type: "mousedown",
+ shiftKey: aMouseDown.shiftKey,
+ ctrlKey: aMouseDown.ctrlKey && !kIsMac,
+ metaKey: aMouseDown.ctrlKey && kIsMac,
+ });
+ if (aExpectedSelectionAnchor === aExpectedSelectionFocus) {
+ ok(selection.isCollapsed,
+ aDescription + aAdditionalDescription + "Selection should be collapsed at mousedown");
+ is(selection.focusNode, aExpectedSelectionFocus,
+ aDescription + aAdditionalDescription + "Selection should be collapsed in the node at mousedown");
+ } else {
+ is(selection.anchorNode, aExpectedSelectionAnchor,
+ aDescription + aAdditionalDescription + "Anchor node of Selection should be previous anchor node");
+ is(selection.focusNode, aExpectedSelectionFocus,
+ aDescription + aAdditionalDescription + "Focus node of Selection should be the node at mousedown");
+ }
+ is(pasteEvents.length, 0,
+ aDescription + aAdditionalDescription + "paste event shouldn't be fired when middle mouse button down");
+
+ if (aMouseDown.target != aMouseUp.target) {
+ synthesizeMouseAtCenter(aMouseUp.target, {type: "mousemove"});
+ }
+ synthesizeMouseAtCenter(aMouseUp.target,
+ {
+ button: 1,
+ type: "mouseup",
+ shiftKey: aMouseUp.shiftKey,
+ ctrlKey: aMouseUp.ctrlKey && !kIsMac,
+ metaKey: aMouseUp.ctrlKey && kIsMac,
+ });
+ is(selection.anchorNode, aExpectedSelectionAnchor,
+ aDescription + aAdditionalDescription + "Anchor node of Selection shouldn't be modified at mouseup");
+ is(selection.focusNode, aExpectedSelectionFocus,
+ aDescription + aAdditionalDescription + "Focus node of Selection shouldn't be modified at mouseup");
+ if (aEnableMiddlePaste) {
+ if (aExpectedPastEventTarget === null) {
+ is(pasteEvents.length, 0,
+ aDescription + aAdditionalDescription + "paste event shouldn't be fired even when middle mouse button up");
+ } else {
+ is(pasteEvents.length, 1,
+ aDescription + aAdditionalDescription + "paste event should be fired only once at mouse up");
+ is(pasteEvents[0]?.target, aExpectedPastEventTarget,
+ aDescription + aAdditionalDescription + "paste event should be fired on start of selection");
+ }
+ } else {
+ is(pasteEvents.length, 0,
+ aDescription + aAdditionalDescription + "paste event shouldn't be fired when middle mouse button up when middle mouse paste is disabled");
+ }
+ }
+
+ let span1 = document.getElementById("span1");
+ let span2 = document.getElementById("span2");
+ let link = document.getElementById("link");
+
+ selection.removeAllRanges();
+ doTest({target: span1}, {target: span1},
+ span1.firstChild, span1.firstChild, span1,
+ "Clicking span1 when there is no selection: ");
+ doTest({target: span2}, {target: span2},
+ span2.firstChild, span2.firstChild, span2,
+ "Clicking span2 when selection is collapsed in span1: ");
+ doTest({target: span1}, {target: span2},
+ span1.firstChild, span1.firstChild, span1,
+ "Dragging from span1 to span2: ");
+ doTest({target: span2}, {target: span1},
+ span2.firstChild, span2.firstChild, span2,
+ "Dragging from span2 to span1: ");
+ doTest({target: span1, shiftKey: true}, {target: span1, shiftKey: true},
+ span2.firstChild, span1.firstChild, span1,
+ "Expanding selection with Shift key from span2 to span1: ");
+ selection.collapse(span1.firstChild, 3);
+ if (aEditable) {
+ // Collapse link into editable link.
+ doTest({target: link, shiftKey: true}, {target: link, shiftKey: true},
+ link.firstChild, link.firstChild,
+ link /* TODO: Perhaps, the "paste" event target should be the link */,
+ "Clicking an editable link with middle-button with Shift key when selection is collapsed in span1: ");
+ } else {
+ // Don't extend selection into a link.
+ link.onauxclick = event => event.preventDefault();
+ doTest({target: link, shiftKey: true}, {target: link, shiftKey: true},
+ span1.firstChild, span1.firstChild,
+ null /* due to the call of preventDefault */,
+ "Clicking a link with middle-button with Shift key when selection is collapsed in span1: ");
+ link.onauxclick = null;
+ }
+ // "paste" event should be fired in the "start" of selection.
+ selection.collapse(span1.firstChild, 3);
+ doTest({target: span2, shiftKey: true}, {target: span2, shiftKey: true},
+ span1.firstChild, span2.firstChild, span1,
+ "Expanding selection with Shift key from span1 to span2: ");
+ // XXX This case is different from Chrome for Linux.
+ // In this case, Chrome does not collapse Selection at mousedown,
+ // but collapse at click. So, if mouseup occurs different element,
+ // Selection isn't modified.
+ selection.selectAllChildren(span1);
+ doTest({target: span1}, {target: span1},
+ span1.firstChild, span1.firstChild, span1,
+ "Clicking span1 when span1 is selected: ");
+
+ let td1 = document.getElementById("td1");
+ let td2 = document.getElementById("td2");
+
+ selection.removeAllRanges();
+ doTest({target: td1}, {target: td1},
+ td1.firstChild, td1.firstChild, td1,
+ "Clicking td1 when there is no selection: ");
+ if (aEditable) {
+ // XXX In this case, we don't allow to expand selection with Shift key
+ // click across table cell boundary.
+ doTest({target: td2, shiftKey: true}, {target: td2, shiftKey: true},
+ td1.firstChild, td1.firstChild, td1,
+ "Expanding selection with Shift key from td1 to td2: ");
+ } else {
+ doTest({target: td2, shiftKey: true}, {target: td2, shiftKey: true},
+ td1.firstChild, td2.firstChild, td1,
+ "Expanding selection with Shift key from td1 to td2: ");
+ }
+ // Shouldn't select per table cell when the button is middle mouse button.
+ doTest({target: td1, ctrlKey: true}, {target: td1, ctrlKey: true},
+ td1.firstChild, td1.firstChild, td1,
+ "Click td1 with Control key: ");
+
+ document.getElementById("container").removeEventListener("paste", pasteEventHandler, true);
+}
+
+async function runTestPreventingToCollapseSelectionByPrefs() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["middlemouse.contentLoadURL", false],
+ ["general.autoscroll.prevent_to_collapse_selection_by_middle_mouse_down", true],
+ ]});
+
+ // If middle click paste is disabled and autoscroll is enabled, we should
+ // allow users to prevent to collapse selection by middle mouse down.
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["middlemouse.paste", false],
+ ["general.autoScroll", true],
+ ]});
+
+ const container = document.getElementById("container");
+ container.removeAttribute("contenteditable");
+ container.getBoundingClientRect();
+
+ const span1 = container.querySelector("span#span1");
+ const span2 = container.querySelector("span#span2");
+ function checkSelectAllChildrenOfSpan2(description) {
+ is(
+ getSelection().focusNode,
+ span2,
+ `Selection shouldn't be collapsed by ${description} (focusNode)`
+ );
+ is(
+ getSelection().focusOffset,
+ span2.childNodes.length,
+ `Selection shouldn't be collapsed by ${description} (focusOffset)`
+ );
+ is(
+ getSelection().anchorNode,
+ span2,
+ `Selection shouldn't be collapsed by ${description} (anchorNode)`
+ );
+ is(
+ getSelection().anchorOffset,
+ 0,
+ `Selection shouldn't be collapsed by ${description} (anchorOffset)`
+ );
+ }
+
+ function checkSelectionCollapsedInSpan1(description) {
+ ok(
+ getSelection().isCollapsed,
+ `Selection should be collapsed into the clicked text node ${description} (isCollapsed)`
+ );
+ is(
+ getSelection().focusNode,
+ span1.firstChild,
+ `Selection should be collapsed into the clicked text node ${description} (focusNode)`
+ );
+ is(
+ getSelection().anchorNode,
+ span1.firstChild,
+ `Selection should be collapsed into the clicked text node ${description} (anchorNode)`
+ );
+ }
+ getSelection().selectAllChildren(span2);
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mousedown", button: 1 });
+ checkSelectAllChildrenOfSpan2("middle mousedown if the pref is enabled and middle click paste is disabled and autoscroll are enabled");
+
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mouseup", button: 1 });
+ checkSelectAllChildrenOfSpan2("middle mouseup if the pref is enabled and middle click paste is disabled and autoscroll are enabled");
+
+ container.setAttribute("contenteditable", "true");
+ container.getBoundingClientRect();
+ getSelection().selectAllChildren(span2);
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mousedown", button: 1 });
+ checkSelectionCollapsedInSpan1("by middle mousedown if it's editable content");
+
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mouseup", button: 1 });
+ checkSelectionCollapsedInSpan1("after middle mouseup if it's editable content");
+
+ container.removeAttribute("contenteditable");
+ container.getBoundingClientRect();
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["middlemouse.paste", false],
+ ["general.autoScroll", false],
+ ]});
+
+ getSelection().selectAllChildren(span2);
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mousedown", button: 1 });
+ checkSelectionCollapsedInSpan1("by middle mousedown if the pref is enabled but autoscroll are disabled");
+
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mouseup", button: 1 });
+ checkSelectionCollapsedInSpan1("after middle mousedown if the pref is enabled but autoscroll are disabled");
+
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["middlemouse.paste", true],
+ ["general.autoScroll", true],
+ ]});
+
+ getSelection().selectAllChildren(span2);
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mousedown", button: 1 });
+ checkSelectionCollapsedInSpan1("by middle mousedown if the pref is enabled but middle mouse paste is enabled");
+
+ synthesizeMouseAtCenter(document.getElementById("span1"), { type: "mouseup", button: 1 });
+ checkSelectionCollapsedInSpan1("after middle mousedown if the pref is enabled but middle mouse paste is enabled");
+}
+
+async function runAllTests() {
+ let textarea = document.getElementById("textarea");
+ textarea.focus();
+ await new Promise((resolve, reject) => {
+ SimpleTest.waitForClipboard(textarea.value,
+ () => {
+ synthesizeKey("a", {accelKey: true});
+ synthesizeKey("c", {accelKey: true});
+ },
+ () => {
+ ok(true, `Succeeded to copy "${textarea.value}" to clipboard`);
+ textarea.style.display = "none";
+ resolve();
+ },
+ () => {
+ ok(false, `Failed to copy "${textarea.value}" to clipboard`);
+ reject();
+ });
+ });
+
+ await doTests(true, false, "Testing with the middle paste enabled: ");
+ await doTests(false, false, "Testing with the middle paste disabled: ");
+
+ await doTests(true, true, "Testing in editable content with the middle paste enabled: ");
+ await doTests(false, true, "Testing in editable content with the middle paste disabled: ");
+
+ await runTestPreventingToCollapseSelectionByPrefs();
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runAllTests);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_selection_doubleclick.html b/layout/generic/test/test_selection_doubleclick.html
new file mode 100644
index 0000000000..54e472b8b8
--- /dev/null
+++ b/layout/generic/test/test_selection_doubleclick.html
@@ -0,0 +1,93 @@
+<!DOCTYPE>
+<html>
+<head>
+<title>selection preventDefault test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+ <div id="wbrs" class="testingDiv">
+ abc<wbr>def<br>
+ ghi<wbr>jkl<br>
+ mno<wbr>pqr<br>
+ </div>
+
+ <p id="paragraphWbr">
+ abc<wbr>def<wbr>ghi<wbr>jkl<wbr>mno<wbr>pqr
+ </p>
+
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+ function test()
+ {
+ const kLF = !navigator.platform.indexOf("Win") ? "\r\n" : "\n";
+ const isMac = navigator.platform.indexOf("Mac") >= 0;
+ const combinators = isMac ? {shiftKey: true, altKey: true}
+ : {shiftKey: true, accelKey: true};
+
+ var wbrElement = document.getElementById("wbrs");
+
+ //double clicking selects across wbr elements.
+ synthesizeMouse(wbrElement, 5, 5, { clickCount: 2 });
+ var selectedText = window.getSelection().toString();
+ console.log(selectedText);
+ is(selectedText,"abcdef", "Select across WBR elements with double click: OK");
+
+ //selecting first letter of wbr element and using shift + arrowRight selects across wbr elements.
+ synthesizeMouse(wbrElement, 1, 5, { type: "mousedown" });
+ synthesizeMouse(wbrElement, 2, 5, { type: "mousemove" });
+ synthesizeMouse(wbrElement, 2, 5, { type: "mouseup" });
+ synthesizeKey("KEY_ArrowRight", {shiftKey:true});
+ synthesizeKey("KEY_ArrowRight", {shiftKey:true});
+ synthesizeKey("KEY_ArrowRight", {shiftKey:true});
+ synthesizeKey("KEY_ArrowRight", {shiftKey:true});
+ var selectedText = window.getSelection().toString();
+ console.log(selectedText);
+ is(selectedText, "abcd", "Select across WBR elements using shift and right arrow: OK");
+
+ //selection using ctrl + shift + rightArrow selects across wbr elements
+ synthesizeMouse(wbrElement, 1, 5, { type: "mousedown" });
+ synthesizeMouse(wbrElement, 2, 5, { type: "mousemove" });
+ synthesizeMouse(wbrElement, 2, 5, { type: "mouseup" });
+ synthesizeKey("KEY_ArrowRight", combinators);
+ var selectedText = window.getSelection().toString();
+ console.log(selectedText);
+ is(selectedText, "abcdef", "Select across WBR elements using shift + ctrl + right arrow: OK");
+
+ //selection using ctrl + shift + rightArrow selects across wbr and br elements
+ synthesizeMouse(wbrElement, 1, 5, { type: "mousedown" });
+ synthesizeMouse(wbrElement, 2, 5, { type: "mousemove" });
+ synthesizeMouse(wbrElement, 2, 5, { type: "mouseup" });
+ synthesizeKey("KEY_ArrowRight", combinators);
+ synthesizeKey("KEY_ArrowRight", combinators);
+ var selectedText = window.getSelection().toString();
+ console.log(selectedText);
+ is(selectedText, "abcdef" + kLF + "ghijkl", "Select across WBR elements using shift + ctrl + right arrow: OK");
+
+ //double clicking on a paragraph selects across wbr elements
+ var paragraphWbr = document.getElementById("paragraphWbr");
+ synthesizeMouse(paragraphWbr, 5, 5, { clickCount: 2 });
+ var selectedText = window.getSelection().toString();
+ console.log(selectedText);
+ is(selectedText,"abcdefghijklmnopqr", "Select across WBR elements in paragraph with double click: OK");
+
+ SimpleTest.finish();
+ }
+ window.onload = function() {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ // Prevent whitespace between lines from being selected.
+ ["layout.word_select.eat_space_to_next_word", false],
+ ],
+ }).then(() => {
+ setTimeout(test, 0);
+ });
+ };
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_selection_expanding.html b/layout/generic/test/test_selection_expanding.html
new file mode 100644
index 0000000000..f9d25bfd17
--- /dev/null
+++ b/layout/generic/test/test_selection_expanding.html
@@ -0,0 +1,419 @@
+<!DOCTYPE>
+<html>
+<head>
+<title>selection expanding test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script>
+ customElements.define("custom-element", class extends HTMLElement {
+ constructor() {
+ super();
+ const template = document.getElementById("template");
+ const shadowRoot = this.attachShadow({mode: "open"})
+ .appendChild(template.content.cloneNode(true));
+ }
+ });
+</script>
+<style type="text/css">
+ .testingDiv {
+ font-size: 16px;
+ width: 300px;
+ height: 140px;
+ background-color: white;
+ }
+ #fixedDiv1, #fixedDiv2 {
+ position: fixed;
+ right: 0;
+ overflow: scroll;
+ width: 200px;
+ }
+ #fixedDiv1 {
+ top: 0;
+ }
+ #fixedDiv2 {
+ top: 150px;
+ }
+ iframe, input, textarea {
+ font-size: 16px;
+ height: 16px;
+ width: 80px;
+ margin: 0;
+ padding: 0;
+ }
+</style>
+
+</head>
+<body>
+<template id="template">
+ <div>
+ <p>xxxxxxx xxxxxxx xxxxxxx</p>
+ <slot></slot>
+ </div>
+</template>
+<div id="div1" class="testingDiv">
+ aaaaaaa
+ <iframe id="iframe" srcdoc="<style type='text/css'>*{margin: 0; padding: 0; font-size: 16px;}</style><div>ffffff ffffff ffffff ffffff</div>"></iframe>
+ aaaaaaa aaaaaaa<br>aaaaaaa aaaaaaa aaaaaaa aaaaaaa<br>aaaaaaa
+</div>
+<div id="div2" class="testingDiv">
+ bbbbbbb
+ <input id="input" type="text" value="iiiiiiiii iiiiiiiii iiiiiiiii">
+ bbbbbbb bbbbbbb<br>bbbbbbb bbbbbbb bbbbbbb<br>bbbbbbb
+</div>
+<div id="div3" class="testingDiv">
+ ccccccc
+ <textarea id="textarea">tttttt tttttt tttttt</textarea>
+ ccccccc ccccccc<br>ccccccc ccccccc ccccccc ccccccc<br>ccccccc
+ <div id="fixedDiv1" class="testingDiv">
+ dddddd dddddd dddddd
+ </div>
+</div>
+<custom-element id="custom">
+ <p id="custom_child">yyyyyyy yyyyyyy yyyyyyy</p>
+</custom-element>
+<div id="fixedDiv2" class="testingDiv">
+ eeeeee eeeeee eeeeee
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var div1 = document.getElementById("div1");
+var div2 = document.getElementById("div2");
+var div3 = document.getElementById("div3");
+var custom_child = document.getElementById("custom_child");
+var fixedDiv1 = document.getElementById("fixedDiv1");
+var fixedDiv2 = document.getElementById("fixedDiv2");
+var iframe = document.getElementById("iframe");
+var input = document.getElementById("input");
+var textarea = SpecialPowers.wrap(document.getElementById("textarea"));
+
+function test()
+{
+ function getSelectionForEditor(aEditorElement)
+ {
+ return SpecialPowers.wrap(aEditorElement).editor.selection;
+ }
+
+ function clear()
+ {
+ synthesizeMouse(div1, 10, 5, { type: "mouseup" });
+ var sel = window.getSelection();
+ if (sel.rangeCount > 0)
+ sel.collapseToEnd();
+ sel = iframe.contentWindow.getSelection();
+ if (sel.rangeCount > 0)
+ sel.collapseToEnd();
+ sel = getSelectionForEditor(input);
+ if (sel.rangeCount > 0)
+ sel.collapseToEnd();
+ sel = getSelectionForEditor(textarea);
+ if (sel.rangeCount > 0)
+ sel.collapseToEnd();
+
+ div1.scrollTop = 0;
+ div1.scrollLeft = 0;
+ div2.scrollTop = 0;
+ div2.scrollLeft = 0;
+ div3.scrollTop = 0;
+ div3.scrollLeft = 0;
+ }
+
+ const kFalse = 0;
+ const kTrue = 1;
+ const kToDo = 2;
+
+ function check(aDiv1ShouldBeSelected,
+ aDiv2ShouldBeSelected,
+ aDiv3ShouldBeSelected,
+ aFixedDiv1ShouldBeSelected,
+ aCustomChildShouldBeSelected,
+ aFixedDiv2ShouldBeSelected,
+ aIFrameShouldBeSelected,
+ aInputShouldBeSelected,
+ aTextareaShouldBeSelected,
+ aTestingDescription)
+ {
+ function checkCharacter(aSelectedText,
+ aShouldBeIncludedCharacter,
+ aSouldBeSelected,
+ aElementName)
+ {
+ var boolvalue = aSouldBeSelected & kTrue;
+ var f = aSouldBeSelected & kToDo ? todo : ok;
+ var str = aSelectedText.replace('\n', '\\n');
+ if (boolvalue) {
+ f(aSelectedText.includes(aShouldBeIncludedCharacter),
+ "The contents of " + aElementName +
+ " aren't selected (" + aTestingDescription +
+ "): Selected String: \"" + str + "\"");
+ } else {
+ f(!aSelectedText.includes(aShouldBeIncludedCharacter),
+ "The contents of " + aElementName +
+ " are selected (" + aTestingDescription +
+ "): Selected String: \"" + str + "\"");
+ }
+ }
+
+ var sel = window.getSelection().toString();
+ checkCharacter(sel, "a", aDiv1ShouldBeSelected, "div1");
+ checkCharacter(sel, "b", aDiv2ShouldBeSelected, "div2");
+ checkCharacter(sel, "c", aDiv3ShouldBeSelected, "div3");
+ checkCharacter(sel, "y", aCustomChildShouldBeSelected, "custom_child");
+ checkCharacter(sel, "d", aFixedDiv1ShouldBeSelected, "fixedDiv1");
+ checkCharacter(sel, "e", aFixedDiv2ShouldBeSelected, "fixedDiv2");
+
+ // iframe/input/custom-element contents must not be included on the parent
+ // selection.
+ checkCharacter(sel, "f", false, "iframe (checking on parent)");
+ checkCharacter(sel, "i", false, "input (checking on parent)");
+ checkCharacter(sel, "x", false, "Custom element contents (checking on parent)");
+
+ var selInIFrame = iframe.contentWindow.getSelection().toString();
+ checkCharacter(selInIFrame, "f", aIFrameShouldBeSelected, "iframe");
+
+ var selInput = getSelectionForEditor(input).toString();
+ checkCharacter(selInput, "i", aInputShouldBeSelected, "input");
+ var selTextarea = getSelectionForEditor(textarea).toString();
+ checkCharacter(selTextarea, "t", aTextareaShouldBeSelected, "textarea");
+ }
+
+ // ***********************************************************
+ // Set all divs to overflow: auto;
+ const kOverflows = ["visible", "hidden", "scroll", "auto"];
+ for (var i = 0; i < kOverflows.length; i++) {
+ div1.style.overflow = kOverflows[i];
+ div2.style.overflow = kOverflows[i];
+ div3.style.overflow = kOverflows[i];
+
+ // ***********************************************************
+ // selection starting at div1
+ synthesizeMouse(div1, 30, 5, { type: "mousedown" });
+
+ // XXX if we move the mouse cursor to another document, the
+ // nsFrameSelection::HandleDrag method is called on the another document's.
+
+ // to iframe
+ synthesizeMouse(iframe, 30, 5, { type: "mousemove" });
+ check(kTrue | kToDo, kFalse, kFalse,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div1-iframe, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // XXX if the overflow is visible, synthesizeMouse with the input element
+ // or textarea element doesn't work fine.
+ var isVisibleTesting = kOverflows[i] == "visible";
+ var todoFlag = isVisibleTesting ? kToDo : 0;
+ // to input
+ synthesizeMouse(input, 30, 5, { type: "mousemove" });
+ check(kTrue | todoFlag, kTrue | todoFlag, kFalse,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div1-input, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to textarea
+ synthesizeMouse(textarea, 30, 5, { type: "mousemove" });
+ check(kTrue | todoFlag, kTrue | todoFlag, kTrue | todoFlag,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div1-textarea, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to div2
+ synthesizeMouse(div2, 30, 5, { type: "mousemove" });
+ check(kTrue, kTrue, kFalse,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div1-div2, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to div3
+ synthesizeMouse(div3, 30, 5, { type: "mousemove" });
+ check(kTrue, kTrue, kTrue,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div1-div3, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to fixedDiv1 (child of div3)
+ synthesizeMouse(fixedDiv1, 30, 5, { type: "mousemove" });
+ check(kTrue, kTrue, kTrue,
+ kTrue, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div1-fixedDiv1, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to custom_child
+ synthesizeMouse(custom_child, 30, 5, { type: "mousemove" });
+ check(kTrue, kTrue, kTrue,
+ kTrue, kTrue, kFalse, kFalse, kFalse, kFalse,
+ "div1-custom_child, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to fixedDiv2 (sibling of div*)
+ synthesizeMouse(fixedDiv2, 30, 5, { type: "mousemove" });
+ check(kTrue, kTrue, kTrue,
+ kTrue, kTrue, kTrue, kFalse, kFalse, kFalse,
+ "div1-fixedDiv2, all boxes are overflow: " + kOverflows[i] + ";");
+
+ clear();
+
+ // ***********************************************************
+ // selection starting at fixedDiv1
+ synthesizeMouse(fixedDiv1, 30, 5, { type: "mousedown" });
+
+ // to custom_child
+ synthesizeMouse(custom_child, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kTrue, kTrue, kFalse, kFalse, kFalse, kFalse,
+ "fixedDiv1-custom_child, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to fixedDiv2
+ synthesizeMouse(fixedDiv2, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kTrue, kTrue, kTrue, kFalse, kFalse, kFalse,
+ "fixedDiv1-fixedDiv2, all boxes are overflow: " + kOverflows[i] + ";");
+
+ clear();
+
+ // ***********************************************************
+ // selection starting at fixedDiv2
+ synthesizeMouse(fixedDiv2, 30, 5, { type: "mousedown" });
+
+ // to custom_child
+ synthesizeMouse(custom_child, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kFalse, kTrue, kTrue, kFalse, kFalse, kFalse,
+ "fixedDiv2-custom_child, all boxes are overflow: " + kOverflows[i] + ";");
+
+ // to fixedDiv1
+ synthesizeMouse(fixedDiv1, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kTrue, kTrue, kTrue, kFalse, kFalse, kFalse,
+ "fixedDiv2-fixedDiv1, all boxes are overflow: " + kOverflows[i] + ";");
+
+ clear();
+
+ // ***********************************************************
+ div2.style.overflow = "visible";
+
+ // ***********************************************************
+ // selection starting at div2
+ synthesizeMouse(div2, 30, 5, { type: "mousedown" });
+
+ // to div3
+ synthesizeMouse(div3, 30, 5, { type: "mousemove" });
+ check(kFalse, kTrue, kTrue,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div2-div3, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ // to fixedDiv1 (child of div3)
+ synthesizeMouse(fixedDiv1, 30, 5, { type: "mousemove" });
+ check(kFalse, kTrue, kTrue,
+ kTrue, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div2-fixedDiv1, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ // to custom_child
+ synthesizeMouse(custom_child, 30, 5, { type: "mousemove" });
+ check(kFalse, kTrue, kTrue,
+ kTrue, kTrue, kFalse, kFalse, kFalse, kFalse,
+ "div2-custom_child, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ // to fixedDiv2 (sibling of div*)
+ synthesizeMouse(fixedDiv2, 30, 5, { type: "mousemove" });
+ check(kFalse, kTrue, kTrue,
+ kTrue, kTrue, kTrue, kFalse, kFalse, kFalse,
+ "div2-fixedDiv2, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ clear();
+
+ // ***********************************************************
+ // selection starting at div3
+ synthesizeMouse(div3, 30, 5, { type: "mousedown" });
+
+ // to div2
+ synthesizeMouse(div2, 30, 5, { type: "mousemove" });
+ check(kFalse, kTrue, kTrue,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div3-div2, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ // to fixedDiv1 (child of div3)
+ synthesizeMouse(fixedDiv1, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kTrue,
+ kTrue, kFalse, kFalse, kFalse, kFalse, kFalse,
+ "div3-fixedDiv1, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ // to custom_child
+ synthesizeMouse(custom_child, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kTrue,
+ kTrue, kTrue, kFalse, kFalse, kFalse, kFalse,
+ "div3-custom_child, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ // to fixedDiv2 (sibling of div*)
+ synthesizeMouse(fixedDiv2, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kTrue,
+ kTrue, kTrue, kTrue, kFalse, kFalse, kFalse,
+ "div3-fixedDiv2, div3 is overflow: " + kOverflows[i] +
+ ";, but div2 is overflow: visible;");
+
+ clear();
+ }
+
+ // ***********************************************************
+ // selection starting at iframe
+ synthesizeMouse(iframe, 20, 5, { type: "mousedown" });
+
+ // inside iframe
+ synthesizeMouse(iframe, 50, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kFalse, kFalse, kFalse, kTrue, kFalse, kFalse,
+ "iframe-iframe");
+
+ // to div2
+ synthesizeMouse(div2, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kFalse, kFalse, kFalse, kTrue, kFalse, kFalse,
+ "iframe-div2");
+
+ clear();
+
+ // ***********************************************************
+ // selection starting at input
+ synthesizeMouse(input, 20, 5, { type: "mousedown" });
+
+ // inside input
+ synthesizeMouse(input, 40, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kFalse, kFalse, kFalse, kFalse, kTrue, kFalse,
+ "input-input");
+
+ // to div3
+ synthesizeMouse(div3, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kFalse, kFalse, kFalse, kFalse, kTrue, kFalse,
+ "input-div3");
+
+ clear();
+
+ // ***********************************************************
+ // selection starting at textarea
+ synthesizeMouse(textarea, 30, 5, { type: "mousedown" });
+
+ // inside textarea
+ synthesizeMouse(textarea, 50, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kTrue,
+ "textarea-textarea");
+
+ // to div2
+ synthesizeMouse(div2, 30, 5, { type: "mousemove" });
+ check(kFalse, kFalse, kFalse,
+ kFalse, kFalse, kFalse, kFalse, kFalse, kTrue,
+ "textarea-div2");
+
+ clear();
+
+ SimpleTest.finish();
+}
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_selection_multiclick_drag.html b/layout/generic/test/test_selection_multiclick_drag.html
new file mode 100644
index 0000000000..c38b8c77d7
--- /dev/null
+++ b/layout/generic/test/test_selection_multiclick_drag.html
@@ -0,0 +1,137 @@
+<!DOCTYPE>
+<html>
+<head>
+<meta charset="utf-8">
+<title>multi-click selection extension test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+ .testingDiv { font: 16px/1.25 monospace; width: -moz-fit-content; }
+ p { margin: 0; unicode-bidi: bidi-override; }
+</style>
+</head>
+
+<body>
+ <div id="testDiv" class="testingDiv">
+ <p>one two three</p>
+ <p>un deux trois</p>
+ <p>ein zwei drei</p>
+ </div>
+ <br>
+ <div id="rtlTestDiv" class="testingDiv" dir="rtl">
+ <p>one two three</p>
+ <p>un deux trois</p>
+ <p>ein zwei drei</p>
+ </div>
+
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+ function test() {
+ const testDiv = document.getElementById("testDiv");
+ const rtlTestDiv = document.getElementById("rtlTestDiv");
+
+ // Expected line height (CSS 'lh' unit), given that font-size = 16px and
+ // line-height = 1.25.
+ const lh = 20;
+
+ // Width of one character in the monospace font (the CSS 'ch' unit),
+ // calculated by measuring the test text that contains 13 chars per line.
+ const ch = testDiv.offsetWidth / 13;
+
+ const platformIsWindows = !navigator.platform.indexOf("Win");
+ const eatSpaceAfterWord = SpecialPowers.getBoolPref("layout.word_select.eat_space_to_next_word");
+
+ // Y-coordinates that will fall within each line:
+ const firstLine = 0.5 * lh;
+ const secondLine = 1.5 * lh;
+ const thirdLine = 2.5 * lh;
+
+ // Return the character offset of "^", representing the position to click/drag
+ // within the test string.
+ function xPosForClick(s) {
+ return s.indexOf("^") * ch;
+ }
+
+ // Test expectation strings use "_" to represent a space that is only expected
+ // to be present on Windows, where layout.word_select.eat_space_to_next_word
+ // is set by default, and <PAR> to denote a paragraph separator sequence (two
+ // platform-specific newlines).
+ function expectation(s) {
+ let result = s.replace("_", eatSpaceAfterWord ? " " : "");
+ result = result.replace("<PAR>", platformIsWindows ? "\r\n\r\n" : "\n\n");
+ return result;
+ }
+
+ // Sanity-check that we're hitting what we expect with our clicks:
+ synthesizeMouse(testDiv, xPosForClick("un de^ux trois"), secondLine, { clickCount: 2 });
+ is(window.getSelection().toString(), expectation("deux_"), "Double-click middle word");
+
+ // Each entry in the array specifies which line to drag to, the character position
+ // within that line (indicated by the caret ^ in the given string), and what the
+ // expected selection should then be.
+ const wordSelectTests = [
+ [secondLine, "un^ deux trois", "un deux_", "Drag left to extend by word"],
+ [secondLine, "un ^deux trois", "deux_", "Return to edge of anchor word"],
+ [secondLine, "un d^eux trois", "deux_", "Return to within anchor word"],
+ [secondLine, "un deux t^rois", "deux trois", "Drag right to extend by word"],
+ [firstLine, "one t^wo three", "two three<PAR>un deux_", "Drag into previous line"],
+ [secondLine, "un ^deux trois", "deux_", "Drag back to start of anchor word"],
+ [secondLine, "un de^ux trois", "deux_", "Drag back to middle of anchor word"],
+ [thirdLine, "ein zw^ei drei", "deux trois<PAR>ein zwei_", "Drag into next line"],
+ [secondLine, "un de^ux trois", "deux_", "Return to anchor word"],
+ ];
+
+ const lineSelectTests = [
+ [secondLine, "un de^ux trois", "un deux trois", "Triple-click selected whole line"],
+ [firstLine, "one t^wo three", "one two three<PAR>un deux trois", "Drag up into previous line"],
+ [secondLine, "un de^ux trois", "un deux trois", "Return to middle line"],
+ [thirdLine, "ein z^wei drei", "un deux trois<PAR>ein zwei drei", "Drag down into next line"],
+ [secondLine, "un de^ux trois", "un deux trois", "Return to middle line"],
+ ];
+
+ // The RTL tests use monospaced latin text with bidi-override applied;
+ // we specify caret positions by counting characters in a reversed string.
+ const rtlWordSelectTests = [
+ [secondLine, "siort xued ^nu", "un deux_", "Drag right to extend by word"],
+ [secondLine, "siort xued^ nu", "deux_", "Return to edge of anchor word"],
+ [secondLine, "siort xu^ed nu", "deux_", "Return to within anchor word"],
+ [secondLine, "siort^ xued nu", "deux trois", "Drag left to extend by word"],
+ [firstLine, "eerht ow^t eno", "two three<PAR>un deux_", "Drag into previous line"],
+ [secondLine, "siort xued^ nu", "deux_", "Drag back to start of anchor word"],
+ [secondLine, "siort xu^ed nu", "deux_", "Drag back to middle of anchor word"],
+ [thirdLine, "ierd ie^wz nie", "deux trois<PAR>ein zwei_", "Drag into next line"],
+ [secondLine, "siort xu^ed nu", "deux_", "Return to anchor word"],
+ ];
+
+ function testMultiClickAndDrag(elem, initialPos, clickCount, tests) {
+ // Initial multi-click in the middle word of the middle line:
+ synthesizeMouse(elem, xPosForClick(initialPos), secondLine, { type: "mousedown", clickCount: clickCount });
+
+ // Now drag to each test position and check the resulting selection:
+ tests.forEach(function(t) {
+ synthesizeMouse(elem, xPosForClick(t[1]), t[0], { type: "mousemove" });
+ is(window.getSelection().toString(), expectation(t[2]), t[3]);
+ });
+
+ // Finish the test sequence with mouseUp.
+ synthesizeMouse(elem, xPosForClick(initialPos), secondLine, { type: "mouseup" });
+ }
+
+ testMultiClickAndDrag(testDiv, "un de^ux trois", 2, wordSelectTests);
+
+ testMultiClickAndDrag(testDiv, "un de^ux trois", 3, lineSelectTests);
+
+ testMultiClickAndDrag(rtlTestDiv, "siort xu^ed nu", 2, rtlWordSelectTests);
+
+ SimpleTest.finish();
+ }
+ window.onload = function() {
+ setTimeout(test, 0);
+ };
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_selection_preventDefault.html b/layout/generic/test/test_selection_preventDefault.html
new file mode 100644
index 0000000000..66a33896fa
--- /dev/null
+++ b/layout/generic/test/test_selection_preventDefault.html
@@ -0,0 +1,173 @@
+<!DOCTYPE>
+<html>
+<head>
+<title>selection preventDefault test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<style type="text/css">
+ #fixedDiv1 {
+ position: fixed;
+ right: 0;
+ overflow: scroll;
+ width: 200px;
+ top: 0;
+ }
+ input {
+ font-size: 16px;
+ height: 16px;
+ width: 80px;
+ margin: 0;
+ padding: 0;
+ -moz-appearance: none;
+ }
+</style>
+
+</head>
+<body>
+<input id="input" type="text" value="iiiiiiiii iiiiiiiii iiiiiiiii">
+<div id="fixedDiv1" class="testingDiv">
+dddddd dddddd dddddd
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var fixedDiv1 = document.getElementById("fixedDiv1");
+var input = document.getElementById("input");
+
+function test()
+{
+ function getSelectionForEditor(aEditorElement)
+ {
+ return SpecialPowers.wrap(aEditorElement).editor.selection;
+ }
+
+ function clear()
+ {
+ var sel = window.getSelection();
+ if (sel.rangeCount > 0)
+ sel.collapseToEnd();
+ sel = getSelectionForEditor(input);
+ if (sel.rangeCount > 0)
+ sel.collapseToEnd();
+ }
+
+ const kFalse = 0;
+ const kTrue = 1;
+ const kToDo = 2;
+
+ function check(aFixedDiv1ShouldBeSelected,
+ aInputShouldBeSelected,
+ aTestingDescription)
+ {
+ function checkCharacter(aSelectedText,
+ aShouldBeIncludedCharacter,
+ aSouldBeSelected,
+ aElementName)
+ {
+ var boolvalue = aSouldBeSelected & kTrue;
+ var f = aSouldBeSelected & kToDo ? todo : ok;
+ var str = aSelectedText.replace('\n', '\\n');
+ if (boolvalue) {
+ f(aSelectedText.includes(aShouldBeIncludedCharacter),
+ "The contents of " + aElementName +
+ " aren't selected (" + aTestingDescription +
+ "): Selected String: \"" + str + "\"");
+ } else {
+ f(!aSelectedText.includes(aShouldBeIncludedCharacter),
+ "The contents of " + aElementName +
+ " are selected (" + aTestingDescription +
+ "): Selected String: \"" + str + "\"");
+ }
+ }
+
+ var sel = window.getSelection().toString();
+ checkCharacter(sel, "d", aFixedDiv1ShouldBeSelected, "fixedDiv1");
+
+ // input contents must not be included on the parent
+ // selection.
+ checkCharacter(sel, "i", false, "input (checking on parent)");
+
+ var selInput = getSelectionForEditor(input).toString();
+ checkCharacter(selInput, "i", aInputShouldBeSelected, "input");
+ }
+
+ function eventHandler(evt) {
+ evt.preventDefault();
+ }
+
+ // prevent default action on mousedown should prevent selection
+ fixedDiv1.addEventListener("mousedown", eventHandler);
+ synthesizeMouse(fixedDiv1, 30, 5, { type: "mousedown" });
+ synthesizeMouse(fixedDiv1, 40, 5, { type: "mousemove" });
+ synthesizeMouse(fixedDiv1, 40, 5, { type: "mouseup" });
+ check(kFalse, kFalse, "fixedDiv1-fixedDiv1-mousedown");
+ clear();
+
+ input.addEventListener("mousedown", eventHandler);
+ synthesizeMouse(input, 20, 5, { type: "mousedown" });
+ synthesizeMouse(input, 40, 5, { type: "mousemove" });
+ synthesizeMouse(input, 40, 5, { type: "mouseup" });
+ check(kFalse, kFalse, "input-input-mousedown");
+ clear();
+
+ // clean up mousedown listener
+ [fixedDiv1, input].forEach(function(element) {
+ element.removeEventListener("mousedown", eventHandler);
+ });
+
+ // prevent default action on mouseup should not affect the selection state
+ fixedDiv1.addEventListener("mouseup", eventHandler);
+ synthesizeMouse(fixedDiv1, 30, 5, { type: "mousedown" });
+ synthesizeMouse(fixedDiv1, 40, 5, { type: "mousemove" });
+ synthesizeMouse(fixedDiv1, 40, 5, { type: "mouseup" });
+ check(kTrue, kFalse, "fixedDiv1-fixedDiv1-mouseup");
+ clear();
+
+ input.addEventListener("mouseup", eventHandler);
+ synthesizeMouse(input, 20, 5, { type: "mousedown" });
+ synthesizeMouse(input, 40, 5, { type: "mousemove" });
+ synthesizeMouse(input, 40, 5, { type: "mouseup" });
+ check(kFalse, kTrue, "input-input-mouseup");
+ clear();
+
+ [fixedDiv1, input].forEach(function(element) {
+ element.removeEventListener("mouseup", eventHandler);
+ });
+
+ // touchmove event should not affect the selection state
+ synthesizeTouch(fixedDiv1, 30, 5, { type: "touchstart" });
+ synthesizeTouch(fixedDiv1, 40, 5, { type: "touchmove" });
+ check(kFalse, kFalse, "fixedDiv1-fixedDiv1-touchmove");
+ synthesizeTouch(fixedDiv1, 40, 5, { type: "touchend" });
+ clear();
+
+ synthesizeTouch(input, 20, 5, { type: "touchstart" });
+ synthesizeTouch(input, 40, 5, { type: "touchmove" });
+ check(kFalse, kFalse, "input-input-touchmove");
+ synthesizeTouch(input, 40, 5, { type: "touchend" });
+ clear();
+
+ fixedDiv1.addEventListener("touchmove", eventHandler);
+ synthesizeTouch(fixedDiv1, 30, 5, { type: "touchstart" });
+ synthesizeTouch(fixedDiv1, 40, 5, { type: "touchmove" });
+ check(kFalse, kFalse, "fixedDiv1-fixedDiv1-touchmove-preventDefault");
+ synthesizeTouch(fixedDiv1, 40, 5, { type: "touchend" });
+ clear();
+
+ input.addEventListener("touchmove", eventHandler);
+ synthesizeTouch(input, 20, 5, { type: "touchstart" });
+ synthesizeTouch(input, 40, 5, { type: "touchmove" });
+ check(kFalse, kFalse, "input-input-touchmove-preventDefault");
+ synthesizeTouch(input, 40, 5, { type: "touchend" });
+ clear();
+
+ SimpleTest.finish();
+}
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_selection_splitText-normalize.html b/layout/generic/test/test_selection_splitText-normalize.html
new file mode 100644
index 0000000000..5b1cf08627
--- /dev/null
+++ b/layout/generic/test/test_selection_splitText-normalize.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=191864
+-->
+<head>
+ <title>Test for Bug 191864</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=191864">Mozilla Bug 191864</a>
+<p id="display">
+<span id="col1" style="float:left; height:800px; width:180px;"></span>
+<span id="col2" style="float:left; height:800px; width:180px;"></span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+var tests = [
+ [ {}, [0,4], "012345678" ],
+ [ {}, [0,0], "012345678" ],
+ [ {}, [0,9], "012345678" ],
+ [ {startOffset:4}, [0,4], "012345678" ],
+ [ {startOffset:5}, [0,4], "012345678" ],
+ [ {startOffset:5,endOffset:6}, [0,4], "012345678" ],
+ [ {endOffset:5}, [0,4], "012345678" ],
+ [ {endOffset:4}, [0,4], "012345678" ],
+ [ {endOffset:3}, [0,4], "012345678" ],
+ [ {startOffset:1,endOffset:3}, [0,4], "012345678" ],
+ [ {startOffset:7,endOffset:7}, [0,4], "012345678" ],
+ [ {startOffset:4,endOffset:4}, [0,4], "012345678" ],
+ [ {endNode:1}, [0,4], "012345678", "" ],
+ [ {endNode:1}, [0,4], "01234567", "8" ],
+ [ {endNode:1}, [1,4], "0", "12345678" ],
+ [ {endNode:2}, [1,4], "0", "12345", "678" ],
+]
+
+function runtest(r,p,t) {
+ // create content
+ document.original_nodes = [];
+ for (let i = 2; i < t.length; ++i) {
+ c = document.createTextNode(t[i]);
+ p.appendChild(c);
+ document.original_nodes.push(c);
+ }
+
+ // setup the range
+ let sel = t[0]
+ let startNode = p.firstChild;
+ let startOffset = sel.startOffset === undefined ? 0 : sel.startOffset;
+ let endNode = sel.endNode === undefined ? startNode : p.childNodes[sel.endNode];
+ let endOffset = sel.endOffset === undefined ? endNode.length : sel.endOffset;
+ r.setStart(startNode, startOffset);
+ r.setEnd(endNode, endOffset);
+
+ // splitText
+ let split = t[1]
+ p.childNodes[split[0]].splitText(split[1])
+}
+function test_split(r,p,t) {
+ runtest(r,p,t);
+}
+function test_split_merge(r,p,t) {
+ runtest(r,p,t);
+ p.normalize();
+}
+</script>
+
+<script type="application/javascript">
+
+/** Test for Bug 191864 **/
+
+var results = [
+/* test_split */
+ [ {}, [ [0, "0123"], "45678" ]],
+ [ {}, [ [0, ""], "012345678" ]],
+ [ {endNode:0, endOffset:9}, [ [0, "012345678"], "" ]],
+ [ {startOffset:4}, [ [0, "0123"], "45678" ]],
+ [ {startNode:1, startOffset:1}, [ [0, "0123"], "45678" ]],
+ [ {startNode:1, startOffset:1, endOffset:2}, [ [0, "0123"], "45678" ]],
+ [ {endOffset:1}, [ [0, "0123"], "45678" ]],
+ [ {endNode:0}, [ [0, "0123"], "45678" ]],
+ [ {endNode:0, endOffset:3}, [ [0, "0123"], "45678" ]],
+ [ {startOffset:1, endNode:0, endOffset:3}, [ [0, "0123"], "45678" ]],
+ [ {startNode:1, startOffset:3, endOffset:3}, [ [0, "0123"], "45678" ]],
+ [ {startOffset:4, endNode:0}, [ [0, "0123"], "45678" ]],
+ [ {endNode:2, endOffset:0}, [ [0, "0123"], "45678", [1, ""] ]],
+ [ {endNode:2}, [ [0, "0123"], "4567", [1, "8"] ]],
+ [ {endNode:2}, [ [0, "0"], [1, "1234"], "5678" ]],
+ [ {endNode:3}, [ [0, "0"], [1, "1234"], "5", [2, "678"] ]],
+/* test_split_merge */
+ [ {}, [ [0, "012345678" ] ]],
+ [ {startParent:true}, [ "012345678" ]], /* splitText() creates an empty first child which is removed by normalize() */
+ [ {}, [ [0, "012345678" ] ]],
+ [ {startOffset:4}, [ [0, "012345678" ] ]],
+ [ {startOffset:5}, [ [0, "012345678" ] ]],
+ [ {startOffset:5,endOffset:6}, [ [0, "012345678" ] ]],
+ [ {endOffset:5}, [ [0, "012345678" ] ]],
+ [ {endOffset:4}, [ [0, "012345678" ] ]],
+ [ {endOffset:3}, [ [0, "012345678" ] ]],
+ [ {startOffset:1,endOffset:3}, [ [0, "012345678" ] ]],
+ [ {startOffset:7,endOffset:7}, [ [0, "012345678" ] ]],
+ [ {startOffset:4,endOffset:4}, [ [0, "012345678" ] ]],
+ [ {endParent:true}, [ [0, "012345678" ] ]],
+ [ {}, [ [0, "012345678" ] ]],
+ [ {}, [ [0, "012345678" ] ]],
+ [ {}, [ [0, "012345678" ] ]],
+]
+
+function verifyResults(r,p,i) {
+ let nodes = results[i][1];
+ is(p.childNodes.length, nodes.length, "same number of DOM nodes" + " (test " + i + ")");
+ for (let j = 0; j < nodes.length; ++j) {
+ let a = nodes[j];
+ let b = p.childNodes[j];
+ if (a instanceof Array) {
+ is(b, document.original_nodes[a[0]], "same node" + " (test " + i + " child " + j + ")");
+ is(b.textContent, a[1], "contents2" + " (test " + i + " child " + j + ")");
+ } else {
+ is(b.nodeType, Node.TEXT_NODE, "text node" + " (test " + i + " child " + j + ")");
+ is(b.textContent, a, "contents1" + " (test " + i + " child " + j + ")");
+ }
+ }
+ let sel = results[i][0];
+ if (sel.todo) {
+ alert(r.startContainer + '\n' + r.startOffset + '\n' + r.endContainer + '\n' + r.endOffset + '\n')
+ return;
+ }
+ let startNode = sel.startNode === undefined ? p.firstChild : p.childNodes[sel.startNode];
+ let startOffset = sel.startOffset === undefined ? 0 : sel.startOffset;
+ if (sel.startParent) { startNode = p; startOffset = 0; }
+ let endNode = sel.endNode === undefined ? p.childNodes[p.childNodes.length>1 ? 1 : 0] : p.childNodes[sel.endNode];
+ let endOffset = sel.endOffset === undefined ? endNode.length : sel.endOffset;
+ if (sel.endParent) { endNode = p; endOffset = 1; }
+ is(r.startContainer, startNode, "range start node" + " (test " + i + ")");
+ is(r.startOffset, startOffset, "range start offset" + " (test " + i + ")");
+ is(r.endContainer, endNode, "range end node" + " (test " + i + ")");
+ is(r.endOffset, endOffset, "range end offset" + " (test " + i + ")");
+}
+
+function runTest() {
+ let col1 = document.getElementById('col1');
+ let col2 = document.getElementById('col2');
+ for (let i=0; i < tests.length; ++i) {
+ let t = tests[i];
+ let p = document.createElement('p')
+ col1.appendChild(p);
+ let r = document.createRange();
+ test_split(r,p,t);
+ verifyResults(r,p,i);
+ }
+ for (let i=0; i < tests.length; ++i) {
+ let t = tests[i];
+ let p = document.createElement('p')
+ col2.appendChild(p);
+ let r = document.createRange();
+ test_split_merge(r,p,t);
+ verifyResults(r,p,i+tests.length);
+ }
+ SimpleTest.finish();
+}
+
+window.onload = function() { setTimeout(runTest, 0); };
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_selection_touchevents.html b/layout/generic/test/test_selection_touchevents.html
new file mode 100644
index 0000000000..082f879d01
--- /dev/null
+++ b/layout/generic/test/test_selection_touchevents.html
@@ -0,0 +1,55 @@
+<!DOCTYPE>
+<html>
+<head>
+<title>selection expanding test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="div1" class="testingDiv">
+ aaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaa
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var div1 = document.getElementById("div1");
+
+function sendTouch(aType, aX, aY) {
+ var cwu = SpecialPowers.getDOMWindowUtils(window);
+ cwu.sendTouchEvent(aType, [0], [aX], [aY], [1], [1], [0], [1], [0], [0], [0], 0, true);
+}
+
+function test()
+{
+ var selection = window.getSelection();
+ selection.removeAllRanges();
+ var rect = div1.getBoundingClientRect();
+
+ // Position the caret using a fake mouse click
+ var Ci = SpecialPowers.Ci;
+ var cwu = SpecialPowers.getDOMWindowUtils(window);
+ cwu.sendMouseEventToWindow("mousedown", rect.left + rect.width/2, rect.top + rect.height/2, 0, 0, 0, true);
+ cwu.sendMouseEventToWindow("mouseup", rect.left + rect.width/2, rect.top + rect.height/2, 0, 0, 0, true);
+ var selectionController = SpecialPowers.wrap(window).docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsISelectionDisplay).
+ QueryInterface(Ci.nsISelectionController);
+
+ selectionController.wordMove(false, false);
+ selectionController.wordMove(true, true);
+ isnot(selection.rangeCount, 0, "Something should be selected");
+ var string = selection.toString();
+
+ sendTouch("touchstart", rect.right, rect.top + rect.height/2);
+ sendTouch("touchmove", rect.left, rect.top + rect.height/2);
+ sendTouch("touchend", rect.left, rect.top + rect.height/2);
+ is(selection.toString(), string, "touch events should not affect the selection");
+
+ SimpleTest.finish();
+}
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/generic/test/test_selection_tripleclick.html b/layout/generic/test/test_selection_tripleclick.html
new file mode 100644
index 0000000000..a912eb4686
--- /dev/null
+++ b/layout/generic/test/test_selection_tripleclick.html
@@ -0,0 +1,63 @@
+<!DOCTYPE>
+<title>Selection test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ body { margin: 0; font: 16px/1 sans-serif; }
+ code { display: inline-block }
+</style>
+<p id="a">Some <code>code</code> with <span>text</span> with <code>code</code> in it</p>
+
+<p id="b">Here's <span>some</span> <code><span>code</span><br><span>broken</span></code> with <span>text</span> with <code><span>code</span><br><span>broken</span></code> in <span>it and such</span> and so on</p>
+
+<pre id="c">Here's some text and
+some <span>more</span> code <span>with</span> pre-formatted
+whitespace <span>that</span> should be selected
+line <span>by</span> line</pre>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function testTripleClick(elem, expectation) {
+ window.getSelection().removeAllRanges();
+ synthesizeMouse(elem, 5, 5, { clickCount: 3 });
+ is(window.getSelection().toString(), expectation);
+ }
+
+ SimpleTest.waitForFocus(function () {
+ for (let child of document.querySelectorAll("#a span, #a code"))
+ testTripleClick(child, "Some code with text with code in it");
+
+ {
+ let spans = document.querySelectorAll("#b span");
+ let expectations = [
+ "Here's some code", // First span, at the beginning of the text.
+ "Here's some code", // Top span in the inline-block, before break.
+ "broken with text with code", // Bottom span in inline-block, after break
+ "broken with text with code", // Center of the text.
+ "broken with text with code", // Top span in the second inline-block, before break.
+ "broken in it and such and so on", // Bottom span in the second inline-block, after break.
+ "broken in it and such and so on", // Last span, at the end of the text.
+ ];
+ is(spans.length, expectations.length);
+ for (let i = 0; i < expectations.length; ++i)
+ testTripleClick(spans[i], expectations[i]);
+ }
+
+ {
+ testTripleClick(document.getElementById("c"), "Here's some text and");
+ let spans = document.querySelectorAll("#c span");
+ let expectations = [
+ "some more code with pre-formatted",
+ "some more code with pre-formatted",
+ "whitespace that should be selected",
+ "line by line",
+ ];
+ is(spans.length, expectations.length);
+ for (let i = 0; i < expectations.length; ++i)
+ testTripleClick(spans[i], expectations[i]);
+ }
+
+ SimpleTest.finish();
+ });
+</script>
diff --git a/layout/generic/test/test_selection_underline.html b/layout/generic/test/test_selection_underline.html
new file mode 100644
index 0000000000..042c75cd29
--- /dev/null
+++ b/layout/generic/test/test_selection_underline.html
@@ -0,0 +1,246 @@
+<html>
+
+<head>
+ <title>Test for selection underline</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+// Canvas related code stolen from layout/base/tests/bidi_numeral_test.js which
+// stole from http://developer.mozilla.org/en/docs/Code_snippets:Canvas
+
+var RemoteCanvas = function(aIFrame, aTest) {
+ this.iframe = aIFrame;
+ this.test = aTest;
+ this.snapshot = null;
+};
+
+RemoteCanvas.CANVAS_WIDTH = 200;
+RemoteCanvas.CANVAS_HEIGHT = 100;
+
+RemoteCanvas.prototype.isReference = function() {
+ return this.iframe && (this.iframe.id == "reference");
+}
+
+RemoteCanvas.prototype.load = function(callback) {
+ this.iframe.contentWindow.wrappedJSObject.init(this.test);
+ var me = this;
+ setTimeout(function () { me.remotePagePrepared(callback) }, 100);
+}
+
+RemoteCanvas.prototype.remotePagePrepared = function(callback) {
+ this.snapshot = snapshotWindow(this.iframe.contentWindow);
+ callback(this);
+}
+
+var gPrefs = [
+ [ "ui.SpellCheckerUnderline", "#ff0000" ],
+ [ "ui.IMERawInputBackground", "transparent" ],
+ [ "ui.IMERawInputForeground", "#000000" ],
+ [ "ui.IMERawInputUnderline", "#00ff00" ],
+ [ "ui.IMESelectedRawTextBackground", "transparent" ],
+ [ "ui.IMESelectedRawTextForeground", "#000000" ],
+ [ "ui.IMESelectedRawTextUnderline", "#0000ff" ],
+ [ "ui.IMEConvertedTextBackground", "transparent" ],
+ [ "ui.IMEConvertedTextForeground", "#000000" ],
+ [ "ui.IMEConvertedTextUnderline", "#ffff00" ],
+ [ "ui.IMESelectedConvertedTextBackground", "transparent" ],
+ [ "ui.IMESelectedConvertedTextForeground", "#000000" ],
+ [ "ui.IMESelectedConvertedTextUnderline", "#00ffff" ],
+ [ "ui.SpellCheckerUnderlineStyle", 0 ],
+ [ "ui.IMERawInputUnderlineStyle", 0 ],
+ [ "ui.IMESelectedRawTextUnderlineStyle", 0 ],
+ [ "ui.IMEConvertedTextUnderlineStyle", 0 ],
+ [ "ui.IMESelectedConvertedTextUnderlineStyle", 0 ],
+ [ "ui.SpellCheckerUnderlineRelativeSize", 1.0 ],
+ [ "ui.IMEUnderlineRelativeSize", 1.0 ]
+];
+
+const nsISelectionController = Ci.nsISelectionController;
+
+var gSelectionIndex = -1;
+const kSelections = [
+ { type: nsISelectionController.SELECTION_SPELLCHECK,
+ typeName: "SpellCheck", isIME: false,
+ decorationColor: "#ff0000" },
+ { type: nsISelectionController.SELECTION_IME_RAWINPUT,
+ typeName: "IME-RawInput", isIME: true,
+ decorationColor: "#00ff00" },
+ { type: nsISelectionController.SELECTION_IME_SELECTEDRAWTEXT,
+ typeName: "IME-SelectedRawText", isIME: true,
+ decorationColor: "#0000ff" },
+ { type: nsISelectionController.SELECTION_IME_CONVERTEDTEXT,
+ typeName: "IME-ConvertedText", isIME: true,
+ decorationColor: "#ffff00" },
+ { type: nsISelectionController.SELECTION_IME_SELECTEDCONVERTEDTEXT,
+ typeName: "IME-SelectedConvertedText", isIME: true,
+ decorationColor: "#00ffff" },
+];
+
+const kFontName_Ahem = "AhemTest";
+const kFontName_MPlus = "mplusTest";
+
+var gFontIndex = 0;
+const kFonts = [
+ { family: kFontName_Ahem, defaultSize: 16 },
+ { family: kFontName_Ahem, defaultSize: 20 },
+ { family: kFontName_Ahem, defaultSize: 32 },
+ { family: kFontName_Ahem, defaultSize: 52 },
+
+ { family: kFontName_MPlus, defaultSize: 16 },
+ { family: kFontName_MPlus, defaultSize: 20 },
+ { family: kFontName_MPlus, defaultSize: 32 },
+ { family: kFontName_MPlus, defaultSize: 52 },
+];
+
+const kDecorationStyleNone = 0;
+const kDecorationStyleDotted = 1;
+const kDecorationStyleDashed = 2;
+const kDecorationStyleSolid = 3;
+const kDecorationStyleDouble = 4;
+const kDecorationStyleWavy = 5;
+
+var gDecorationIndex = 0;
+const kDecorations = [
+ { relativeSize: 1.0, style: kDecorationStyleNone, styleName: "-moz-none" },
+ { relativeSize: 1.0, style: kDecorationStyleSolid, styleName: "solid" },
+ { relativeSize: 1.0, style: kDecorationStyleDotted, styleName: "dotted" },
+ { relativeSize: 1.0, style: kDecorationStyleDashed, styleName: "dashed" },
+ { relativeSize: 1.0, style: kDecorationStyleDouble, styleName: "double" },
+ { relativeSize: 1.0, style: kDecorationStyleWavy, styleName: "wavy" },
+
+// XXX relativeSize 2.0 cannot be tested by CSS3 text-decoration
+
+];
+
+function IsD2DEnabled() {
+ var enabled = false;
+
+ try {
+ enabled = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).D2DEnabled;
+ } catch(e) {}
+
+ return enabled;
+}
+
+function getFuzz(test) {
+ // Only failing on Windows with Direct2D enabled, and only for 16 permutations.
+ if (IsD2DEnabled() &&
+ test.decoration.styleName == "solid" &&
+ test.decoration.relativeSize == "1" &&
+ test.font.family == "mplusTest" &&
+ test.selection.typeName != "SpellCheck") {
+ return { numDifferentPixels: 194, maxDifference: 1 };
+ }
+ return null;
+}
+
+async function run()
+{
+ let prefs = [];
+ if (++gSelectionIndex == kSelections.length) {
+ if (++gFontIndex == kFonts.length) {
+ if (++gDecorationIndex == kDecorations.length) {
+ SimpleTest.finish();
+ return;
+ }
+ gFontIndex = 0;
+ }
+ gSelectionIndex = 0;
+ prefs.push([ "font.size.variable.x-western", kFonts[gFontIndex].defaultSize ]);
+ }
+
+ var test = {
+ font: kFonts[gFontIndex],
+ decoration: kDecorations[gDecorationIndex],
+ selection: kSelections[gSelectionIndex],
+ };
+
+ prefs.push(
+ ["ui.SpellCheckerUnderlineRelativeSize", test.decoration.relativeSize * 100],
+ ["ui.IMEUnderlineRelativeSize", test.decoration.relativeSize * 100],
+ ["ui.SpellCheckerUnderlineStyle", test.decoration.style],
+ ["ui.IMERawInputUnderlineStyle", test.decoration.style],
+ ["ui.IMESelectedRawTextUnderlineStyle", test.decoration.style],
+ ["ui.IMEConvertedTextUnderlineStyle", test.decoration.style],
+ ["ui.IMESelectedConvertedTextUnderlineStyle", test.decoration.style],
+ );
+
+ await SpecialPowers.pushPrefEnv({ set: prefs });
+ doTest(test);
+}
+
+function doTest(aTest)
+{
+
+ var canvases = [];
+ function callbackTestCanvas(canvas)
+ {
+ canvases.push(canvas);
+
+ if (canvases.length != 2)
+ return;
+
+ var result = !canvases[0].isReference() ? canvases[0] : canvases[1];
+ var reference = canvases[0].isReference() ? canvases[0] : canvases[1];
+
+ var description = "(selection: " + aTest.selection.typeName +
+ ", style: " + aTest.decoration.styleName +
+ ", relativeSize: " + aTest.decoration.relativeSize +
+ ", font: " + aTest.font.family +
+ ", default font size: " + aTest.font.defaultSize + ")";
+
+ // If the decoration line is thick and the descender of the text isn't
+ // enough for containing it, selection underline may be painted lower
+ // if it's possible. Then, we cannot test it with CSS3 text-decoration.
+ if (aTest.decoration.style == kDecorationStyleDouble ||
+ aTest.decoration.style == kDecorationStyleWavy) {
+ todo(false, "Rendering of" + description);
+ } else {
+ assertSnapshots(result.snapshot, reference.snapshot, true,
+ getFuzz(aTest), description, "");
+ }
+
+ canvases = [];
+
+ run();
+ }
+
+ var testCanvas = new RemoteCanvas(document.getElementById("result"), aTest);
+ testCanvas.load(callbackTestCanvas);
+
+ var refCanvas = new RemoteCanvas(document.getElementById("reference"), aTest);
+ refCanvas.load(callbackTestCanvas);
+}
+
+async function onLoad()
+{
+ await SpecialPowers.pushPrefEnv({ set: gPrefs });
+
+ var iframe = document.getElementById("result");
+ iframe.width = RemoteCanvas.CANVAS_WIDTH + "px";
+ iframe.height = RemoteCanvas.CANVAS_HEIGHT + "px";
+ iframe = document.getElementById("reference");
+ iframe.width = RemoteCanvas.CANVAS_WIDTH + "px";
+ iframe.height = RemoteCanvas.CANVAS_HEIGHT + "px";
+
+ run();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(onLoad, window);
+
+</script>
+
+</head>
+<body>
+
+<iframe src="frame_selection_underline.xhtml" id="result"></iframe>
+<iframe src="frame_selection_underline-ref.xhtml" id="reference"></iframe>
+<pre id="test">
+</pre>
+
+</body>
+</html>
diff --git a/layout/generic/test/test_taintedfilters.html b/layout/generic/test/test_taintedfilters.html
new file mode 100644
index 0000000000..b0bfab2bd4
--- /dev/null
+++ b/layout/generic/test/test_taintedfilters.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=941887
+-->
+<head>
+ <title>Test for Bug 941887</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ iframe {
+ width: 500px;
+ height: 300px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=941887">Mozilla Bug 941887</a>
+<p id="display"></p>
+<div id="content">
+<iframe id="f1"></iframe>
+<iframe id="f2"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 941887 **/
+SimpleTest.waitForExplicitFinish();
+
+var f = [document.getElementById("f1"), document.getElementById("f2")];
+
+var testList = [
+ ["file_taintedfilters_feDisplacementMap-untainted-1.svg", "file_taintedfilters_feDisplacementMap-untainted-ref.svg"],
+
+ // Disabled until CORS for feImage is implemented.
+ // ["file_taintedfilters_feDisplacementMap-untainted-2.svg", "file_taintedfilters_feDisplacementMap-untainted-ref.svg"],
+
+ ["file_taintedfilters_feDisplacementMap-tainted-1.svg", "file_taintedfilters_feDisplacementMap-tainted-ref.svg"],
+ ["file_taintedfilters_feDisplacementMap-tainted-2.svg", "file_taintedfilters_feDisplacementMap-tainted-ref.svg"],
+ ["file_taintedfilters_feDisplacementMap-tainted-3.svg", "file_taintedfilters_feDisplacementMap-tainted-ref.svg"],
+];
+
+var currentTestIndex = 0;
+var currentTest = testList[0];
+var loaded = [false, false];
+
+function didLoadIframe(iframe, index) {
+ if (iframe.contentWindow.location.href == iframe.src) {
+ loaded[index] = true;
+ if (loaded[0] && loaded[1]) {
+ checkCurrentTest();
+ }
+ }
+}
+
+f[0].onload = function (e) { didLoadIframe(e.target, 0); }
+f[1].onload = function (e) { didLoadIframe(e.target, 1); }
+
+function loadCurrentTest() {
+ currentTest = testList[currentTestIndex];
+ f[0].contentWindow.stop();
+ f[0].src = currentTest[0];
+ f[1].contentWindow.stop();
+ f[1].src = currentTest[1];
+ loaded = [false, false];
+}
+
+function okEqualSnapshots(c1, c2, msg) {
+ var [correct, c1url, c2url] = compareSnapshots(c1, c2, true);
+ if (correct) {
+ ok(true, msg);
+ } else {
+ ok(false, msg + "\nTEST: " + c1url + "\nREFERENCE: " + c1url);
+ }
+}
+
+function checkCurrentTest() {
+ okEqualSnapshots(snapshotWindow(f[0].contentWindow),
+ snapshotWindow(f[1].contentWindow),
+ currentTest[0] + " and " + currentTest[1] + " should match.");
+
+ currentTestIndex++;
+
+ if (currentTestIndex < testList.length)
+ loadCurrentTest();
+ else
+ SimpleTest.finish();
+}
+
+loadCurrentTest();
+
+</script>
+</pre>
+</body>
+</html>